From 16409b0bb832ae376894cbad5892bf7623caeaaf Mon Sep 17 00:00:00 2001 From: Gerd Moellmann Date: Tue, 19 Sep 2000 13:37:09 +0000 Subject: [PATCH] Update to emacs-21-branch of the Gnus CVS repository. --- lisp/gnus/earcon.el | 11 +- lisp/gnus/gnus-agent.el | 758 +++++++---- lisp/gnus/gnus-art.el | 2747 +++++++++++++++++++++++++++++---------- lisp/gnus/gnus-async.el | 168 ++- lisp/gnus/gnus-audio.el | 70 +- lisp/gnus/gnus-bcklg.el | 22 +- lisp/gnus/gnus-cache.el | 94 +- lisp/gnus/gnus-cite.el | 148 ++- lisp/gnus/gnus-cus.el | 257 +++- lisp/gnus/gnus-demon.el | 35 +- lisp/gnus/gnus-draft.el | 60 +- lisp/gnus/gnus-dup.el | 15 +- lisp/gnus/gnus-eform.el | 6 +- lisp/gnus/gnus-ems.el | 228 ++-- lisp/gnus/gnus-gl.el | 36 +- lisp/gnus/gnus-group.el | 820 +++++++++--- lisp/gnus/gnus-int.el | 66 +- lisp/gnus/gnus-kill.el | 22 +- lisp/gnus/gnus-logic.el | 11 +- lisp/gnus/gnus-mh.el | 7 +- lisp/gnus/gnus-move.el | 8 +- lisp/gnus/gnus-msg.el | 514 +++++--- lisp/gnus/gnus-nocem.el | 41 +- lisp/gnus/gnus-range.el | 153 ++- lisp/gnus/gnus-salt.el | 172 +-- lisp/gnus/gnus-score.el | 370 +++--- lisp/gnus/gnus-setup.el | 32 +- lisp/gnus/gnus-soup.el | 71 +- lisp/gnus/gnus-spec.el | 40 +- lisp/gnus/gnus-srvr.el | 142 +- lisp/gnus/gnus-start.el | 577 ++++---- lisp/gnus/gnus-sum.el | 1797 +++++++++++++++++-------- lisp/gnus/gnus-topic.el | 290 ++++- lisp/gnus/gnus-undo.el | 16 +- lisp/gnus/gnus-util.el | 377 +++--- lisp/gnus/gnus-uu.el | 284 ++-- lisp/gnus/gnus-vm.el | 16 +- lisp/gnus/gnus-win.el | 103 +- lisp/gnus/gnus.el | 625 +++++---- lisp/gnus/message.el | 1542 ++++++++++++++-------- lisp/gnus/messcompat.el | 5 +- lisp/gnus/nnagent.el | 85 +- lisp/gnus/nnbabyl.el | 29 +- lisp/gnus/nndir.el | 4 +- lisp/gnus/nndoc.el | 304 +++-- lisp/gnus/nndraft.el | 68 +- lisp/gnus/nneething.el | 55 +- lisp/gnus/nnfolder.el | 244 ++-- lisp/gnus/nngateway.el | 6 +- lisp/gnus/nnheader.el | 327 ++--- lisp/gnus/nnkiboze.el | 33 +- lisp/gnus/nnlistserv.el | 29 +- lisp/gnus/nnmail.el | 1049 +++++++-------- lisp/gnus/nnmbox.el | 115 +- lisp/gnus/nnmh.el | 82 +- lisp/gnus/nnml.el | 141 +- lisp/gnus/nnoo.el | 14 +- lisp/gnus/nnsoup.el | 69 +- lisp/gnus/nnspool.el | 45 +- lisp/gnus/nntp.el | 531 ++++---- lisp/gnus/nnvirtual.el | 157 ++- lisp/gnus/nnweb.el | 416 +++--- lisp/gnus/parse-time.el | 117 +- lisp/gnus/pop3.el | 3 +- lisp/gnus/score-mode.el | 19 +- 65 files changed, 10738 insertions(+), 5960 deletions(-) diff --git a/lisp/gnus/earcon.el b/lisp/gnus/earcon.el index 4302182733c..017cdd47b3d 100644 --- a/lisp/gnus/earcon.el +++ b/lisp/gnus/earcon.el @@ -1,7 +1,11 @@ ;;; earcon.el --- Sound effects for messages -;; Copyright (C) 1996 Free Software Foundation + +;; Copyright (C) 1996, 2000 Free Software Foundation ;; Author: Steven L. Baur + +;; This file is part of GNU Emacs. + ;; GNU Emacs 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 2, or (at your option) @@ -16,8 +20,10 @@ ;; along with GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. +;; This file is part of GNU Emacs. ;;; Commentary: + ;; This file provides access to sound effects in Gnus. ;;; Code: @@ -74,8 +80,6 @@ (defvar earcon-button-marker-list nil) (make-variable-buffer-local 'earcon-button-marker-list) - - ;;; FIXME!! clone of code from gnus-vis.el FIXME!! (defun earcon-article-push-button (event) "Check text under the mouse pointer for a callback function. @@ -156,7 +160,6 @@ If N is negative, move backward instead." (setq entry nil))) entry)) - (defun earcon-button-push (marker) ;; Push button starting at MARKER. (save-excursion diff --git a/lisp/gnus/gnus-agent.el b/lisp/gnus/gnus-agent.el index 434d4f1d1a2..3a4d4bb81f6 100644 --- a/lisp/gnus/gnus-agent.el +++ b/lisp/gnus/gnus-agent.el @@ -1,5 +1,5 @@ ;;; gnus-agent.el --- unplugged support for Gnus -;; Copyright (C) 1997,98 Free Software Foundation, Inc. +;; Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; This file is part of GNU Emacs. @@ -27,7 +27,10 @@ (require 'gnus-cache) (require 'nnvirtual) (require 'gnus-sum) -(eval-when-compile (require 'cl)) +(eval-when-compile + (require 'timer) + (require 'cl) + (require 'gnus-score)) (defcustom gnus-agent-directory (nnheader-concat gnus-directory "agent/") "Where the Gnus agent will store its files." @@ -75,9 +78,12 @@ If nil, only read articles will be expired." :group 'gnus-agent :type 'hook) -;;; Internal variables +(defcustom gnus-agent-confirmation-function 'y-or-n-p + "Function to confirm when error happens." + :group 'gnus-agent + :type 'function) -(defvar gnus-agent-meta-information-header "X-Gnus-Agent-Meta-Information") +;;; Internal variables (defvar gnus-agent-history-buffers nil) (defvar gnus-agent-buffer-alist nil) @@ -92,7 +98,11 @@ If nil, only read articles will be expired." (defvar gnus-agent-spam-hashtb nil) (defvar gnus-agent-file-name nil) (defvar gnus-agent-send-mail-function nil) -(defvar gnus-agent-file-coding-system 'no-conversion) +(defvar gnus-agent-file-coding-system 'raw-text) + +(defconst gnus-agent-scoreable-headers + '("subject" "from" "date" "message-id" "references" "chars" "lines" "xref") + "Headers that are considered when scoring articles for download via the Agent.") ;; Dynamic variables (defvar gnus-headers) @@ -106,12 +116,20 @@ If nil, only read articles will be expired." (setq gnus-agent t) (gnus-agent-read-servers) (gnus-category-read) - (setq gnus-agent-overview-buffer - (gnus-get-buffer-create " *Gnus agent overview*")) + (gnus-agent-create-buffer) (add-hook 'gnus-group-mode-hook 'gnus-agent-mode) (add-hook 'gnus-summary-mode-hook 'gnus-agent-mode) (add-hook 'gnus-server-mode-hook 'gnus-agent-mode)) +(defun gnus-agent-create-buffer () + (if (gnus-buffer-live-p gnus-agent-overview-buffer) + t + (setq gnus-agent-overview-buffer + (gnus-get-buffer-create " *Gnus agent overview*")) + (with-current-buffer gnus-agent-overview-buffer + (mm-enable-multibyte)) + nil)) + (gnus-add-shutdown 'gnus-close-agent 'gnus) (defun gnus-close-agent () @@ -127,7 +145,7 @@ If nil, only read articles will be expired." (defun gnus-agent-read-file (file) "Load FILE and do a `read' there." - (nnheader-temp-write nil + (with-temp-buffer (ignore-errors (nnheader-insert-file-contents file) (goto-char (point-min)) @@ -153,7 +171,8 @@ If nil, only read articles will be expired." (defun gnus-agent-start-fetch () "Initialize data structures for efficient fetching." (gnus-agent-open-history) - (setq gnus-agent-current-history (gnus-agent-history-buffer))) + (setq gnus-agent-current-history (gnus-agent-history-buffer)) + (gnus-agent-create-buffer)) (defun gnus-agent-stop-fetch () "Save all data structures and clean up." @@ -214,8 +233,10 @@ If nil, only read articles will be expired." "Jc" gnus-enter-category-buffer "Jj" gnus-agent-toggle-plugged "Js" gnus-agent-fetch-session + "JY" gnus-agent-synchronize "JS" gnus-group-send-drafts - "Ja" gnus-agent-add-group) + "Ja" gnus-agent-add-group + "Jr" gnus-agent-remove-group) (defun gnus-agent-group-make-menu-bar () (unless (boundp 'gnus-agent-group-menu) @@ -334,7 +355,7 @@ agent minor mode in all Gnus buffers." (concat "^" (regexp-quote mail-header-separator) "\n")) (replace-match "\n") (gnus-agent-insert-meta-information 'mail) - (gnus-request-accept-article "nndraft:queue"))) + (gnus-request-accept-article "nndraft:queue" nil t t))) (defun gnus-agent-insert-meta-information (type &optional method) "Insert meta-information into the message that says how it's to be posted. @@ -357,11 +378,15 @@ be a select method." (defun gnus-agent-fetch-groups (n) "Put all new articles in the current groups into the Agent." (interactive "P") + (unless gnus-plugged + (error "Groups can't be fetched when Gnus is unplugged")) (gnus-group-iterate n 'gnus-agent-fetch-group)) (defun gnus-agent-fetch-group (group) "Put all new articles in GROUP into the Agent." (interactive (list (gnus-group-group-name))) + (unless gnus-plugged + (error "Groups can't be fetched when Gnus is unplugged")) (unless group (error "No group on the current line")) (let ((gnus-command-method (gnus-find-method-for-group group))) @@ -390,6 +415,38 @@ be a select method." (setf (cadddr cat) (nconc (cadddr cat) groups)) (gnus-category-write))) +(defun gnus-agent-remove-group (arg) + "Remove the current group from its agent category, if any." + (interactive "P") + (let (c) + (gnus-group-iterate arg + (lambda (group) + (when (cadddr (setq c (gnus-group-category group))) + (setf (cadddr c) (delete group (cadddr c)))))) + (gnus-category-write))) + +(defun gnus-agent-synchronize () + "Synchronize local, unplugged, data with backend. +Currently sends flag setting requests, if any." + (interactive) + (save-excursion + (dolist (gnus-command-method gnus-agent-covered-methods) + (when (file-exists-p (gnus-agent-lib-file "flags")) + (set-buffer (get-buffer-create " *Gnus Agent flag synchronize*")) + (erase-buffer) + (nnheader-insert-file-contents (gnus-agent-lib-file "flags")) + (if (null (gnus-check-server gnus-command-method)) + (message "Couldn't open server %s" (nth 1 gnus-command-method)) + (while (not (eobp)) + (if (null (eval (read (current-buffer)))) + (progn (forward-line) + (kill-line -1)) + (write-file (gnus-agent-lib-file "flags")) + (error "Couldn't set flags from file %s" + (gnus-agent-lib-file "flags")))) + (write-file (gnus-agent-lib-file "flags"))) + (kill-buffer nil))))) + ;;; ;;; Server mode commands ;;; @@ -427,8 +484,11 @@ be a select method." (defun gnus-agent-write-servers () "Write the alist of covered servers." - (nnheader-temp-write (nnheader-concat gnus-agent-directory "lib/servers") - (prin1 gnus-agent-covered-methods (current-buffer)))) + (gnus-make-directory (nnheader-concat gnus-agent-directory "lib")) + (let ((coding-system-for-write nnheader-file-coding-system) + (file-name-coding-system nnmail-pathname-coding-system)) + (with-temp-file (nnheader-concat gnus-agent-directory "lib/servers") + (prin1 gnus-agent-covered-methods (current-buffer))))) ;;; ;;; Summary commands @@ -492,12 +552,24 @@ the actual number of articles toggled is returned." (when (and (not gnus-plugged) (gnus-agent-method-p gnus-command-method)) (gnus-agent-load-alist gnus-newsgroup-name) - (let ((articles gnus-newsgroup-unreads) + ;; First mark all undownloaded articles as undownloaded. + (let ((articles (append gnus-newsgroup-unreads + gnus-newsgroup-marked + gnus-newsgroup-dormant)) article) (while (setq article (pop articles)) (unless (or (cdr (assq article gnus-agent-article-alist)) - (memq article gnus-newsgroup-downloadable)) - (push article gnus-newsgroup-undownloaded))))))) + (memq article gnus-newsgroup-downloadable) + (memq article gnus-newsgroup-cached)) + (push article gnus-newsgroup-undownloaded)))) + ;; Then mark downloaded downloadable as not-downloadable, + ;; if you get my drift. + (let ((articles gnus-newsgroup-downloadable) + article) + (while (setq article (pop articles)) + (when (cdr (assq article gnus-agent-article-alist)) + (setq gnus-newsgroup-downloadable + (delq article gnus-newsgroup-downloadable)))))))) (defun gnus-agent-catchup () "Mark all undownloaded articles as read." @@ -513,53 +585,86 @@ the actual number of articles toggled is returned." ;;; (defun gnus-agent-save-active (method) + (gnus-agent-save-active-1 method 'gnus-active-to-gnus-format)) + +(defun gnus-agent-save-active-1 (method function) (when (gnus-agent-method-p method) (let* ((gnus-command-method method) + (new (gnus-make-hashtable (count-lines (point-min) (point-max)))) (file (gnus-agent-lib-file "active"))) - (gnus-make-directory (file-name-directory file)) - (let ((coding-system-for-write gnus-agent-file-coding-system)) - (write-region (point-min) (point-max) file nil 'silent)) - (when (file-exists-p (gnus-agent-lib-file "groups")) - (delete-file (gnus-agent-lib-file "groups")))))) + (funcall function nil new) + (gnus-agent-write-active file new) + (erase-buffer) + (nnheader-insert-file-contents file)))) -(defun gnus-agent-save-groups (method) - (let* ((gnus-command-method method) - (file (gnus-agent-lib-file "groups"))) +(defun gnus-agent-write-active (file new) + (let ((orig (gnus-make-hashtable (count-lines (point-min) (point-max)))) + (file (gnus-agent-lib-file "active")) + elem osym) + (when (file-exists-p file) + (with-temp-buffer + (nnheader-insert-file-contents file) + (gnus-active-to-gnus-format nil orig)) + (mapatoms + (lambda (sym) + (when (and sym (boundp sym)) + (if (and (boundp (setq osym (intern (symbol-name sym) orig))) + (setq elem (symbol-value osym))) + (setcdr elem (cdr (symbol-value sym))) + (set (intern (symbol-name sym) orig) (symbol-value sym))))) + new)) (gnus-make-directory (file-name-directory file)) (let ((coding-system-for-write gnus-agent-file-coding-system)) - (write-region (point-min) (point-max) file nil 'silent)) - (when (file-exists-p (gnus-agent-lib-file "active")) - (delete-file (gnus-agent-lib-file "active"))))) + ;; The hashtable contains real names of groups, no more prefix + ;; removing, so set `full' to `t'. + (gnus-write-active-file file orig t)))) + +(defun gnus-agent-save-groups (method) + (gnus-agent-save-active-1 method 'gnus-groups-to-gnus-format)) (defun gnus-agent-save-group-info (method group active) (when (gnus-agent-method-p method) (let* ((gnus-command-method method) - (file (if nntp-server-list-active-group - (gnus-agent-lib-file "active") - (gnus-agent-lib-file "groups")))) + (coding-system-for-write nnheader-file-coding-system) + (file-name-coding-system nnmail-pathname-coding-system) + (file (gnus-agent-lib-file "active")) + oactive) (gnus-make-directory (file-name-directory file)) - (nnheader-temp-write file + (with-temp-file file + ;; Emacs got problem to match non-ASCII group in multibyte buffer. + (mm-disable-multibyte) (when (file-exists-p file) (nnheader-insert-file-contents file)) (goto-char (point-min)) - (if nntp-server-list-active-group - (progn - (when (re-search-forward - (concat "^" (regexp-quote group) " ") nil t) - (gnus-delete-line)) - (insert group " " (number-to-string (cdr active)) " " - (number-to-string (car active)) " y\n")) - (when (re-search-forward (concat (regexp-quote group) " ") nil t) - (gnus-delete-line)) - (insert-buffer-substring nntp-server-buffer)))))) + (when (re-search-forward + (concat "^" (regexp-quote group) " ") nil t) + (save-excursion + (save-restriction + (narrow-to-region (match-beginning 0) + (progn + (forward-line 1) + (point))) + (setq oactive (car (nnmail-parse-active))))) + (gnus-delete-line)) + (insert (format "%S %d %d y\n" (intern group) + (cdr active) + (or (car oactive) (car active)))) + (goto-char (point-max)) + (while (search-backward "\\." nil t) + (delete-char 1)))))) (defun gnus-agent-group-path (group) "Translate GROUP into a path." (if nnmail-use-long-file-names (gnus-group-real-name group) - (nnheader-replace-chars-in-string - (nnheader-translate-file-chars (gnus-group-real-name group)) - ?. ?/))) + (nnheader-translate-file-chars + (nnheader-replace-chars-in-string + (nnheader-replace-duplicate-chars-in-string + (nnheader-replace-chars-in-string + (gnus-group-real-name group) + ?/ ?_) + ?. ?_) + ?. ?/)))) @@ -587,11 +692,12 @@ the actual number of articles toggled is returned." (format " *Gnus agent %s history*" (gnus-agent-method))))) gnus-agent-history-buffers) + (mm-disable-multibyte) ;; everything is binary (erase-buffer) (insert "\n") (let ((file (gnus-agent-lib-file "history"))) (when (file-exists-p file) - (insert-file file)) + (nnheader-insert-file-contents file)) (set (make-local-variable 'gnus-agent-file-name) file)))) (defun gnus-agent-save-history () @@ -613,11 +719,15 @@ the actual number of articles toggled is returned." (save-excursion (set-buffer gnus-agent-current-history) (goto-char (point-max)) - (insert id "\t" (number-to-string date) "\t") - (while group-arts - (insert (caar group-arts) " " (number-to-string (cdr (pop group-arts))) - " ")) - (insert "\n"))) + (let ((p (point))) + (insert id "\t" (number-to-string date) "\t") + (while group-arts + (insert (format "%S" (intern (caar group-arts))) + " " (number-to-string (cdr (pop group-arts))) + " ")) + (insert "\n") + (while (search-backward "\\." p t) + (delete-char 1))))) (defun gnus-agent-article-in-history-p (id) (save-excursion @@ -646,7 +756,7 @@ the actual number of articles toggled is returned." ;; Prune off articles that we have already fetched. (while (and articles (cdr (assq (car articles) gnus-agent-article-alist))) - (pop articles)) + (pop articles)) (let ((arts articles)) (while (cdr arts) (if (cdr (assq (cadr arts) gnus-agent-article-alist)) @@ -656,7 +766,7 @@ the actual number of articles toggled is returned." (let ((dir (concat (gnus-agent-directory) (gnus-agent-group-path group) "/")) - (date (gnus-time-to-day (current-time))) + (date (time-to-days (current-time))) (case-fold-search t) pos crosses id elem) (gnus-make-directory dir) @@ -664,10 +774,13 @@ the actual number of articles toggled is returned." ;; Fetch the articles from the backend. (if (gnus-check-backend-function 'retrieve-articles group) (setq pos (gnus-retrieve-articles articles group)) - (nnheader-temp-write nil + (with-temp-buffer (let (article) (while (setq article (pop articles)) - (when (gnus-request-article article group) + (when (or + (gnus-backlog-request-article group article + nntp-server-buffer) + (gnus-request-article article group)) (goto-char (point-max)) (push (cons article (point)) pos) (insert-buffer-substring nntp-server-buffer))) @@ -726,7 +839,7 @@ the actual number of articles toggled is returned." (setcdr alist (cons (cons (cdar crosses) t) (cdr alist))) (save-excursion (set-buffer (gnus-get-buffer-create (format " *Gnus agent overview %s*" - group))) + group))) (when (= (point-max) (point-min)) (push (cons group (current-buffer)) gnus-agent-buffer-alist) (ignore-errors @@ -749,43 +862,65 @@ the actual number of articles toggled is returned." nil 'silent)) (pop gnus-agent-buffer-alist)) (while gnus-agent-group-alist - (nnheader-temp-write (caar gnus-agent-group-alist) + (with-temp-file (caar gnus-agent-group-alist) (princ (cdar gnus-agent-group-alist)) (insert "\n")) (pop gnus-agent-group-alist)))) +(if (fboundp 'union) + (defalias 'gnus-agent-union 'union) + (defun gnus-agent-union (l1 l2) + "Set union of lists L1 and L2." + (cond ((null l1) l2) + ((null l2) l1) + ((equal l1 l2) l1) + (t + (or (>= (length l1) (length l2)) + (setq l1 (prog1 l2 (setq l2 l1)))) + (while l2 + (or (memq (car l2) l1) + (push (car l2) l1)) + (pop l2)) + l1)))) + (defun gnus-agent-fetch-headers (group &optional force) - (let ((articles (if (gnus-agent-load-alist group) - (gnus-sorted-intersection - (gnus-list-of-unread-articles group) - (gnus-uncompress-range - (cons (1+ (caar (last gnus-agent-article-alist))) - (cdr (gnus-active group))))) - (gnus-list-of-unread-articles group)))) + (let ((articles (gnus-list-of-unread-articles group)) + (gnus-decode-encoded-word-function 'identity) + (file (gnus-agent-article-name ".overview" group))) + ;; Add article with marks to list of article headers we want to fetch. + (dolist (arts (gnus-info-marks (gnus-get-info group))) + (setq articles (gnus-agent-union (gnus-uncompress-sequence (cdr arts)) + articles))) + (setq articles (sort articles '<)) + ;; Remove known articles. + (when (gnus-agent-load-alist group) + (setq articles (gnus-sorted-intersection + articles + (gnus-uncompress-range + (cons (1+ (caar (last gnus-agent-article-alist))) + (cdr (gnus-active group))))))) ;; Fetch them. + (gnus-make-directory (nnheader-translate-file-chars + (file-name-directory file) t)) (when articles (gnus-message 7 "Fetching headers for %s..." group) (save-excursion - (set-buffer nntp-server-buffer) - (unless (eq 'nov (gnus-retrieve-headers articles group)) - (nnvirtual-convert-headers)) - ;; Save these headers for later processing. - (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max)) - (let (file) - (when (file-exists-p - (setq file (gnus-agent-article-name ".overview" group))) - (gnus-agent-braid-nov group articles file)) - (gnus-make-directory (nnheader-translate-file-chars - (file-name-directory file))) - (let ((coding-system-for-write - gnus-agent-file-coding-system)) - (write-region (point-min) (point-max) file nil 'silent)) - (gnus-agent-save-alist group articles nil) - (gnus-agent-enter-history - "last-header-fetched-for-session" - (list (cons group (nth (- (length articles) 1) articles))) - (gnus-time-to-day (current-time))) - articles))))) + (set-buffer nntp-server-buffer) + (unless (eq 'nov (gnus-retrieve-headers articles group)) + (nnvirtual-convert-headers)) + ;; Save these headers for later processing. + (copy-to-buffer gnus-agent-overview-buffer (point-min) (point-max)) + (when (file-exists-p file) + (gnus-agent-braid-nov group articles file)) + (let ((coding-system-for-write + gnus-agent-file-coding-system)) + (write-region (point-min) (point-max) file nil 'silent)) + (gnus-agent-save-alist group articles nil) + (gnus-agent-enter-history + "last-header-fetched-for-session" + (list (cons group (nth (- (length articles) 1) articles))) + (time-to-days (current-time))) + articles)))) (defsubst gnus-agent-copy-nov-line (article) (let (b e) @@ -846,26 +981,33 @@ the actual number of articles toggled is returned." (defun gnus-agent-save-alist (group &optional articles state dir) "Save the article-state alist for GROUP." - (nnheader-temp-write (if dir - (concat dir ".agentview") - (gnus-agent-article-name ".agentview" group)) - (princ (setq gnus-agent-article-alist - (nconc gnus-agent-article-alist - (mapcar (lambda (article) (cons article state)) - articles))) - (current-buffer)) - (insert "\n"))) + (let ((file-name-coding-system nnmail-pathname-coding-system)) + (with-temp-file (if dir + (concat dir ".agentview") + (gnus-agent-article-name ".agentview" group)) + (princ (setq gnus-agent-article-alist + (nconc gnus-agent-article-alist + (mapcar (lambda (article) (cons article state)) + articles))) + (current-buffer)) + (insert "\n")))) (defun gnus-agent-article-name (article group) (concat (gnus-agent-directory) (gnus-agent-group-path group) "/" (if (stringp article) article (string-to-number article)))) +(defun gnus-agent-batch-confirmation (msg) + "Show error message and return t." + (gnus-message 1 msg) + t) + ;;;###autoload (defun gnus-agent-batch-fetch () "Start Gnus and fetch session." (interactive) (gnus) - (gnus-agent-fetch-session) + (let ((gnus-agent-confirmation-function 'gnus-agent-batch-confirmation)) + (gnus-agent-fetch-session)) (gnus-group-exit)) (defun gnus-agent-fetch-session () @@ -879,51 +1021,108 @@ the actual number of articles toggled is returned." groups group gnus-command-method) (save-excursion (while methods - (setq gnus-command-method (car methods)) - (when (or (gnus-server-opened gnus-command-method) - (gnus-open-server gnus-command-method)) - (setq groups (gnus-groups-from-server (car methods))) - (gnus-agent-with-fetch - (while (setq group (pop groups)) - (when (<= (gnus-group-level group) gnus-agent-handle-level) - (gnus-agent-fetch-group-1 group gnus-command-method))))) + (condition-case err + (progn + (setq gnus-command-method (car methods)) + (when (or (gnus-server-opened gnus-command-method) + (gnus-open-server gnus-command-method)) + (setq groups (gnus-groups-from-server (car methods))) + (gnus-agent-with-fetch + (while (setq group (pop groups)) + (when (<= (gnus-group-level group) gnus-agent-handle-level) + (gnus-agent-fetch-group-1 group gnus-command-method)))))) + (error + (unless (funcall gnus-agent-confirmation-function + (format "Error (%s). Continue? " err)) + (error "Cannot fetch articles into the Gnus agent.")))) (pop methods)) (gnus-message 6 "Finished fetching articles into the Gnus agent")))) (defun gnus-agent-fetch-group-1 (group method) "Fetch GROUP." (let ((gnus-command-method method) + (gnus-newsgroup-name group) gnus-newsgroup-dependencies gnus-newsgroup-headers gnus-newsgroup-scored gnus-headers gnus-score gnus-use-cache articles arts - category predicate info marks score-param) + category predicate info marks score-param + (gnus-summary-expunge-below gnus-summary-expunge-below) + (gnus-summary-mark-below gnus-summary-mark-below) + (gnus-orphan-score gnus-orphan-score) + ;; Maybe some other gnus-summary local variables should also + ;; be put here. + ) + (unless (gnus-check-group group) + (error "Can't open server for %s" group)) ;; Fetch headers. (when (and (or (gnus-active group) (gnus-activate-group group)) - (setq articles (gnus-agent-fetch-headers group))) - ;; Parse them and see which articles we want to fetch. - (setq gnus-newsgroup-dependencies - (make-vector (length articles) 0)) - (setq gnus-newsgroup-headers - (gnus-get-newsgroup-headers-xover articles nil nil group)) + (setq articles (gnus-agent-fetch-headers group)) + (progn + ;; Parse them and see which articles we want to fetch. + (setq gnus-newsgroup-dependencies + (make-vector (length articles) 0)) + ;; No need to call `gnus-get-newsgroup-headers-xover' with + ;; the entire .overview for group as we still have the just + ;; downloaded headers in `gnus-agent-overview-buffer'. + (let ((nntp-server-buffer gnus-agent-overview-buffer)) + (setq gnus-newsgroup-headers + (gnus-get-newsgroup-headers-xover articles nil nil + group))) + ;; `gnus-agent-overview-buffer' may be killed for + ;; timeout reason. If so, recreate it. + (gnus-agent-create-buffer))) (setq category (gnus-group-category group)) (setq predicate (gnus-get-predicate - (or (gnus-group-get-parameter group 'agent-predicate) + (or (gnus-group-find-parameter group 'agent-predicate t) (cadr category)))) - (setq score-param - (or (gnus-group-get-parameter group 'agent-score) - (caddr category))) - (when score-param - (gnus-score-headers (list (list score-param)))) - (setq arts nil) - (while (setq gnus-headers (pop gnus-newsgroup-headers)) - (setq gnus-score - (or (cdr (assq (mail-header-number gnus-headers) - gnus-newsgroup-scored)) - gnus-summary-default-score)) - (when (funcall predicate) - (push (mail-header-number gnus-headers) - arts))) + ;; Do we want to download everything, or nothing? + (if (or (eq (caaddr predicate) 'gnus-agent-true) + (eq (caaddr predicate) 'gnus-agent-false)) + ;; Yes. + (setq arts (symbol-value + (cadr (assoc (caaddr predicate) + '((gnus-agent-true articles) + (gnus-agent-false nil)))))) + ;; No, we need to decide what we want. + (setq score-param + (let ((score-method + (or + (gnus-group-find-parameter group 'agent-score t) + (caddr category)))) + (when score-method + (require 'gnus-score) + (if (eq score-method 'file) + (let ((entries + (gnus-score-load-files + (gnus-all-score-files group))) + list score-file) + (while (setq list (car entries)) + (push (car list) score-file) + (setq list (cdr list)) + (while list + (when (member (caar list) + gnus-agent-scoreable-headers) + (push (car list) score-file)) + (setq list (cdr list))) + (setq score-param + (append score-param (list (nreverse score-file))) + score-file nil entries (cdr entries))) + (list score-param)) + (if (stringp (car score-method)) + score-method + (list (list score-method))))))) + (when score-param + (gnus-score-headers score-param)) + (setq arts nil) + (while (setq gnus-headers (pop gnus-newsgroup-headers)) + (setq gnus-score + (or (cdr (assq (mail-header-number gnus-headers) + gnus-newsgroup-scored)) + gnus-summary-default-score)) + (when (funcall predicate) + (push (mail-header-number gnus-headers) + arts)))) ;; Fetch the articles. (when arts (gnus-agent-fetch-articles group arts))) @@ -934,7 +1133,11 @@ the actual number of articles toggled is returned." (gnus-agent-fetch-articles group (gnus-uncompress-range (cdr arts))) (setq marks (delq arts (gnus-info-marks info))) - (gnus-info-set-marks info marks)))) + (gnus-info-set-marks info marks) + (gnus-dribble-enter + (concat "(gnus-group-set-info '" + (gnus-prin1-to-string info) + ")"))))) ;;; ;;; Agent Category Mode @@ -1036,7 +1239,7 @@ The following commands are available: (gnus-set-default-directory) (setq mode-line-process nil) (use-local-map gnus-category-mode-map) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq truncate-lines t) (setq buffer-read-only t) (gnus-run-hooks 'gnus-category-mode-hook)) @@ -1093,7 +1296,8 @@ The following commands are available: "Write the category alist." (setq gnus-category-predicate-cache nil gnus-category-group-cache nil) - (nnheader-temp-write (nnheader-concat gnus-agent-directory "lib/categories") + (gnus-make-directory (nnheader-concat gnus-agent-directory "lib")) + (with-temp-file (nnheader-concat gnus-agent-directory "lib/categories") (prin1 gnus-category-alist (current-buffer)))) (defun gnus-category-edit-predicate (category) @@ -1103,7 +1307,7 @@ The following commands are available: (gnus-edit-form (cadr info) (format "Editing the predicate for category %s" category) `(lambda (predicate) - (setf (cadr (assq ',category gnus-category-alist)) predicate) + (setcar (cdr (assq ',category gnus-category-alist)) predicate) (gnus-category-write) (gnus-category-list))))) @@ -1115,7 +1319,7 @@ The following commands are available: (caddr info) (format "Editing the score expression for category %s" category) `(lambda (groups) - (setf (caddr (assq ',category gnus-category-alist)) groups) + (setcar (cddr (assq ',category gnus-category-alist)) groups) (gnus-category-write) (gnus-category-list))))) @@ -1126,7 +1330,7 @@ The following commands are available: (gnus-edit-form (cadddr info) (format "Editing the group list for category %s" category) `(lambda (groups) - (setf (cadddr (assq ',category gnus-category-alist)) groups) + (setcar (nthcdr 3 (assq ',category gnus-category-alist)) groups) (gnus-category-write) (gnus-category-list))))) @@ -1136,8 +1340,8 @@ The following commands are available: (let ((info (assq category gnus-category-alist)) (buffer-read-only nil)) (gnus-delete-line) - (gnus-category-write) - (setq gnus-category-alist (delq info gnus-category-alist)))) + (setq gnus-category-alist (delq info gnus-category-alist)) + (gnus-category-write))) (defun gnus-category-copy (category to) "Copy the current category." @@ -1154,7 +1358,7 @@ The following commands are available: (interactive "SCategory name: ") (when (assq category gnus-category-alist) (error "Category %s already exists" category)) - (push (list category 'true nil nil) + (push (list category 'false nil nil) gnus-category-alist) (gnus-category-write) (gnus-category-list)) @@ -1267,145 +1471,169 @@ The following commands are available: "Expire all old articles." (interactive) (let ((methods gnus-agent-covered-methods) - (day (- (gnus-time-to-day (current-time)) gnus-agent-expire-days)) + (day (- (time-to-days (current-time)) gnus-agent-expire-days)) gnus-command-method sym group articles history overview file histories elem art nov-file low info - unreads marked article) + unreads marked article orig lowest highest) (save-excursion (setq overview (gnus-get-buffer-create " *expire overview*")) (while (setq gnus-command-method (pop methods)) - (let ((expiry-hashtb (gnus-make-hashtable 1023))) - (gnus-agent-open-history) - (set-buffer - (setq gnus-agent-current-history - (setq history (gnus-agent-history-buffer)))) - (goto-char (point-min)) - (when (> (buffer-size) 1) - (goto-char (point-min)) - (while (not (eobp)) - (skip-chars-forward "^\t") - (if (> (read (current-buffer)) day) - ;; New article; we don't expire it. - (forward-line 1) - ;; Old article. Schedule it for possible nuking. - (while (not (eolp)) - (setq sym (let ((obarray expiry-hashtb)) - (read (current-buffer)))) - (if (boundp sym) - (set sym (cons (cons (read (current-buffer)) (point)) - (symbol-value sym))) - (set sym (list (cons (read (current-buffer)) (point))))) - (skip-chars-forward " ")) - (forward-line 1))) - ;; We now have all articles that can possibly be expired. - (mapatoms - (lambda (sym) - (setq group (symbol-name sym) - articles (sort (symbol-value sym) 'car-less-than-car) - low (car (gnus-active group)) - info (gnus-get-info group) - unreads (ignore-errors (gnus-list-of-unread-articles group)) - marked (nconc (gnus-uncompress-range - (cdr (assq 'tick (gnus-info-marks info)))) - (gnus-uncompress-range - (cdr (assq 'dormant - (gnus-info-marks info))))) - nov-file (gnus-agent-article-name ".overview" group)) - (when info - (gnus-agent-load-alist group) - (gnus-message 5 "Expiring articles in %s" group) - (set-buffer overview) - (erase-buffer) - (when (file-exists-p nov-file) - (nnheader-insert-file-contents nov-file)) - (goto-char (point-min)) - (setq article 0) - (while (setq elem (pop articles)) - (setq article (car elem)) - (when (or (null low) - (< article low) - gnus-agent-expire-all - (and (not (memq article unreads)) - (not (memq article marked)))) - ;; Find and nuke the NOV line. - (while (and (not (eobp)) - (or (not (numberp - (setq art (read (current-buffer))))) - (< art article))) - (if (file-exists-p - (gnus-agent-article-name - (number-to-string art) group)) - (forward-line 1) - ;; Remove old NOV lines that have no articles. - (gnus-delete-line))) - (if (or (eobp) - (/= art article)) - (beginning-of-line) - (gnus-delete-line)) - ;; Nuke the article. - (when (file-exists-p (setq file (gnus-agent-article-name - (number-to-string article) - group))) - (delete-file file)) - ;; Schedule the history line for nuking. - (push (cdr elem) histories))) - (gnus-make-directory (file-name-directory nov-file)) - (let ((coding-system-for-write - gnus-agent-file-coding-system)) - (write-region (point-min) (point-max) nov-file nil 'silent)) - ;; Delete the unwanted entries in the alist. - (setq gnus-agent-article-alist - (sort gnus-agent-article-alist 'car-less-than-car)) - (let* ((alist gnus-agent-article-alist) - (prev (cons nil alist)) - (first prev) - expired) - (while (and alist - (<= (caar alist) article)) - (if (or (not (cdar alist)) - (not (file-exists-p + (when (file-exists-p (gnus-agent-lib-file "active")) + (with-temp-buffer + (nnheader-insert-file-contents (gnus-agent-lib-file "active")) + (gnus-active-to-gnus-format + gnus-command-method + (setq orig (gnus-make-hashtable + (count-lines (point-min) (point-max)))))) + (let ((expiry-hashtb (gnus-make-hashtable 1023))) + (gnus-agent-open-history) + (set-buffer + (setq gnus-agent-current-history + (setq history (gnus-agent-history-buffer)))) + (goto-char (point-min)) + (when (> (buffer-size) 1) + (goto-char (point-min)) + (while (not (eobp)) + (skip-chars-forward "^\t") + (if (> (read (current-buffer)) day) + ;; New article; we don't expire it. + (forward-line 1) + ;; Old article. Schedule it for possible nuking. + (while (not (eolp)) + (setq sym (let ((obarray expiry-hashtb) s) + (setq s (read (current-buffer))) + (if (stringp s) (intern s) s))) + (if (boundp sym) + (set sym (cons (cons (read (current-buffer)) (point)) + (symbol-value sym))) + (set sym (list (cons (read (current-buffer)) (point))))) + (skip-chars-forward " ")) + (forward-line 1))) + ;; We now have all articles that can possibly be expired. + (mapatoms + (lambda (sym) + (setq group (symbol-name sym) + articles (sort (symbol-value sym) 'car-less-than-car) + low (car (gnus-active group)) + info (gnus-get-info group) + unreads (ignore-errors + (gnus-list-of-unread-articles group)) + marked (nconc + (gnus-uncompress-range + (cdr (assq 'tick (gnus-info-marks info)))) + (gnus-uncompress-range + (cdr (assq 'dormant + (gnus-info-marks info))))) + nov-file (gnus-agent-article-name ".overview" group) + lowest nil + highest nil) + (gnus-agent-load-alist group) + (gnus-message 5 "Expiring articles in %s" group) + (set-buffer overview) + (erase-buffer) + (when (file-exists-p nov-file) + (nnheader-insert-file-contents nov-file)) + (goto-char (point-min)) + (setq article 0) + (while (setq elem (pop articles)) + (setq article (car elem)) + (when (or (null low) + (< article low) + gnus-agent-expire-all + (and (not (memq article unreads)) + (not (memq article marked)))) + ;; Find and nuke the NOV line. + (while (and (not (eobp)) + (or (not (numberp + (setq art (read (current-buffer))))) + (< art article))) + (if (and (numberp art) + (file-exists-p (gnus-agent-article-name - (number-to-string - (caar alist)) - group)))) - (progn - (push (caar alist) expired) - (setcdr prev (setq alist (cdr alist)))) - (setq prev alist - alist (cdr alist)))) - (setq gnus-agent-article-alist (cdr first)) - (gnus-agent-save-alist group) - ;; Mark all articles up to the first article - ;; in `gnus-article-alist' as read. - (when (and info (caar gnus-agent-article-alist)) - (setcar (nthcdr 2 info) - (gnus-range-add - (nth 2 info) - (cons 1 (- (caar gnus-agent-article-alist) 1))))) - ;; Maybe everything has been expired from `gnus-article-alist' - ;; and so the above marking as read could not be conducted, - ;; or there are expired article within the range of the alist. - (when (and (car expired) - (or (not (caar gnus-agent-article-alist)) - (> (car expired) - (caar gnus-agent-article-alist))) ) - (setcar (nthcdr 2 info) - (gnus-add-to-range - (nth 2 info) - (nreverse expired)))) - (gnus-dribble-enter - (concat "(gnus-group-set-info '" - (gnus-prin1-to-string info) - ")"))))) - expiry-hashtb) - (set-buffer history) - (setq histories (nreverse (sort histories '<))) - (while histories - (goto-char (pop histories)) - (gnus-delete-line)) - (gnus-agent-save-history) - (gnus-agent-close-history)) - (gnus-message 4 "Expiry...done")))))) + (number-to-string art) group))) + (progn + (unless lowest + (setq lowest art)) + (setq highest art) + (forward-line 1)) + ;; Remove old NOV lines that have no articles. + (gnus-delete-line))) + (if (or (eobp) + (/= art article)) + (beginning-of-line) + (gnus-delete-line)) + ;; Nuke the article. + (when (file-exists-p + (setq file (gnus-agent-article-name + (number-to-string article) + group))) + (delete-file file)) + ;; Schedule the history line for nuking. + (push (cdr elem) histories))) + (gnus-make-directory (file-name-directory nov-file)) + (let ((coding-system-for-write + gnus-agent-file-coding-system)) + (write-region (point-min) (point-max) nov-file nil 'silent)) + ;; Delete the unwanted entries in the alist. + (setq gnus-agent-article-alist + (sort gnus-agent-article-alist 'car-less-than-car)) + (let* ((alist gnus-agent-article-alist) + (prev (cons nil alist)) + (first prev) + expired) + (while (and alist + (<= (caar alist) article)) + (if (or (not (cdar alist)) + (not (file-exists-p + (gnus-agent-article-name + (number-to-string + (caar alist)) + group)))) + (progn + (push (caar alist) expired) + (setcdr prev (setq alist (cdr alist)))) + (setq prev alist + alist (cdr alist)))) + (setq gnus-agent-article-alist (cdr first)) + (gnus-agent-save-alist group) + ;; Mark all articles up to the first article + ;; in `gnus-article-alist' as read. + (when (and info (caar gnus-agent-article-alist)) + (setcar (nthcdr 2 info) + (gnus-range-add + (nth 2 info) + (cons 1 (- (caar gnus-agent-article-alist) 1))))) + ;; Maybe everything has been expired from `gnus-article-alist' + ;; and so the above marking as read could not be conducted, + ;; or there are expired article within the range of the alist. + (when (and info + expired + (or (not (caar gnus-agent-article-alist)) + (> (car expired) + (caar gnus-agent-article-alist)))) + (setcar (nthcdr 2 info) + (gnus-add-to-range + (nth 2 info) + (nreverse expired)))) + (gnus-dribble-enter + (concat "(gnus-group-set-info '" + (gnus-prin1-to-string info) + ")"))) + (when lowest + (if (gnus-gethash group orig) + (setcar (gnus-gethash group orig) lowest) + (gnus-sethash group (cons lowest highest) orig)))) + expiry-hashtb) + (set-buffer history) + (setq histories (nreverse (sort histories '<))) + (while histories + (goto-char (pop histories)) + (gnus-delete-line)) + (gnus-agent-save-history) + (gnus-agent-close-history) + (gnus-write-active-file + (gnus-agent-lib-file "active") orig)) + (gnus-message 4 "Expiry...done"))))))) ;;;###autoload (defun gnus-agent-batch () diff --git a/lisp/gnus/gnus-art.el b/lisp/gnus/gnus-art.el index ac2aed4ba71..c552c966d10 100644 --- a/lisp/gnus/gnus-art.el +++ b/lisp/gnus/gnus-art.el @@ -1,5 +1,5 @@ ;;; gnus-art.el --- article mode commands for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,20 +27,27 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - -(require 'custom) (require 'gnus) (require 'gnus-sum) (require 'gnus-spec) (require 'gnus-int) -(require 'browse-url) +(require 'mm-bodies) +(require 'mail-parse) +(require 'mm-decode) +(require 'mm-view) +(require 'wid-edit) +(require 'mm-uu) (defgroup gnus-article nil "Article display." :link '(custom-manual "(gnus)The Article Buffer") :group 'gnus) +(defgroup gnus-article-treat nil + "Treating article parts." + :link '(custom-manual "(gnus)Article Hiding") + :group 'gnus-article) + (defgroup gnus-article-hiding nil "Hiding article parts." :link '(custom-manual "(gnus)Article Hiding") @@ -107,11 +114,19 @@ "^Originator:" "^X-Problems-To:" "^X-Auth-User:" "^X-Post-Time:" "^X-Admin:" "^X-UID:" "^Resent-[-A-Za-z]+:" "^X-Mailing-List:" "^Precedence:" "^Original-[-A-Za-z]+:" "^X-filename:" "^X-Orcpt:" - "^Old-Received:" "^X-Pgp-Fingerprint:" "^X-Pgp-Key-Id:" - "^X-Pgp-Public-Key-Url:" "^X-Auth:" "^X-From-Line:" + "^Old-Received:" "^X-Pgp" "^X-Auth:" "^X-From-Line:" "^X-Gnus-Article-Number:" "^X-Majordomo:" "^X-Url:" "^X-Sender:" - "^X-Mailing-List:" "^MBOX-Line" "^Priority:" "^X-Pgp" "^X400-[-A-Za-z]+:" - "^Status:") + "^MBOX-Line" "^Priority:" "^X-Pgp" "^X400-[-A-Za-z]+:" + "^Status:" "^X-Gnus-Mail-Source:" "^Cancel-Lock:" + "^X-FTN" "^X-EXP32-SerialNo:" "^Encoding:" "^Importance:" + "^Autoforwarded:" "^Original-Encoded-Information-Types:" "^X-Ya-Pop3:" + "^X-Face-Version:" "^X-Vms-To:" "^X-ML-NAME:" "^X-ML-COUNT:" + "^Mailing-List:" "^X-finfo:" "^X-md5sum:" "^X-md5sum-Origin:" + "^X-Sun-Charset:" "^X-Accept-Language:" "^X-Envelope-Sender:" + "^List-[A-Za-z]+:" "^X-Listprocessor-Version:" + "^X-Received:" "^X-Distribute:" "^X-Sequence:" "^X-Juno-Line-Breaks:" + "^X-Notes-Item:" "^X-MS-TNEF-Correlator:" "^x-uunet-gateway:" + "^X-Received:" "^Content-length:" "X-precedence:") "*All headers that start with this regexp will be hidden. This variable can also be a list of regexps of headers to be ignored. If `gnus-visible-headers' is non-nil, this variable will be ignored." @@ -121,7 +136,7 @@ If `gnus-visible-headers' is non-nil, this variable will be ignored." :group 'gnus-article-hiding) (defcustom gnus-visible-headers - "From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From:\\|X-Sent:" + "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^[BGF]?Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-From:\\|^X-Sent:" "*All headers that do not match this regexp will be hidden. This variable can also be a list of regexp of headers to remain visible. If this variable is non-nil, `gnus-ignored-headers' will be ignored." @@ -152,8 +167,8 @@ Possible values in this list are `empty', `newsgroups', `followup-to', (const :tag "Followup-to identical to newsgroups." followup-to) (const :tag "Reply-to identical to from." reply-to) (const :tag "Date less than four days old." date) - (const :tag "Very long To header." long-to) - (const :tag "Multiple To headers." many-to)) + (const :tag "Very long To and/or Cc header." long-to) + (const :tag "Multiple To and/or Cc headers." many-to)) :group 'gnus-article-hiding) (defcustom gnus-signature-separator '("^-- $" "^-- *$") @@ -165,7 +180,7 @@ the end of the buffer." :group 'gnus-article-signature) (defcustom gnus-signature-limit nil - "Provide a limit to what is considered a signature. + "Provide a limit to what is considered a signature. If it is a number, no signature may not be longer (in characters) than that number. If it is a floating point number, no signature may be longer (in lines) than that number. If it is a function, the function @@ -183,12 +198,20 @@ regexp. If it matches, the text in question is not a signature." :type 'sexp :group 'gnus-article-hiding) +;; Fixme: This isn't the right thing for mixed graphical and and +;; non-graphical frames in a session. +;; gnus-xmas.el overrides this for XEmacs. (defcustom gnus-article-x-face-command - "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | xv -quit -" + (if (and (fboundp 'image-type-available-p) + (image-type-available-p 'xbm)) + 'gnus-article-display-xface + "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | display -") "*String or function to be executed to display an X-Face header. If it is a string, the command will be executed in a sub-shell asynchronously. The compressed face will be piped to this command." - :type 'string ;Leave function case to Lisp. + :type '(choice string + (function-item gnus-article-display-xface) + function) :group 'gnus-article-washing) (defcustom gnus-article-x-face-too-ugly nil @@ -198,7 +221,7 @@ asynchronously. The compressed face will be piped to this command." (defcustom gnus-emphasis-alist (let ((format - "\\(\\s-\\|^\\|[-\"]\\|\\s(\\|\\s)\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\s-\\|[-?!.,;:\"]\\|\\s(\\|\\s)\\)") + "\\(\\s-\\|^\\|[-\"]\\|\\s(\\)\\(%s\\(\\w+\\(\\s-+\\w+\\)*[.,]?\\)%s\\)\\(\\s-\\|[-,;:\"]\\s-\\|[?!.]+\\s-\\|\\s)\\)") (types '(("_" "_" underline) ("/" "/" italic) @@ -232,6 +255,14 @@ is the face used for highlighting." face)) :group 'gnus-article-emphasis) +(defcustom gnus-emphasize-whitespace-regexp "^[ \t]+\\|[ \t]*\n" + "A regexp to describe whitespace which should not be emphasized. +Typical values are \"^[ \\t]+\\\\|[ \\t]*\\n\" and \"[ \\t]+\\\\|[ \\t]*\\n\". +The former avoids underlining of leading and trailing whitespace, +and the latter avoids underlining any whitespace at all." + :group 'gnus-article-emphasis + :type 'regexp) + (defface gnus-emphasis-bold '((t (:bold t))) "Face used for displaying strong emphasized text (*word*)." :group 'gnus-article-emphasis) @@ -262,6 +293,11 @@ is the face used for highlighting." Esample: (_/*word*/_)." :group 'gnus-article-emphasis) +(defface gnus-emphasis-highlight-words + '((t (:background "black" :foreground "yellow"))) + "Face used for displaying highlighted words." + :group 'gnus-article-emphasis) + (defcustom gnus-article-time-format "%a, %b %d %Y %T %Z" "Format for display of Date headers in article bodies. See `format-time-string' for the possible values. @@ -274,8 +310,6 @@ be fed to `format-time-string'." :group 'gnus-article-washing) (eval-and-compile - (autoload 'hexl-hex-string-to-integer "hexl") - (autoload 'timezone-make-date-arpa-standard "timezone") (autoload 'mail-extract-address-components "mail-extr")) (defcustom gnus-save-all-headers t @@ -377,34 +411,6 @@ be used as possible file names." (cons :value ("" "") regexp (repeat string)) (sexp :value nil)))) -(defcustom gnus-strict-mime t - "*If nil, MIME-decode even if there is no Mime-Version header." - :group 'gnus-article-mime - :type 'boolean) - -(defcustom gnus-show-mime-method 'metamail-buffer - "Function to process a MIME message. -The function is called from the article buffer." - :group 'gnus-article-mime - :type 'function) - -(defcustom gnus-decode-encoded-word-method 'gnus-article-de-quoted-unreadable - "*Function to decode MIME encoded words. -The function is called from the article buffer." - :group 'gnus-article-mime - :type 'function) - -(defcustom gnus-show-traditional-method - (if (and (featurep 'mule) - (boundp 'enable-multibyte-characters)) - (lambda () - (if enable-multibyte-characters (gnus-mule-decode-article))) - (lambda ())) - "Function to decode ``localized RFC 822 messages''. -The function is called from the article buffer." - :group 'gnus-article-mime - :type 'function) - (defcustom gnus-page-delimiter "^\^L" "*Regexp describing what to use as article page delimiters. The default value is \"^\^L\", which is a form linefeed at the @@ -412,9 +418,14 @@ beginning of a line." :type 'regexp :group 'gnus-article-various) -(defcustom gnus-article-mode-line-format "Gnus: %%b %S" +(defcustom gnus-article-mode-line-format "Gnus: %g [%w] %S%m" "*The format specification for the article mode line. -See `gnus-summary-mode-line-format' for a closer description." +See `gnus-summary-mode-line-format' for a closer description. + +The following additional specs are available: + +%w The article washing status. +%m The number of MIME parts in the article." :type 'string :group 'gnus-article-various) @@ -429,8 +440,7 @@ See `gnus-summary-mode-line-format' for a closer description." :group 'gnus-article-various) (defcustom gnus-article-prepare-hook nil - "*A hook called after an article has been prepared in the article buffer. -If you want to run a special decoding program like nkf, use this hook." + "*A hook called after an article has been prepared in the article buffer." :type 'hook :group 'gnus-article-various) @@ -559,8 +569,410 @@ displayed by the first non-nil matching CONTENT face." (item :tag "skip" nil) (face :value default))))) +(defcustom gnus-article-decode-hook + '(article-decode-charset article-decode-encoded-words) + "*Hook run to decode charsets in articles." + :group 'gnus-article-headers + :type 'hook) + +(defcustom gnus-display-mime-function 'gnus-display-mime + "Function to display MIME articles." + :group 'gnus-article-mime + :type 'function) + +(defvar gnus-decode-header-function 'mail-decode-encoded-word-region + "Function used to decode headers.") + +(defvar gnus-article-dumbquotes-map + '(("\202" ",") + ("\203" "f") + ("\204" ",,") + ("\205" "...") + ("\213" "<") + ("\214" "OE") + ("\221" "`") + ("\222" "'") + ("\223" "``") + ("\224" "\"") + ("\225" "*") + ("\226" "---") + ("\227" "-") + ("\231" "(TM)") + ("\233" ">") + ("\234" "oe") + ("\264" "'")) + "Table for MS-to-Latin1 translation.") + +(defcustom gnus-ignored-mime-types nil + "List of MIME types that should be ignored by Gnus." + :group 'gnus-article-mime + :type '(repeat regexp)) + +(defcustom gnus-unbuttonized-mime-types '(".*/.*") + "List of MIME types that should not be given buttons when rendered inline." + :group 'gnus-article-mime + :type '(repeat regexp)) + +(defcustom gnus-article-mime-part-function nil + "Function called with a MIME handle as the argument. +This is meant for people who want to do something automatic based +on parts -- for instance, adding Vcard info to a database." + :group 'gnus-article-mime + :type 'function) + +(defcustom gnus-mime-multipart-functions nil + "An alist of MIME types to functions to display them.") + +(defcustom gnus-article-date-lapsed-new-header nil + "Whether the X-Sent and Date headers can coexist. +When using `gnus-treat-date-lapsed', the \"X-Sent:\" header will +either replace the old \"Date:\" header (if this variable is nil), or +be added below it (otherwise)." + :group 'gnus-article-headers + :type 'boolean) + +(defcustom gnus-article-mime-match-handle-function 'undisplayed-alternative + "Function called with a MIME handle as the argument. +This is meant for people who want to view first matched part. +For `undisplayed-alternative' (default), the first undisplayed +part or alternative part is used. For `undisplayed', the first +undisplayed part is used. For a function, the first part which +the function return `t' is used. For `nil', the first part is +used." + :group 'gnus-article-mime + :type '(choice + (item :tag "first" :value nil) + (item :tag "undisplayed" :value undisplayed) + (item :tag "undisplayed or alternative" + :value undisplayed-alternative) + (function))) + +;;; +;;; The treatment variables +;;; + +(defvar gnus-part-display-hook nil + "Hook called on parts that are to receive treatment.") + +(defvar gnus-article-treat-custom + '(choice (const :tag "Off" nil) + (const :tag "On" t) + (const :tag "Header" head) + (const :tag "Last" last) + (integer :tag "Less") + (repeat :tag "Groups" regexp) + (sexp :tag "Predicate"))) + +(defvar gnus-article-treat-head-custom + '(choice (const :tag "Off" nil) + (const :tag "Header" head))) + +(defvar gnus-article-treat-types '("text/plain") + "Parts to treat.") + +(defvar gnus-inhibit-treatment nil + "Whether to inhibit treatment.") + +(defcustom gnus-treat-highlight-signature '(or last (typep "text/x-vcard")) + "Highlight the signature. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) +(put 'gnus-treat-highlight-signature 'highlight t) + +(defcustom gnus-treat-buttonize 100000 + "Add buttons. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) +(put 'gnus-treat-buttonize 'highlight t) + +(defcustom gnus-treat-buttonize-head 'head + "Add buttons to the head. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) +(put 'gnus-treat-buttonize-head 'highlight t) + +(defcustom gnus-treat-emphasize 50000 + "Emphasize text. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) +(put 'gnus-treat-emphasize 'highlight t) + +(defcustom gnus-treat-strip-cr nil + "Remove carriage returns. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-hide-headers 'head + "Hide headers. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) + +(defcustom gnus-treat-hide-boring-headers nil + "Hide boring headers. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) + +(defcustom gnus-treat-hide-signature nil + "Hide the signature. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-fill-article nil + "Fill the article. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-hide-citation nil + "Hide cited text. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-strip-list-identifiers 'head + "Strip list identifiers from `gnus-list-identifiers`. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-strip-pgp t + "Strip PGP signatures. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-strip-pem nil + "Strip PEM signatures. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-strip-banner t + "Strip banners from articles. +The banner to be stripped is specified in the `banner' group parameter. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-highlight-headers 'head + "Highlight the headers. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) +(put 'gnus-treat-highlight-headers 'highlight t) + +(defcustom gnus-treat-highlight-citation t + "Highlight cited text. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) +(put 'gnus-treat-highlight-citation 'highlight t) + +(defcustom gnus-treat-date-ut nil + "Display the Date in UT (GMT). +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) + +(defcustom gnus-treat-date-local nil + "Display the Date in the local timezone. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) + +(defcustom gnus-treat-date-lapsed nil + "Display the Date header in a way that says how much time has elapsed. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) + +(defcustom gnus-treat-date-original nil + "Display the date in the original timezone. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) + +(defcustom gnus-treat-date-iso8601 nil + "Display the date in the ISO8601 format. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) + +(defcustom gnus-treat-date-user-defined nil + "Display the date in a user-defined format. +The format is defined by the `gnus-article-time-format' variable. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) + +(defcustom gnus-treat-strip-headers-in-body t + "Strip the X-No-Archive header line from the beginning of the body. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-strip-trailing-blank-lines nil + "Strip trailing blank lines. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-strip-leading-blank-lines nil + "Strip leading blank lines. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-strip-multiple-blank-lines nil + "Strip multiple blank lines. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-overstrike t + "Treat overstrike highlighting. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) +(put 'gnus-treat-overstrike 'highlight t) + +(defcustom gnus-treat-display-xface (if (and gnus-xemacs (featurep 'xface)) + 'head nil) + "Display X-Face headers. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) +(put 'gnus-treat-display-xface 'highlight t) + +(defcustom gnus-treat-display-smileys (if (and gnus-xemacs + (featurep 'xpm)) + t nil) + "Display smileys. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) +(put 'gnus-treat-display-smileys 'highlight t) + +(defcustom gnus-treat-display-picons (if gnus-xemacs 'head nil) + "Display picons. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-head-custom) +(put 'gnus-treat-display-picons 'highlight t) + +(defcustom gnus-treat-capitalize-sentences nil + "Capitalize sentence-starting words. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-fill-long-lines nil + "Fill long lines. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-play-sounds nil + "Play sounds. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + +(defcustom gnus-treat-translate nil + "Translate articles from one language to another. +Valid values are nil, t, `head', `last', an integer or a predicate. +See the manual for details." + :group 'gnus-article-treat + :type gnus-article-treat-custom) + ;;; Internal variables +(defvar article-goto-body-goes-to-point-min-p nil) +(defvar gnus-article-wash-types nil) +(defvar gnus-article-emphasis-alist nil) + +(defvar gnus-article-mime-handle-alist-1 nil) +(defvar gnus-treatment-function-alist + '((gnus-treat-strip-banner gnus-article-strip-banner) + (gnus-treat-strip-headers-in-body gnus-article-strip-headers-in-body) + (gnus-treat-highlight-signature gnus-article-highlight-signature) + (gnus-treat-buttonize gnus-article-add-buttons) + (gnus-treat-fill-article gnus-article-fill-cited-article) + (gnus-treat-fill-long-lines gnus-article-fill-long-lines) + (gnus-treat-strip-cr gnus-article-remove-cr) + (gnus-treat-emphasize gnus-article-emphasize) + (gnus-treat-display-xface gnus-article-display-x-face) + (gnus-treat-hide-headers gnus-article-maybe-hide-headers) + (gnus-treat-hide-boring-headers gnus-article-hide-boring-headers) + (gnus-treat-hide-signature gnus-article-hide-signature) + (gnus-treat-hide-citation gnus-article-hide-citation) + (gnus-treat-strip-list-identifiers gnus-article-hide-list-identifiers) + (gnus-treat-strip-pgp gnus-article-hide-pgp) + (gnus-treat-strip-pem gnus-article-hide-pem) + (gnus-treat-highlight-headers gnus-article-highlight-headers) + (gnus-treat-highlight-citation gnus-article-highlight-citation) + (gnus-treat-highlight-signature gnus-article-highlight-signature) + (gnus-treat-date-ut gnus-article-date-ut) + (gnus-treat-date-local gnus-article-date-local) + (gnus-treat-date-lapsed gnus-article-date-lapsed) + (gnus-treat-date-original gnus-article-date-original) + (gnus-treat-date-user-defined gnus-article-date-user) + (gnus-treat-date-iso8601 gnus-article-date-iso8601) + (gnus-treat-strip-trailing-blank-lines + gnus-article-remove-trailing-blank-lines) + (gnus-treat-strip-leading-blank-lines + gnus-article-strip-leading-blank-lines) + (gnus-treat-strip-multiple-blank-lines + gnus-article-strip-multiple-blank-lines) + (gnus-treat-overstrike gnus-article-treat-overstrike) + (gnus-treat-buttonize-head gnus-article-add-buttons-to-head) + (gnus-treat-display-smileys gnus-smiley-display) + (gnus-treat-capitalize-sentences gnus-article-capitalize-sentences) + (gnus-treat-display-picons gnus-article-display-picons) + (gnus-treat-play-sounds gnus-earcon-display))) + +(defvar gnus-article-mime-handle-alist nil) (defvar article-lapsed-timer nil) (defvar gnus-article-current-summary nil) @@ -576,7 +988,8 @@ Initialized from `text-mode-syntax-table.") (defvar gnus-save-article-buffer nil) (defvar gnus-article-mode-line-format-alist - (nconc '((?w (gnus-article-wash-status) ?s)) + (nconc '((?w (gnus-article-wash-status) ?s) + (?m (gnus-article-mime-part-status) ?s)) gnus-summary-mode-line-format-alist)) (defvar gnus-number-of-articles-to-be-saved nil) @@ -590,7 +1003,6 @@ Initialized from `text-mode-syntax-table.") (put-text-property (max (1- b) (point-min)) b 'intangible (cddr (memq 'intangible props))))) - (defsubst gnus-article-unhide-text (b e) "Remove hidden text properties from region between B and E." (remove-text-properties b e gnus-hidden-properties) @@ -600,11 +1012,14 @@ Initialized from `text-mode-syntax-table.") (defun gnus-article-hide-text-type (b e type) "Hide text of TYPE between B and E." + (push type gnus-article-wash-types) (gnus-article-hide-text b e (cons 'article-type (cons type gnus-hidden-properties)))) (defun gnus-article-unhide-text-type (b e type) "Unhide text of TYPE between B and E." + (setq gnus-article-wash-types + (delq type gnus-article-wash-types)) (remove-text-properties b e (cons 'article-type (cons type gnus-hidden-properties))) (when (memq 'intangible gnus-hidden-properties) @@ -653,79 +1068,60 @@ Initialized from `text-mode-syntax-table.") i)) (defun article-hide-headers (&optional arg delete) - "Toggle whether to hide unwanted headers and possibly sort them as well. -If given a negative prefix, always show; if given a positive prefix, -always hide." - (interactive (gnus-article-hidden-arg)) - (current-buffer) - (if (gnus-article-check-hidden-text 'headers arg) - ;; Show boring headers as well. - (gnus-article-show-hidden-text 'boring-headers) - ;; This function might be inhibited. - (unless gnus-inhibit-hiding - (save-excursion - (save-restriction - (let ((buffer-read-only nil) - (case-fold-search t) - (props (nconc (list 'article-type 'headers) - gnus-hidden-properties)) - (max (1+ (length gnus-sorted-header-list))) - (ignored (when (not gnus-visible-headers) - (cond ((stringp gnus-ignored-headers) - gnus-ignored-headers) - ((listp gnus-ignored-headers) - (mapconcat 'identity gnus-ignored-headers - "\\|"))))) - (visible - (cond ((stringp gnus-visible-headers) - gnus-visible-headers) - ((and gnus-visible-headers - (listp gnus-visible-headers)) - (mapconcat 'identity gnus-visible-headers "\\|")))) - (inhibit-point-motion-hooks t) - beg) - ;; First we narrow to just the headers. - (widen) - (goto-char (point-min)) - ;; Hide any "From " lines at the beginning of (mail) articles. - (while (looking-at "From ") - (forward-line 1)) - (unless (bobp) - (if delete - (delete-region (point-min) (point)) - (gnus-article-hide-text (point-min) (point) props))) - ;; Then treat the rest of the header lines. - (narrow-to-region - (point) - (if (search-forward "\n\n" nil t) ; if there's a body - (progn (forward-line -1) (point)) - (point-max))) - ;; Then we use the two regular expressions - ;; `gnus-ignored-headers' and `gnus-visible-headers' to - ;; select which header lines is to remain visible in the - ;; article buffer. - (goto-char (point-min)) - (while (re-search-forward "^[^ \t]*:" nil t) - (beginning-of-line) - ;; Mark the rank of the header. - (put-text-property - (point) (1+ (point)) 'message-rank - (if (or (and visible (looking-at visible)) - (and ignored - (not (looking-at ignored)))) - (gnus-article-header-rank) - (+ 2 max))) - (forward-line 1)) - (message-sort-headers-1) - (when (setq beg (text-property-any - (point-min) (point-max) 'message-rank (+ 2 max))) - ;; We make the unwanted headers invisible. - (if delete - (delete-region beg (point-max)) - ;; Suggested by Sudish Joseph . - (gnus-article-hide-text-type beg (point-max) 'headers)) - ;; Work around XEmacs lossage. - (put-text-property (point-min) beg 'invisible nil)))))))) + "Hide unwanted headers and possibly sort them as well." + (interactive) + ;; This function might be inhibited. + (unless gnus-inhibit-hiding + (save-excursion + (save-restriction + (let ((buffer-read-only nil) + (case-fold-search t) + (max (1+ (length gnus-sorted-header-list))) + (ignored (when (not gnus-visible-headers) + (cond ((stringp gnus-ignored-headers) + gnus-ignored-headers) + ((listp gnus-ignored-headers) + (mapconcat 'identity gnus-ignored-headers + "\\|"))))) + (visible + (cond ((stringp gnus-visible-headers) + gnus-visible-headers) + ((and gnus-visible-headers + (listp gnus-visible-headers)) + (mapconcat 'identity gnus-visible-headers "\\|")))) + (inhibit-point-motion-hooks t) + beg) + ;; First we narrow to just the headers. + (article-narrow-to-head) + ;; Hide any "From " lines at the beginning of (mail) articles. + (while (looking-at "From ") + (forward-line 1)) + (unless (bobp) + (delete-region (point-min) (point))) + ;; Then treat the rest of the header lines. + ;; Then we use the two regular expressions + ;; `gnus-ignored-headers' and `gnus-visible-headers' to + ;; select which header lines is to remain visible in the + ;; article buffer. + (while (re-search-forward "^[^ \t]*:" nil t) + (beginning-of-line) + ;; Mark the rank of the header. + (put-text-property + (point) (1+ (point)) 'message-rank + (if (or (and visible (looking-at visible)) + (and ignored + (not (looking-at ignored)))) + (gnus-article-header-rank) + (+ 2 max))) + (forward-line 1)) + (message-sort-headers-1) + (when (setq beg (text-property-any + (point-min) (point-max) 'message-rank (+ 2 max))) + ;; We delete the unwanted headers. + (push 'headers gnus-article-wash-types) + (add-text-properties (point-min) (+ 5 (point-min)) + '(article-type headers dummy-invisible t)) + (delete-region beg (point-max)))))))) (defun article-hide-boring-headers (&optional arg) "Toggle hiding of headers that aren't very interesting. @@ -740,14 +1136,14 @@ always hide." (list gnus-boring-article-headers) (inhibit-point-motion-hooks t) elem) - (nnheader-narrow-to-headers) + (article-narrow-to-head) (while list (setq elem (pop list)) (goto-char (point-min)) (cond ;; Hide empty headers. ((eq elem 'empty) - (while (re-search-forward "^[^:]+:[ \t]*\n[^ \t]" nil t) + (while (re-search-forward "^[^: \t]+:[ \t]*\n[^ \t]" nil t) (forward-line -1) (gnus-article-hide-text-type (progn (beginning-of-line) (point)) @@ -782,15 +1178,19 @@ always hide." ((eq elem 'date) (let ((date (message-fetch-field "date"))) (when (and date - (< (gnus-days-between (current-time-string) date) + (< (days-between (current-time-string) date) 4)) (gnus-article-hide-header "date")))) ((eq elem 'long-to) - (let ((to (message-fetch-field "to"))) + (let ((to (message-fetch-field "to")) + (cc (message-fetch-field "cc"))) (when (> (length to) 1024) - (gnus-article-hide-header "to")))) + (gnus-article-hide-header "to")) + (when (> (length cc) 1024) + (gnus-article-hide-header "cc")))) ((eq elem 'many-to) - (let ((to-count 0)) + (let ((to-count 0) + (cc-count 0)) (goto-char (point-min)) (while (re-search-forward "^to:" nil t) (setq to-count (1+ to-count))) @@ -802,7 +1202,19 @@ always hide." (forward-line -1) (narrow-to-region (point) (point-max)) (gnus-article-hide-header "to")) - (setq to-count (1- to-count))))))))))))) + (setq to-count (1- to-count)))) + (goto-char (point-min)) + (while (re-search-forward "^cc:" nil t) + (setq cc-count (1+ cc-count))) + (when (> cc-count 1) + (while (> cc-count 0) + (goto-char (point-min)) + (save-restriction + (re-search-forward "^cc:" nil nil cc-count) + (forward-line -1) + (narrow-to-region (point) (point-max)) + (gnus-article-hide-header "cc")) + (setq cc-count (1- cc-count))))))))))))) (defun gnus-article-hide-header (header) (save-excursion @@ -817,18 +1229,50 @@ always hide." (point-max))) 'boring-headers)))) -(defun article-treat-dumbquotes () - "Translate M******** sm*rtq**t*s into proper text." +(defvar gnus-article-normalized-header-length 40 + "Length of normalized headers.") + +(defun article-normalize-headers () + "Make all header lines 40 characters long." (interactive) - (article-translate-characters "\221\222\223\223" "`'\"\"")) + (let ((buffer-read-only nil) + column) + (save-excursion + (save-restriction + (article-narrow-to-head) + (while (not (eobp)) + (cond + ((< (setq column (- (gnus-point-at-eol) (point))) + gnus-article-normalized-header-length) + (end-of-line) + (insert (make-string + (- gnus-article-normalized-header-length column) + ? ))) + ((> column gnus-article-normalized-header-length) + (gnus-put-text-property + (progn + (forward-char gnus-article-normalized-header-length) + (point)) + (gnus-point-at-eol) + 'invisible t)) + (t + ;; Do nothing. + )) + (forward-line 1)))))) + +(defun article-treat-dumbquotes () + "Translate M******** sm*rtq**t*s into proper text. +Note that this function guesses whether a character is a sm*rtq**t* or +not, so it should only be used interactively." + (interactive) + (article-translate-strings gnus-article-dumbquotes-map)) (defun article-translate-characters (from to) "Translate all characters in the body of the article according to FROM and TO. FROM is a string of characters to translate from; to is a string of characters to translate to." (save-excursion - (goto-char (point-min)) - (when (search-forward "\n\n" nil t) + (when (article-goto-body) (let ((buffer-read-only nil) (x (make-string 225 ?x)) (i -1)) @@ -840,15 +1284,26 @@ characters to translate to." (incf i)) (translate-region (point) (point-max) x))))) +(defun article-translate-strings (map) + "Translate all string in the body of the article according to MAP. +MAP is an alist where the elements are on the form (\"from\" \"to\")." + (save-excursion + (when (article-goto-body) + (let ((buffer-read-only nil) + elem) + (while (setq elem (pop map)) + (save-excursion + (while (search-forward (car elem) nil t) + (replace-match (cadr elem))))))))) + (defun article-treat-overstrike () "Translate overstrikes into bold text." (interactive) (save-excursion - (goto-char (point-min)) - (when (search-forward "\n\n" nil t) + (when (article-goto-body) (let ((buffer-read-only nil)) (while (search-forward "\b" nil t) - (let ((next (following-char)) + (let ((next (char-after)) (previous (char-after (- (point) 2)))) ;; We do the boldification/underlining by hiding the ;; overstrikes and putting the proper text property @@ -867,32 +1322,46 @@ characters to translate to." (put-text-property (point) (1+ (point)) 'face 'underline))))))))) -(defun article-fill () - "Format too long lines." +(defun article-fill-long-lines () + "Fill lines that are wider than the window width." (interactive) (save-excursion - (let ((buffer-read-only nil)) - (widen) - (goto-char (point-min)) - (search-forward "\n\n" nil t) - (end-of-line 1) - (let ((paragraph-start "^[>|#:<;* ]*[ \t]*$") - (adaptive-fill-regexp "[ \t]*\\([|#:<;>*]+ *\\)?") - (adaptive-fill-mode t)) - (while (not (eobp)) - (and (>= (current-column) (min fill-column (window-width))) - (/= (preceding-char) ?:) - (fill-paragraph nil)) - (end-of-line 2)))))) + (let ((buffer-read-only nil) + (width (window-width (get-buffer-window (current-buffer))))) + (save-restriction + (article-goto-body) + (let ((adaptive-fill-mode nil)) + (while (not (eobp)) + (end-of-line) + (when (>= (current-column) (min fill-column width)) + (narrow-to-region (point) (gnus-point-at-bol)) + (fill-paragraph nil) + (goto-char (point-max)) + (widen)) + (forward-line 1))))))) + +(defun article-capitalize-sentences () + "Capitalize the first word in each sentence." + (interactive) + (save-excursion + (let ((buffer-read-only nil) + (paragraph-start "^[\n\^L]")) + (article-goto-body) + (while (not (eobp)) + (capitalize-word 1) + (forward-sentence))))) (defun article-remove-cr () - "Remove carriage returns from an article." + "Remove trailing CRs and then translate remaining CRs into LFs." (interactive) (save-excursion (let ((buffer-read-only nil)) (goto-char (point-min)) + (while (re-search-forward "\r+$" nil t) + (replace-match "" t t)) + (goto-char (point-min)) (while (search-forward "\r" nil t) - (replace-match "" t t))))) + (replace-match "\n" t t))))) (defun article-remove-trailing-blank-lines () "Remove all trailing blank lines from the article." @@ -904,7 +1373,9 @@ characters to translate to." (point) (progn (while (and (not (bobp)) - (looking-at "^[ \t]*$")) + (looking-at "^[ \t]*$") + (not (gnus-annotation-in-region-p + (point) (gnus-point-at-eol)))) (forward-line -1)) (forward-line 1) (point)))))) @@ -920,7 +1391,8 @@ characters to translate to." (case-fold-search t) from last) (save-restriction - (nnheader-narrow-to-headers) + (article-narrow-to-head) + (goto-char (point-min)) (setq from (message-fetch-field "from")) (goto-char (point-min)) (while (and gnus-article-x-face-command @@ -959,99 +1431,176 @@ characters to translate to." (process-send-region "article-x-face" beg end) (process-send-eof "article-x-face")))))))))) -(defun gnus-hack-decode-rfc1522 () - "Emergency hack function for avoiding problems when decoding." - (let ((buffer-read-only nil)) - (goto-char (point-min)) - ;; Remove encoded TABs. - (while (search-forward "=09" nil t) - (replace-match " " t t)) - ;; Remove encoded newlines. - (goto-char (point-min)) - (while (search-forward "=10" nil t) - (replace-match " " t t)))) +(defun article-decode-mime-words () + "Decode all MIME-encoded words in the article." + (interactive) + (save-excursion + (set-buffer gnus-article-buffer) + (let ((inhibit-point-motion-hooks t) + buffer-read-only + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (set-buffer gnus-summary-buffer) + gnus-newsgroup-ignored-charsets))) + (mail-decode-encoded-word-region (point-min) (point-max))))) -(defalias 'gnus-decode-rfc1522 'article-decode-rfc1522) -(defalias 'gnus-article-decode-rfc1522 'article-decode-rfc1522) -(defun article-decode-rfc1522 () - "Hack to remove QP encoding from headers." - (let ((case-fold-search t) - (inhibit-point-motion-hooks t) - (buffer-read-only nil) - string) +(defun article-decode-charset (&optional prompt) + "Decode charset-encoded text in the article. +If PROMPT (the prefix), prompt for a coding system to use." + (interactive "P") + (let ((inhibit-point-motion-hooks t) (case-fold-search t) + buffer-read-only + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (condition-case nil + (set-buffer gnus-summary-buffer) + (error)) + gnus-newsgroup-ignored-charsets)) + ct cte ctl charset format) + (save-excursion (save-restriction - (narrow-to-region - (goto-char (point-min)) - (or (search-forward "\n\n" nil t) (point-max))) - (goto-char (point-min)) - (while (re-search-forward - "=\\?iso-8859-1\\?q\\?\\([^?\t\n]*\\)\\?=" nil t) - (setq string (match-string 1)) - (save-restriction - (narrow-to-region (match-beginning 0) (match-end 0)) - (delete-region (point-min) (point-max)) - (insert string) - (article-mime-decode-quoted-printable - (goto-char (point-min)) (point-max)) - (subst-char-in-region (point-min) (point-max) ?_ ? ) - (goto-char (point-max))) - (goto-char (point-min)))))) + (article-narrow-to-head) + (setq ct (message-fetch-field "Content-Type" t) + cte (message-fetch-field "Content-Transfer-Encoding" t) + ctl (and ct (ignore-errors + (mail-header-parse-content-type ct))) + charset (cond + (prompt + (mm-read-coding-system "Charset to decode: ")) + (ctl + (mail-content-type-get ctl 'charset))) + format (and ctl (mail-content-type-get ctl 'format))) + (when cte + (setq cte (mail-header-strip cte))) + (if (and ctl (not (string-match "/" (car ctl)))) + (setq ctl nil)) + (goto-char (point-max))) + (forward-line 1) + (save-restriction + (narrow-to-region (point) (point-max)) + (when (and (eq mail-parse-charset 'gnus-decoded) + (eq (mm-body-7-or-8) '8bit)) + ;; The text code could have been decoded. + (setq charset mail-parse-charset)) + (when (and (or (not ctl) + (equal (car ctl) "text/plain")) + (not format)) ;; article with format will decode later. + (mm-decode-body + charset (and cte (intern (downcase + (gnus-strip-whitespace cte)))) + (car ctl))))))) + +(defun article-decode-encoded-words () + "Remove encoded-word encoding from headers." + (let ((inhibit-point-motion-hooks t) + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (condition-case nil + (set-buffer gnus-summary-buffer) + (error)) + gnus-newsgroup-ignored-charsets)) + buffer-read-only) + (save-restriction + (article-narrow-to-head) + (funcall gnus-decode-header-function (point-min) (point-max))))) (defun article-de-quoted-unreadable (&optional force) - "Do a naive translation of a quoted-printable-encoded article. -This is in no way, shape or form meant as a replacement for real MIME -processing, but is simply a stop-gap measure until MIME support is -written. + "Translate a quoted-printable-encoded article. If FORCE, decode the article whether it is marked as quoted-printable or not." (interactive (list 'force)) (save-excursion - (let ((case-fold-search t) - (buffer-read-only nil) - (type (gnus-fetch-field "content-transfer-encoding"))) - (gnus-article-decode-rfc1522) + (let ((buffer-read-only nil) + (type (gnus-fetch-field "content-transfer-encoding")) + (charset gnus-newsgroup-charset)) (when (or force (and type (string-match "quoted-printable" (downcase type)))) - (goto-char (point-min)) - (search-forward "\n\n" nil 'move) - (article-mime-decode-quoted-printable (point) (point-max)))))) + (article-goto-body) + (quoted-printable-decode-region (point) (point-max) charset))))) -(defun article-mime-decode-quoted-printable-buffer () - "Decode Quoted-Printable in the current buffer." - (article-mime-decode-quoted-printable (point-min) (point-max))) +(defun article-de-base64-unreadable (&optional force) + "Translate a base64 article. +If FORCE, decode the article whether it is marked as base64 not." + (interactive (list 'force)) + (save-excursion + (let ((buffer-read-only nil) + (type (gnus-fetch-field "content-transfer-encoding")) + (charset gnus-newsgroup-charset)) + (when (or force + (and type (string-match "base64" (downcase type)))) + (article-goto-body) + (save-restriction + (narrow-to-region (point) (point-max)) + (base64-decode-region (point-min) (point-max)) + (if (mm-coding-system-p charset) + (mm-decode-coding-region (point-min) (point-max) charset))))))) -(defun article-mime-decode-quoted-printable (from to) - "Decode Quoted-Printable in the region between FROM and TO." - (interactive "r") - (goto-char from) - (while (search-forward "=" to t) - (cond ((eq (following-char) ?\n) - (delete-char -1) - (delete-char 1)) - ((looking-at "[0-9A-F][0-9A-F]") - (subst-char-in-region - (1- (point)) (point) ?= - (hexl-hex-string-to-integer - (buffer-substring (point) (+ 2 (point))))) - (delete-char 2)) - ((looking-at "=") - (delete-char 1)) - ((gnus-message 3 "Malformed MIME quoted-printable message"))))) +(eval-when-compile + (require 'rfc1843)) -(defun article-hide-pgp (&optional arg) - "Toggle hiding of any PGP headers and signatures in the current article. -If given a negative prefix, always show; if given a positive prefix, -always hide." - (interactive (gnus-article-hidden-arg)) - (unless (gnus-article-check-hidden-text 'pgp arg) - (save-excursion +(defun article-decode-HZ () + "Translate a HZ-encoded article." + (interactive) + (require 'rfc1843) + (save-excursion + (let ((buffer-read-only nil)) + (rfc1843-decode-region (point-min) (point-max))))) + +(defun article-wash-html () + "Format an html article." + (interactive) + (save-excursion + (let ((buffer-read-only nil) + (charset gnus-newsgroup-charset)) + (article-goto-body) + (save-window-excursion + (save-restriction + (narrow-to-region (point) (point-max)) + (mm-setup-w3) + (let ((w3-strict-width (window-width)) + (url-standalone-mode t)) + (condition-case var + (w3-region (point-min) (point-max)) + (error)))))))) + +(defun article-hide-list-identifiers () + "Remove list identifies from the Subject header. +The `gnus-list-identifiers' variable specifies what to do." + (interactive) + (save-excursion + (save-restriction + (let ((inhibit-point-motion-hooks t) + buffer-read-only) + (article-narrow-to-head) + (let ((regexp (if (stringp gnus-list-identifiers) gnus-list-identifiers + (mapconcat 'identity gnus-list-identifiers " *\\|")))) + (when regexp + (goto-char (point-min)) + (when (re-search-forward + (concat "^Subject: +\\(\\(\\(Re: +\\)?\\(" regexp + " *\\)\\)+\\(Re: +\\)?\\)") + nil t) + (let ((s (or (match-string 3) (match-string 5)))) + (delete-region (match-beginning 1) (match-end 1)) + (when s + (goto-char (match-beginning 1)) + (insert s)))))))))) + +(defun article-hide-pgp () + "Remove any PGP headers and signatures in the current article." + (interactive) + (save-excursion + (save-restriction (let ((inhibit-point-motion-hooks t) buffer-read-only beg end) - (widen) - (goto-char (point-min)) + (article-goto-body) ;; Hide the "header". - (when (search-forward "\n-----BEGIN PGP SIGNED MESSAGE-----\n" nil t) - (delete-region (1+ (match-beginning 0)) (match-end 0)) + (when (re-search-forward "^-----BEGIN PGP SIGNED MESSAGE-----\n" nil t) + (push 'pgp gnus-article-wash-types) + (delete-region (match-beginning 0) (match-end 0)) + ;; Remove armor headers (rfc2440 6.2) + (delete-region (point) (or (re-search-forward "^[ \t]*\n" nil t) + (point))) (setq beg (point)) ;; Hide the actual signature. (and (search-forward "\n-----BEGIN PGP SIGNATURE-----\n" nil t) @@ -1081,25 +1630,62 @@ always hide." (unless (gnus-article-check-hidden-text 'pem arg) (save-excursion (let (buffer-read-only end) - (widen) (goto-char (point-min)) - ;; hide the horrendously ugly "header". - (and (search-forward "\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n" - nil - t) - (setq end (1+ (match-beginning 0))) - (gnus-article-hide-text-type - end - (if (search-forward "\n\n" nil t) - (match-end 0) - (point-max)) - 'pem)) - ;; hide the trailer as well - (and (search-forward "\n-----END PRIVACY-ENHANCED MESSAGE-----\n" - nil - t) - (gnus-article-hide-text-type - (match-beginning 0) (match-end 0) 'pem)))))) + ;; Hide the horrendously ugly "header". + (when (and (search-forward + "\n-----BEGIN PRIVACY-ENHANCED MESSAGE-----\n" + nil t) + (setq end (1+ (match-beginning 0)))) + (push 'pem gnus-article-wash-types) + (gnus-article-hide-text-type + end + (if (search-forward "\n\n" nil t) + (match-end 0) + (point-max)) + 'pem) + ;; Hide the trailer as well + (when (search-forward "\n-----END PRIVACY-ENHANCED MESSAGE-----\n" + nil t) + (gnus-article-hide-text-type + (match-beginning 0) (match-end 0) 'pem))))))) + +(defun article-strip-banner () + "Strip the banner specified by the `banner' group parameter." + (interactive) + (save-excursion + (save-restriction + (let ((inhibit-point-motion-hooks t) + (banner (gnus-group-get-parameter gnus-newsgroup-name 'banner)) + (gnus-signature-limit nil) + buffer-read-only beg end) + (when banner + (article-goto-body) + (cond + ((eq banner 'signature) + (when (gnus-article-narrow-to-signature) + (widen) + (forward-line -1) + (delete-region (point) (point-max)))) + ((stringp banner) + (while (re-search-forward banner nil t) + (delete-region (match-beginning 0) (match-end 0)))))))))) + +(defun article-babel () + "Translate article using an online translation service." + (interactive) + (require 'babel) + (save-excursion + (set-buffer gnus-article-buffer) + (when (article-goto-body) + (let* ((buffer-read-only nil) + (start (point)) + (end (point-max)) + (orig (buffer-substring start end)) + (trans (babel-as-string orig))) + (save-restriction + (narrow-to-region start end) + (delete-region start end) + (insert trans)))))) (defun article-hide-signature (&optional arg) "Hide the signature in the current article. @@ -1114,18 +1700,50 @@ always hide." (gnus-article-hide-text-type (point-min) (point-max) 'signature))))))) +(defun article-strip-headers-in-body () + "Strip offensive headers from bodies." + (interactive) + (save-excursion + (article-goto-body) + (let ((case-fold-search t)) + (when (looking-at "x-no-archive:") + (gnus-delete-line))))) + (defun article-strip-leading-blank-lines () "Remove all blank lines from the beginning of the article." (interactive) (save-excursion (let ((inhibit-point-motion-hooks t) buffer-read-only) - (goto-char (point-min)) - (when (search-forward "\n\n" nil t) + (when (article-goto-body) (while (and (not (eobp)) (looking-at "[ \t]*$")) (gnus-delete-line)))))) +(defun article-narrow-to-head () + "Narrow the buffer to the head of the message. +Point is left at the beginning of the narrowed-to region." + (narrow-to-region + (goto-char (point-min)) + (if (search-forward "\n\n" nil 1) + (1- (point)) + (point-max))) + (goto-char (point-min))) + +(defun article-goto-body () + "Place point at the start of the body." + (goto-char (point-min)) + (cond + ;; This variable is only bound when dealing with separate + ;; MIME body parts. + (article-goto-body-goes-to-point-min-p + t) + ((search-forward "\n\n" nil t) + t) + (t + (goto-char (point-max)) + nil))) + (defun article-strip-multiple-blank-lines () "Replace consecutive blank lines with one empty line." (interactive) @@ -1133,15 +1751,17 @@ always hide." (let ((inhibit-point-motion-hooks t) buffer-read-only) ;; First make all blank lines empty. - (goto-char (point-min)) - (search-forward "\n\n" nil t) + (article-goto-body) (while (re-search-forward "^[ \t]+$" nil t) - (replace-match "" nil t)) + (unless (gnus-annotation-in-region-p + (match-beginning 0) (match-end 0)) + (replace-match "" nil t))) ;; Then replace multiple empty lines with a single empty line. - (goto-char (point-min)) - (search-forward "\n\n" nil t) + (article-goto-body) (while (re-search-forward "\n\n\n+" nil t) - (replace-match "\n\n" t t))))) + (unless (gnus-annotation-in-region-p + (match-beginning 0) (match-end 0)) + (replace-match "\n\n" t t)))))) (defun article-strip-leading-space () "Remove all white space from the beginning of the lines in the article." @@ -1149,11 +1769,20 @@ always hide." (save-excursion (let ((inhibit-point-motion-hooks t) buffer-read-only) - (goto-char (point-min)) - (search-forward "\n\n" nil t) + (article-goto-body) (while (re-search-forward "^[ \t]+" nil t) (replace-match "" t t))))) +(defun article-strip-trailing-space () + "Remove all white space from the end of the lines in the article." + (interactive) + (save-excursion + (let ((inhibit-point-motion-hooks t) + buffer-read-only) + (article-goto-body) + (while (re-search-forward "[ \t]+$" nil t) + (replace-match "" t t))))) + (defun article-strip-blank-lines () "Strip leading, trailing and multiple blank lines." (interactive) @@ -1167,26 +1796,13 @@ always hide." (save-excursion (let ((inhibit-point-motion-hooks t) buffer-read-only) - (goto-char (point-min)) - (search-forward "\n\n" nil t) + (article-goto-body) (while (re-search-forward "^[ \t]*\n" nil t) (replace-match "" t t))))) -(defvar mime::preview/content-list) -(defvar mime::preview-content-info/point-min) (defun gnus-article-narrow-to-signature () "Narrow to the signature; return t if a signature is found, else nil." - (widen) (let ((inhibit-point-motion-hooks t)) - (when (and (boundp 'mime::preview/content-list) - mime::preview/content-list) - ;; We have a MIMEish article, so we use the MIME data to narrow. - (let ((pcinfo (car (last mime::preview/content-list)))) - (ignore-errors - (narrow-to-region - (funcall (intern "mime::preview-content-info/point-min") pcinfo) - (point-max))))) - (when (gnus-article-search-signature) (forward-line 1) ;; Check whether we have some limits to what we consider @@ -1226,38 +1842,6 @@ Put point at the beginning of the signature separator." (goto-char cur) nil))) -(eval-and-compile - (autoload 'w3-display "w3-parse") - (autoload 'w3-do-setup "w3" "" t) - (autoload 'w3-region "w3-display" "" t)) - -(defun gnus-article-treat-html () - "Render HTML." - (interactive) - (let ((cbuf (current-buffer))) - (set-buffer gnus-article-buffer) - (let (buf buffer-read-only b e) - (w3-do-setup) - (goto-char (point-min)) - (narrow-to-region - (if (search-forward "\n\n" nil t) - (setq b (point)) - (point-max)) - (setq e (point-max))) - (nnheader-temp-write nil - (insert-buffer-substring gnus-article-buffer b e) - (require 'url) - (save-window-excursion - (w3-region (point-min) (point-max)) - (setq buf (buffer-substring-no-properties (point-min) (point-max))))) - (when buf - (delete-region (point-min) (point-max)) - (insert buf)) - (widen) - (goto-char (point-min)) - (set-window-start (get-buffer-window (current-buffer)) (point-min)) - (set-buffer cbuf)))) - (defun gnus-article-hidden-arg () "Return the current prefix arg as a number, or 0 if no prefix." (list (if current-prefix-arg @@ -1270,7 +1854,6 @@ Arg can be nil or a number. Nil and positive means hide, negative means show, 0 means toggle." (save-excursion (save-restriction - (widen) (let ((hide (gnus-article-hidden-text-p type))) (cond ((or (null arg) @@ -1287,12 +1870,13 @@ means show, 0 means toggle." "Say whether the current buffer contains hidden text of type TYPE." (let ((pos (text-property-any (point-min) (point-max) 'article-type type))) (while (and pos - (not (get-text-property pos 'invisible))) + (not (get-text-property pos 'invisible)) + (not (get-text-property pos 'dummy-invisible))) (setq pos (text-property-any (1+ pos) (point-max) 'article-type type))) (if pos 'hidden - 'shown))) + nil))) (defun gnus-article-show-hidden-text (type &optional hide) "Show all hidden text of type TYPE. @@ -1325,144 +1909,158 @@ If HIDE, hide the text instead." (defun article-date-ut (&optional type highlight header) "Convert DATE date to universal time in the current article. If TYPE is `local', convert to local time; if it is `lapsed', output -how much time has lapsed since DATE." +how much time has lapsed since DATE. For `lapsed', the value of +`gnus-article-date-lapsed-new-header' says whether the \"X-Sent:\" header +should replace the \"Date:\" one, or should be added below it." (interactive (list 'ut t)) (let* ((header (or header - (mail-header-date gnus-current-headers) (message-fetch-field "date") "")) + (tdate-regexp "^Date:[ \t]\\|^X-Sent:[ \t]") + (date-regexp + (cond + ((not gnus-article-date-lapsed-new-header) + tdate-regexp) + ((eq type 'lapsed) + "^X-Sent:[ \t]") + (t + "^Date:[ \t]"))) (date (if (vectorp header) (mail-header-date header) header)) - (date-regexp "^Date:[ \t]\\|^X-Sent:[ \t]") (inhibit-point-motion-hooks t) - bface eface newline) - (when (and date (not (string= date ""))) - (save-excursion - (save-restriction - (nnheader-narrow-to-headers) + pos + bface eface) + (save-excursion + (save-restriction + (article-narrow-to-head) + (when (re-search-forward tdate-regexp nil t) + (setq bface (get-text-property (gnus-point-at-bol) 'face) + date (or (get-text-property (gnus-point-at-bol) + 'original-date) + date) + eface (get-text-property (1- (gnus-point-at-eol)) 'face)) + (forward-line 1)) + (when (and date (not (string= date ""))) + (goto-char (point-min)) (let ((buffer-read-only nil)) - ;; Delete any old Date headers. - (if (re-search-forward date-regexp nil t) - (progn - (setq bface (get-text-property (gnus-point-at-bol) 'face) - eface (get-text-property (1- (gnus-point-at-eol)) - 'face)) + ;; Delete any old Date headers. + (while (re-search-forward date-regexp nil t) + (if pos (delete-region (progn (beginning-of-line) (point)) - (progn (end-of-line) (point))) - (beginning-of-line)) - (goto-char (point-max)) - (setq newline t)) - (insert (article-make-date-line date type)) + (progn (forward-line 1) (point))) + (delete-region (progn (beginning-of-line) (point)) + (progn (end-of-line) (point))) + (setq pos (point)))) + (when (and (not pos) (re-search-forward tdate-regexp nil t)) + (forward-line 1)) + (if pos (goto-char pos)) + (insert (article-make-date-line date (or type 'ut))) + (when (not pos) + (insert "\n") + (forward-line -1)) ;; Do highlighting. (beginning-of-line) (when (looking-at "\\([^:]+\\): *\\(.*\\)$") + (put-text-property (match-beginning 1) (1+ (match-end 1)) + 'original-date date) (put-text-property (match-beginning 1) (1+ (match-end 1)) 'face bface) (put-text-property (match-beginning 2) (match-end 2) - 'face eface)) - (when newline - (end-of-line) - (insert "\n")))))))) + 'face eface)))))))) (defun article-make-date-line (date type) "Return a DATE line of TYPE." - (cond - ;; Convert to the local timezone. We have to slap a - ;; `condition-case' round the calls to the timezone - ;; functions since they aren't particularly resistant to - ;; buggy dates. - ((eq type 'local) - (concat "Date: " (condition-case () - (timezone-make-date-arpa-standard date) - (error date)))) - ;; Convert to Universal Time. - ((eq type 'ut) - (concat "Date: " - (condition-case () - (timezone-make-date-arpa-standard date nil "UT") - (error date)))) - ;; Get the original date from the article. - ((eq type 'original) - (concat "Date: " date)) - ;; Let the user define the format. - ((eq type 'user) - (if (gnus-functionp gnus-article-time-format) - (funcall - gnus-article-time-format - (ignore-errors - (gnus-encode-date - (timezone-make-date-arpa-standard - date nil "UT")))) - (concat - "Date: " - (format-time-string gnus-article-time-format - (ignore-errors - (gnus-encode-date - (timezone-make-date-arpa-standard - date nil "UT"))))))) - ;; ISO 8601. - ((eq type 'iso8601) - (concat - "Date: " - (format-time-string "%Y%M%DT%h%m%s" - (ignore-errors - (gnus-encode-date - (timezone-make-date-arpa-standard - date nil "UT")))))) - ;; Do an X-Sent lapsed format. - ((eq type 'lapsed) - ;; If the date is seriously mangled, the timezone functions are - ;; liable to bug out, so we ignore all errors. - (let* ((now (current-time)) - (real-time - (ignore-errors - (gnus-time-minus - (gnus-encode-date - (timezone-make-date-arpa-standard - (current-time-string now) - (current-time-zone now) "UT")) - (gnus-encode-date - (timezone-make-date-arpa-standard - date nil "UT"))))) - (real-sec (and real-time - (+ (* (float (car real-time)) 65536) - (cadr real-time)))) - (sec (and real-time (abs real-sec))) - num prev) - (cond - ((null real-time) - "X-Sent: Unknown") - ((zerop sec) - "X-Sent: Now") - (t + (let ((time (condition-case () + (date-to-time date) + (error '(0 0))))) + (cond + ;; Convert to the local timezone. We have to slap a + ;; `condition-case' round the calls to the timezone + ;; functions since they aren't particularly resistant to + ;; buggy dates. + ((eq type 'local) + (let ((tz (car (current-time-zone time)))) + (format "Date: %s %s%02d%02d" (current-time-string time) + (if (> tz 0) "+" "-") (/ (abs tz) 3600) + (/ (% (abs tz) 3600) 60)))) + ;; Convert to Universal Time. + ((eq type 'ut) + (concat "Date: " + (current-time-string + (let* ((e (parse-time-string date)) + (tm (apply 'encode-time e)) + (ms (car tm)) + (ls (- (cadr tm) (car (current-time-zone time))))) + (cond ((< ls 0) (list (1- ms) (+ ls 65536))) + ((> ls 65535) (list (1+ ms) (- ls 65536))) + (t (list ms ls))))) + " UT")) + ;; Get the original date from the article. + ((eq type 'original) + (concat "Date: " (if (string-match "\n+$" date) + (substring date 0 (match-beginning 0)) + date))) + ;; Let the user define the format. + ((eq type 'user) + (if (gnus-functionp gnus-article-time-format) + (funcall gnus-article-time-format time) (concat - "X-Sent: " - ;; This is a bit convoluted, but basically we go - ;; through the time units for years, weeks, etc, - ;; and divide things to see whether that results - ;; in positive answers. - (mapconcat - (lambda (unit) - (if (zerop (setq num (ffloor (/ sec (cdr unit))))) - ;; The (remaining) seconds are too few to - ;; be divided into this time unit. - "" - ;; It's big enough, so we output it. - (setq sec (- sec (* num (cdr unit)))) - (prog1 - (concat (if prev ", " "") (int-to-string - (floor num)) - " " (symbol-name (car unit)) - (if (> num 1) "s" "")) - (setq prev t)))) - article-time-units "") - ;; If dates are odd, then it might appear like the - ;; article was sent in the future. - (if (> real-sec 0) - " ago" - " in the future")))))) - (t - (error "Unknown conversion type: %s" type)))) + "Date: " + (format-time-string gnus-article-time-format time)))) + ;; ISO 8601. + ((eq type 'iso8601) + (let ((tz (car (current-time-zone time)))) + (concat + "Date: " + (format-time-string "%Y%m%dT%H%M%S" time) + (format "%s%02d%02d" + (if (> tz 0) "+" "-") (/ (abs tz) 3600) + (/ (% (abs tz) 3600) 60))))) + ;; Do an X-Sent lapsed format. + ((eq type 'lapsed) + ;; If the date is seriously mangled, the timezone functions are + ;; liable to bug out, so we ignore all errors. + (let* ((now (current-time)) + (real-time (subtract-time now time)) + (real-sec (and real-time + (+ (* (float (car real-time)) 65536) + (cadr real-time)))) + (sec (and real-time (abs real-sec))) + num prev) + (cond + ((null real-time) + "X-Sent: Unknown") + ((zerop sec) + "X-Sent: Now") + (t + (concat + "X-Sent: " + ;; This is a bit convoluted, but basically we go + ;; through the time units for years, weeks, etc, + ;; and divide things to see whether that results + ;; in positive answers. + (mapconcat + (lambda (unit) + (if (zerop (setq num (ffloor (/ sec (cdr unit))))) + ;; The (remaining) seconds are too few to + ;; be divided into this time unit. + "" + ;; It's big enough, so we output it. + (setq sec (- sec (* num (cdr unit)))) + (prog1 + (concat (if prev ", " "") (int-to-string + (floor num)) + " " (symbol-name (car unit)) + (if (> num 1) "s" "")) + (setq prev t)))) + article-time-units "") + ;; If dates are odd, then it might appear like the + ;; article was sent in the future. + (if (> real-sec 0) + " ago" + " in the future")))))) + (t + (error "Unknown conversion type: %s" type))))) (defun article-date-local (&optional highlight) "Convert the current article date to the local timezone." @@ -1486,11 +2084,14 @@ function and want to see what the date was before converting." (let (deactivate-mark) (save-excursion (ignore-errors - (when (gnus-buffer-live-p gnus-article-buffer) - (set-buffer gnus-article-buffer) - (goto-char (point-min)) - (when (re-search-forward "^X-Sent:" nil t) - (article-date-lapsed t))))))) + (walk-windows + (lambda (w) + (set-buffer (window-buffer w)) + (when (eq major-mode 'gnus-article-mode) + (goto-char (point-min)) + (when (re-search-forward "^X-Sent:" nil t) + (article-date-lapsed t)))) + nil 'visible))))) (defun gnus-start-date-timer (&optional n) "Start a timer to update the X-Sent header in the article buffers. @@ -1533,13 +2134,17 @@ This format is defined by the `gnus-article-time-format' variable." (interactive (gnus-article-hidden-arg)) (unless (gnus-article-check-hidden-text 'emphasis arg) (save-excursion - (let ((alist gnus-emphasis-alist) + (let ((alist (or + (condition-case nil + (with-current-buffer gnus-summary-buffer + gnus-article-emphasis-alist) + (error)) + gnus-emphasis-alist)) (buffer-read-only nil) (props (append '(article-type emphasis) gnus-hidden-properties)) regexp elem beg invisible visible face) - (goto-char (point-min)) - (search-forward "\n\n" nil t) + (article-goto-body) (setq beg (point)) (while (setq elem (pop alist)) (goto-char beg) @@ -1549,6 +2154,7 @@ This format is defined by the `gnus-article-time-format' variable." face (nth 3 elem)) (while (re-search-forward regexp nil t) (when (and (match-beginning visible) (match-beginning invisible)) + (push 'emphasis gnus-article-wash-types) (gnus-article-hide-text (match-beginning invisible) (match-end invisible) props) (gnus-article-unhide-text-type @@ -1557,6 +2163,26 @@ This format is defined by the `gnus-article-time-format' variable." (match-beginning visible) (match-end visible) 'face face) (goto-char (match-end invisible))))))))) +(defun gnus-article-setup-highlight-words (&optional highlight-words) + "Setup newsgroup emphasis alist." + (unless gnus-article-emphasis-alist + (let ((name (and gnus-newsgroup-name + (gnus-group-real-name gnus-newsgroup-name)))) + (make-local-variable 'gnus-article-emphasis-alist) + (setq gnus-article-emphasis-alist + (nconc + (let ((alist gnus-group-highlight-words-alist) elem highlight) + (while (setq elem (pop alist)) + (when (and name (string-match (car elem) name)) + (setq alist nil + highlight (copy-sequence (cdr elem))))) + highlight) + (copy-sequence highlight-words) + (if gnus-newsgroup-name + (copy-sequence (gnus-group-find-parameter + gnus-newsgroup-name 'highlight-words t))) + gnus-emphasis-alist))))) + (defvar gnus-summary-article-menu) (defvar gnus-summary-post-menu) @@ -1576,7 +2202,7 @@ This format is defined by the `gnus-article-time-format' variable." (if (not gnus-default-article-saver) (error "No default saver is defined") ;; !!! Magic! The saving functions all save - ;; `gnus-original-article-buffer' (or so they think), but we + ;; `gnus-save-article-buffer' (or so they think), but we ;; bind that variable to our save-buffer. (set-buffer gnus-article-buffer) (let* ((gnus-save-article-buffer save-buffer) @@ -1662,8 +2288,8 @@ This format is defined by the `gnus-article-time-format' variable." (gnus-make-directory (file-name-directory file)) ;; If we have read a directory, we append the default file name. (when (file-directory-p file) - (setq file (concat (file-name-as-directory file) - (file-name-nondirectory default-name)))) + (setq file (expand-file-name (file-name-nondirectory default-name) + (file-name-as-directory file)))) ;; Possibly translate some characters. (nnheader-translate-file-chars file))))) (gnus-make-directory (file-name-directory result)) @@ -1710,7 +2336,7 @@ Directory to save to is default to `gnus-article-save-directory'." (widen) (if (and (file-readable-p filename) (mail-file-babyl-p filename)) - (gnus-output-to-rmail filename t) + (rmail-output-to-rmail-file filename t) (gnus-output-to-mail filename))))) filename) @@ -1750,8 +2376,7 @@ The directory to save in defaults to `gnus-article-save-directory'." (save-excursion (save-restriction (widen) - (goto-char (point-min)) - (when (search-forward "\n\n" nil t) + (when (article-goto-body) (narrow-to-region (point) (point-max))) (gnus-output-to-file filename)))) filename) @@ -1759,7 +2384,8 @@ The directory to save in defaults to `gnus-article-save-directory'." (defun gnus-summary-save-in-pipe (&optional command) "Pipe this article to subprocess." (setq command - (cond ((eq command 'default) + (cond ((and (eq command 'default) + gnus-last-shell-command) gnus-last-shell-command) (command command) (t (read-string @@ -1823,17 +2449,6 @@ If variable `gnus-use-long-file-name' is non-nil, it is default (or last-file default)))) -(defun gnus-Plain-save-name (newsgroup headers &optional last-file) - "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE. -If variable `gnus-use-long-file-name' is non-nil, it is -~/News/News.group. Otherwise, it is like ~/News/news/group/news." - (or last-file - (expand-file-name - (if (gnus-use-long-file-name 'not-save) - (gnus-capitalize-newsgroup newsgroup) - (concat (gnus-newsgroup-directory-form newsgroup) "/news")) - gnus-article-save-directory))) - (defun gnus-plain-save-name (newsgroup headers &optional last-file) "Generate file name from NEWSGROUP, HEADERS, and optional LAST-FILE. If variable `gnus-use-long-file-name' is non-nil, it is @@ -1842,7 +2457,7 @@ If variable `gnus-use-long-file-name' is non-nil, it is (expand-file-name (if (gnus-use-long-file-name 'not-save) newsgroup - (concat (gnus-newsgroup-directory-form newsgroup) "/news")) + (expand-file-name "news" (gnus-newsgroup-directory-form newsgroup))) gnus-article-save-directory))) (eval-and-compile @@ -1854,42 +2469,53 @@ If variable `gnus-use-long-file-name' is non-nil, it is gfunc (cdr func)) (setq afunc func gfunc (intern (format "gnus-%s" func)))) - (fset gfunc - (if (not (fboundp afunc)) - nil - `(lambda (&optional interactive &rest args) - ,(documentation afunc t) - (interactive (list t)) - (save-excursion - (set-buffer gnus-article-buffer) - (if interactive - (call-interactively ',afunc) - (apply ',afunc args)))))))) + (defalias gfunc + (if (fboundp afunc) + `(lambda (&optional interactive &rest args) + ,(documentation afunc t) + (interactive (list t)) + (save-excursion + (set-buffer gnus-article-buffer) + (if interactive + (call-interactively ',afunc) + (apply ',afunc args)))))))) '(article-hide-headers article-hide-boring-headers article-treat-overstrike - (article-fill . gnus-article-word-wrap) + article-fill-long-lines + article-capitalize-sentences article-remove-cr article-display-x-face article-de-quoted-unreadable - article-mime-decode-quoted-printable + article-de-base64-unreadable + article-decode-HZ + article-wash-html + article-hide-list-identifiers article-hide-pgp + article-strip-banner + article-babel article-hide-pem article-hide-signature + article-strip-headers-in-body article-remove-trailing-blank-lines article-strip-leading-blank-lines article-strip-multiple-blank-lines article-strip-leading-space + article-strip-trailing-space article-strip-blank-lines article-strip-all-blank-lines article-date-local article-date-iso8601 article-date-original article-date-ut + article-decode-mime-words + article-decode-charset + article-decode-encoded-words article-date-user article-date-lapsed article-emphasize article-treat-dumbquotes + article-normalize-headers (article-show-all . gnus-article-show-all-headers)))) ;;; @@ -1898,20 +2524,19 @@ If variable `gnus-use-long-file-name' is non-nil, it is (put 'gnus-article-mode 'mode-class 'special) +(set-keymap-parent gnus-article-mode-map widget-keymap) + (gnus-define-keys gnus-article-mode-map " " gnus-article-goto-next-page "\177" gnus-article-goto-prev-page [delete] gnus-article-goto-prev-page + [backspace] gnus-article-goto-prev-page "\C-c^" gnus-article-refer-article "h" gnus-article-show-summary "s" gnus-article-show-summary "\C-c\C-m" gnus-article-mail "?" gnus-article-describe-briefly - gnus-mouse-2 gnus-article-push-button - "\r" gnus-article-press-button - "\t" gnus-article-next-button - "\M-\t" gnus-article-prev-button - "e" gnus-article-edit + "e" gnus-summary-edit-article "<" beginning-of-buffer ">" end-of-buffer "\C-c\C-i" gnus-info-find-node @@ -1947,7 +2572,10 @@ If variable `gnus-use-long-file-name' is non-nil, it is ["Hide citation" gnus-article-hide-citation t] ["Treat overstrike" gnus-article-treat-overstrike t] ["Remove carriage return" gnus-article-remove-cr t] - ["Remove quoted-unreadable" gnus-article-de-quoted-unreadable t])) + ["Remove quoted-unreadable" gnus-article-de-quoted-unreadable t] + ["Remove base64" gnus-article-de-base64-unreadable t] + ["Treat html" gnus-article-wash-html t] + ["Decode HZ" gnus-article-decode-HZ t])) ;; Note "Commands" menu is defined in gnus-sum.el for consistency @@ -1979,18 +2607,21 @@ commands: (setq mode-name "Article") (setq major-mode 'gnus-article-mode) (make-local-variable 'minor-mode-alist) - (unless (assq 'gnus-show-mime minor-mode-alist) - (push (list 'gnus-show-mime " MIME") minor-mode-alist)) (use-local-map gnus-article-mode-map) (gnus-update-format-specifications nil 'article-mode) (set (make-local-variable 'page-delimiter) gnus-page-delimiter) (make-local-variable 'gnus-page-broken) (make-local-variable 'gnus-button-marker-list) (make-local-variable 'gnus-article-current-summary) + (make-local-variable 'gnus-article-mime-handles) + (make-local-variable 'gnus-article-decoded-p) + (make-local-variable 'gnus-article-mime-handle-alist) + (make-local-variable 'gnus-article-wash-types) (gnus-set-default-directory) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq buffer-read-only t) (set-syntax-table gnus-article-mode-syntax-table) + (mm-enable-multibyte) (gnus-run-hooks 'gnus-article-mode-hook)) (defun gnus-article-setup-buffer () @@ -2003,6 +2634,7 @@ commands: (substring name (match-end 0)))))) (setq gnus-article-buffer name) (setq gnus-original-article-buffer original) + (setq gnus-article-mime-handle-alist nil) ;; This might be a variable local to the summary buffer. (unless gnus-single-article-buffer (save-excursion @@ -2010,16 +2642,22 @@ commands: (setq gnus-article-buffer name) (setq gnus-original-article-buffer original) (gnus-set-global-variables))) + (gnus-article-setup-highlight-words) ;; Init original article buffer. (save-excursion (set-buffer (gnus-get-buffer-create gnus-original-article-buffer)) - (buffer-disable-undo (current-buffer)) + (mm-enable-multibyte) (setq major-mode 'gnus-original-article-mode) (make-local-variable 'gnus-original-article)) (if (get-buffer name) (save-excursion (set-buffer name) - (buffer-disable-undo (current-buffer)) + (when gnus-article-mime-handles + (mm-destroy-parts gnus-article-mime-handles) + (setq gnus-article-mime-handles nil)) + ;; Set it to nil in article-buffer! + (setq gnus-article-mime-handle-alist nil) + (buffer-disable-undo) (setq buffer-read-only t) (unless (eq major-mode 'gnus-article-mode) (gnus-article-mode)) @@ -2028,6 +2666,7 @@ commands: (set-buffer (gnus-get-buffer-create name)) (gnus-article-mode) (make-local-variable 'gnus-summary-buffer) + (gnus-summary-set-local-parameters gnus-newsgroup-name) (current-buffer))))) ;; Set article window start at LINE, where LINE is the number of lines @@ -2084,8 +2723,7 @@ If ALL-HEADERS is non-nil, no headers are hidden." (message "Message marked for downloading")) (gnus-summary-mark-article article gnus-canceled-mark) (unless (memq article gnus-newsgroup-sparse) - (gnus-error 1 - "No such article (may have expired or been canceled)"))))) + (gnus-error 1 "No such article (may have expired or been canceled)"))))) (if (or (eq result 'pseudo) (eq result 'nneething)) (progn @@ -2100,7 +2738,9 @@ If ALL-HEADERS is non-nil, no headers are hidden." (gnus-configure-windows 'summary) (gnus-configure-windows 'article)) (gnus-set-global-variables)) - (gnus-set-mode-line 'article)) + (let ((gnus-article-mime-handle-alist-1 + gnus-article-mime-handle-alist)) + (gnus-set-mode-line 'article))) ;; The result from the `request' was an actual article - ;; or at least some text that is now displayed in the ;; article buffer. @@ -2131,85 +2771,723 @@ If ALL-HEADERS is non-nil, no headers are hidden." (when (gnus-visual-p 'article-highlight 'highlight) (gnus-run-hooks 'gnus-visual-mark-article-hook)) ;; Set the global newsgroup variables here. - ;; Suggested by Jim Sisolak - ;; . (gnus-set-global-variables) (setq gnus-have-all-headers (or all-headers gnus-show-all-headers)))) (when (or (numberp article) (stringp article)) - ;; Hooks for getting information from the article. - ;; This hook must be called before being narrowed. - (let (buffer-read-only) - (gnus-run-hooks 'gnus-tmp-internal-hook) - (gnus-run-hooks 'gnus-article-prepare-hook) - ;; Decode MIME message. - (if gnus-show-mime - (if (or (not gnus-strict-mime) - (gnus-fetch-field "Mime-Version")) - (let ((coding-system-for-write 'binary) - (coding-system-for-read 'binary)) - (funcall gnus-show-mime-method)) - (funcall gnus-decode-encoded-word-method)) - (funcall gnus-show-traditional-method)) - ;; Perform the article display hooks. - (gnus-run-hooks 'gnus-article-display-hook)) + (gnus-article-prepare-display) ;; Do page break. (goto-char (point-min)) (setq gnus-page-broken (when gnus-break-pages (gnus-narrow-to-page) t))) - (gnus-set-mode-line 'article) - (gnus-configure-windows 'article) - (goto-char (point-min)) - (search-forward "\n\n" nil t) + (let ((gnus-article-mime-handle-alist-1 + gnus-article-mime-handle-alist)) + (gnus-set-mode-line 'article)) + (article-goto-body) (set-window-point (get-buffer-window (current-buffer)) (point)) + (gnus-configure-windows 'article) t)))))) +;;;###autoload +(defun gnus-article-prepare-display () + "Make the current buffer look like a nice article." + ;; Hooks for getting information from the article. + ;; This hook must be called before being narrowed. + (let ((gnus-article-buffer (current-buffer)) + buffer-read-only) + (unless (eq major-mode 'gnus-article-mode) + (gnus-article-mode)) + (setq buffer-read-only nil + gnus-article-wash-types nil) + (gnus-run-hooks 'gnus-tmp-internal-hook) + (when gnus-display-mime-function + (funcall gnus-display-mime-function)) + (gnus-run-hooks 'gnus-article-prepare-hook))) + +;;; +;;; Gnus MIME viewing functions +;;; + +(defvar gnus-mime-button-line-format "%{%([%p. %d%T]%)%}%e\n" + "The following specs can be used: +%t The MIME type +%T MIME type, along with additional info +%n The `name' parameter +%d The description, if any +%l The length of the encoded part +%p The part identifier number +%e Dots if the part isn't displayed") + +(defvar gnus-mime-button-line-format-alist + '((?t gnus-tmp-type ?s) + (?T gnus-tmp-type-long ?s) + (?n gnus-tmp-name ?s) + (?d gnus-tmp-description ?s) + (?p gnus-tmp-id ?s) + (?l gnus-tmp-length ?d) + (?e gnus-tmp-dots ?s))) + +(defvar gnus-mime-button-commands + '((gnus-article-press-button "\r" "Toggle Display") + (gnus-mime-view-part "v" "View Interactively...") + (gnus-mime-view-part-as-type "t" "View As Type...") + (gnus-mime-save-part "o" "Save...") + (gnus-mime-copy-part "c" "View As Text, In Other Buffer") + (gnus-mime-inline-part "i" "View As Text, In This Buffer") + (gnus-mime-internalize-part "E" "View Internally") + (gnus-mime-externalize-part "e" "View Externally") + (gnus-mime-pipe-part "|" "Pipe To Command..."))) + +(defun gnus-article-mime-part-status () + (if gnus-article-mime-handle-alist-1 + (format " (%d parts)" (length gnus-article-mime-handle-alist-1)) + "")) + +(defvar gnus-mime-button-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map gnus-article-mode-map) + (define-key map gnus-mouse-2 'gnus-article-push-button) + (define-key map gnus-down-mouse-3 'gnus-mime-button-menu) + (dolist (c gnus-mime-button-commands) + (define-key map (cadr c) (car c))) + map)) + +(defun gnus-mime-button-menu (event) + "Construct a context-sensitive menu of MIME commands." + (interactive "e") + (save-excursion + (let ((pos (event-start event))) + (set-buffer (window-buffer (posn-window pos))) + (goto-char (posn-point pos)) + (gnus-article-check-buffer) + (let ((response (x-popup-menu + t `("MIME Part" + ("" ,@(mapcar (lambda (c) + (cons (caddr c) (car c))) + gnus-mime-button-commands)))))) + (if response + (call-interactively response)))))) + +(defun gnus-mime-view-all-parts (&optional handles) + "View all the MIME parts." + (interactive) + (save-current-buffer + (set-buffer gnus-article-buffer) + (let ((handles (or handles gnus-article-mime-handles)) + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (set-buffer gnus-summary-buffer) + gnus-newsgroup-ignored-charsets))) + (if (stringp (car handles)) + (gnus-mime-view-all-parts (cdr handles)) + (mapcar 'mm-display-part handles))))) + +(defun gnus-mime-save-part () + "Save the MIME part under point." + (interactive) + (gnus-article-check-buffer) + (let ((data (get-text-property (point) 'gnus-data))) + (mm-save-part data))) + +(defun gnus-mime-pipe-part () + "Pipe the MIME part under point to a process." + (interactive) + (gnus-article-check-buffer) + (let ((data (get-text-property (point) 'gnus-data))) + (mm-pipe-part data))) + +(defun gnus-mime-view-part () + "Interactively choose a viewing method for the MIME part under point." + (interactive) + (gnus-article-check-buffer) + (let ((data (get-text-property (point) 'gnus-data))) + (mm-interactively-view-part data))) + +(defun gnus-mime-view-part-as-type-internal () + (gnus-article-check-buffer) + (let* ((name (mail-content-type-get + (mm-handle-type (get-text-property (point) 'gnus-data)) + 'name)) + (def-type (and name (mm-default-file-encoding name)))) + (and def-type (cons def-type 0)))) + +(defun gnus-mime-view-part-as-type (mime-type) + "Choose a MIME media type, and view the part as such." + (interactive + (list (completing-read + "View as MIME type: " + (mapcar #'list (mailcap-mime-types)) + nil nil + (gnus-mime-view-part-as-type-internal)))) + (gnus-article-check-buffer) + (let ((handle (get-text-property (point) 'gnus-data))) + (gnus-mm-display-part + (mm-make-handle (mm-handle-buffer handle) + (cons mime-type (cdr (mm-handle-type handle))) + (mm-handle-encoding handle) + (mm-handle-undisplayer handle) + (mm-handle-disposition handle) + (mm-handle-description handle) + (mm-handle-cache handle) + (mm-handle-id handle))))) + +(defun gnus-mime-copy-part (&optional handle) + "Put the the MIME part under point into a new buffer." + (interactive) + (gnus-article-check-buffer) + (let* ((handle (or handle (get-text-property (point) 'gnus-data))) + (contents (mm-get-part handle))| + (base (file-name-nondirectory + (or + (mail-content-type-get (mm-handle-type handle) 'name) + (mail-content-type-get (mm-handle-type handle) + 'filename) + "*decoded*"))) + (buffer (generate-new-buffer base))) + (switch-to-buffer buffer) + (insert contents) + ;; We do it this way to make `normal-mode' set the appropriate mode. + (unwind-protect + (progn + (setq buffer-file-name (expand-file-name base)) + (normal-mode)) + (setq buffer-file-name nil)) + (goto-char (point-min)))) + +(defun gnus-mime-inline-part (&optional handle) + "Insert the MIME part under point into the current buffer." + (interactive) + (gnus-article-check-buffer) + (let* ((handle (or handle (get-text-property (point) 'gnus-data))) + contents + (b (point)) + buffer-read-only) + (if (mm-handle-undisplayer handle) + (mm-remove-part handle) + (setq contents (mm-get-part handle)) + (forward-line 2) + (mm-insert-inline handle contents) + (goto-char b)))) + +(defun gnus-mime-externalize-part (&optional handle) + "View the MIME part under point with an external viewer." + (interactive) + (gnus-article-check-buffer) + (let* ((handle (or handle (get-text-property (point) 'gnus-data))) + (mm-user-display-methods nil) + (mm-inlined-types nil) + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (set-buffer gnus-summary-buffer) + gnus-newsgroup-ignored-charsets))) + (if (mm-handle-undisplayer handle) + (mm-remove-part handle) + (mm-display-part handle)))) + +(defun gnus-mime-internalize-part (&optional handle) + "View the MIME part under point with an internal viewer. +In no internal viewer is available, use an external viewer." + (interactive) + (gnus-article-check-buffer) + (let* ((handle (or handle (get-text-property (point) 'gnus-data))) + (mm-inlined-types '(".*")) + (mm-inline-large-images t) + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (set-buffer gnus-summary-buffer) + gnus-newsgroup-ignored-charsets))) + (if (mm-handle-undisplayer handle) + (mm-remove-part handle) + (mm-display-part handle)))) + +(defun gnus-article-part-wrapper (n function) + (save-current-buffer + (set-buffer gnus-article-buffer) + (when (> n (length gnus-article-mime-handle-alist)) + (error "No such part")) + (gnus-article-goto-part n) + (let ((handle (cdr (assq n gnus-article-mime-handle-alist)))) + (funcall function handle)))) + +(defun gnus-article-pipe-part (n) + "Pipe MIME part N, which is the numerical prefix." + (interactive "p") + (gnus-article-part-wrapper n 'mm-pipe-part)) + +(defun gnus-article-save-part (n) + "Save MIME part N, which is the numerical prefix." + (interactive "p") + (gnus-article-part-wrapper n 'mm-save-part)) + +(defun gnus-article-interactively-view-part (n) + "View MIME part N interactively, which is the numerical prefix." + (interactive "p") + (gnus-article-part-wrapper n 'mm-interactively-view-part)) + +(defun gnus-article-copy-part (n) + "Copy MIME part N, which is the numerical prefix." + (interactive "p") + (gnus-article-part-wrapper n 'gnus-mime-copy-part)) + +(defun gnus-article-externalize-part (n) + "View MIME part N externally, which is the numerical prefix." + (interactive "p") + (gnus-article-part-wrapper n 'gnus-mime-externalize-part)) + +(defun gnus-article-inline-part (n) + "Inline MIME part N, which is the numerical prefix." + (interactive "p") + (gnus-article-part-wrapper n 'gnus-mime-inline-part)) + +(defun gnus-article-mime-match-handle-first (condition) + (if condition + (let ((alist gnus-article-mime-handle-alist) ihandle n) + (while (setq ihandle (pop alist)) + (if (and (cond + ((functionp condition) + (funcall condition (cdr ihandle))) + ((eq condition 'undisplayed) + (not (or (mm-handle-undisplayer (cdr ihandle)) + (equal (mm-handle-media-type (cdr ihandle)) + "multipart/alternative")))) + ((eq condition 'undisplayed-alternative) + (not (mm-handle-undisplayer (cdr ihandle)))) + (t t)) + (gnus-article-goto-part (car ihandle)) + (or (not n) (< (car ihandle) n))) + (setq n (car ihandle)))) + (or n 1)) + 1)) + +(defun gnus-article-view-part (&optional n) + "View MIME part N, which is the numerical prefix." + (interactive "P") + (save-current-buffer + (set-buffer gnus-article-buffer) + (or (numberp n) (setq n (gnus-article-mime-match-handle-first + gnus-article-mime-match-handle-function))) + (when (> n (length gnus-article-mime-handle-alist)) + (error "No such part")) + (let ((handle (cdr (assq n gnus-article-mime-handle-alist)))) + (when (gnus-article-goto-part n) + (if (equal (car handle) "multipart/alternative") + (gnus-article-press-button) + (when (eq (gnus-mm-display-part handle) 'internal) + (gnus-set-window-start))))))) + +(defun gnus-mm-display-part (handle) + "Display HANDLE and fix MIME button." + (let ((id (get-text-property (point) 'gnus-part)) + (point (point)) + buffer-read-only) + (forward-line 1) + (prog1 + (let ((window (selected-window)) + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (set-buffer gnus-summary-buffer) + gnus-newsgroup-ignored-charsets))) + (save-excursion + (unwind-protect + (let ((win (get-buffer-window (current-buffer) t)) + (beg (point))) + (when win + (select-window win)) + (goto-char point) + (forward-line) + (if (mm-handle-displayed-p handle) + ;; This will remove the part. + (mm-display-part handle) + (save-restriction + (narrow-to-region (point) (1+ (point))) + (mm-display-part handle) + ;; We narrow to the part itself and + ;; then call the treatment functions. + (goto-char (point-min)) + (forward-line 1) + (narrow-to-region (point) (point-max)) + (gnus-treat-article + nil id + (1- (length gnus-article-mime-handles)) + (mm-handle-media-type handle))))) + (select-window window)))) + (goto-char point) + (delete-region (gnus-point-at-bol) (progn (forward-line 1) (point))) + (gnus-insert-mime-button + handle id (list (mm-handle-displayed-p handle))) + (goto-char point)))) + +(defun gnus-article-goto-part (n) + "Go to MIME part N." + (let ((point (text-property-any (point-min) (point-max) 'gnus-part n))) + (when point + (goto-char point)))) + +(defun gnus-insert-mime-button (handle gnus-tmp-id &optional displayed) + (let ((gnus-tmp-name + (or (mail-content-type-get (mm-handle-type handle) + 'name) + (mail-content-type-get (mm-handle-disposition handle) + 'filename) + "")) + (gnus-tmp-type (mm-handle-media-type handle)) + (gnus-tmp-description + (mail-decode-encoded-word-string (or (mm-handle-description handle) + ""))) + (gnus-tmp-dots + (if (if displayed (car displayed) + (mm-handle-displayed-p handle)) + "" "...")) + (gnus-tmp-length (with-current-buffer (mm-handle-buffer handle) + (buffer-size))) + gnus-tmp-type-long b e) + (when (string-match ".*/" gnus-tmp-name) + (setq gnus-tmp-name (replace-match "" t t gnus-tmp-name))) + (setq gnus-tmp-type-long (concat gnus-tmp-type + (and (not (equal gnus-tmp-name "")) + (concat "; " gnus-tmp-name)))) + (or (equal gnus-tmp-description "") + (setq gnus-tmp-type-long (concat " --- " gnus-tmp-type-long))) + (unless (bolp) + (insert "\n")) + (setq b (point)) + (gnus-eval-format + gnus-mime-button-line-format gnus-mime-button-line-format-alist + `(local-map ,gnus-mime-button-map + keymap ,gnus-mime-button-map + gnus-callback gnus-mm-display-part + gnus-part ,gnus-tmp-id + article-type annotation + gnus-data ,handle)) + (setq e (point)) + (widget-convert-button + 'link b e + :mime-handle handle + :action 'gnus-widget-press-button + :button-keymap gnus-mime-button-map + :help-echo + (lambda (widget/window &optional overlay pos) + ;; Needed to properly clear the message due to a bug in + ;; wid-edit (XEmacs only). + (if (boundp 'help-echo-owns-message) + (setq help-echo-owns-message t)) + (format + "%S: %s the MIME part; %S: more options" + (aref gnus-mouse-2 0) + ;; XEmacs will get a single widget arg; Emacs 21 will get + ;; window, overlay, position. + (if (mm-handle-displayed-p + (if overlay + (with-current-buffer (overlay-buffer overlay) + (widget-get (widget-at (overlay-start overlay)) + :mime-handle)) + (widget-get widget/window :mime-handle))) + "hide" "show") + (aref gnus-down-mouse-3 0)))))) + +(defun gnus-widget-press-button (elems el) + (goto-char (widget-get elems :from)) + (gnus-article-press-button)) + +(defvar gnus-displaying-mime nil) + +(defun gnus-display-mime (&optional ihandles) + "Display the MIME parts." + (save-excursion + (save-selected-window + (let ((window (get-buffer-window gnus-article-buffer)) + (point (point))) + (when window + (select-window window) + ;; We have to do this since selecting the window + ;; may change the point. So we set the window point. + (set-window-point window point))) + (let* ((handles (or ihandles (mm-dissect-buffer) (mm-uu-dissect))) + buffer-read-only handle name type b e display) + (when (and (not ihandles) + (not gnus-displaying-mime)) + ;; Top-level call; we clean up. + (when gnus-article-mime-handles + (mm-destroy-parts gnus-article-mime-handles) + (setq gnus-article-mime-handle-alist nil));; A trick. + (setq gnus-article-mime-handles handles) + ;; We allow users to glean info from the handles. + (when gnus-article-mime-part-function + (gnus-mime-part-function handles))) + (if (and handles + (or (not (stringp (car handles))) + (cdr handles))) + (progn + (when (and (not ihandles) + (not gnus-displaying-mime)) + ;; Clean up for mime parts. + (article-goto-body) + (delete-region (point) (point-max))) + (let ((gnus-displaying-mime t)) + (gnus-mime-display-part handles))) + (save-restriction + (article-goto-body) + (narrow-to-region (point) (point-max)) + (gnus-treat-article nil 1 1) + (widen))) + (unless ihandles + ;; Highlight the headers. + (save-excursion + (save-restriction + (article-goto-body) + (narrow-to-region (point-min) (point)) + (gnus-treat-article 'head)))))))) + +(defvar gnus-mime-display-multipart-as-mixed nil) + +(defun gnus-mime-display-part (handle) + (cond + ;; Single part. + ((not (stringp (car handle))) + (gnus-mime-display-single handle)) + ;; User-defined multipart + ((cdr (assoc (car handle) gnus-mime-multipart-functions)) + (funcall (cdr (assoc (car handle) gnus-mime-multipart-functions)) + handle)) + ;; multipart/alternative + ((and (equal (car handle) "multipart/alternative") + (not gnus-mime-display-multipart-as-mixed)) + (let ((id (1+ (length gnus-article-mime-handle-alist)))) + (push (cons id handle) gnus-article-mime-handle-alist) + (gnus-mime-display-alternative (cdr handle) nil nil id))) + ;; multipart/related + ((and (equal (car handle) "multipart/related") + (not gnus-mime-display-multipart-as-mixed)) + ;;;!!!We should find the start part, but we just default + ;;;!!!to the first part. + (gnus-mime-display-part (cadr handle))) + ;; Other multiparts are handled like multipart/mixed. + (t + (gnus-mime-display-mixed (cdr handle))))) + +(defun gnus-mime-part-function (handles) + (if (stringp (car handles)) + (mapcar 'gnus-mime-part-function (cdr handles)) + (funcall gnus-article-mime-part-function handles))) + +(defun gnus-mime-display-mixed (handles) + (mapcar 'gnus-mime-display-part handles)) + +(defun gnus-mime-display-single (handle) + (let ((type (mm-handle-media-type handle)) + (ignored gnus-ignored-mime-types) + (not-attachment t) + (move nil) + display text) + (catch 'ignored + (progn + (while ignored + (when (string-match (pop ignored) type) + (throw 'ignored nil))) + (if (and (setq not-attachment + (and (not (mm-inline-override-p handle)) + (or (not (mm-handle-disposition handle)) + (equal (car (mm-handle-disposition handle)) + "inline") + (mm-attachment-override-p handle)))) + (mm-automatic-display-p handle) + (or (mm-inlined-p handle) + (mm-automatic-external-display-p type))) + (setq display t) + (when (equal (mm-handle-media-supertype handle) "text") + (setq text t))) + (let ((id (1+ (length gnus-article-mime-handle-alist)))) + (push (cons id handle) gnus-article-mime-handle-alist) + (when (or (not display) + (not (gnus-unbuttonized-mime-type-p type))) + ;(gnus-article-insert-newline) + (gnus-insert-mime-button + handle id (list (or display (and not-attachment text)))) + (gnus-article-insert-newline) + ;(gnus-article-insert-newline) + (setq move t))) + (let ((beg (point))) + (cond + (display + (when move + (forward-line -2) + (setq beg (point))) + (let ((mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (condition-case () + (set-buffer gnus-summary-buffer) + (error)) + gnus-newsgroup-ignored-charsets))) + (mm-display-part handle t)) + (goto-char (point-max))) + ((and text not-attachment) + (when move + (forward-line -2) + (setq beg (point))) + (gnus-article-insert-newline) + (mm-insert-inline handle (mm-get-part handle)) + (goto-char (point-max)))) + ;; Do highlighting. + (save-excursion + (save-restriction + (narrow-to-region beg (point)) + (gnus-treat-article + nil (length gnus-article-mime-handle-alist) + (1- (length gnus-article-mime-handles)) + (mm-handle-media-type handle))))))))) + +(defun gnus-unbuttonized-mime-type-p (type) + "Say whether TYPE is to be unbuttonized." + (unless gnus-inhibit-mime-unbuttonizing + (catch 'found + (let ((types gnus-unbuttonized-mime-types)) + (while types + (when (string-match (pop types) type) + (throw 'found t))))))) + +(defun gnus-article-insert-newline () + "Insert a newline, but mark it as undeletable." + (gnus-put-text-property + (point) (progn (insert "\n") (point)) 'gnus-undeletable t)) + +(defun gnus-mime-display-alternative (handles &optional preferred ibegend id) + (let* ((preferred (or preferred (mm-preferred-alternative handles))) + (ihandles handles) + (point (point)) + handle buffer-read-only from props begend not-pref) + (save-window-excursion + (save-restriction + (when ibegend + (narrow-to-region (car ibegend) + (or (cdr ibegend) + (progn + (goto-char (car ibegend)) + (forward-line 2) + (point)))) + (delete-region (point-min) (point-max)) + (mm-remove-parts handles)) + (setq begend (list (point-marker))) + ;; Do the toggle. + (unless (setq not-pref (cadr (member preferred ihandles))) + (setq not-pref (car ihandles))) + (when (or ibegend + (not (gnus-unbuttonized-mime-type-p + "multipart/alternative"))) + (gnus-add-text-properties + (setq from (point)) + (progn + (insert (format "%d. " id)) + (point)) + `(gnus-callback + (lambda (handles) + (unless ,(not ibegend) + (setq gnus-article-mime-handle-alist + ',gnus-article-mime-handle-alist)) + (gnus-mime-display-alternative + ',ihandles ',not-pref ',begend ,id)) + local-map ,gnus-mime-button-map + ,gnus-mouse-face-prop ,gnus-article-mouse-face + face ,gnus-article-button-face + keymap ,gnus-mime-button-map + gnus-part ,id + gnus-data ,handle)) + (widget-convert-button 'link from (point) + :action 'gnus-widget-press-button + :button-keymap gnus-widget-button-keymap) + ;; Do the handles + (while (setq handle (pop handles)) + (gnus-add-text-properties + (setq from (point)) + (progn + (insert (format "(%c) %-18s" + (if (equal handle preferred) ?* ? ) + (mm-handle-media-type handle))) + (point)) + `(gnus-callback + (lambda (handles) + (unless ,(not ibegend) + (setq gnus-article-mime-handle-alist + ',gnus-article-mime-handle-alist)) + (gnus-mime-display-alternative + ',ihandles ',handle ',begend ,id)) + local-map ,gnus-mime-button-map + ,gnus-mouse-face-prop ,gnus-article-mouse-face + face ,gnus-article-button-face + keymap ,gnus-mime-button-map + gnus-part ,id + gnus-data ,handle)) + (widget-convert-button 'link from (point) + :action 'gnus-widget-press-button + :button-keymap gnus-widget-button-keymap) + (insert " ")) + (insert "\n\n")) + (when preferred + (if (stringp (car preferred)) + (gnus-display-mime preferred) + (let ((mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (set-buffer gnus-summary-buffer) + gnus-newsgroup-ignored-charsets))) + (mm-display-part preferred) + ;; Do highlighting. + (save-excursion + (save-restriction + (narrow-to-region (car begend) (point-max)) + (gnus-treat-article + nil (length gnus-article-mime-handle-alist) + (1- (length gnus-article-mime-handles)) + (mm-handle-media-type handle)))))) + (goto-char (point-max)) + (setcdr begend (point-marker))))) + (when ibegend + (goto-char point)))) + (defun gnus-article-wash-status () "Return a string which display status of article washing." (save-excursion (set-buffer gnus-article-buffer) - (let ((cite (gnus-article-hidden-text-p 'cite)) - (headers (gnus-article-hidden-text-p 'headers)) - (boring (gnus-article-hidden-text-p 'boring-headers)) - (pgp (gnus-article-hidden-text-p 'pgp)) - (pem (gnus-article-hidden-text-p 'pem)) - (signature (gnus-article-hidden-text-p 'signature)) - (overstrike (gnus-article-hidden-text-p 'overstrike)) - (emphasis (gnus-article-hidden-text-p 'emphasis)) - (mime gnus-show-mime)) - (format "%c%c%c%c%c%c%c" + (let ((cite (memq 'cite gnus-article-wash-types)) + (headers (memq 'headers gnus-article-wash-types)) + (boring (memq 'boring-headers gnus-article-wash-types)) + (pgp (memq 'pgp gnus-article-wash-types)) + (pem (memq 'pem gnus-article-wash-types)) + (signature (memq 'signature gnus-article-wash-types)) + (overstrike (memq 'overstrike gnus-article-wash-types)) + (emphasis (memq 'emphasis gnus-article-wash-types))) + (format "%c%c%c%c%c%c" (if cite ?c ? ) (if (or headers boring) ?h ? ) (if (or pgp pem) ?p ? ) (if signature ?s ? ) (if overstrike ?o ? ) - (if mime ?m ? ) (if emphasis ?e ? ))))) -(fset 'gnus-article-hide-headers-if-wanted 'gnus-article-maybe-hide-headers) +(defalias 'gnus-article-hide-headers-if-wanted 'gnus-article-maybe-hide-headers) (defun gnus-article-maybe-hide-headers () "Hide unwanted headers if `gnus-have-all-headers' is nil. Provided for backwards compatibility." - (or (save-excursion (set-buffer gnus-summary-buffer) gnus-have-all-headers) - gnus-inhibit-hiding - (gnus-article-hide-headers))) + (when (and (or (not (gnus-buffer-live-p gnus-summary-buffer)) + (not (save-excursion (set-buffer gnus-summary-buffer) + gnus-have-all-headers))) + (not gnus-inhibit-hiding)) + (gnus-article-hide-headers))) ;;; Article savers. (defun gnus-output-to-file (file-name) "Append the current article to a file named FILE-NAME." (let ((artbuf (current-buffer))) - (nnheader-temp-write nil + (with-temp-buffer (insert-buffer-substring artbuf) ;; Append newline at end of the buffer as separator, and then ;; save it to file. (goto-char (point-max)) (insert "\n") - (append-to-file (point-min) (point-max) file-name) + (mm-append-to-file (point-min) (point-max) file-name) t))) (defun gnus-narrow-to-page (&optional arg) @@ -2337,8 +3615,7 @@ Argument LINES specifies lines to be scrolled down." (defun gnus-article-describe-briefly () "Describe article mode commands briefly." (interactive) - (gnus-message 6 - (substitute-command-keys "\\\\[gnus-article-goto-next-page]:Next page \\[gnus-article-goto-prev-page]:Prev page \\[gnus-article-show-summary]:Show summary \\[gnus-info-find-node]:Run Info \\[gnus-article-describe-briefly]:This help"))) + (gnus-message 6 (substitute-command-keys "\\\\[gnus-article-goto-next-page]:Next page \\[gnus-article-goto-prev-page]:Prev page \\[gnus-article-show-summary]:Show summary \\[gnus-info-find-node]:Run Info \\[gnus-article-describe-briefly]:This help"))) (defun gnus-article-summary-command () "Execute the last keystroke in the summary buffer." @@ -2361,9 +3638,15 @@ Argument LINES specifies lines to be scrolled down." (setq func (lookup-key (current-local-map) (this-command-keys))) (call-interactively func))) +(defun gnus-article-check-buffer () + "Beep if not in an article buffer." + (unless (equal major-mode 'gnus-article-mode) + (error "Command invoked outside of a Gnus article buffer"))) + (defun gnus-article-read-summary-keys (&optional arg key not-restore-window) "Read a summary buffer key sequence and execute it from the article buffer." (interactive "P") + (gnus-article-check-buffer) (let ((nosaves '("q" "Q" "c" "r" "R" "\C-c\C-f" "m" "a" "f" "F" "Zc" "ZC" "ZE" "ZQ" "ZZ" "Zn" "ZR" "ZG" "ZN" "ZP" @@ -2379,7 +3662,10 @@ Argument LINES specifies lines to be scrolled down." (set-buffer gnus-article-current-summary) (let (gnus-pick-mode) (push (or key last-command-event) unread-command-events) - (setq keys (read-key-sequence nil)))) + (setq keys (if gnus-xemacs + (events-to-keys (read-key-sequence nil)) + (read-key-sequence nil))))) + (message "") (if (or (member keys nosaves) @@ -2391,7 +3677,8 @@ Argument LINES specifies lines to be scrolled down." ;; We disable the pick minor mode commands. (let (gnus-pick-mode) (setq func (lookup-key (current-local-map) keys)))) - (if (not func) + (if (or (not func) + (numberp func)) (ding) (unless (member keys nosave-in-article) (set-buffer gnus-article-current-summary)) @@ -2421,9 +3708,12 @@ Argument LINES specifies lines to be scrolled down." (set-buffer obuf) (unless not-restore-window (set-window-configuration owin)) - (unless (or (not (eq selected 'old)) (member keys up-to-top)) + (when (eq selected 'old) + (article-goto-body) + (set-window-start (get-buffer-window (current-buffer)) + 1) (set-window-point (get-buffer-window (current-buffer)) - opoint)) + (point))) (let ((win (get-buffer-window gnus-article-current-summary))) (when win (set-window-point win new-sum-point)))))))) @@ -2435,6 +3725,7 @@ headers will be hidden. If given a prefix, show the hidden text instead." (interactive (append (gnus-article-hidden-arg) (list 'force))) (gnus-article-hide-headers arg) + (gnus-article-hide-list-identifiers arg) (gnus-article-hide-pgp arg) (gnus-article-hide-citation-maybe arg force) (gnus-article-hide-signature arg)) @@ -2467,8 +3758,7 @@ If given a prefix, show the hidden text instead." ;; We only request an article by message-id when we do not have the ;; headers for it, so we'll have to get those. (when (stringp article) - (let ((gnus-override-method gnus-refer-article-method)) - (gnus-read-header article))) + (gnus-read-header article)) ;; If the article number is negative, that means that this article ;; doesn't belong in this newsgroup (possibly), so we find its @@ -2486,8 +3776,7 @@ If given a prefix, show the hidden text instead." ;; This is a sparse gap article. (setq do-update-line article) (setq article (mail-header-id header)) - (let ((gnus-override-method gnus-refer-article-method)) - (setq sparse-header (gnus-read-header article))) + (setq sparse-header (gnus-read-header article)) (setq gnus-newsgroup-sparse (delq article gnus-newsgroup-sparse))) ((vectorp header) @@ -2502,11 +3791,11 @@ If given a prefix, show the hidden text instead." gnus-newsgroup-name))) (when (and (eq (car method) 'nneething) (vectorp header)) - (let ((dir (concat + (let ((dir (expand-file-name + (mail-header-subject header) (file-name-as-directory (or (cadr (assq 'nneething-address method)) - (nth 1 method))) - (mail-header-subject header)))) + (nth 1 method)))))) (when (file-directory-p dir) (setq article 'nneething) (gnus-group-enter-directory dir)))))))) @@ -2547,20 +3836,40 @@ If given a prefix, show the hidden text instead." (gnus-cache-request-article article group)) 'article) ;; Get the article and put into the article buffer. - ((or (stringp article) (numberp article)) - (let ((gnus-override-method - (and (stringp article) gnus-refer-article-method)) + ((or (stringp article) + (numberp article)) + (let ((gnus-override-method gnus-override-method) + (methods (and (stringp article) + gnus-refer-article-method)) + result (buffer-read-only nil)) - (erase-buffer) - (gnus-kill-all-overlays) - (gnus-check-group-server) - (when (gnus-request-article article group (current-buffer)) - (when (numberp article) - (gnus-async-prefetch-next group article gnus-summary-buffer) - (when gnus-keep-backlog - (gnus-backlog-enter-article - group article (current-buffer)))) - 'article))) + (setq methods + (if (listp methods) + methods + (list methods))) + (when (and (null gnus-override-method) + methods) + (setq gnus-override-method (pop methods))) + (while (not result) + (when (eq gnus-override-method 'current) + (setq gnus-override-method gnus-current-select-method)) + (erase-buffer) + (gnus-kill-all-overlays) + (let ((gnus-newsgroup-name group)) + (gnus-check-group-server)) + (when (gnus-request-article article group (current-buffer)) + (when (numberp article) + (gnus-async-prefetch-next group article + gnus-summary-buffer) + (when gnus-keep-backlog + (gnus-backlog-enter-article + group article (current-buffer)))) + (setq result 'article)) + (if (not result) + (if methods + (setq gnus-override-method (pop methods)) + (setq result 'done)))) + (and (eq result 'article) 'article))) ;; It was a pseudo. (t article))) @@ -2576,13 +3885,18 @@ If given a prefix, show the hidden text instead." (if (get-buffer gnus-original-article-buffer) (set-buffer gnus-original-article-buffer) (set-buffer (gnus-get-buffer-create gnus-original-article-buffer)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq major-mode 'gnus-original-article-mode) (setq buffer-read-only t)) (let (buffer-read-only) (erase-buffer) (insert-buffer-substring gnus-article-buffer)) - (setq gnus-original-article (cons group article)))) + (setq gnus-original-article (cons group article))) + + ;; Decode charsets. + (run-hooks 'gnus-article-decode-hook) + ;; Mark article as decoded or not. + (setq gnus-article-decoded-p gnus-article-decode-hook)) ;; Update sparse articles. (when (and do-update-line @@ -2609,8 +3923,10 @@ If given a prefix, show the hidden text instead." (defvar gnus-article-edit-mode-map nil) +;; Should we be using derived.el for this? (unless gnus-article-edit-mode-map - (setq gnus-article-edit-mode-map (copy-keymap text-mode-map)) + (setq gnus-article-edit-mode-map (make-sparse-keymap)) + (set-keymap-parent gnus-article-edit-mode-map text-mode-map) (gnus-define-keys gnus-article-edit-mode-map "\C-c\C-c" gnus-article-edit-done @@ -2647,18 +3963,19 @@ groups." (error "The current newsgroup does not support article editing")) (gnus-article-date-original) (gnus-article-edit-article + 'ignore `(lambda (no-highlight) + 'ignore (gnus-summary-edit-article-done ,(or (mail-header-references gnus-current-headers) "") ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight)))) -(defun gnus-article-edit-article (exit-func) +(defun gnus-article-edit-article (start-func exit-func) "Start editing the contents of the current article buffer." (let ((winconf (current-window-configuration))) (set-buffer gnus-article-buffer) (gnus-article-edit-mode) - (gnus-article-delete-text-of-type 'annotation) - (gnus-set-text-properties (point-min) (point-max) nil) + (funcall start-func) (gnus-configure-windows 'edit-article) (setq gnus-article-edit-done-function exit-func) (setq gnus-prev-winconf winconf) @@ -2670,8 +3987,7 @@ groups." (save-excursion (save-restriction (widen) - (goto-char (point-min)) - (when (search-forward "\n\n" nil 1) + (when (article-goto-body) (let ((lines (count-lines (point) (point-max))) (length (- (point-max) (point))) (case-fold-search t) @@ -2696,27 +4012,8 @@ groups." (save-excursion (set-buffer buf) (let ((buffer-read-only nil)) - (funcall func arg))) - (set-buffer buf) - (set-window-start (get-buffer-window buf) start) - (set-window-point (get-buffer-window buf) (point)))) - -(defun gnus-article-edit-exit () - "Exit the article editing without updating." - (interactive) - ;; We remove all text props from the article buffer. - (let ((buf (format "%s" (buffer-string))) - (curbuf (current-buffer)) - (p (point)) - (window-start (window-start))) - (erase-buffer) - (insert buf) - (let ((winconf gnus-prev-winconf)) - (gnus-article-mode) + (funcall func arg)) ;; The cache and backlog have to be flushed somewhat. - (when gnus-use-cache - (gnus-cache-update-article - (car gnus-article-current) (cdr gnus-article-current))) (when gnus-keep-backlog (gnus-backlog-remove-article (car gnus-article-current) (cdr gnus-article-current))) @@ -2725,13 +4022,31 @@ groups." (when (get-buffer gnus-original-article-buffer) (set-buffer gnus-original-article-buffer) (setq gnus-original-article nil))) + (when gnus-use-cache + (gnus-cache-update-article + (car gnus-article-current) (cdr gnus-article-current)))) + (set-buffer buf) + (set-window-start (get-buffer-window buf) start) + (set-window-point (get-buffer-window buf) (point)))) + +(defun gnus-article-edit-exit () + "Exit the article editing without updating." + (interactive) + ;; We remove all text props from the article buffer. + (let ((buf (buffer-substring-no-properties (point-min) (point-max))) + (curbuf (current-buffer)) + (p (point)) + (window-start (window-start))) + (erase-buffer) + (insert buf) + (let ((winconf gnus-prev-winconf)) + (gnus-article-mode) (set-window-configuration winconf) ;; Tippy-toe some to make sure that point remains where it was. - (let ((buf (current-buffer))) + (save-current-buffer (set-buffer curbuf) (set-window-start (get-buffer-window (current-buffer)) window-start) - (goto-char p) - (set-buffer buf))))) + (goto-char p))))) (defun gnus-article-edit-full-stops () "Interactively repair spacing at end of sentences." @@ -2750,15 +4065,15 @@ groups." ;;; Internal Variables: -(defcustom gnus-button-url-regexp "\\b\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\):\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?\\([-a-zA-Z0-9_=!?#$@~`%&*+|\\/:;.,]\\|\\w\\)+\\([-a-zA-Z0-9_=#$@~`%&*+|\\/]\\|\\w\\)" +(defcustom gnus-button-url-regexp "\\b\\(\\(www\\.\\|\\(s?https?\\|ftp\\|file\\|gopher\\|news\\|telnet\\|wais\\|mailto\\):\\)\\(//[-a-zA-Z0-9_.]+:[0-9]*\\)?\\([-a-zA-Z0-9_=!?#$@~`%&*+|\\/:;.,]\\|\\w\\)+\\([-a-zA-Z0-9_=#$@~`%&*+|\\/]\\|\\w\\)\\)" "Regular expression that matches URLs." :group 'gnus-article-buttons :type 'regexp) (defcustom gnus-button-alist - `(("<\\(url:[>\n\t ]*?\\)?news:[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>" 0 t - gnus-button-message-id 2) - ("\\bnews:\\([^>\n\t ]*@[^>\n\t ]*\\)" 0 t gnus-button-message-id 1) + `(("<\\(url:[>\n\t ]*?\\)?news:[>\n\t ]*\\([^>\n\t ]*@[^>\n\t ]*\\)>" + 0 t gnus-button-message-id 2) + ("\\bnews:\\([^>\n\t ]*@[^>)!;:,\n\t ]*\\)" 0 t gnus-button-message-id 1) ("\\(\\b<\\(url:[>\n\t ]*\\)?news:[>\n\t ]*\\(//\\)?\\([^>\n\t ]*\\)>\\)" 1 t gnus-button-fetch-group 4) @@ -2766,12 +4081,12 @@ groups." ("\\bin\\( +article\\| +message\\)? +\\(<\\([^\n @<>]+@[^\n @<>]+\\)>\\)" 2 t gnus-button-message-id 3) ("\\( \n\t]+\\)>" 0 t gnus-url-mailto 2) - ("mailto:\\([a-zA-Z.-@_+0-9%]+\\)" 0 t gnus-url-mailto 1) + ("mailto:\\([-a-zA-Z.@_+0-9%]+\\)" 0 t gnus-url-mailto 1) ("\\bmailto:\\([^ \n\t]+\\)" 0 t gnus-url-mailto 1) ;; This is how URLs _should_ be embedded in text... - ("]*\\)>" 0 t gnus-button-embedded-url 1) + ("]*\\)>" 0 t gnus-button-embedded-url 1) ;; Raw URLs. - (,gnus-button-url-regexp 0 t gnus-button-url 0)) + (,gnus-button-url-regexp 0 t browse-url 0)) "*Alist of regexps matching buttons in article bodies. Each entry has the form (REGEXP BUTTON FORM CALLBACK PAR...), where @@ -2799,9 +4114,9 @@ variable it the real callback function." ("^\\(From\\|Reply-To\\):" ": *\\(.+\\)$" 1 t gnus-button-reply 1) ("^\\(Cc\\|To\\):" "[^ \t\n<>,()\"]+@[^ \t\n<>,()\"]+" 0 t gnus-button-mailto 0) - ("^X-[Uu][Rr][Ll]:" ,gnus-button-url-regexp 0 t gnus-button-url 0) - ("^Subject:" ,gnus-button-url-regexp 0 t gnus-button-url 0) - ("^[^:]+:" ,gnus-button-url-regexp 0 t gnus-button-url 0) + ("^X-[Uu][Rr][Ll]:" ,gnus-button-url-regexp 0 t browse-url 0) + ("^Subject:" ,gnus-button-url-regexp 0 t browse-url 0) + ("^[^:]+:" ,gnus-button-url-regexp 0 t browse-url 0) ("^[^:]+:" "\\(<\\(url: \\)?news:\\([^>\n ]*\\)>\\)" 1 t gnus-button-message-id 3)) "*Alist of headers and regexps to match buttons in article heads. @@ -2856,40 +4171,6 @@ call it with the value of the `gnus-data' text property." (when fun (funcall fun data)))) -(defun gnus-article-prev-button (n) - "Move point to N buttons backward. -If N is negative, move forward instead." - (interactive "p") - (gnus-article-next-button (- n))) - -(defun gnus-article-next-button (n) - "Move point to N buttons forward. -If N is negative, move backward instead." - (interactive "p") - (let ((function (if (< n 0) 'previous-single-property-change - 'next-single-property-change)) - (inhibit-point-motion-hooks t) - (backward (< n 0)) - (limit (if (< n 0) (point-min) (point-max)))) - (setq n (abs n)) - (while (and (not (= limit (point))) - (> n 0)) - ;; Skip past the current button. - (when (get-text-property (point) 'gnus-callback) - (goto-char (funcall function (point) 'gnus-callback nil limit))) - ;; Go to the next (or previous) button. - (gnus-goto-char (funcall function (point) 'gnus-callback nil limit)) - ;; Put point at the start of the button. - (when (and backward (not (get-text-property (point) 'gnus-callback))) - (goto-char (funcall function (point) 'gnus-callback nil limit))) - ;; Skip past intangible buttons. - (when (get-text-property (point) 'intangible) - (incf n)) - (decf n)) - (unless (zerop n) - (gnus-message 5 "No more buttons")) - n)) - (defun gnus-article-highlight (&optional force) "Highlight current article. This function calls `gnus-article-highlight-headers', @@ -2924,7 +4205,7 @@ do the highlighting. See the documentation for those functions." (case-fold-search t) (inhibit-point-motion-hooks t) entry regexp header-face field-face from hpoints fpoints) - (message-narrow-to-head) + (article-narrow-to-head) (while (setq entry (pop alist)) (goto-char (point-min)) (setq regexp (concat "^\\(" @@ -2990,18 +4271,19 @@ specified by `gnus-button-alist'." (alist gnus-button-alist) beg entry regexp) ;; Remove all old markers. - (let (marker entry) + (let (marker entry new-list) (while (setq marker (pop gnus-button-marker-list)) - (goto-char marker) - (when (setq entry (gnus-button-entry)) - (put-text-property (match-beginning (nth 1 entry)) - (match-end (nth 1 entry)) - 'gnus-callback nil)) - (set-marker marker nil))) + (if (or (< marker (point-min)) (>= marker (point-max))) + (push marker new-list) + (goto-char marker) + (when (setq entry (gnus-button-entry)) + (put-text-property (match-beginning (nth 1 entry)) + (match-end (nth 1 entry)) + 'gnus-callback nil)) + (set-marker marker nil))) + (setq gnus-button-marker-list new-list)) ;; We skip the headers. - (goto-char (point-min)) - (unless (search-forward "\n\n" nil t) - (goto-char (point-max))) + (article-goto-body) (setq beg (point)) (while (setq entry (pop alist)) (setq regexp (car entry)) @@ -3027,38 +4309,38 @@ specified by `gnus-button-alist'." (interactive) (save-excursion (set-buffer gnus-article-buffer) - (let ((buffer-read-only nil) - (inhibit-point-motion-hooks t) - (case-fold-search t) - (alist gnus-header-button-alist) - entry beg end) - (nnheader-narrow-to-headers) - (while alist - ;; Each alist entry. - (setq entry (car alist) - alist (cdr alist)) - (goto-char (point-min)) - (while (re-search-forward (car entry) nil t) - ;; Each header matching the entry. - (setq beg (match-beginning 0)) - (setq end (or (and (re-search-forward "^[^ \t]" nil t) - (match-beginning 0)) - (point-max))) - (goto-char beg) - (while (re-search-forward (nth 1 entry) end t) - ;; Each match within a header. - (let* ((entry (cdr entry)) - (start (match-beginning (nth 1 entry))) - (end (match-end (nth 1 entry))) - (form (nth 2 entry))) - (goto-char (match-end 0)) - (when (eval form) - (gnus-article-add-button - start end (nth 3 entry) - (buffer-substring (match-beginning (nth 4 entry)) - (match-end (nth 4 entry))))))) - (goto-char end)))) - (widen))) + (save-restriction + (let ((buffer-read-only nil) + (inhibit-point-motion-hooks t) + (case-fold-search t) + (alist gnus-header-button-alist) + entry beg end) + (article-narrow-to-head) + (while alist + ;; Each alist entry. + (setq entry (car alist) + alist (cdr alist)) + (goto-char (point-min)) + (while (re-search-forward (car entry) nil t) + ;; Each header matching the entry. + (setq beg (match-beginning 0)) + (setq end (or (and (re-search-forward "^[^ \t]" nil t) + (match-beginning 0)) + (point-max))) + (goto-char beg) + (while (re-search-forward (nth 1 entry) end t) + ;; Each match within a header. + (let* ((entry (cdr entry)) + (start (match-beginning (nth 1 entry))) + (end (match-end (nth 1 entry))) + (form (nth 2 entry))) + (goto-char (match-end 0)) + (when (eval form) + (gnus-article-add-button + start end (nth 3 entry) + (buffer-substring (match-beginning (nth 4 entry)) + (match-end (nth 4 entry))))))) + (goto-char end))))))) ;;; External functions: @@ -3072,7 +4354,9 @@ specified by `gnus-button-alist'." (nconc (and gnus-article-mouse-face (list gnus-mouse-face-prop gnus-article-mouse-face)) (list 'gnus-callback fun) - (and data (list 'gnus-data data))))) + (and data (list 'gnus-data data)))) + (widget-convert-button 'link from to :action 'gnus-widget-press-button + :button-keymap gnus-widget-button-keymap)) ;;; Internal functions: @@ -3104,7 +4388,6 @@ specified by `gnus-button-alist'." (defun gnus-button-push (marker) ;; Push button starting at MARKER. (save-excursion - (set-buffer gnus-article-buffer) (goto-char marker) (let* ((entry (gnus-button-entry)) (inhibit-point-motion-hooks t) @@ -3149,7 +4432,7 @@ specified by `gnus-button-alist'." (defun gnus-url-parse-query-string (query &optional downcase) (let (retval pairs cur key val) - (setq pairs (gnus-split-string query "&")) + (setq pairs (split-string query "&")) (while pairs (setq cur (car pairs) pairs (cdr pairs)) @@ -3230,13 +4513,8 @@ forbidden in URL encoding." ;; Reply to ADDRESS. (message-reply address)) -(defun gnus-button-url (address) - "Browse ADDRESS." - (browse-url address)) - (defun gnus-button-embedded-url (address) "Browse ADDRESS." - ;; In Emacs 20, `browse-url-browser-function' may be an alist. (browse-url (gnus-strip-whitespace address))) ;;; Next/prev buttons in the article buffer. @@ -3256,7 +4534,7 @@ forbidden in URL encoding." gnus-prev-page-line-format nil `(gnus-prev t local-map ,gnus-prev-page-map gnus-callback gnus-article-button-prev-page - gnus-type annotation)))) + article-type annotation)))) (defvar gnus-next-page-map nil) (unless gnus-next-page-map @@ -3287,7 +4565,7 @@ forbidden in URL encoding." `(gnus-next t local-map ,gnus-next-page-map gnus-callback gnus-article-button-next-page - gnus-type annotation)))) + article-type annotation)))) (defun gnus-article-button-next-page (arg) "Go to the next page." @@ -3305,6 +4583,117 @@ forbidden in URL encoding." (gnus-article-prev-page) (select-window win))) +(defvar gnus-decode-header-methods + '(mail-decode-encoded-word-region) + "List of methods used to decode headers. + +This variable is a list of FUNCTION or (REGEXP . FUNCTION). If item +is FUNCTION, FUNCTION will be apply to all newsgroups. If item is a +(REGEXP . FUNCTION), FUNCTION will be only apply to thes newsgroups +whose names match REGEXP. + +For example: +((\"chinese\" . gnus-decode-encoded-word-region-by-guess) + mail-decode-encoded-word-region + (\"chinese\" . rfc1843-decode-region)) +") + +(defvar gnus-decode-header-methods-cache nil) + +(defun gnus-multi-decode-header (start end) + "Apply the functions from `gnus-encoded-word-methods' that match." + (unless (and gnus-decode-header-methods-cache + (eq gnus-newsgroup-name + (car gnus-decode-header-methods-cache))) + (setq gnus-decode-header-methods-cache (list gnus-newsgroup-name)) + (mapcar (lambda (x) + (if (symbolp x) + (nconc gnus-decode-header-methods-cache (list x)) + (if (and gnus-newsgroup-name + (string-match (car x) gnus-newsgroup-name)) + (nconc gnus-decode-header-methods-cache + (list (cdr x)))))) + gnus-decode-header-methods)) + (let ((xlist gnus-decode-header-methods-cache)) + (pop xlist) + (save-restriction + (narrow-to-region start end) + (while xlist + (funcall (pop xlist) (point-min) (point-max)))))) + +;;; +;;; Treatment top-level handling. +;;; + +(defun gnus-treat-article (condition &optional part-number total-parts type) + (let ((length (- (point-max) (point-min))) + (alist gnus-treatment-function-alist) + (article-goto-body-goes-to-point-min-p t) + (treated-type + (or (not type) + (catch 'found + (let ((list gnus-article-treat-types)) + (while list + (when (string-match (pop list) type) + (throw 'found t))))))) + (highlightp (gnus-visual-p 'article-highlight 'highlight)) + val elem) + (gnus-run-hooks 'gnus-part-display-hook) + (while (setq elem (pop alist)) + (setq val + (save-excursion + (if (gnus-buffer-live-p gnus-summary-buffer) + (set-buffer gnus-summary-buffer)) + (symbol-value (car elem)))) + (when (and (or (consp val) + treated-type) + (gnus-treat-predicate val) + (or (not (get (car elem) 'highlight)) + highlightp)) + (save-restriction + (funcall (cadr elem))))))) + +;; Dynamic variables. +(defvar part-number) +(defvar total-parts) +(defvar type) +(defvar condition) +(defvar length) +(defun gnus-treat-predicate (val) + (cond + ((null val) + nil) + ((and (listp val) + (stringp (car val))) + (apply 'gnus-or (mapcar `(lambda (s) + (string-match s ,(or gnus-newsgroup-name ""))) + val))) + ((listp val) + (let ((pred (pop val))) + (cond + ((eq pred 'or) + (apply 'gnus-or (mapcar 'gnus-treat-predicate val))) + ((eq pred 'and) + (apply 'gnus-and (mapcar 'gnus-treat-predicate val))) + ((eq pred 'not) + (not (gnus-treat-predicate (car val)))) + ((eq pred 'typep) + (equal (car val) type)) + (t + (error "%S is not a valid predicate" pred))))) + (condition + (eq condition val)) + ((eq val t) + t) + ((eq val 'head) + nil) + ((eq val 'last) + (eq part-number total-parts)) + ((numberp val) + (< length val)) + (t + (error "%S is not a valid value" val)))) + (gnus-ems-redefine) (provide 'gnus-art) diff --git a/lisp/gnus/gnus-async.el b/lisp/gnus/gnus-async.el index 01d02a59cf6..e6616588cd6 100644 --- a/lisp/gnus/gnus-async.el +++ b/lisp/gnus/gnus-async.el @@ -1,5 +1,5 @@ ;;; gnus-async.el --- asynchronous support for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +27,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-sum) (require 'nntp) @@ -37,7 +35,7 @@ "Support for asynchronous operations." :group 'gnus) -(defcustom gnus-asynchronous t +(defcustom gnus-asynchronous nil "*If nil, inhibit all Gnus asynchronicity. If non-nil, let the other asynch variables be heeded." :group 'gnus-asynchronous @@ -49,8 +47,8 @@ If a number, prefetch only that many articles forward; if t, prefetch as many articles as possible." :group 'gnus-asynchronous :type '(choice (const :tag "off" nil) - (integer :tag "some" 0) - (other :tag "all" t))) + (const :tag "all" t) + (integer :tag "some" 0))) (defcustom gnus-prefetched-article-deletion-strategy '(read exit) "List of symbols that say when to remove articles from the prefetch buffer. @@ -79,7 +77,10 @@ It should return non-nil if the article is to be prefetched." (defvar gnus-async-article-alist nil) (defvar gnus-async-article-semaphore '(nil)) (defvar gnus-async-fetch-list nil) -(defvar gnus-asynch-obarray nil) +(defvar gnus-async-hashtb nil) +(defvar gnus-async-current-prefetch-group nil) +(defvar gnus-async-current-prefetch-article nil) +(defvar gnus-async-timer nil) (defvar gnus-async-prefetch-headers-buffer " *Async Prefetch Headers*") (defvar gnus-async-header-prefetched nil) @@ -108,8 +109,8 @@ It should return non-nil if the article is to be prefetched." ,@forms) (gnus-async-release-semaphore 'gnus-async-article-semaphore))) -(put 'gnus-asynch-with-semaphore 'lisp-indent-function 0) -(put 'gnus-asynch-with-semaphore 'edebug-form-spec '(body)) +(put 'gnus-async-with-semaphore 'lisp-indent-function 0) +(put 'gnus-async-with-semaphore 'edebug-form-spec '(body)) ;;; ;;; Article prefetch @@ -119,14 +120,14 @@ It should return non-nil if the article is to be prefetched." (defun gnus-async-close () (gnus-kill-buffer gnus-async-prefetch-article-buffer) (gnus-kill-buffer gnus-async-prefetch-headers-buffer) - (setq gnus-async-article-alist nil + (setq gnus-async-hashtb nil + gnus-async-article-alist nil gnus-async-header-prefetched nil)) (defun gnus-async-set-buffer () (nnheader-set-temp-buffer gnus-async-prefetch-article-buffer t) - (unless gnus-asynch-obarray - (set (make-local-variable 'gnus-asynch-obarray) - (gnus-make-hashtable 1023)))) + (unless gnus-async-hashtb + (setq gnus-async-hashtb (gnus-make-hashtable 1023)))) (defun gnus-async-halt-prefetch () "Stop prefetching." @@ -146,49 +147,54 @@ It should return non-nil if the article is to be prefetched." ;; do this, which leads to slightly slower article ;; buffer display. (gnus-async-prefetch-article group next summary) - (run-with-idle-timer - 0.1 nil 'gnus-async-prefetch-article group next summary))))))) + (when gnus-async-timer + (ignore-errors + (nnheader-cancel-timer 'gnus-async-timer))) + (setq gnus-async-timer + (run-with-idle-timer + 0.1 nil 'gnus-async-prefetch-article + group next summary)))))))) (defun gnus-async-prefetch-article (group article summary &optional next) "Possibly prefetch several articles starting with ARTICLE." (if (not (gnus-buffer-live-p summary)) (gnus-async-with-semaphore - (setq gnus-async-fetch-list nil)) + (setq gnus-async-fetch-list nil)) (when (and gnus-asynchronous (gnus-alive-p)) (when next (gnus-async-with-semaphore - (pop gnus-async-fetch-list))) + (pop gnus-async-fetch-list))) (let ((do-fetch next) - (do-message t)) ;(eq major-mode 'gnus-summary-mode))) + (do-message t)) ;(eq major-mode 'gnus-summary-mode))) (when (and (gnus-group-asynchronous-p group) (gnus-buffer-live-p summary) (or (not next) gnus-async-fetch-list)) (gnus-async-with-semaphore - (unless next - (setq do-fetch (not gnus-async-fetch-list)) - ;; Nix out any outstanding requests. - (setq gnus-async-fetch-list nil) - ;; Fill in the new list. - (let ((n gnus-use-article-prefetch) - (data (gnus-data-find-list article)) - d) - (while (and (setq d (pop data)) - (if (numberp n) - (natnump (decf n)) - n)) - (unless (or (gnus-async-prefetched-article-entry - group (setq article (gnus-data-number d))) - (not (natnump article)) - (not (funcall gnus-async-prefetch-article-p d))) - ;; Not already fetched -- so we add it to the list. - (push article gnus-async-fetch-list))) - (setq gnus-async-fetch-list - (nreverse gnus-async-fetch-list)))) + (unless next + (setq do-fetch (not gnus-async-fetch-list)) + ;; Nix out any outstanding requests. + (setq gnus-async-fetch-list nil) + ;; Fill in the new list. + (let ((n gnus-use-article-prefetch) + (data (gnus-data-find-list article)) + d) + (while (and (setq d (pop data)) + (if (numberp n) + (natnump (decf n)) + n)) + (unless (or (gnus-async-prefetched-article-entry + group (setq article (gnus-data-number d))) + (not (natnump article)) + (not (funcall gnus-async-prefetch-article-p d))) + ;; Not already fetched -- so we add it to the list. + (push article gnus-async-fetch-list))) + (setq gnus-async-fetch-list + (nreverse gnus-async-fetch-list)))) - (when do-fetch - (setq article (car gnus-async-fetch-list)))) + (when do-fetch + (setq article (car gnus-async-fetch-list)))) (when (and do-fetch article) ;; We want to fetch some more articles. @@ -206,26 +212,33 @@ It should return non-nil if the article is to be prefetched." (when do-message (gnus-message 9 "Prefetching article %d in group %s" article group)) + (setq gnus-async-current-prefetch-group group) + (setq gnus-async-current-prefetch-article article) (gnus-request-article article group)))))))))) (defun gnus-make-async-article-function (group article mark summary next) "Return a callback function." `(lambda (arg) - (save-excursion - (when arg - (gnus-async-set-buffer) - (gnus-async-with-semaphore - (setq - gnus-async-article-alist - (cons (list ',(intern (format "%s-%d" group article) - gnus-asynch-obarray) - ,mark (set-marker (make-marker) (point-max)) - ,group ,article) - gnus-async-article-alist)))) - (if (not (gnus-buffer-live-p ,summary)) - (gnus-async-with-semaphore - (setq gnus-async-fetch-list nil)) - (gnus-async-prefetch-article ,group ,next ,summary t))))) + (gnus-async-article-callback arg ,group ,article ,mark ,summary ,next))) + +(defun gnus-async-article-callback (arg group article mark summary next) + "Function called when an async article is done being fetched." + (save-excursion + (setq gnus-async-current-prefetch-article nil) + (when arg + (gnus-async-set-buffer) + (gnus-async-with-semaphore + (setq + gnus-async-article-alist + (cons (list (intern (format "%s-%d" group article) + gnus-async-hashtb) + mark (set-marker (make-marker) (point-max)) + group article) + gnus-async-article-alist)))) + (if (not (gnus-buffer-live-p summary)) + (gnus-async-with-semaphore + (setq gnus-async-fetch-list nil)) + (gnus-async-prefetch-article group next summary t)))) (defun gnus-async-unread-p (data) "Return non-nil if DATA represents an unread article." @@ -234,6 +247,9 @@ It should return non-nil if the article is to be prefetched." (defun gnus-async-request-fetched-article (group article buffer) "See whether we have ARTICLE from GROUP and put it in BUFFER." (when (numberp article) + (when (and (equal group gnus-async-current-prefetch-group) + (eq article gnus-async-current-prefetch-article)) + (gnus-async-wait-for-article article)) (let ((entry (gnus-async-prefetched-article-entry group article))) (when entry (save-excursion @@ -241,18 +257,48 @@ It should return non-nil if the article is to be prefetched." (copy-to-buffer buffer (cadr entry) (caddr entry)) ;; Remove the read article from the prefetch buffer. (when (memq 'read gnus-prefetched-article-deletion-strategy) - (gnus-async-delete-prefected-entry entry)) + (gnus-async-delete-prefetched-entry entry)) t))))) -(defun gnus-async-delete-prefected-entry (entry) +(defun gnus-async-wait-for-article (article) + "Wait until ARTICLE is no longer the currently-being-fetched article." + (save-excursion + (gnus-async-set-buffer) + (let ((proc (nntp-find-connection (current-buffer))) + (nntp-server-buffer (current-buffer)) + (nntp-have-messaged nil) + (tries 0)) + (condition-case nil + ;; FIXME: we could stop waiting after some + ;; timeout, but this is the wrong place to do it. + ;; rather than checking time-spent-waiting, we + ;; should check time-since-last-output, which + ;; needs to be done in nntp.el. + (while (eq article gnus-async-current-prefetch-article) + (incf tries) + (when (nntp-accept-process-output proc 1) + (setq tries 0)) + (when (and (not nntp-have-messaged) (eq 3 tries)) + (gnus-message 5 "Waiting for async article...") + (setq nntp-have-messaged t))) + (quit + ;; if the user interrupted on a slow/hung connection, + ;; do something friendly. + (when (< 3 tries) + (setq gnus-async-current-prefetch-article nil)) + (signal 'quit nil))) + (when nntp-have-messaged + (gnus-message 5 ""))))) + +(defun gnus-async-delete-prefetched-entry (entry) "Delete ENTRY from buffer and alist." (ignore-errors (delete-region (cadr entry) (caddr entry)) (set-marker (cadr entry) nil) (set-marker (caddr entry) nil)) (gnus-async-with-semaphore - (setq gnus-async-article-alist - (delq entry gnus-async-article-alist)))) + (setq gnus-async-article-alist + (delq entry gnus-async-article-alist)))) (defun gnus-async-prefetch-remove-group (group) "Remove all articles belonging to GROUP from the prefetch buffer." @@ -263,7 +309,7 @@ It should return non-nil if the article is to be prefetched." (gnus-async-set-buffer) (while alist (when (equal group (nth 3 (car alist))) - (gnus-async-delete-prefected-entry (car alist))) + (gnus-async-delete-prefetched-entry (car alist))) (pop alist)))))) (defun gnus-async-prefetched-article-entry (group article) @@ -271,7 +317,7 @@ It should return non-nil if the article is to be prefetched." (let ((entry (save-excursion (gnus-async-set-buffer) (assq (intern (format "%s-%d" group article) - gnus-asynch-obarray) + gnus-async-hashtb) gnus-async-article-alist)))) ;; Perhaps something has emptied the buffer? (if (and entry diff --git a/lisp/gnus/gnus-audio.el b/lisp/gnus/gnus-audio.el index f3bb686d8c9..e84c1dfd9df 100644 --- a/lisp/gnus/gnus-audio.el +++ b/lisp/gnus/gnus-audio.el @@ -47,37 +47,37 @@ "Executable program for playing WAV files.") ;;; The following isn't implemented yet. Wait for Millennium Gnus. -;(defvar gnus-audio-effects-enabled t -; "When t, Gnus will use sound effects.") -;(defvar gnus-audio-enable-hooks nil -; "Functions run when enabling sound effects.") -;(defvar gnus-audio-disable-hooks nil -; "Functions run when disabling sound effects.") -;(defvar gnus-audio-theme-song nil -; "Theme song for Gnus.") -;(defvar gnus-audio-enter-group nil -; "Sound effect played when selecting a group.") -;(defvar gnus-audio-exit-group nil -; "Sound effect played when exiting a group.") -;(defvar gnus-audio-score-group nil -; "Sound effect played when scoring a group.") -;(defvar gnus-audio-busy-sound nil -; "Sound effect played when going into a ... sequence.") +;;(defvar gnus-audio-effects-enabled t +;; "When t, Gnus will use sound effects.") +;;(defvar gnus-audio-enable-hooks nil +;; "Functions run when enabling sound effects.") +;;(defvar gnus-audio-disable-hooks nil +;; "Functions run when disabling sound effects.") +;;(defvar gnus-audio-theme-song nil +;; "Theme song for Gnus.") +;;(defvar gnus-audio-enter-group nil +;; "Sound effect played when selecting a group.") +;;(defvar gnus-audio-exit-group nil +;; "Sound effect played when exiting a group.") +;;(defvar gnus-audio-score-group nil +;; "Sound effect played when scoring a group.") +;;(defvar gnus-audio-busy-sound nil +;; "Sound effect played when going into a ... sequence.") ;;;###autoload - ;(defun gnus-audio-enable-sound () -; "Enable Sound Effects for Gnus." -; (interactive) -; (setq gnus-audio-effects-enabled t) -; (gnus-run-hooks gnus-audio-enable-hooks)) +;;(defun gnus-audio-enable-sound () +;; "Enable Sound Effects for Gnus." +;; (interactive) +;; (setq gnus-audio-effects-enabled t) +;; (gnus-run-hooks gnus-audio-enable-hooks)) ;;;###autoload ;(defun gnus-audio-disable-sound () -; "Disable Sound Effects for Gnus." -; (interactive) -; (setq gnus-audio-effects-enabled nil) -; (gnus-run-hooks gnus-audio-disable-hooks)) +;; "Disable Sound Effects for Gnus." +;; (interactive) +;; (setq gnus-audio-effects-enabled nil) +;; (gnus-run-hooks gnus-audio-disable-hooks)) ;;;###autoload (defun gnus-audio-play (file) @@ -104,16 +104,16 @@ ;;; The following isn't implemented yet, wait for Red Gnus - ;(defun gnus-audio-startrek-sounds () -; "Enable sounds from Star Trek the original series." -; (interactive) -; (setq gnus-audio-busy-sound "working.au") -; (setq gnus-audio-enter-group "bulkhead_door.au") -; (setq gnus-audio-exit-group "bulkhead_door.au") -; (setq gnus-audio-score-group "ST_laser.au") -; (setq gnus-audio-theme-song "startrek.au") -; (add-hook 'gnus-select-group-hook 'gnus-audio-startrek-select-group) -; (add-hook 'gnus-exit-group-hook 'gnus-audio-startrek-exit-group)) +;;(defun gnus-audio-startrek-sounds () +;; "Enable sounds from Star Trek the original series." +;; (interactive) +;; (setq gnus-audio-busy-sound "working.au") +;; (setq gnus-audio-enter-group "bulkhead_door.au") +;; (setq gnus-audio-exit-group "bulkhead_door.au") +;; (setq gnus-audio-score-group "ST_laser.au") +;; (setq gnus-audio-theme-song "startrek.au") +;; (add-hook 'gnus-select-group-hook 'gnus-audio-startrek-select-group) +;; (add-hook 'gnus-exit-group-hook 'gnus-audio-startrek-exit-group)) ;;;*** (defvar gnus-startup-jingle "Tuxedomoon.Jingle4.au" diff --git a/lisp/gnus/gnus-bcklg.el b/lisp/gnus/gnus-bcklg.el index 323bb9ff041..5a9cbf6c10f 100644 --- a/lisp/gnus/gnus-bcklg.el +++ b/lisp/gnus/gnus-bcklg.el @@ -1,5 +1,5 @@ ;;; gnus-bcklg.el --- backlog functions for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +27,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) ;;; @@ -44,7 +42,7 @@ (or (get-buffer gnus-backlog-buffer) (save-excursion (set-buffer (gnus-get-buffer-create gnus-backlog-buffer)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq buffer-read-only t) (get-buffer gnus-backlog-buffer)))) @@ -84,7 +82,9 @@ (setq b (point)) (insert-buffer-substring buffer) ;; Tag the beginning of the article with the ident. - (gnus-put-text-property b (1+ b) 'gnus-backlog ident)))))) + (if (> (point-max) b) + (gnus-put-text-property b (1+ b) 'gnus-backlog ident) + (gnus-error 3 "Article %d is blank" number))))))) (defun gnus-backlog-remove-oldest-article () (save-excursion @@ -126,7 +126,7 @@ t)) (setq gnus-backlog-articles (delq ident gnus-backlog-articles))))))) -(defun gnus-backlog-request-article (group number buffer) +(defun gnus-backlog-request-article (group number &optional buffer) (when (numberp number) (gnus-backlog-setup) (let ((ident (intern (concat group ":" (int-to-string number)) @@ -146,10 +146,12 @@ (setq end (next-single-property-change (1+ beg) 'gnus-backlog (current-buffer) (point-max))))) - (let ((buffer-read-only nil)) - (erase-buffer) - (insert-buffer-substring gnus-backlog-buffer beg end) - t))))) + (save-excursion + (and buffer (set-buffer buffer)) + (let ((buffer-read-only nil)) + (erase-buffer) + (insert-buffer-substring gnus-backlog-buffer beg end))) + t)))) (provide 'gnus-bcklg) diff --git a/lisp/gnus/gnus-cache.el b/lisp/gnus/gnus-cache.el index ce97a82a6ea..3e80bdc1e46 100644 --- a/lisp/gnus/gnus-cache.el +++ b/lisp/gnus/gnus-cache.el @@ -1,5 +1,6 @@ ;;; gnus-cache.el --- cache interface for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-int) (require 'gnus-range) @@ -62,7 +61,7 @@ If a group matches both gnus-cacheable-groups and gnus-uncacheable-groups it's not cached." :group 'gnus-cache :type '(choice (const :tag "off" nil) - regexp)) + regexp)) (defcustom gnus-uncacheable-groups nil "*Groups that match this regexp will not be cached. @@ -79,6 +78,9 @@ it's not cached." (defvar gnus-cache-overview-coding-system 'raw-text "Coding system used on Gnus cache files.") +(defvar gnus-cache-coding-system 'raw-text + "Coding system used on Gnus cache files.") + ;;; Internal variables. @@ -144,20 +146,17 @@ it's not cached." (setq gnus-cache-buffer nil)))) (defun gnus-cache-possibly-enter-article - (group article headers ticked dormant unread &optional force) + (group article ticked dormant unread &optional force) (when (and (or force (not (eq gnus-use-cache 'passive))) (numberp article) - (> article 0) - (vectorp headers)) ; This might be a dummy article. - ;; If this is a virtual group, we find the real group. - (when (gnus-virtual-group-p group) - (let ((result (nnvirtual-find-group-art - (gnus-group-real-name group) article))) - (setq group (car result) - headers (copy-sequence headers)) - (mail-header-set-number headers (cdr result)))) - (let ((number (mail-header-number headers)) - file) + (> article 0)) ; This might be a dummy article. + (let ((number article) file headers) + ;; If this is a virtual group, we find the real group. + (when (gnus-virtual-group-p group) + (let ((result (nnvirtual-find-group-art + (gnus-group-real-name group) article))) + (setq group (car result) + number (cdr result)))) (when (and number (> number 0) ; Reffed article. (or force @@ -177,10 +176,15 @@ it's not cached." t ; The article already is saved. (save-excursion (set-buffer nntp-server-buffer) - (let ((gnus-use-cache nil)) + (require 'gnus-art) + (let ((gnus-use-cache nil) + (gnus-article-decode-hook nil)) (gnus-request-article-this-buffer number group)) (when (> (buffer-size) 0) - (gnus-write-buffer file) + (let ((coding-system-for-write gnus-cache-coding-system)) + (gnus-write-buffer file)) + (setq headers (nnheader-parse-head t)) + (mail-header-set-number headers number) (gnus-cache-change-buffer group) (set-buffer (cdr gnus-cache-buffer)) (goto-char (point-max)) @@ -202,17 +206,7 @@ it's not cached." (beginning-of-line)) (forward-line 1)) (beginning-of-line) - ;; [number subject from date id references chars lines xref] - (insert (format "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t\n" - (mail-header-number headers) - (mail-header-subject headers) - (mail-header-from headers) - (mail-header-date headers) - (mail-header-id headers) - (or (mail-header-references headers) "") - (or (mail-header-chars headers) "") - (or (mail-header-lines headers) "") - (or (mail-header-xref headers) ""))) + (nnheader-insert-nov headers) ;; Update the active info. (set-buffer gnus-summary-buffer) (gnus-cache-update-active group number) @@ -266,7 +260,8 @@ it's not cached." (when (file-exists-p file) (erase-buffer) (gnus-kill-all-overlays) - (insert-file-contents file) + (let ((coding-system-for-read gnus-cache-coding-system)) + (insert-file-contents file)) t))) (defun gnus-cache-possibly-alter-active (group active) @@ -312,7 +307,9 @@ it's not cached." ;; unsuccessful), so we use the cached headers exclusively. (set-buffer nntp-server-buffer) (erase-buffer) - (insert-file-contents cache-file) + (let ((coding-system-for-read + gnus-cache-overview-coding-system)) + (insert-file-contents cache-file)) 'nov) ((eq type 'nov) ;; We have both cached and uncached NOV headers, so we @@ -337,7 +334,6 @@ Returns the list of articles entered." (if (natnump article) (when (gnus-cache-possibly-enter-article gnus-newsgroup-name article - (gnus-summary-article-header article) nil nil nil t) (push article out)) (gnus-message 2 "Can't cache article %d" article)) @@ -371,7 +367,7 @@ Returns the list of articles removed." (defun gnus-summary-insert-cached-articles () "Insert all the articles cached for this group into the current buffer." (interactive) - (let ((cached (sort (copy-sequence gnus-newsgroup-cached) '<)) + (let ((cached (sort (copy-sequence gnus-newsgroup-cached) '>)) (gnus-verbose (max 6 gnus-verbose))) (unless cached (gnus-message 3 "No cached articles for this group")) @@ -397,7 +393,6 @@ Returns the list of articles removed." (cons group (set-buffer (gnus-get-buffer-create " *gnus-cache-overview*")))) - (buffer-disable-undo (current-buffer)) ;; Insert the contents of this group's cache overview. (erase-buffer) (let ((file (gnus-cache-file-name group ".overview"))) @@ -420,7 +415,9 @@ Returns the list of articles removed." (nnheader-translate-file-chars (if (gnus-use-long-file-name 'not-cache) group - (let ((group (nnheader-replace-chars-in-string group ?/ ?_))) + (let ((group (nnheader-replace-duplicate-chars-in-string + (nnheader-replace-chars-in-string group ?/ ?_) + ?. ?_))) ;; Translate the first colon into a slash. (when (string-match ":" group) (aset group (match-beginning 0) ?/)) @@ -431,10 +428,10 @@ Returns the list of articles removed." (defun gnus-cache-update-article (group article) "If ARTICLE is in the cache, remove it and re-enter it." (gnus-cache-change-buffer group) - (when (gnus-cache-possibly-remove-article article nil nil nil t) + (when (gnus-cache-possibly-remove-article article nil nil nil t) (let ((gnus-use-cache nil)) (gnus-cache-possibly-enter-article - gnus-newsgroup-name article (gnus-summary-article-header article) + gnus-newsgroup-name article nil nil nil t)))) (defun gnus-cache-possibly-remove-article (article ticked dormant unread @@ -489,9 +486,11 @@ Returns the list of articles removed." (gnus-cache-save-buffers) (save-excursion (set-buffer cache-buf) - (buffer-disable-undo (current-buffer)) (erase-buffer) - (insert-file-contents (or file (gnus-cache-file-name group ".overview"))) + (let ((coding-system-for-read + gnus-cache-overview-coding-system)) + (insert-file-contents + (or file (gnus-cache-file-name group ".overview")))) (goto-char (point-min)) (insert "\n") (goto-char (point-min))) @@ -519,7 +518,6 @@ Returns the list of articles removed." (let ((cache-buf (gnus-get-buffer-create " *gnus-cache*"))) (save-excursion (set-buffer cache-buf) - (buffer-disable-undo (current-buffer)) (erase-buffer)) (set-buffer nntp-server-buffer) (goto-char (point-min)) @@ -534,7 +532,9 @@ Returns the list of articles removed." (save-excursion (set-buffer cache-buf) (erase-buffer) - (insert-file-contents (gnus-cache-file-name group (car cached))) + (let ((coding-system-for-read + gnus-cache-coding-system)) + (insert-file-contents (gnus-cache-file-name group (car cached)))) (goto-char (point-min)) (insert "220 ") (princ (car cached) (current-buffer)) @@ -557,6 +557,7 @@ $ emacs -batch -l ~/.emacs -l gnus -f gnus-jog-cache" (let ((gnus-mark-article-hook nil) (gnus-expert-user t) (nnmail-spool-file nil) + (mail-sources nil) (gnus-use-dribble-file nil) (gnus-novice-user nil) (gnus-large-newsgroup nil)) @@ -585,7 +586,7 @@ $ emacs -batch -l ~/.emacs -l gnus -f gnus-jog-cache" ;; We simply read the active file. (save-excursion (gnus-set-work-buffer) - (insert-file-contents gnus-cache-active-file) + (nnheader-insert-file-contents gnus-cache-active-file) (gnus-active-to-gnus-format nil (setq gnus-cache-active-hashtb (gnus-make-hashtable @@ -597,14 +598,7 @@ $ emacs -batch -l ~/.emacs -l gnus -f gnus-jog-cache" (when (or force (and gnus-cache-active-hashtb gnus-cache-active-altered)) - (nnheader-temp-write gnus-cache-active-file - (mapatoms - (lambda (sym) - (when (and sym (boundp sym)) - (insert (format "%s %d %d y\n" - (symbol-name sym) (cdr (symbol-value sym)) - (car (symbol-value sym)))))) - gnus-cache-active-hashtb)) + (gnus-write-active-file gnus-cache-active-file gnus-cache-active-hashtb t) ;; Mark the active hashtb as unaltered. (setq gnus-cache-active-altered nil))) diff --git a/lisp/gnus/gnus-cite.el b/lisp/gnus/gnus-cite.el index b7093c99adc..fc5380258f5 100644 --- a/lisp/gnus/gnus-cite.el +++ b/lisp/gnus/gnus-cite.el @@ -1,7 +1,13 @@ ;;; gnus-cite.el --- parse citations in articles for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. -;; Author: Per Abhiddenware; you can redistribute it and/or modify +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. + +;; Author: Per Abhiddenware + +;; This file is part of GNU Emacs. + +;; GNU Emacs 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 2, or (at your option) ;; any later version. @@ -22,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-art) (require 'gnus-range) @@ -44,10 +48,10 @@ article has citations." :type 'string) (defcustom gnus-cite-always-check nil - "Check article always for citations. Set it t to check all articles." + "Check article always for citations. Set it t to check all articles." :group 'gnus-cite :type '(choice (const :tag "no" nil) - (const :tag "yes" t))) + (const :tag "yes" t))) (defcustom gnus-cited-opened-text-button-line-format "%(%{[-]%}%)\n" "Format of opened cited text buttons." @@ -60,10 +64,13 @@ article has citations." :type 'string) (defcustom gnus-cited-lines-visible nil - "The number of lines of hidden cited text to remain visible." + "The number of lines of hidden cited text to remain visible. +Or a pair (cons) of numbers which are the number of lines at the top +and bottom of the text, respectively, to remain visible." :group 'gnus-cite :type '(choice (const :tag "none" nil) - integer)) + integer + (cons :tag "Top and Bottom" integer integer))) (defcustom gnus-cite-parse-max-size 25000 "Maximum article size (in bytes) where parsing citations is allowed. @@ -73,7 +80,7 @@ Set it to nil to parse all articles." integer)) (defcustom gnus-cite-prefix-regexp - "^[]>|:}+ ]*[]>|:}+]\\(.*>\\)?\\|^.*>" + "^[]>»|:}+ ]*[]>»|:}+]\\(.*>»\\)?\\|^.*>" "*Regexp matching the longest possible citation prefix on a line." :group 'gnus-cite :type 'regexp) @@ -103,13 +110,13 @@ The first regexp group should match the Supercite attribution." :type 'integer) (defcustom gnus-cite-attribution-prefix - "In article\\|in <\\|On \\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\)," + "In article\\|in <\\|On \\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\),\\|-----Original Message-----" "*Regexp matching the beginning of an attribution line." :group 'gnus-cite :type 'regexp) (defcustom gnus-cite-attribution-suffix - "\\(\\(wrote\\|writes\\|said\\|says\\|>\\)\\(:\\|\\.\\.\\.\\)\\)[ \t]*$" + "\\(\\(wrote\\|writes\\|said\\|says\\|>\\)\\(:\\|\\.\\.\\.\\)\\|-----Original Message-----\\)[ \t]*$" "*Regexp matching the end of an attribution line. The text matching the first grouping will be used as a button." :group 'gnus-cite @@ -237,8 +244,8 @@ It is merged with the face for the cited text belonging to the attribution." (defcustom gnus-cite-face-list '(gnus-cite-face-1 gnus-cite-face-2 gnus-cite-face-3 gnus-cite-face-4 - gnus-cite-face-5 gnus-cite-face-6 gnus-cite-face-7 gnus-cite-face-8 - gnus-cite-face-9 gnus-cite-face-10 gnus-cite-face-11) + gnus-cite-face-5 gnus-cite-face-6 gnus-cite-face-7 gnus-cite-face-8 + gnus-cite-face-9 gnus-cite-face-10 gnus-cite-face-11) "*List of faces used for highlighting citations. When there are citations from multiple articles in the same message, @@ -342,7 +349,8 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps skip (gnus-cite-find-prefix number) face (cdr (assoc prefix face-alist))) ;; Add attribution button. - (goto-line number) + (goto-char (point-min)) + (forward-line (1- number)) (when (re-search-forward gnus-cite-attribution-suffix (save-excursion (end-of-line 1) (point)) t) @@ -364,7 +372,7 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps "Dissect the article buffer looking for cited text." (save-excursion (set-buffer gnus-article-buffer) - (gnus-cite-parse-maybe) + (gnus-cite-parse-maybe nil t) (let ((alist gnus-cite-prefix-alist) prefix numbers number marks m) ;; Loop through citation prefixes. @@ -383,8 +391,7 @@ Lines matching `gnus-cite-attribution-suffix' and perhaps (forward-line (1- number)) (push (cons (point-marker) prefix) marks))) ;; Skip to the beginning of the body. - (goto-char (point-min)) - (search-forward "\n\n" nil t) + (article-goto-body) (push (cons (point-marker) "") marks) ;; Find the end of the body. (goto-char (point-max)) @@ -434,7 +441,6 @@ If WIDTH (the numerical prefix), use that text width when filling." (fill-column (if width (prefix-numeric-value width) fill-column))) (save-restriction (while (cdr marks) - (widen) (narrow-to-region (caar marks) (caadr marks)) (let ((adaptive-fill-regexp (concat "^" (regexp-quote (cdar marks)) " *")) @@ -488,10 +494,18 @@ always hide." ;; Skip past lines we want to leave visible. (when (and beg end gnus-cited-lines-visible) (goto-char beg) - (forward-line gnus-cited-lines-visible) + (forward-line (if (consp gnus-cited-lines-visible) + (car gnus-cited-lines-visible) + gnus-cited-lines-visible)) (if (>= (point) end) (setq beg nil) - (setq beg (point-marker)))) + (setq beg (point-marker)) + (when (consp gnus-cited-lines-visible) + (goto-char end) + (forward-line (- (cdr gnus-cited-lines-visible))) + (if (<= (point) beg) + (setq beg nil) + (setq end (point-marker)))))) (when (and beg end) ;; We use markers for the end-points to facilitate later ;; wrapping and mangling of text. @@ -517,17 +531,19 @@ always hide." (defun gnus-article-toggle-cited-text (args) "Toggle hiding the text in REGION." (let* ((region (car args)) + (beg (car region)) + (end (cdr region)) (start (cadr args)) (hidden (text-property-any - (car region) (1- (cdr region)) + beg (1- end) (car gnus-hidden-properties) (cadr gnus-hidden-properties))) (inhibit-point-motion-hooks t) buffer-read-only) (funcall (if hidden 'remove-text-properties 'gnus-add-text-properties) - (car region) (cdr region) gnus-hidden-properties) + beg end gnus-hidden-properties) (save-excursion (goto-char start) (gnus-delete-line) @@ -560,8 +576,7 @@ See also the documentation for `gnus-article-highlight-citation'." (save-excursion (set-buffer gnus-article-buffer) (gnus-cite-parse-maybe force) - (goto-char (point-min)) - (search-forward "\n\n" nil t) + (article-goto-body) (let ((start (point)) (atts gnus-cite-attribution-alist) (buffer-read-only nil) @@ -585,7 +600,8 @@ See also the documentation for `gnus-article-highlight-citation'." (while total (setq hidden (car total) total (cdr total)) - (goto-line hidden) + (goto-char (point-min)) + (forward-line (1- hidden)) (unless (assq hidden gnus-cite-attribution-alist) (gnus-add-text-properties (point) (progn (forward-line 1) (point)) @@ -605,45 +621,42 @@ See also the documentation for `gnus-article-highlight-citation'." ;;; Internal functions: -(defun gnus-cite-parse-maybe (&optional force) - ;; Parse if the buffer has changes since last time. - (if (and (not force) - (equal gnus-cite-article gnus-article-current)) +(defun gnus-cite-parse-maybe (&optional force no-overlay) + "Always parse the buffer." + (gnus-cite-localize) + ;;Reset parser information. + (setq gnus-cite-prefix-alist nil + gnus-cite-attribution-alist nil + gnus-cite-loose-prefix-alist nil + gnus-cite-loose-attribution-alist nil) + (unless no-overlay + (gnus-cite-delete-overlays)) + ;; Parse if not too large. + (if (and gnus-cite-parse-max-size + (> (buffer-size) gnus-cite-parse-max-size)) () - (gnus-cite-localize) - ;;Reset parser information. - (setq gnus-cite-prefix-alist nil - gnus-cite-attribution-alist nil - gnus-cite-loose-prefix-alist nil - gnus-cite-loose-attribution-alist nil) - (while gnus-cite-overlay-list - (gnus-delete-overlay (pop gnus-cite-overlay-list))) - ;; Parse if not too large. - (if (and (not force) - gnus-cite-parse-max-size - (> (buffer-size) gnus-cite-parse-max-size)) - () - (setq gnus-cite-article (cons (car gnus-article-current) - (cdr gnus-article-current))) - (gnus-cite-parse-wrapper)))) + (setq gnus-cite-article (cons (car gnus-article-current) + (cdr gnus-article-current))) + (gnus-cite-parse-wrapper))) + +(defun gnus-cite-delete-overlays () + (dolist (overlay gnus-cite-overlay-list) + (when (or (not (gnus-overlay-end overlay)) + (and (>= (gnus-overlay-end overlay) (point-min)) + (<= (gnus-overlay-end overlay) (point-max)))) + (setq gnus-cite-overlay-list (delete overlay gnus-cite-overlay-list)) + (gnus-delete-overlay overlay)))) (defun gnus-cite-parse-wrapper () - ;; Wrap chopped gnus-cite-parse - (goto-char (point-min)) - (unless (search-forward "\n\n" nil t) - (goto-char (point-max))) - (save-excursion - (gnus-cite-parse-attributions)) - ;; Try to avoid check citation if there is no reason to believe - ;; that article has citations - (if (or gnus-cite-always-check - (save-excursion - (re-search-backward gnus-cite-reply-regexp nil t)) - gnus-cite-loose-attribution-alist) - (progn (save-excursion - (gnus-cite-parse)) - (save-excursion - (gnus-cite-connect-attributions))))) + ;; Wrap chopped gnus-cite-parse. + (article-goto-body) + (let ((inhibit-point-motion-hooks t)) + (save-excursion + (gnus-cite-parse-attributions)) + (save-excursion + (gnus-cite-parse)) + (save-excursion + (gnus-cite-connect-attributions)))) (defun gnus-cite-parse () ;; Parse and connect citation prefixes and attribution lines. @@ -898,8 +911,8 @@ See also the documentation for `gnus-article-highlight-citation'." (when face (let ((inhibit-point-motion-hooks t) from to overlay) - (goto-line number) - (unless (eobp) ; Sometimes things become confused. + (goto-char (point-min)) + (when (zerop (forward-line (1- number))) (forward-char (length prefix)) (skip-chars-forward " \t") (setq from (point)) @@ -914,7 +927,7 @@ See also the documentation for `gnus-article-highlight-citation'." (defun gnus-cite-toggle (prefix) (save-excursion (set-buffer gnus-article-buffer) - (gnus-cite-parse-maybe) + (gnus-cite-parse-maybe nil t) (let ((buffer-read-only nil) (numbers (cdr (assoc prefix gnus-cite-prefix-alist))) (inhibit-point-motion-hooks t) @@ -922,7 +935,8 @@ See also the documentation for `gnus-article-highlight-citation'." (while numbers (setq number (car numbers) numbers (cdr numbers)) - (goto-line number) + (goto-char (point-min)) + (forward-line (1- number)) (cond ((get-text-property (point) 'invisible) (remove-text-properties (point) (progn (forward-line 1) (point)) gnus-hidden-properties)) @@ -958,4 +972,8 @@ See also the documentation for `gnus-article-highlight-citation'." (provide 'gnus-cite) +;; Local Variables: +;; coding: iso-8859-1 +;; End: + ;;; gnus-cite.el ends here diff --git a/lisp/gnus/gnus-cus.el b/lisp/gnus/gnus-cus.el index 025273b6add..142049a7f08 100644 --- a/lisp/gnus/gnus-cus.el +++ b/lisp/gnus/gnus-cus.el @@ -1,6 +1,6 @@ ;;; gnus-cus.el --- customization commands for Gnus ;; -;; Copyright (C) 1996 Free Software Foundation, Inc. +;; Copyright (C) 1996,1999, 2000 Free Software Foundation, Inc. ;; Author: Per Abrahamsen ;; Keywords: news @@ -28,6 +28,7 @@ (require 'wid-edit) (require 'gnus-score) +(require 'gnus-topic) ;;; Widgets: @@ -51,6 +52,21 @@ if that value is non-nil." (setq major-mode 'gnus-custom-mode mode-name "Gnus Customize") (use-local-map widget-keymap) + ;; Emacs 21 stuff: + (when (and (facep 'custom-button-face) + (facep 'custom-button-pressed-face)) + (set (make-local-variable 'widget-button-face) + 'custom-button-face) + (set (make-local-variable 'widget-button-pressed-face) + 'custom-button-pressed-face) + (set (make-local-variable 'widget-mouse-face) + 'custom-button-pressed-face)) + (when (and (boundp 'custom-raised-buttons) + (symbol-value 'custom-raised-buttons)) + (set (make-local-variable 'widget-push-button-prefix) "") + (set (make-local-variable 'widget-push-button-suffix) "") + (set (make-local-variable 'widget-link-prefix) "") + (set (make-local-variable 'widget-link-suffix) "")) (gnus-run-hooks 'gnus-custom-mode-hook)) ;;; Group Customization: @@ -70,14 +86,63 @@ not. Let's say there's a group on the server that is called `fa.4ad-l'. This is a real newsgroup, but the server has gotten the articles from a mail-to-news gateway. Posting directly to this group is therefore impossible--you have to send mail to the mailing list -address instead.") +address instead. + +The gnus-group-split mail splitting mechanism will behave as if this +address was listed in gnus-group-split Addresses (see below).") (to-list (gnus-email-address :tag "To List") "\ This address will be used when doing a `a' in the group. It is totally ignored when doing a followup--except that if it is present in a news group, you'll get mail group semantics when doing -`f'.") +`f'. + +The gnus-group-split mail splitting mechanism will behave as if this +address was listed in gnus-group-split Addresses (see below).") + + (extra-aliases (choice + :tag "Extra Aliases" + (list + :tag "List" + (editable-list + :inline t + (gnus-email-address :tag "Address"))) + (gnus-email-address :tag "Address")) "\ +Store messages posted from or to this address in this group. + +You must be using gnus-group-split for this to work. The VALUE of the +nnmail-split-fancy SPLIT generated for this group will match these +addresses.") + + (split-regexp (regexp :tag "gnus-group-split Regular Expression") "\ +Like gnus-group-split Address, but expects a regular expression.") + + (split-exclude (list :tag "gnus-group-split Restricts" + (editable-list + :inline t (regexp :tag "Restrict"))) "\ +Regular expression that cancels gnus-group-split matches. + +Each entry is added to the nnmail-split-fancy SPLIT as a separate +RESTRICT clause.") + + (split-spec (choice :tag "gnus-group-split Overrider" + (sexp :tag "Fancy Split") + (const :tag "Catch All" catch-all) + (const :tag "Ignore" nil)) "\ +Override all other gnus-group-split fields. + +In `Fancy Split', you can enter any nnmail-split-fancy SPLIT. Note +that the name of this group won't be automatically assumed, you have +to add it to the SPLITs yourself. This means you can use such splits +to split messages to other groups too. + +If you select `Catch All', this group will get postings for any +messages not matched in any other group. It overrides the variable +gnus-group-split-default-catch-all-group. + +Selecting `Ignore' forces no SPLIT to be generated for this group, +disabling all other gnus-group-split fields.") (broken-reply-to (const :tag "Broken Reply To" t) "\ Ignore `Reply-To' headers in this group. @@ -87,7 +152,7 @@ listserv has inserted `Reply-To' headers that point back to the listserv itself. This is broken behavior. So there!") (to-group (string :tag "To Group") "\ -All posts will be send to the specified group.") +All posts will be sent to the specified group.") (gcc-self (choice :tag "GCC" :value t @@ -97,12 +162,18 @@ All posts will be send to the specified group.") Specify default value for GCC header. If this symbol is present in the group parameter list and set to `t', -new composed messages will be `Gcc''d to the current group. If it is +new composed messages will be `Gcc''d to the current group. If it is present and set to `none', no `Gcc:' header will be generated, if it is present and a string, this string will be inserted literally as a `gcc' header (this symbol takes precedence over any default `Gcc' rules as described later).") + (banner (choice :tag "Banner" + (const signature) + regexp + (const :tag "None" nil)) "\ +Regular expression matching banners to be removed from articles.") + (auto-expire (const :tag "Automatic Expire" t) "\ All articles that are read will be marked as expirable.") @@ -121,10 +192,19 @@ Use with caution.") When to expire. Overrides any `nnmail-expiry-wait' and `nnmail-expiry-wait-function' -when expiring expirable messages. The value can either be a number of +when expiring expirable messages. The value can either be a number of days (not necessarily an integer) or the symbols `never' or `immediate'.") + (expiry-target (choice :tag "Expiry Target" + :value delete + (const delete) + (function :format "%v" nnmail-) + string) "\ +Where expired messages end up. + +Overrides `nnmail-expiry-target', which see.") + (score-file (file :tag "Score File") "\ Make the specified file into the current score file. This means that all score commands you issue will end up in this file.") @@ -159,30 +239,78 @@ An arbitrary comment on the group.") (visible (const :tag "Permanently visible" t) "\ Always display this group, even when there are no unread articles -in it..")) - "Alist of valid group parameters. +in it..") + + (charset (symbol :tag "Charset") "\ +The default charset to use in the group.") + + (ignored-charsets + (choice :tag "Ignored charsets" + :value nil + (repeat (symbol))) "\ +List of charsets that should be ignored. + +When these charsets are used in the \"charset\" parameter, the +default charset will be used instead.") + + (highlight-words + (choice :tag "Highlight words" + :value nil + (repeat (list (regexp :tag "Highlight regexp") + (number :tag "Group for entire word" 0) + (number :tag "Group for displayed part" 0) + (symbol :tag "Face" + gnus-emphasis-highlight-words)))) + "highlight regexps. +See gnus-emphasis-alist.")) + "Alist of valid group or topic parameters. Each entry has the form (NAME TYPE DOC), where NAME is the parameter itself (a symbol), TYPE is the parameters type (a sexp widget), and DOC is a documentation string for the parameter.") +(defconst gnus-extra-topic-parameters + '((subscribe (regexp :tag "Subscribe") "\ +If `gnus-subscribe-newsgroup-method' is set to +`gnus-subscribe-topics', new groups that matches this regexp will +automatically be subscribed to this topic")) + "Alist of topic parameters that are not also group parameters. + +Each entry has the form (NAME TYPE DOC), where NAME is the parameter +itself (a symbol), TYPE is the parameters type (a sexp widget), and +DOC is a documentation string for the parameter.") + +(defconst gnus-extra-group-parameters + '((uidvalidity (string :tag "IMAP uidvalidity") "\ +Server-assigned value attached to IMAP groups, used to maintain consistency.")) + "Alist of group parameters that are not also topic parameters. + +Each entry has the form (NAME TYPE DOC), where NAME is the parameter +itself (a symbol), TYPE is the parameters type (a sexp widget), and +DOC is a documentation string for the parameter.") (defvar gnus-custom-params) (defvar gnus-custom-method) (defvar gnus-custom-group) +(defvar gnus-custom-topic) -(defun gnus-group-customize (group) - "Edit the group on the current line." - (interactive (list (gnus-group-group-name))) +(defun gnus-group-customize (group &optional topic) + "Edit the group or topic on the current line." + (interactive (list (gnus-group-group-name) (gnus-group-topic-name))) (let (info (types (mapcar (lambda (entry) `(cons :format "%v%h\n" :doc ,(nth 2 entry) (const :format "" ,(nth 0 entry)) ,(nth 1 entry))) - gnus-group-parameters))) - (unless group + (append gnus-group-parameters + (if group + gnus-extra-group-parameters + gnus-extra-topic-parameters))))) + (unless (or group topic) (error "No group on current line")) - (unless (setq info (gnus-get-info group)) + (when (and group topic) + (error "Both a group an topic on current line")) + (unless (or topic (setq info (gnus-get-info group))) (error "Killed group; can't be edited")) ;; Ready. (kill-buffer (gnus-get-buffer-create "*Gnus Customize*")) @@ -190,13 +318,21 @@ DOC is a documentation string for the parameter.") (gnus-custom-mode) (make-local-variable 'gnus-custom-group) (setq gnus-custom-group group) + (make-local-variable 'gnus-custom-topic) + (setq gnus-custom-topic topic) + (buffer-disable-undo) (widget-insert "Customize the ") - (widget-create 'info-link - :help-echo "Push me to learn more." - :tag "group parameters" - "(gnus)Group Parameters") + (if group + (widget-create 'info-link + :help-echo "Push me to learn more." + :tag "group parameters" + "(gnus)Group Parameters") + (widget-create 'info-link + :help-echo "Push me to learn more." + :tag "topic parameters" + "(gnus)Topic Parameters")) (widget-insert " for <") - (widget-insert group) + (widget-insert (gnus-group-decoded-name (or group topic))) (widget-insert "> and press ") (widget-create 'push-button :tag "done" @@ -206,15 +342,17 @@ DOC is a documentation string for the parameter.") (make-local-variable 'gnus-custom-params) (setq gnus-custom-params (widget-create 'group - :value (gnus-info-params info) + :value (if group + (gnus-info-params info) + (gnus-topic-parameters topic)) `(set :inline t :greedy t :tag "Parameters" :format "%t:\n%h%v" :doc "\ -These special paramerters are recognized by Gnus. -Check the [ ] for the parameters you want to apply to this group, then -edit the value to suit your taste." +These special parameters are recognized by Gnus. +Check the [ ] for the parameters you want to apply to this group or +to the groups in this topic, then edit the value to suit your taste." ,@types) '(repeat :inline t :tag "Variables" @@ -232,34 +370,40 @@ like. If you want to hear a beep when you enter a group, you could put something like `(dummy-variable (ding))' in the parameters of that group. `dummy-variable' will be set to the result of the `(ding)' form, but who cares?" - (group :value (nil nil) - (symbol :tag "Variable") - (sexp :tag - "Value"))) + (list :format "%v" :value (nil nil) + (symbol :tag "Variable") + (sexp :tag + "Value"))) '(repeat :inline t :tag "Unknown entries" sexp))) - (widget-insert "\n\nYou can also edit the ") - (widget-create 'info-link - :tag "select method" - :help-echo "Push me to learn more about select methods." - "(gnus)Select Methods") - (widget-insert " for the group.\n") - (setq gnus-custom-method - (widget-create 'sexp - :tag "Method" - :value (gnus-info-method info))) + (when group + (widget-insert "\n\nYou can also edit the ") + (widget-create 'info-link + :tag "select method" + :help-echo "Push me to learn more about select methods." + "(gnus)Select Methods") + (widget-insert " for the group.\n") + (setq gnus-custom-method + (widget-create 'sexp + :tag "Method" + :value (gnus-info-method info)))) (use-local-map widget-keymap) - (widget-setup))) + (widget-setup) + (buffer-enable-undo) + (goto-char (point-min)))) (defun gnus-group-customize-done (&rest ignore) "Apply changes and bury the buffer." (interactive) - (gnus-group-edit-group-done 'params gnus-custom-group - (widget-value gnus-custom-params)) - (gnus-group-edit-group-done 'method gnus-custom-group - (widget-value gnus-custom-method)) + (if gnus-custom-topic + (gnus-topic-set-parameters gnus-custom-topic + (widget-value gnus-custom-params)) + (gnus-group-edit-group-done 'params gnus-custom-group + (widget-value gnus-custom-params)) + (gnus-group-edit-group-done 'method gnus-custom-group + (widget-value gnus-custom-method))) (bury-buffer)) ;;; Score Customization: @@ -375,9 +519,9 @@ documentation string for the parameter.") (item `(const :format "" :value ,(downcase tag))) (match '(string :tag "Match")) (score '(choice :tag "Score" - (const :tag "default" nil) - (integer :format "%v" - :hide-front-space t))) + (const :tag "default" nil) + (integer :format "%v" + :hide-front-space t))) (expire '(choice :tag "Expire" (const :tag "off" nil) (integer :format "%v" @@ -448,9 +592,9 @@ each score entry has four elements: (item `(const :format "" :value ,(downcase tag))) (match '(integer :tag "Match")) (score '(choice :tag "Score" - (const :tag "default" nil) - (integer :format "%v" - :hide-front-space t))) + (const :tag "default" nil) + (integer :format "%v" + :hide-front-space t))) (expire '(choice :tag "Expire" (const :tag "off" nil) (integer :format "%v" @@ -485,9 +629,9 @@ each score entry has four elements: (item `(const :format "" :value ,(downcase tag))) (match '(string :tag "Match")) (score '(choice :tag "Score" - (const :tag "default" nil) - (integer :format "%v" - :hide-front-space t))) + (const :tag "default" nil) + (integer :format "%v" + :hide-front-space t))) (expire '(choice :tag "Expire" (const :tag "off" nil) (integer :format "%v" @@ -537,11 +681,11 @@ eh?"))) (interactive (list gnus-current-score-file)) (let ((scores (gnus-score-load file)) (types (mapcar (lambda (entry) - `(group :format "%v%h\n" - :doc ,(nth 2 entry) - (const :format "" ,(nth 0 entry)) - ,(nth 1 entry))) - gnus-score-parameters))) + `(group :format "%v%h\n" + :doc ,(nth 2 entry) + (const :format "" ,(nth 0 entry)) + ,(nth 1 entry))) + gnus-score-parameters))) ;; Ready. (kill-buffer (gnus-get-buffer-create "*Gnus Customize*")) (switch-to-buffer (gnus-get-buffer-create "*Gnus Customize*")) @@ -580,6 +724,7 @@ if you do all your changes will be lost. ") (gnus-score-string :tag "Subject") (gnus-score-string :tag "References") (gnus-score-string :tag "Xref") + (gnus-score-string :tag "Extra") (gnus-score-string :tag "Message-ID") (gnus-score-integer :tag "Lines") (gnus-score-integer :tag "Chars") diff --git a/lisp/gnus/gnus-demon.el b/lisp/gnus/gnus-demon.el index 58f26e85d51..78d7286d34c 100644 --- a/lisp/gnus/gnus-demon.el +++ b/lisp/gnus/gnus-demon.el @@ -1,5 +1,5 @@ ;;; gnus-demon.el --- daemonic Gnus behaviour -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995,96,97,98,99 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +27,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-int) (require 'nnheader) @@ -84,10 +82,6 @@ time Emacs has been idle for IDLE `gnus-demon-timestep's." (defvar gnus-inhibit-demon nil "*If non-nil, no daemonic function will be run.") -(eval-and-compile - (autoload 'timezone-parse-date "timezone") - (autoload 'timezone-make-arpa-date "timezone")) - ;;; Functions. (defun gnus-demon-add-handler (function time idle) @@ -121,8 +115,7 @@ time Emacs has been idle for IDLE `gnus-demon-timestep's." (nth 2 handler))) gnus-demon-handlers)) (setq gnus-demon-idle-time 0) - (setq gnus-demon-idle-has-been-called nil) - (setq gnus-use-demon t))) + (setq gnus-demon-idle-has-been-called nil))) (gnus-add-shutdown 'gnus-demon-cancel 'gnus) @@ -132,7 +125,6 @@ time Emacs has been idle for IDLE `gnus-demon-timestep's." (when gnus-demon-timer (nnheader-cancel-timer gnus-demon-timer)) (setq gnus-demon-timer nil - gnus-use-demon nil gnus-demon-idle-has-been-called nil) (condition-case () (nnheader-cancel-function-timers 'gnus-demon) @@ -157,17 +149,17 @@ time Emacs has been idle for IDLE `gnus-demon-timestep's." time (let* ((now (current-time)) ;; obtain NOW as discrete components -- make a vector for speed - (nowParts (apply 'vector (decode-time now))) + (nowParts (decode-time now)) ;; obtain THEN as discrete components - (thenParts (timezone-parse-time time)) - (thenHour (string-to-int (elt thenParts 0))) - (thenMin (string-to-int (elt thenParts 1))) + (thenParts (parse-time-string time)) + (thenHour (elt thenParts 0)) + (thenMin (elt thenParts 1)) ;; convert time as elements into number of seconds since EPOCH. (then (encode-time 0 thenMin thenHour ;; If THEN is earlier than NOW, make it - ;; same time tomorrow. Doc for encode-time + ;; same time tomorrow. Doc for encode-time ;; says that this is OK. (+ (elt nowParts 3) (if (or (< thenHour (elt nowParts 2)) @@ -199,6 +191,10 @@ time Emacs has been idle for IDLE `gnus-demon-timestep's." ;; sufficiently ripe. (let ((handlers gnus-demon-handler-state) (gnus-inhibit-demon t) + ;; Try to avoid dialog boxes, e.g. by Mailcrypt. + ;; Unfortunately, Emacs 20's `message-or-box...' doesn't + ;; obey `use-dialog-box'. + use-dialog-box (last-nonmenu-event 10) handler time idle) (while handlers (setq handler (pop handlers)) @@ -266,12 +262,11 @@ time Emacs has been idle for IDLE `gnus-demon-timestep's." "Add daemonic nntp server disconnection to Gnus. If no commands have gone out via nntp during the last five minutes, the connection is closed." - (gnus-demon-add-handler 'gnus-demon-close-connections 5 nil)) + (gnus-demon-add-handler 'gnus-demon-nntp-close-connections 5 nil)) (defun gnus-demon-nntp-close-connection () (save-window-excursion - (when (nnmail-time-less '(0 300) - (nnmail-time-since nntp-last-command-time)) + (when (time-less-p '(0 300) (time-since nntp-last-command-time)) (nntp-close-server)))) (defun gnus-demon-add-scanmail () @@ -281,8 +276,8 @@ minutes, the connection is closed." (defun gnus-demon-scan-mail () (save-window-excursion (let ((servers gnus-opened-servers) - server) - (gnus-clear-inboxes-moved) + server + (nnmail-fetched-sources (list t))) (while (setq server (car (pop servers))) (and (gnus-check-backend-function 'request-scan (car server)) (or (gnus-server-opened server) diff --git a/lisp/gnus/gnus-draft.el b/lisp/gnus/gnus-draft.el index 88a132e5266..5e7850e0eb1 100644 --- a/lisp/gnus/gnus-draft.el +++ b/lisp/gnus/gnus-draft.el @@ -1,5 +1,6 @@ ;;; gnus-draft.el --- draft message support for Gnus -;; Copyright (C) 1997,98 Free Software Foundation, Inc. +;; Copyright (C) 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -67,12 +68,13 @@ (interactive "P") (when (eq major-mode 'gnus-summary-mode) (when (set (make-local-variable 'gnus-draft-mode) - (if (null arg) (not gnus-draft-mode) - (> (prefix-numeric-value arg) 0))) + (if (null arg) (not gnus-draft-mode) + (> (prefix-numeric-value arg) 0))) ;; Set up the menu. (when (gnus-visual-p 'draft-menu 'menu) (gnus-draft-make-menu-bar)) (gnus-add-minor-mode 'gnus-draft-mode " Draft" gnus-draft-mode-map) + (mml-mode) (gnus-run-hooks 'gnus-draft-mode-hook)))) ;;; Commands @@ -94,9 +96,11 @@ (interactive) (let ((article (gnus-summary-article-number))) (gnus-summary-mark-as-read article gnus-canceled-mark) - (gnus-draft-setup article gnus-newsgroup-name) + (gnus-draft-setup article gnus-newsgroup-name t) (set-buffer-modified-p t) (save-buffer) + (let ((gnus-verbose-backends nil)) + (gnus-request-expire-articles (list article) gnus-newsgroup-name t)) (push `((lambda () (when (gnus-buffer-exists-p ,gnus-summary-buffer) @@ -113,14 +117,22 @@ (while (setq article (pop articles)) (gnus-summary-remove-process-mark article) (unless (memq article gnus-newsgroup-unsendable) - (gnus-draft-send article gnus-newsgroup-name) + (gnus-draft-send article gnus-newsgroup-name t) (gnus-summary-mark-article article gnus-canceled-mark))))) -(defun gnus-draft-send (article &optional group) +(defun gnus-draft-send (article &optional group interactive) "Send message ARTICLE." - (gnus-draft-setup article (or group "nndraft:queue")) - (let ((message-syntax-checks 'dont-check-for-anything-just-trust-me) - message-send-hook type method) + (let ((message-syntax-checks (if interactive nil + 'dont-check-for-anything-just-trust-me)) + (message-inhibit-body-encoding (or (not group) + (equal group "nndraft:queue") + message-inhibit-body-encoding)) + (message-send-hook (and group (not (equal group "nndraft:queue")) + message-send-hook)) + (message-setup-hook (and group (not (equal group "nndraft:queue")) + message-setup-hook)) + type method) + (gnus-draft-setup article (or group "nndraft:queue")) ;; We read the meta-information that says how and where ;; this message is to be sent. (save-restriction @@ -176,20 +188,22 @@ ;;;!!!but for the time being, we'll just run this tiny function uncompiled. (progn -(defun gnus-draft-setup (narticle group) - (gnus-setup-message 'forward - (let ((article narticle)) - (message-mail) - (erase-buffer) - (if (not (gnus-request-restore-buffer article group)) - (error "Couldn't restore the article") - ;; Insert the separator. - (goto-char (point-min)) - (search-forward "\n\n") - (forward-char -1) - (insert mail-header-separator) - (forward-line 1) - (message-set-auto-save-file-name)))))) + (defun gnus-draft-setup (narticle group &optional restore) + (gnus-setup-message 'forward + (let ((article narticle)) + (message-mail) + (erase-buffer) + (if (not (gnus-request-restore-buffer article group)) + (error "Couldn't restore the article") + (if (and restore (equal group "nndraft:queue")) + (mime-to-mml)) + ;; Insert the separator. + (goto-char (point-min)) + (search-forward "\n\n") + (forward-char -1) + (insert mail-header-separator) + (forward-line 1) + (message-set-auto-save-file-name)))))) (defun gnus-draft-article-sendable-p (article) "Say whether ARTICLE is sendable." diff --git a/lisp/gnus/gnus-dup.el b/lisp/gnus/gnus-dup.el index ac0ac315fb1..e148f4574bd 100644 --- a/lisp/gnus/gnus-dup.el +++ b/lisp/gnus/gnus-dup.el @@ -1,5 +1,6 @@ ;;; gnus-dup.el --- suppression of duplicate articles in Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -32,8 +33,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-art) @@ -100,7 +99,7 @@ seen in the same session." "Save the duplicate suppression list." (when (and gnus-save-duplicate-list gnus-dup-list-dirty) - (nnheader-temp-write gnus-duplicate-file + (with-temp-file gnus-duplicate-file (gnus-prin1 `(setq gnus-dup-list ',gnus-dup-list)))) (setq gnus-dup-list-dirty nil)) @@ -138,6 +137,8 @@ seen in the same session." (gnus-dup-open)) (gnus-message 6 "Suppressing duplicates...") (let ((headers gnus-newsgroup-headers) + (auto (and gnus-newsgroup-auto-expire + (memq gnus-duplicate-mark gnus-auto-expirable-marks))) number header) (while (setq header (pop headers)) (when (and (intern-soft (mail-header-id header) gnus-dup-hashtb) @@ -145,8 +146,10 @@ seen in the same session." (setq gnus-newsgroup-unreads (delq (setq number (mail-header-number header)) gnus-newsgroup-unreads)) - (push (cons number gnus-duplicate-mark) - gnus-newsgroup-reads)))) + (if (not auto) + (push (cons number gnus-duplicate-mark) gnus-newsgroup-reads) + (push number gnus-newsgroup-expirable) + (push (cons number gnus-expirable-mark) gnus-newsgroup-reads))))) (gnus-message 6 "Suppressing duplicates...done")) (defun gnus-dup-unsuppress-article (article) diff --git a/lisp/gnus/gnus-eform.el b/lisp/gnus/gnus-eform.el index 6a93242feaf..9fe72420fc3 100644 --- a/lisp/gnus/gnus-eform.el +++ b/lisp/gnus/gnus-eform.el @@ -1,5 +1,6 @@ ;;; gnus-eform.el --- a mode for editing forms for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -53,7 +54,8 @@ (defvar gnus-edit-form-mode-map nil) (unless gnus-edit-form-mode-map - (setq gnus-edit-form-mode-map (copy-keymap emacs-lisp-mode-map)) + (setq gnus-edit-form-mode-map (make-sparse-keymap)) + (set-keymap-parent gnus-edit-form-mode-map emacs-lisp-mode-map) (gnus-define-keys gnus-edit-form-mode-map "\C-c\C-c" gnus-edit-form-done "\C-c\C-k" gnus-edit-form-exit)) diff --git a/lisp/gnus/gnus-ems.el b/lisp/gnus/gnus-ems.el index 39bb98d1d5f..647c12c27cb 100644 --- a/lisp/gnus/gnus-ems.el +++ b/lisp/gnus/gnus-ems.el @@ -1,5 +1,6 @@ ;;; gnus-ems.el --- functions for making Gnus work under different Emacsen -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -29,11 +30,14 @@ ;;; Function aliases later to be redefined for XEmacs usage. -(defvar gnus-xemacs (string-match "XEmacs\\|Lucid" emacs-version) - "Non-nil if running under XEmacs.") +(eval-and-compile + (defvar gnus-xemacs (featurep 'xemacs) + "Non-nil if running under XEmacs.")) (defvar gnus-mouse-2 [mouse-2]) +(defvar gnus-down-mouse-3 [down-mouse-3]) (defvar gnus-down-mouse-2 [down-mouse-2]) +(defvar gnus-widget-button-keymap nil) (defvar gnus-mode-line-modified (if (or gnus-xemacs (< emacs-major-version 20)) @@ -45,103 +49,42 @@ (autoload 'gnus-xmas-redefine "gnus-xmas") (autoload 'appt-select-lowest-window "appt")) -(or (fboundp 'mail-file-babyl-p) - (fset 'mail-file-babyl-p 'rmail-file-p)) - ;;; Mule functions. -(defun gnus-mule-cite-add-face (number prefix face) - ;; At line NUMBER, ignore PREFIX and add FACE to the rest of the line. - (when face - (let ((inhibit-point-motion-hooks t) - from to) - (goto-line number) - (unless (eobp) ; Sometimes things become confused (broken). - (if (boundp 'MULE) - (forward-char (chars-in-string prefix)) - (forward-char (length prefix))) - (skip-chars-forward " \t") - (setq from (point)) - (end-of-line 1) - (skip-chars-backward " \t") - (setq to (point)) - (when (< from to) - (push (setq overlay (gnus-make-overlay from to)) - gnus-cite-overlay-list) - (gnus-overlay-put (gnus-make-overlay from to) 'face face)))))) - (defun gnus-mule-max-width-function (el max-width) - (` (let* ((val (eval (, el))) - (valstr (if (numberp val) - (int-to-string val) val))) - (if (> (length valstr) (, max-width)) - (truncate-string valstr (, max-width)) - valstr)))) - -(defun gnus-encode-coding-string (string system) - string) - -(defun gnus-decode-coding-string (string system) - string) - -(defun gnus-encode-coding-string (string system) - string) + `(let* ((val (eval (, el))) + (valstr (if (numberp val) + (int-to-string val) val))) + (if (> (length valstr) ,max-width) + (truncate-string-to-width valstr ,max-width) + valstr))) (eval-and-compile - (if (string-match "XEmacs\\|Lucid" emacs-version) - nil - + (if gnus-xemacs + (gnus-xmas-define) (defvar gnus-mouse-face-prop 'mouse-face - "Property used for highlighting mouse regions.")) - - (cond - ((string-match "XEmacs\\|Lucid" emacs-version) - (gnus-xmas-define)) - - ((or (not (boundp 'emacs-minor-version)) - (and (< emacs-major-version 20) - (< emacs-minor-version 30))) - ;; Remove the `intangible' prop. - (let ((props (and (boundp 'gnus-hidden-properties) - gnus-hidden-properties))) - (while (and props (not (eq (car (cdr props)) 'intangible))) - (setq props (cdr props))) - (when props - (setcdr props (cdr (cdr (cdr props)))))) - (unless (fboundp 'buffer-substring-no-properties) - (defun buffer-substring-no-properties (beg end) - (format "%s" (buffer-substring beg end))))) - - ((boundp 'MULE) - (provide 'gnusutil)))) + "Property used for highlighting mouse regions."))) (eval-and-compile (cond ((not window-system) - (defun gnus-dummy-func (&rest args)) (let ((funcs '(mouse-set-point set-face-foreground set-face-background x-popup-menu))) (while funcs (unless (fboundp (car funcs)) - (fset (car funcs) 'gnus-dummy-func)) - (setq funcs (cdr funcs)))))) - (unless (fboundp 'file-regular-p) - (defun file-regular-p (file) - (and (not (file-directory-p file)) - (not (file-symlink-p file)) - (file-exists-p file)))) - (unless (fboundp 'face-list) - (defun face-list (&rest args)))) + (defalias (car funcs) 'ignore)) + (setq funcs (cdr funcs))))))) (eval-and-compile (let ((case-fold-search t)) (cond - ((string-match "windows-nt\\|os/2\\|emx\\|cygwin" + ((string-match "windows-nt\\|os/2\\|emx\\|cygwin32" (symbol-name system-type)) (setq nnheader-file-name-translation-alist (append nnheader-file-name-translation-alist - '((?: . ?_) - (?+ . ?-)))))))) + (mapcar (lambda (c) (cons c ?_)) + '(?: ?* ?\" ?< ?> ??)) + '((?+ . ?-)))))))) (defvar gnus-tmp-unread) (defvar gnus-tmp-replied) @@ -155,37 +98,31 @@ (defun gnus-ems-redefine () (cond - ((string-match "XEmacs\\|Lucid" emacs-version) + (gnus-xemacs (gnus-xmas-redefine)) ((featurep 'mule) ;; Mule and new Emacs definitions ;; [Note] Now there are three kinds of mule implementations, - ;; original MULE, XEmacs/mule and beta version of Emacs including - ;; some mule features. Unfortunately these API are different. In + ;; original MULE, XEmacs/mule and Emacs 20+ including + ;; MULE features. Unfortunately these API are different. In ;; particular, Emacs (including original MULE) and XEmacs are - ;; quite different. + ;; quite different. Howvere, this version of Gnus doesn't support + ;; anything other than XEmacs 20+ and Emacs 20.3+. + ;; Predicates to check are following: ;; (boundp 'MULE) is t only if MULE (original; anything older than ;; Mule 2.3) is running. ;; (featurep 'mule) is t when every mule variants are running. - ;; These implementations may be able to share between original - ;; MULE and beta version of new Emacs. In addition, it is able to - ;; detect XEmacs/mule by (featurep 'mule) and to check variable - ;; `emacs-version'. In this case, implementation for XEmacs/mule - ;; may be able to share between XEmacs and XEmacs/mule. - - (defalias 'gnus-truncate-string 'truncate-string) + ;; It is possible to detect XEmacs/mule by (featurep 'mule) and + ;; checking `emacs-version'. In this case, the implementation for + ;; XEmacs/mule may be shareable between XEmacs and XEmacs/mule. (defvar gnus-summary-display-table nil "Display table used in summary mode buffers.") - (fset 'gnus-cite-add-face 'gnus-mule-cite-add-face) - (fset 'gnus-max-width-function 'gnus-mule-max-width-function) - (fset 'gnus-summary-set-display-table (lambda ())) - (fset 'gnus-encode-coding-string 'encode-coding-string) - (fset 'gnus-decode-coding-string 'decode-coding-string) + (defalias 'gnus-max-width-function 'gnus-mule-max-width-function) (when (boundp 'gnus-check-before-posting) (setq gnus-check-before-posting @@ -203,18 +140,12 @@ (format "%4d: %-20s" gnus-tmp-lines (if (> (length gnus-tmp-name) 20) - (truncate-string gnus-tmp-name 20) + (truncate-string-to-width gnus-tmp-name 20) gnus-tmp-name)) gnus-tmp-closing-bracket) (point)) gnus-mouse-face-prop gnus-mouse-face) - (insert " " gnus-tmp-subject-or-nil "\n")) - - (when (and (boundp 'enable-multibyte-characters) - enable-multibyte-characters) - (require 'gnus-mule) - (gnus-mule-initialize)) - ))) + (insert " " gnus-tmp-subject-or-nil "\n"))))) (defun gnus-region-active-p () "Say whether the region is active." @@ -223,9 +154,9 @@ (boundp 'mark-active) mark-active)) -(defun gnus-add-minor-mode (mode name map) - (if (fboundp 'add-minor-mode) - (add-minor-mode mode name map) +(if (fboundp 'add-minor-mode) + (defalias 'gnus-add-minor-mode 'add-minor-mode) + (defun gnus-add-minor-mode (mode name map &rest rest) (set (make-local-variable mode) t) (unless (assq mode minor-mode-alist) (push `(,mode ,name) minor-mode-alist)) @@ -242,41 +173,90 @@ (let ((buffer-read-only nil)) (erase-buffer) (when (and dir - (file-exists-p (setq file (concat dir "x-splash")))) - (nnheader-temp-write nil + (file-exists-p (setq file + (expand-file-name "x-splash" dir)))) + (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) (ignore-errors (setq pixmap (read (current-buffer)))))) (when pixmap - (erase-buffer) - (unless (facep 'gnus-splash) - (make-face 'gnus-splash)) + (make-face 'gnus-splash) (setq height (/ (car pixmap) (frame-char-height)) width (/ (cadr pixmap) (frame-char-width))) - (set-face-foreground 'gnus-splash "ForestGreen") + (set-face-foreground 'gnus-splash "Brown") (set-face-stipple 'gnus-splash pixmap) (insert-char ?\n (* (/ (window-height) 2 height) height)) (setq i height) (while (> i 0) - (insert-char ? (* (+ (/ (window-width) 2 width) 1) width)) + (insert-char ?\ (* (/ (window-width) 2 width) width)) (setq beg (point)) - (insert-char ? width) + (insert-char ?\ width) (set-text-properties beg (point) '(face gnus-splash)) - (insert "\n") + (insert ?\n) (decf i)) (goto-char (point-min)) (sit-for 0)))))) -(if (fboundp 'split-string) - (fset 'gnus-split-string 'split-string) - (defun gnus-split-string (string pattern) - "Return a list of substrings of STRING which are separated by PATTERN." - (let (parts (start 0)) - (while (string-match pattern string start) - (setq parts (cons (substring string start (match-beginning 0)) parts) - start (match-end 0))) - (nreverse (cons (substring string start) parts))))) +(defvar gnus-article-xface-ring-internal nil + "Cache for face data.") + +;; Worth customizing? +(defvar gnus-article-xface-ring-size 6 + "Length of the ring used for `gnus-article-xface-ring-internal'.") + +(defun gnus-article-display-xface (beg end) + "Display an XFace header from between BEG and END in the current article. +Requires support for images in your Emacs and the external programs +`uncompface', `icontopbm' and `ppmtoxbm'. On a GNU/Linux system these +might be in packages with names like `compface' or `faces-xface' and +`netpbm' or `libgr-progs', for instance. + +This function is for Emacs 21+. See `gnus-xmas-article-display-xface' +for XEmacs." + ;; It might be worth converting uncompface's output in Lisp. + + (unless gnus-article-xface-ring-internal ; Only load ring when needed. + (setq gnus-article-xface-ring-internal + (make-ring gnus-article-xface-ring-size))) + (save-excursion + (let* ((cur (current-buffer)) + (data (buffer-substring beg end)) + (image (cdr-safe (assoc data (ring-elements + gnus-article-xface-ring-internal))))) + (when (if (fboundp 'display-graphic-p) + (display-graphic-p)) + (unless image + (let ((coding-system-for-read 'binary) + (coding-system-for-write 'binary)) + (with-temp-buffer + (insert data) + (and (eq 0 (call-process-region (point-min) (point-max) + "uncompface" + 'delete '(t nil))) + (goto-char (point-min)) + (progn (insert "/* Width=48, Height=48 */\n") t) + (eq 0 (call-process-region (point-min) (point-max) + "icontopbm" + 'delete '(t nil))) + (eq 0 (call-process-region (point-min) (point-max) + "pbmtoxbm" + 'delete '(t nil))) + ;; Miles Bader says that faces don't look right as + ;; light on dark. + (if (eq 'dark (cdr-safe (assq 'background-mode + (frame-parameters)))) + (setq image (create-image (buffer-string) 'xbm t + :ascent 'center + :foreground "black" + :background "white")) + (setq image (create-image (buffer-string) 'xbm t + :ascent 'center)))))) + (ring-insert gnus-article-xface-ring-internal (cons data image)))) + (when image + (goto-char (point-min)) + (re-search-forward "^From:" nil 'move) + (insert-image image))))) (provide 'gnus-ems) diff --git a/lisp/gnus/gnus-gl.el b/lisp/gnus/gnus-gl.el index 93ef91564a4..a17e0ce9193 100644 --- a/lisp/gnus/gnus-gl.el +++ b/lisp/gnus/gnus-gl.el @@ -1,5 +1,7 @@ ;;; gnus-gl.el --- an interface to GroupLens for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Brad Miller ;; Keywords: news, score @@ -137,10 +139,10 @@ This pseudonym is obtained during the registration process") (defvar grouplens-bbb-host "grouplens.cs.umn.edu" - "Host where the bbbd is running" ) + "Host where the bbbd is running.") (defvar grouplens-bbb-port 9000 - "Port where the bbbd is listening" ) + "Port where the bbbd is listening.") (defvar grouplens-newsgroups '("comp.groupware" "comp.human-factors" "comp.lang.c++" @@ -194,19 +196,19 @@ GroupLens scores can be combined with gnus scores in one of three ways. ;;;; Program global variables ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defvar grouplens-bbb-token nil - "Current session token number") + "Current session token number.") (defvar grouplens-bbb-process nil - "Process Id of current bbbd network stream process") + "Process Id of current bbbd network stream process.") (defvar grouplens-bbb-buffer nil - "Buffer associated with the BBBD process") + "Buffer associated with the BBBD process.") (defvar grouplens-rating-alist nil - "Current set of message-id rating pairs") + "Current set of message-id rating pairs.") (defvar grouplens-current-hashtable nil - "A hashtable to hold predictions from the BBB") + "A hashtable to hold predictions from the BBB.") (defvar grouplens-current-group nil) @@ -312,7 +314,7 @@ If this times out we give up and assume that something has died..." ) (concat "login " grouplens-pseudonym)) (if (bbb-read-response bbb-process) (setq grouplens-bbb-token (bbb-extract-token-number)) - (gnus-message 3 "Error: GroupLens login failed"))))) + (gnus-message 3 "Error: GroupLens login failed"))))) (gnus-message 3 "Error: you must set a pseudonym")) grouplens-bbb-token) @@ -406,7 +408,7 @@ recommend using both scores and grouplens predictions together." pred (bbb-get-pred)) (push `(,mid ,pred nil s) resp) (gnus-sethash mid (list pred (bbb-get-confl) (bbb-get-confh)) - grouplens-current-hashtable) + grouplens-current-hashtable) (forward-line 1) t) ((looking-at "\\(<.*>\\) :pred=\\([0-9]\.[0-9][0-9]\\)") @@ -765,7 +767,7 @@ If prefix argument ALL is non-nil, all articles are marked as read." (defun gnus-gl-get-trace () "Insert the contents of the BBBD trace buffer." (when grouplens-bbb-buffer - (insert-buffer grouplens-bbb-buffer))) + (insert-buffer-substring grouplens-bbb-buffer))) ;; ;; GroupLens minor mode @@ -779,12 +781,12 @@ If prefix argument ALL is non-nil, all articles are marked as read." (unless gnus-grouplens-mode-map (setq gnus-grouplens-mode-map (make-keymap)) (gnus-define-keys - gnus-grouplens-mode-map - "n" grouplens-next-unread-article - "r" bbb-summary-rate-article - "k" grouplens-score-thread - "c" grouplens-summary-catchup-and-exit - "," grouplens-best-unread-article)) + gnus-grouplens-mode-map + "n" grouplens-next-unread-article + "r" bbb-summary-rate-article + "k" grouplens-score-thread + "c" grouplens-summary-catchup-and-exit + "," grouplens-best-unread-article)) (defun gnus-grouplens-make-menu-bar () (unless (boundp 'gnus-grouplens-menu) diff --git a/lisp/gnus/gnus-group.el b/lisp/gnus/gnus-group.el index c881f5976d9..c4ee639c09e 100644 --- a/lisp/gnus/gnus-group.el +++ b/lisp/gnus/gnus-group.el @@ -1,5 +1,6 @@ ;;; gnus-group.el --- group mode commands for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-start) (require 'nnmail) @@ -37,6 +36,7 @@ (require 'gnus-range) (require 'gnus-win) (require 'gnus-undo) +(require 'time-date) (defcustom gnus-group-archive-directory "*ftp@ftp.hpc.uh.edu:/pub/emacs/ding-list/" @@ -50,7 +50,7 @@ :group 'gnus-group-foreign :type 'directory) -(defcustom gnus-no-groups-message "No news is no news" +(defcustom gnus-no-groups-message "No gnus is bad news" "*Message displayed by Gnus when no groups are available." :group 'gnus-start :type 'string) @@ -162,6 +162,7 @@ with some simple extensions. %n Select from where (string) %z A string that look like `<%s:%n>' if a foreign select method is used %d The date the group was last entered. +%E Icon as defined by `gnus-group-icon-list'. %u User defined specifier. The next character in the format string should be a letter. Gnus will call the function gnus-user-format-function-X, where X is the letter following %u. The function will be passed the @@ -300,6 +301,18 @@ variable." gnus-group-news-3-empty-face) ((and (not mailp) (eq level 3)) . gnus-group-news-3-face) + ((and (= unread 0) (not mailp) (eq level 4)) . + gnus-group-news-4-empty-face) + ((and (not mailp) (eq level 4)) . + gnus-group-news-4-face) + ((and (= unread 0) (not mailp) (eq level 5)) . + gnus-group-news-5-empty-face) + ((and (not mailp) (eq level 5)) . + gnus-group-news-5-face) + ((and (= unread 0) (not mailp) (eq level 6)) . + gnus-group-news-6-empty-face) + ((and (not mailp) (eq level 6)) . + gnus-group-news-6-face) ((and (= unread 0) (not mailp)) . gnus-group-news-low-empty-face) ((and (not mailp)) . @@ -320,7 +333,7 @@ variable." ((= unread 0) . gnus-group-mail-low-empty-face) (t . - gnus-group-mail-low-face)) + gnus-group-mail-low-face)) "*Controls the highlighting of group buffer lines. Below is a list of `Form'/`Face' pairs. When deciding how a a @@ -349,6 +362,56 @@ ticked: The number of ticked articles." :group 'gnus-group-visual :type 'character) +(defgroup gnus-group-icons nil + "Add Icons to your group buffer. " + :group 'gnus-group-visual) + +(defcustom gnus-group-icon-list + nil + "*Controls the insertion of icons into group buffer lines. + +Below is a list of `Form'/`File' pairs. When deciding how a +particular group line should be displayed, each form is evaluated. +The icon from the file field after the first true form is used. You +can change how those group lines are displayed by editing the file +field. The File will either be found in the +`gnus-group-glyph-directory' or by designating absolute path to the +file. + +It is also possible to change and add form fields, but currently that +requires an understanding of Lisp expressions. Hopefully this will +change in a future release. For now, you can use the following +variables in the Lisp expression: + +group: The name of the group. +unread: The number of unread articles in the group. +method: The select method used. +mailp: Whether it's a mail group or not. +newsp: Whether it's a news group or not +level: The level of the group. +score: The score of the group. +ticked: The number of ticked articles." + :group 'gnus-group-icons + :type '(repeat (cons (sexp :tag "Form") file))) + +(defcustom gnus-group-name-charset-method-alist nil + "*Alist of method and the charset for group names. + +For example: + (((nntp \"news.com.cn\") . cn-gb-2312)) +" + :group 'gnus-charset + :type '(repeat (cons (sexp :tag "Method") (symbol :tag "Charset")))) + +(defcustom gnus-group-name-charset-group-alist nil + "*Alist of group regexp and the charset for group names. + +For example: + ((\"\\.com\\.cn:\" . cn-gb-2312)) +" + :group 'gnus-charset + :type '(repeat (cons (regexp :tag "Group") (symbol :tag "Charset")))) + ;;; Internal variables (defvar gnus-group-sort-alist-function 'gnus-group-sort-flat @@ -393,6 +456,7 @@ ticked: The number of ticked articles." (?s gnus-tmp-news-server ?s) (?n gnus-tmp-news-method ?s) (?P gnus-group-indentation ?s) + (?E gnus-tmp-group-icon ?s) (?l gnus-tmp-grouplens ?s) (?z gnus-tmp-news-method-string ?s) (?m (gnus-group-new-mail gnus-tmp-group) ?c) @@ -415,6 +479,9 @@ ticked: The number of ticked articles." (defvar gnus-group-list-mode nil) + +(defvar gnus-group-icon-cache nil) + ;;; ;;; Gnus group mode ;;; @@ -427,6 +494,7 @@ ticked: The number of ticked articles." "=" gnus-group-select-group "\r" gnus-group-select-group "\M-\r" gnus-group-quick-select-group + "\M- " gnus-group-visible-select-group [(meta control return)] gnus-group-select-group-ephemerally "j" gnus-group-jump-to-group "n" gnus-group-next-unread-group @@ -503,6 +571,7 @@ ticked: The number of ticked articles." "u" gnus-group-make-useful-group "a" gnus-group-make-archive-group "k" gnus-group-make-kiboze-group + "l" gnus-group-nnimap-edit-acl "m" gnus-group-make-group "E" gnus-group-edit-group "e" gnus-group-edit-group-method @@ -514,6 +583,7 @@ ticked: The number of ticked articles." "w" gnus-group-make-web-group "r" gnus-group-rename-group "c" gnus-group-customize + "x" gnus-group-nnimap-expunge "\177" gnus-group-delete-group [delete] gnus-group-delete-group) @@ -552,7 +622,9 @@ ticked: The number of ticked articles." "d" gnus-group-description-apropos "m" gnus-group-list-matching "M" gnus-group-list-all-matching - "l" gnus-group-list-level) + "l" gnus-group-list-level + "c" gnus-group-list-cached + "?" gnus-group-list-dormant) (gnus-define-keys (gnus-group-score-map "W" gnus-group-mode-map) "f" gnus-score-flush-cache) @@ -628,7 +700,9 @@ ticked: The number of ticked articles." ["Group and description apropos..." gnus-group-description-apropos t] ["List groups matching..." gnus-group-list-matching t] ["List all groups matching..." gnus-group-list-all-matching t] - ["List active file" gnus-group-list-active t]) + ["List active file" gnus-group-list-active t] + ["List groups with cached" gnus-group-list-cached t] + ["List groups with dormant" gnus-group-list-dormant t]) ("Sort" ["Default sort" gnus-group-sort-groups t] ["Sort by method" gnus-group-sort-groups-by-method t] @@ -714,7 +788,6 @@ ticked: The number of ticked articles." ["Pack packet" gnus-soup-pack-packet (fboundp 'gnus-soup-pack-packet)] ["Save areas" gnus-soup-save-areas (fboundp 'gnus-soup-pack-packet)] ["Brew SOUP" gnus-group-brew-soup (fboundp 'gnus-soup-pack-packet)]) - ["Send a bug report" gnus-bug t] ["Send a mail" gnus-group-mail t] ["Post an article..." gnus-group-post-news t] ["Check for new news" gnus-group-get-new-news t] @@ -765,14 +838,12 @@ The following commands are available: (gnus-group-set-mode-line) (setq mode-line-process nil) (use-local-map gnus-group-mode-map) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq truncate-lines t) (setq buffer-read-only t) (gnus-set-default-directory) (gnus-update-format-specifications nil 'group 'group-mode) (gnus-update-group-mark-positions) - (make-local-hook 'post-command-hook) - (add-hook 'post-command-hook 'gnus-clear-inboxes-moved nil t) (when gnus-use-undo (gnus-undo-mode 1)) (when gnus-slave @@ -793,9 +864,6 @@ The following commands are available: (list (cons 'process (and (search-forward "\200" nil t) (- (point) 2)))))))) -(defun gnus-clear-inboxes-moved () - (setq nnmail-moved-inboxes nil)) - (defun gnus-mouse-pick-group (e) "Enter the group under the mouse pointer." (interactive "e") @@ -826,6 +894,29 @@ The following commands are available: (when gnus-carpal (gnus-carpal-setup-buffer 'group)))) +(defsubst gnus-group-name-charset (method group) + (if (null method) + (setq method (gnus-find-method-for-group group))) + (let ((item (assoc method gnus-group-name-charset-method-alist)) + (alist gnus-group-name-charset-group-alist) + result) + (if item + (cdr item) + (while (setq item (pop alist)) + (if (string-match (car item) group) + (setq alist nil + result (cdr item)))) + result))) + +(defsubst gnus-group-name-decode (string charset) + (if (and string charset (featurep 'mule)) + (mm-decode-coding-string string charset) + string)) + +(defun gnus-group-decoded-name (string) + (let ((charset (gnus-group-name-charset nil string))) + (gnus-group-name-decode string charset))) + (defun gnus-group-list-groups (&optional level unread lowest) "List newsgroups with level LEVEL or lower that have unread articles. Default is all subscribed groups. @@ -840,8 +931,6 @@ Also see the `gnus-group-use-permanent-levels' variable." (gnus-group-default-level nil t) gnus-group-default-list-level gnus-level-subscribed)))) - ;; Just do this here, for no particular good reason. - (gnus-clear-inboxes-moved) (unless level (setq level (car gnus-group-list-mode) unread (cdr gnus-group-list-mode))) @@ -920,7 +1009,7 @@ If REGEXP, only list groups matching REGEXP." params (gnus-info-params info) newsrc (cdr newsrc) unread (car (gnus-gethash group gnus-newsrc-hashtb))) - (and unread ; This group might be bogus + (and unread ; This group might be unchecked (or (not regexp) (string-match regexp group)) (<= (setq clevel (gnus-info-level info)) level) @@ -971,16 +1060,24 @@ If REGEXP, only list groups matching REGEXP." (when (string-match regexp group) (gnus-add-text-properties (point) (prog1 (1+ (point)) - (insert " " mark " *: " group "\n")) + (insert " " mark " *: " + (gnus-group-name-decode group + (gnus-group-name-charset + nil group)) + "\n")) (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb) 'gnus-unread t 'gnus-level level)))) ;; This loop is used when listing all groups. (while groups + (setq group (pop groups)) (gnus-add-text-properties (point) (prog1 (1+ (point)) (insert " " mark " *: " - (setq group (pop groups)) "\n")) + (gnus-group-name-decode group + (gnus-group-name-charset + nil group)) + "\n")) (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb) 'gnus-unread t 'gnus-level level)))))) @@ -1032,7 +1129,11 @@ If REGEXP, only list groups matching REGEXP." gnus-tmp-marked number gnus-tmp-method) "Insert a group line in the group buffer." - (let* ((gnus-tmp-active (gnus-active gnus-tmp-group)) + (let* ((gnus-tmp-method + (gnus-server-get-method gnus-tmp-group gnus-tmp-method)) + (group-name-charset (gnus-group-name-charset gnus-tmp-method + gnus-tmp-group)) + (gnus-tmp-active (gnus-active gnus-tmp-group)) (gnus-tmp-number-total (if gnus-tmp-active (1+ (- (cdr gnus-tmp-active) (car gnus-tmp-active))) @@ -1049,10 +1150,14 @@ If REGEXP, only list groups matching REGEXP." ((<= gnus-tmp-level gnus-level-unsubscribed) ?U) ((= gnus-tmp-level gnus-level-zombie) ?Z) (t ?K))) - (gnus-tmp-qualified-group (gnus-group-real-name gnus-tmp-group)) + (gnus-tmp-qualified-group + (gnus-group-name-decode (gnus-group-real-name gnus-tmp-group) + group-name-charset)) (gnus-tmp-newsgroup-description (if gnus-description-hashtb - (or (gnus-gethash gnus-tmp-group gnus-description-hashtb) "") + (or (gnus-group-name-decode + (gnus-gethash gnus-tmp-group gnus-description-hashtb) + group-name-charset) "") "")) (gnus-tmp-moderated (if (and gnus-moderated-hashtb @@ -1060,8 +1165,7 @@ If REGEXP, only list groups matching REGEXP." ?m ? )) (gnus-tmp-moderated-string (if (eq gnus-tmp-moderated ?m) "(m)" "")) - (gnus-tmp-method - (gnus-server-get-method gnus-tmp-group gnus-tmp-method)) ; + (gnus-tmp-group-icon "==&&==") (gnus-tmp-news-server (or (cadr gnus-tmp-method) "")) (gnus-tmp-news-method (or (car gnus-tmp-method) "")) (gnus-tmp-news-method-string @@ -1095,10 +1199,10 @@ If REGEXP, only list groups matching REGEXP." gnus-marked ,gnus-tmp-marked-mark gnus-indentation ,gnus-group-indentation gnus-level ,gnus-tmp-level)) + (forward-line -1) (when (inline (gnus-visual-p 'group-highlight 'highlight)) - (forward-line -1) - (gnus-run-hooks 'gnus-group-update-hook) - (forward-line)) + (gnus-run-hooks 'gnus-group-update-hook)) + (forward-line) ;; Allow XEmacs to remove front-sticky text properties. (gnus-group-remove-excess-properties))) @@ -1317,6 +1421,12 @@ If FIRST-TOO, the current line is also eligible as a target." ;; Group marking. +(defun gnus-group-mark-line-p () + (save-excursion + (beginning-of-line) + (forward-char (or (cdr (assq 'process gnus-group-mark-positions)) 2)) + (eq (char-after) gnus-process-mark))) + (defun gnus-group-mark-group (n &optional unmark no-advance) "Mark the current group." (interactive "p") @@ -1329,7 +1439,7 @@ If FIRST-TOO, the current line is also eligible as a target." (beginning-of-line) (forward-char (or (cdr (assq 'process gnus-group-mark-positions)) 2)) (subst-char-in-region - (point) (1+ (point)) (following-char) + (point) (1+ (point)) (char-after) (if unmark (progn (setq gnus-group-marked (delete group gnus-group-marked)) @@ -1383,10 +1493,10 @@ If UNMARK, remove the mark instead." (gnus-group-set-mark group)))) (gnus-group-position-point)) -(defun gnus-group-remove-mark (group) +(defun gnus-group-remove-mark (group &optional test-marked) "Remove the process mark from GROUP and move point there. Return nil if the group isn't displayed." - (if (gnus-group-goto-group group) + (if (gnus-group-goto-group group nil test-marked) (save-excursion (gnus-group-mark-group 1 'unmark t) t) @@ -1465,12 +1575,14 @@ Take into consideration N (the prefix) and the list of marked groups." (eval `(defun gnus-group-iterate (arg ,function) "Iterate FUNCTION over all process/prefixed groups. -FUNCTION will be called with the group name as the paremeter +FUNCTION will be called with the group name as the parameter and with point over the group in question." (let ((,groups (gnus-group-process-prefix arg)) (,window (selected-window)) ,group) - (while (setq ,group (pop ,groups)) + (while ,groups + (setq ,group (car ,groups) + ,groups (cdr ,groups)) (select-window ,window) (gnus-group-remove-mark ,group) (save-selected-window @@ -1565,7 +1677,7 @@ be permanent." (defun gnus-fetch-group (group) "Start Gnus if necessary and enter GROUP. Returns whether the fetching was successful or not." - (interactive "sGroup name: ") + (interactive (list (completing-read "Group name: " gnus-active-hashtb))) (unless (get-buffer gnus-group-buffer) (gnus-no-server)) (gnus-group-read-group nil nil group)) @@ -1597,7 +1709,7 @@ ephemeral group. If REQUEST-ONLY, don't actually read the group; just request it. If SELECT-ARTICLES, only select those articles. -Return the name of the group is selection was successful." +Return the name of the group if selection was successful." ;; Transform the select method into a unique server. (when (stringp method) (setq method (gnus-server-to-method method))) @@ -1654,41 +1766,56 @@ Return the name of the group is selection was successful." ;; Adjust cursor point. (gnus-group-position-point)) -(defun gnus-group-goto-group (group &optional far) +(defun gnus-group-goto-group (group &optional far test-marked) "Goto to newsgroup GROUP. -If FAR, it is likely that the group is not on the current line." +If FAR, it is likely that the group is not on the current line. +If TEST-MARKED, the line must be marked." (when group - (if far - (gnus-goto-char - (text-property-any - (point-min) (point-max) - 'gnus-group (gnus-intern-safe group gnus-active-hashtb))) - (beginning-of-line) - (cond - ;; It's quite likely that we are on the right line, so - ;; we check the current line first. - ((eq (get-text-property (point) 'gnus-group) - (gnus-intern-safe group gnus-active-hashtb)) - (point)) - ;; Previous and next line are also likely, so we check them as well. - ((save-excursion - (forward-line -1) - (eq (get-text-property (point) 'gnus-group) - (gnus-intern-safe group gnus-active-hashtb))) - (forward-line -1) - (point)) - ((save-excursion - (forward-line 1) - (eq (get-text-property (point) 'gnus-group) - (gnus-intern-safe group gnus-active-hashtb))) - (forward-line 1) - (point)) - (t - ;; Search through the entire buffer. - (gnus-goto-char - (text-property-any - (point-min) (point-max) - 'gnus-group (gnus-intern-safe group gnus-active-hashtb)))))))) + (beginning-of-line) + (cond + ;; It's quite likely that we are on the right line, so + ;; we check the current line first. + ((and (not far) + (eq (get-text-property (point) 'gnus-group) + (gnus-intern-safe group gnus-active-hashtb)) + (or (not test-marked) (gnus-group-mark-line-p))) + (point)) + ;; Previous and next line are also likely, so we check them as well. + ((and (not far) + (save-excursion + (forward-line -1) + (and (eq (get-text-property (point) 'gnus-group) + (gnus-intern-safe group gnus-active-hashtb)) + (or (not test-marked) (gnus-group-mark-line-p))))) + (forward-line -1) + (point)) + ((and (not far) + (save-excursion + (forward-line 1) + (and (eq (get-text-property (point) 'gnus-group) + (gnus-intern-safe group gnus-active-hashtb)) + (or (not test-marked) (gnus-group-mark-line-p))))) + (forward-line 1) + (point)) + (test-marked + (goto-char (point-min)) + (let (found) + (while (and (not found) + (gnus-goto-char + (text-property-any + (point) (point-max) + 'gnus-group + (gnus-intern-safe group gnus-active-hashtb)))) + (if (gnus-group-mark-line-p) + (setq found t) + (forward-line 1))) + found)) + (t + ;; Search through the entire buffer. + (gnus-goto-char + (text-property-any + (point-min) (point-max) + 'gnus-group (gnus-intern-safe group gnus-active-hashtb))))))) (defun gnus-group-next-group (n &optional silent) "Go to next N'th newsgroup. @@ -1804,11 +1931,12 @@ ADDRESS." (gnus-read-method "From method: "))) (when (stringp method) - (setq method (gnus-server-to-method method))) - (let* ((meth (when (and method - (not (gnus-server-equal method gnus-select-method))) - (if address (list (intern method) address) - method))) + (setq method (or (gnus-server-to-method method) method))) + (let* ((meth (gnus-method-simplify + (when (and method + (not (gnus-server-equal method gnus-select-method))) + (if address (list (intern method) address) + method)))) (nname (if method (gnus-group-prefixed-name name meth) name)) backend info) (when (gnus-gethash nname gnus-newsrc-hashtb) @@ -1843,8 +1971,20 @@ ADDRESS." (gnus-request-create-group nname nil args)) t)) -(defun gnus-group-delete-group (group &optional force) - "Delete the current group. Only meaningful with mail groups. +(defun gnus-group-delete-groups (&optional arg) + "Delete the current group. Only meaningful with editable groups." + (interactive "P") + (let ((n (length (gnus-group-process-prefix arg)))) + (when (gnus-yes-or-no-p + (if (= n 1) + "Delete this 1 group? " + (format "Delete these %d groups? " n))) + (gnus-group-iterate arg + (lambda (group) + (gnus-group-delete-group group nil t)))))) + +(defun gnus-group-delete-group (group &optional force no-prompt) + "Delete the current group. Only meaningful with editable groups. If FORCE (the prefix) is non-nil, all the articles in the group will be deleted. This is \"deleted\" as in \"removed forever from the face of the Earth\". There is no undo. The user will be prompted before @@ -1857,10 +1997,11 @@ doing the deletion." (unless (gnus-check-backend-function 'request-delete-group group) (error "This backend does not support group deletion")) (prog1 - (if (not (gnus-yes-or-no-p - (format - "Do you really want to delete %s%s? " - group (if force " and all its contents" "")))) + (if (and (not no-prompt) + (not (gnus-yes-or-no-p + (format + "Do you really want to delete %s%s? " + group (if force " and all its contents" ""))))) () ; Whew! (gnus-message 6 "Deleting group %s..." group) (if (not (gnus-request-delete-group group force)) @@ -1947,7 +2088,7 @@ and NEW-NAME will be prompted for." ((eq part 'method) "select method") ((eq part 'params) "group parameters") (t "group info")) - group) + (gnus-group-decoded-name group)) `(lambda (form) (gnus-group-edit-group-done ',part ,group form))))) @@ -2043,6 +2184,7 @@ and NEW-NAME will be prompted for." ((= char ?d) 'digest) ((= char ?f) 'forward) ((= char ?a) 'mmfd) + ((= char ?g) 'guess) (t (setq err (format "%c unknown. " char)) nil)))) (setq type found))) @@ -2093,6 +2235,42 @@ If SOLID (the prefix), create a solid group." (cons (current-buffer) (if (eq major-mode 'gnus-summary-mode) 'summary 'group)))))) +(defvar nnwarchive-type-definition) +(defvar gnus-group-warchive-type-history nil) +(defvar gnus-group-warchive-login-history nil) +(defvar gnus-group-warchive-address-history nil) + +(defun gnus-group-make-warchive-group () + "Create a nnwarchive group." + (interactive) + (require 'nnwarchive) + (let* ((group (gnus-read-group "Group name: ")) + (default-type (or (car gnus-group-warchive-type-history) + (symbol-name (caar nnwarchive-type-definition)))) + (type + (gnus-string-or + (completing-read + (format "Warchive type (default %s): " default-type) + (mapcar (lambda (elem) (list (symbol-name (car elem)))) + nnwarchive-type-definition) + nil t nil 'gnus-group-warchive-type-history) + default-type)) + (address (read-string "Warchive address: " + nil 'gnus-group-warchive-address-history)) + (default-login (or (car gnus-group-warchive-login-history) + user-mail-address)) + (login + (gnus-string-or + (read-string + (format "Warchive login (default %s): " user-mail-address) + default-login 'gnus-group-warchive-login-history) + user-mail-address)) + (method + `(nnwarchive ,address + (nnwarchive-type ,(intern type)) + (nnwarchive-login ,login)))) + (gnus-group-make-group group method))) + (defun gnus-group-make-archive-group (&optional all) "Create the (ding) Gnus archive group of the most recent articles. Given a prefix, create a full group." @@ -2157,7 +2335,7 @@ score file entries for articles to include in the group." (push (cons header regexps) scores)) scores))) (gnus-group-make-group group "nnkiboze" address) - (nnheader-temp-write (gnus-score-file-name (concat "nnkiboze:" group)) + (with-temp-file (gnus-score-file-name (concat "nnkiboze:" group)) (let (emacs-lisp-mode-hook) (pp scores (current-buffer))))) @@ -2211,6 +2389,62 @@ score file entries for articles to include in the group." 'summary 'group))) (error "Couldn't enter %s" dir)))) +(eval-and-compile + (autoload 'nnimap-expunge "nnimap") + (autoload 'nnimap-acl-get "nnimap") + (autoload 'nnimap-acl-edit "nnimap")) + +(defun gnus-group-nnimap-expunge (group) + "Expunge deleted articles in current nnimap GROUP." + (interactive (list (gnus-group-group-name))) + (let ((mailbox (gnus-group-real-name group)) method) + (unless group + (error "No group on current line")) + (unless (gnus-get-info group) + (error "Killed group; can't be edited")) + (unless (eq 'nnimap (car (setq method (gnus-find-method-for-group group)))) + (error "%s is not an nnimap group" group)) + (nnimap-expunge mailbox (cadr method)))) + +(defun gnus-group-nnimap-edit-acl (group) + "Edit the Access Control List of current nnimap GROUP." + (interactive (list (gnus-group-group-name))) + (let ((mailbox (gnus-group-real-name group)) method acl) + (unless group + (error "No group on current line")) + (unless (gnus-get-info group) + (error "Killed group; can't be edited")) + (unless (eq (car (setq method (gnus-find-method-for-group group))) 'nnimap) + (error "%s is not an nnimap group" group)) + (gnus-edit-form (setq acl (nnimap-acl-get mailbox (cadr method))) + (format "Editing the access control list for `%s'. + + An access control list is a list of (identifier . rights) elements. + + The identifier string specifies the corresponding user. The + identifier \"anyone\" is reserved to refer to the universal identity. + + Rights is a string listing a (possibly empty) set of alphanumeric + characters, each character listing a set of operations which is being + controlled. Letters are reserved for ``standard'' rights, listed + below. Digits are reserved for implementation or site defined rights. + + l - lookup (mailbox is visible to LIST/LSUB commands) + r - read (SELECT the mailbox, perform CHECK, FETCH, PARTIAL, + SEARCH, COPY from mailbox) + s - keep seen/unseen information across sessions (STORE \\SEEN flag) + w - write (STORE flags other than \\SEEN and \\DELETED) + i - insert (perform APPEND, COPY into mailbox) + p - post (send mail to submission address for mailbox, + not enforced by IMAP4 itself) + c - create and delete mailbox (CREATE new sub-mailboxes in any + implementation-defined hierarchy, RENAME or DELETE mailbox) + d - delete messages (STORE \\DELETED flag, perform EXPUNGE) + a - administer (perform SETACL)" group) + `(lambda (form) + (nnimap-acl-edit + ,mailbox ',method ',acl form))))) + ;; Group sorting commands ;; Suggested by Joe Hildebrand . @@ -2302,46 +2536,52 @@ If REVERSE, sort in reverse order." ;; Go through all the infos and replace the old entries ;; with the new infos. (while infos - (setcar entries (pop infos)) + (setcar (car entries) (pop infos)) (pop entries)) ;; Update the hashtable. (gnus-make-hashtable-from-newsrc-alist))) -(defun gnus-group-sort-selected-groups-by-alphabet (&optional reverse) +(defun gnus-group-sort-selected-groups-by-alphabet (&optional n reverse) "Sort the group buffer alphabetically by group name. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-alphabet reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-alphabet reverse)) -(defun gnus-group-sort-selected-groups-by-unread (&optional reverse) +(defun gnus-group-sort-selected-groups-by-unread (&optional n reverse) "Sort the group buffer by number of unread articles. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-unread reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-unread reverse)) -(defun gnus-group-sort-selected-groups-by-level (&optional reverse) +(defun gnus-group-sort-selected-groups-by-level (&optional n reverse) "Sort the group buffer by group level. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-level reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-level reverse)) -(defun gnus-group-sort-selected-groups-by-score (&optional reverse) +(defun gnus-group-sort-selected-groups-by-score (&optional n reverse) "Sort the group buffer by group score. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-score reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-score reverse)) -(defun gnus-group-sort-selected-groups-by-rank (&optional reverse) +(defun gnus-group-sort-selected-groups-by-rank (&optional n reverse) "Sort the group buffer by group rank. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-rank reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-rank reverse)) -(defun gnus-group-sort-selected-groups-by-method (&optional reverse) +(defun gnus-group-sort-selected-groups-by-method (&optional n reverse) "Sort the group buffer alphabetically by backend name. -If REVERSE, sort in reverse order." - (interactive "P") - (gnus-group-sort-selected-groups 'gnus-group-sort-by-method reverse)) +Obeys the process/prefix convention. If REVERSE (the symbolic prefix), +sort in reverse order." + (interactive (gnus-interactive "P\ny")) + (gnus-group-sort-selected-groups n 'gnus-group-sort-by-method reverse)) ;;; Sorting predicates. @@ -2428,7 +2668,7 @@ If REVERSE, sort in reverse order." ;; Group catching up. (defun gnus-group-catchup-current (&optional n all) - "Mark all articles not marked as unread in current newsgroup as read. + "Mark all unread articles in the current newsgroup as read. If prefix argument N is numeric, the next N newsgroups will be caught up. If ALL is non-nil, marked articles will also be marked as read. Cross references (Xref: header) of articles are ignored. @@ -2436,7 +2676,8 @@ The number of newsgroups that this function was unable to catch up is returned." (interactive "P") (let ((groups (gnus-group-process-prefix n)) - (ret 0)) + (ret 0) + group) (unless groups (error "No groups selected")) (if (not (or (not gnus-interactive-catchup) ;Without confirmation? @@ -2450,21 +2691,20 @@ up is returned." (car groups) (format "these %d groups" (length groups))))))) n - (while groups + (while (setq group (pop groups)) + (gnus-group-remove-mark group) ;; Virtual groups have to be given special treatment. - (let ((method (gnus-find-method-for-group (car groups)))) + (let ((method (gnus-find-method-for-group group))) (when (eq 'nnvirtual (car method)) (nnvirtual-catchup-group - (gnus-group-real-name (car groups)) (nth 1 method) all))) - (gnus-group-remove-mark (car groups)) - (if (>= (gnus-group-group-level) gnus-level-zombie) + (gnus-group-real-name group) (nth 1 method) all))) + (if (>= (gnus-group-level group) gnus-level-zombie) (gnus-message 2 "Dead groups can't be caught up") (if (prog1 - (gnus-group-goto-group (car groups)) - (gnus-group-catchup (car groups) all)) + (gnus-group-goto-group group) + (gnus-group-catchup group all)) (gnus-group-update-group-line) - (setq ret (1+ ret)))) - (setq groups (cdr groups))) + (setq ret (1+ ret))))) (gnus-group-next-unread-group 1) ret))) @@ -2481,6 +2721,8 @@ The return value is the number of articles that were marked as read, or nil if no action could be taken." (let* ((entry (gnus-gethash group gnus-newsrc-hashtb)) (num (car entry))) + ;; Remove entries for this group. + (nnmail-purge-split-history (gnus-group-real-name group)) ;; Do the updating only if the newsgroup isn't killed. (if (not (numberp (car entry))) (gnus-message 1 "Can't catch up %s; non-active group" group) @@ -2513,32 +2755,41 @@ or nil if no action could be taken." (error "No groups to expire")) (while (setq group (pop groups)) (gnus-group-remove-mark group) - (when (gnus-check-backend-function 'request-expire-articles group) - (gnus-message 6 "Expiring articles in %s..." group) - (let* ((info (gnus-get-info group)) - (expirable (if (gnus-group-total-expirable-p group) - (cons nil (gnus-list-of-read-articles group)) - (assq 'expire (gnus-info-marks info)))) - (expiry-wait (gnus-group-find-parameter group 'expiry-wait))) - (when expirable - (setcdr - expirable - (gnus-compress-sequence - (if expiry-wait - ;; We set the expiry variables to the group - ;; parameter. - (let ((nnmail-expiry-wait-function nil) - (nnmail-expiry-wait expiry-wait)) - (gnus-request-expire-articles - (gnus-uncompress-sequence (cdr expirable)) group)) - ;; Just expire using the normal expiry values. - (gnus-request-expire-articles - (gnus-uncompress-sequence (cdr expirable)) group)))) - (gnus-close-group group)) - (gnus-message 6 "Expiring articles in %s...done" group))) + (gnus-group-expire-articles-1 group) (gnus-dribble-touch) (gnus-group-position-point)))) +(defun gnus-group-expire-articles-1 (group) + (when (gnus-check-backend-function 'request-expire-articles group) + (gnus-message 6 "Expiring articles in %s..." group) + (let* ((info (gnus-get-info group)) + (expirable (if (gnus-group-total-expirable-p group) + (cons nil (gnus-list-of-read-articles group)) + (assq 'expire (gnus-info-marks info)))) + (expiry-wait (gnus-group-find-parameter group 'expiry-wait)) + (nnmail-expiry-target + (or (gnus-group-find-parameter group 'expiry-target) + nnmail-expiry-target))) + (when expirable + (gnus-check-group group) + (setcdr + expirable + (gnus-compress-sequence + (if expiry-wait + ;; We set the expiry variables to the group + ;; parameter. + (let ((nnmail-expiry-wait-function nil) + (nnmail-expiry-wait expiry-wait)) + (gnus-request-expire-articles + (gnus-uncompress-sequence (cdr expirable)) group)) + ;; Just expire using the normal expiry values. + (gnus-request-expire-articles + (gnus-uncompress-sequence (cdr expirable)) group)))) + (gnus-close-group group)) + (gnus-message 6 "Expiring articles in %s...done" group) + ;; Return the list of un-expired articles. + (cdr expirable)))) + (defun gnus-group-expire-all-groups () "Expire all expirable articles in all newsgroups." (interactive) @@ -2565,7 +2816,7 @@ or nil if no action could be taken." gnus-level-default-subscribed)) s))))) (unless (and (>= level 1) (<= level gnus-level-killed)) - (error "Illegal level: %d" level)) + (error "Invalid level: %d" level)) (let ((groups (gnus-group-process-prefix n)) group) (while (setq group (pop groups)) @@ -2666,13 +2917,15 @@ N and the number of steps taken is returned." (gnus-group-yank-group) (gnus-group-position-point))) -(defun gnus-group-kill-all-zombies () - "Kill all zombie newsgroups." - (interactive) - (setq gnus-killed-list (nconc gnus-zombie-list gnus-killed-list)) - (setq gnus-zombie-list nil) - (gnus-dribble-touch) - (gnus-group-list-groups)) +(defun gnus-group-kill-all-zombies (&optional dummy) + "Kill all zombie newsgroups. +The optional DUMMY should always be nil." + (interactive (list (not (gnus-yes-or-no-p "Really kill all zombies? ")))) + (unless dummy + (setq gnus-killed-list (nconc gnus-zombie-list gnus-killed-list)) + (setq gnus-zombie-list nil) + (gnus-dribble-touch) + (gnus-group-list-groups))) (defun gnus-group-kill-region (begin end) "Kill newsgroups in current region (excluding current point). @@ -2721,7 +2974,8 @@ of groups killed." (push (cons (car entry) (nth 2 entry)) gnus-list-of-killed-groups)) (gnus-group-change-level - (if entry entry group) gnus-level-killed (if entry nil level))) + (if entry entry group) gnus-level-killed (if entry nil level)) + (message "Killed group %s" group)) ;; If there are lots and lots of groups to be killed, we use ;; this thing instead. (let (entry) @@ -2807,7 +3061,7 @@ yanked) a list of yanked groups is returned." (gnus-make-hashtable-from-newsrc-alist) (gnus-group-list-groups))) (t - (error "Can't kill; illegal level: %d" level)))) + (error "Can't kill; invalid level: %d" level)))) (defun gnus-group-list-all-groups (&optional arg) "List all newsgroups with level ARG or lower. @@ -2850,7 +3104,8 @@ entail asking the server for the groups." (interactive) ;; First we make sure that we have really read the active file. (unless (gnus-read-active-file-p) - (let ((gnus-read-active-file t)) + (let ((gnus-read-active-file t) + (gnus-agent nil)) ; Trick the agent into ignoring the active file. (gnus-read-active-file))) ;; Find all groups and sort them. (let ((groups @@ -2868,10 +3123,14 @@ entail asking the server for the groups." group) (erase-buffer) (while groups + (setq group (pop groups)) (gnus-add-text-properties (point) (prog1 (1+ (point)) (insert " *: " - (setq group (pop groups)) "\n")) + (gnus-group-name-decode group + (gnus-group-name-charset + nil group)) + "\n")) (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb) 'gnus-unread t 'gnus-level (inline (gnus-group-level group))))) @@ -2890,7 +3149,11 @@ If ARG is a number, it specifies which levels you are interested in re-scanning. If ARG is non-nil and not a number, this will force \"hard\" re-reading of the active files from all servers." (interactive "P") - (let ((gnus-inhibit-demon t)) + (require 'nnmail) + (let ((gnus-inhibit-demon t) + ;; Binding this variable will inhibit multiple fetchings + ;; of the same mail source. + (nnmail-fetched-sources (list t))) (gnus-run-hooks 'gnus-get-new-news-hook) ;; Read any slave files. @@ -2931,7 +3194,12 @@ If N is negative, this group and the N-1 previous groups will be checked." (ret (if (numberp n) (- n (length groups)) 0)) (beg (unless n (point))) - group method) + group method + (gnus-inhibit-demon t) + ;; Binding this variable will inhibit multiple fetchings + ;; of the same mail source. + (nnmail-fetched-sources (list t))) + (gnus-run-hooks 'gnus-get-new-news-hook) (while (setq group (pop groups)) (gnus-group-remove-mark group) ;; Bypass any previous denials from the server. @@ -2942,8 +3210,9 @@ If N is negative, this group and the N-1 previous groups will be checked." (gnus-get-info group) (gnus-active group) t) (unless (gnus-virtual-group-p group) (gnus-close-group group)) - (gnus-agent-save-group-info - method (gnus-group-real-name group) (gnus-active group)) + (when gnus-agent + (gnus-agent-save-group-info + method (gnus-group-real-name group) (gnus-active group))) (gnus-group-update-group group)) (if (eq (gnus-server-status (gnus-find-method-for-group group)) 'denied) @@ -3020,8 +3289,12 @@ to use." (mapatoms (lambda (group) (setq b (point)) - (insert (format " *: %-20s %s\n" (symbol-name group) - (symbol-value group))) + (let ((charset (gnus-group-name-charset nil (symbol-name group)))) + (insert (format " *: %-20s %s\n" + (gnus-group-name-decode + (symbol-name group) charset) + (gnus-group-name-decode + (symbol-value group) charset)))) (gnus-add-text-properties b (1+ b) (list 'gnus-group group 'gnus-unread t 'gnus-marked nil @@ -3057,17 +3330,19 @@ to use." ;; Print out all the groups. (save-excursion (pop-to-buffer "*Gnus Help*") - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) (setq groups (sort groups 'string<)) (while groups ;; Groups may be entered twice into the list of groups. (when (not (string= (car groups) prev)) - (insert (setq prev (car groups)) "\n") - (when (and gnus-description-hashtb - (setq des (gnus-gethash (car groups) - gnus-description-hashtb))) - (insert " " des "\n"))) + (setq prev (car groups)) + (let ((charset (gnus-group-name-charset nil prev))) + (insert (gnus-group-name-decode prev charset) "\n") + (when (and gnus-description-hashtb + (setq des (gnus-gethash (car groups) + gnus-description-hashtb))) + (insert " " (gnus-group-name-decode des charset) "\n")))) (setq groups (cdr groups))) (goto-char (point-min)))) (pop-to-buffer obuf))) @@ -3267,59 +3542,60 @@ and the second element is the address." (gnus-browse-foreign-server method)) (defun gnus-group-set-info (info &optional method-only-group part) - (let* ((entry (gnus-gethash - (or method-only-group (gnus-info-group info)) - gnus-newsrc-hashtb)) - (part-info info) - (info (if method-only-group (nth 2 entry) info)) - method) - (when method-only-group + (when (or info part) + (let* ((entry (gnus-gethash + (or method-only-group (gnus-info-group info)) + gnus-newsrc-hashtb)) + (part-info info) + (info (if method-only-group (nth 2 entry) info)) + method) + (when method-only-group + (unless entry + (error "Trying to change non-existent group %s" method-only-group)) + ;; We have received parts of the actual group info - either the + ;; select method or the group parameters. We first check + ;; whether we have to extend the info, and if so, do that. + (let ((len (length info)) + (total (if (eq part 'method) 5 6))) + (when (< len total) + (setcdr (nthcdr (1- len) info) + (make-list (- total len) nil))) + ;; Then we enter the new info. + (setcar (nthcdr (1- total) info) part-info))) (unless entry - (error "Trying to change non-existent group %s" method-only-group)) - ;; We have received parts of the actual group info - either the - ;; select method or the group parameters. We first check - ;; whether we have to extend the info, and if so, do that. - (let ((len (length info)) - (total (if (eq part 'method) 5 6))) - (when (< len total) - (setcdr (nthcdr (1- len) info) - (make-list (- total len) nil))) - ;; Then we enter the new info. - (setcar (nthcdr (1- total) info) part-info))) - (unless entry - ;; This is a new group, so we just create it. - (save-excursion - (set-buffer gnus-group-buffer) - (setq method (gnus-info-method info)) - (when (gnus-server-equal method "native") - (setq method nil)) + ;; This is a new group, so we just create it. (save-excursion (set-buffer gnus-group-buffer) - (if method - ;; It's a foreign group... - (gnus-group-make-group - (gnus-group-real-name (gnus-info-group info)) - (if (stringp method) method - (prin1-to-string (car method))) - (and (consp method) - (nth 1 (gnus-info-method info)))) - ;; It's a native group. - (gnus-group-make-group (gnus-info-group info)))) - (gnus-message 6 "Note: New group created") - (setq entry - (gnus-gethash (gnus-group-prefixed-name - (gnus-group-real-name (gnus-info-group info)) - (or (gnus-info-method info) gnus-select-method)) - gnus-newsrc-hashtb)))) - ;; Whether it was a new group or not, we now have the entry, so we - ;; can do the update. - (if entry - (progn - (setcar (nthcdr 2 entry) info) - (when (and (not (eq (car entry) t)) - (gnus-active (gnus-info-group info))) - (setcar entry (length (gnus-list-of-unread-articles (car info)))))) - (error "No such group: %s" (gnus-info-group info))))) + (setq method (gnus-info-method info)) + (when (gnus-server-equal method "native") + (setq method nil)) + (save-excursion + (set-buffer gnus-group-buffer) + (if method + ;; It's a foreign group... + (gnus-group-make-group + (gnus-group-real-name (gnus-info-group info)) + (if (stringp method) method + (prin1-to-string (car method))) + (and (consp method) + (nth 1 (gnus-info-method info)))) + ;; It's a native group. + (gnus-group-make-group (gnus-info-group info)))) + (gnus-message 6 "Note: New group created") + (setq entry + (gnus-gethash (gnus-group-prefixed-name + (gnus-group-real-name (gnus-info-group info)) + (or (gnus-info-method info) gnus-select-method)) + gnus-newsrc-hashtb)))) + ;; Whether it was a new group or not, we now have the entry, so we + ;; can do the update. + (if entry + (progn + (setcar (nthcdr 2 entry) info) + (when (and (not (eq (car entry) t)) + (gnus-active (gnus-info-group info))) + (setcar entry (length (gnus-list-of-unread-articles (car info)))))) + (error "No such group: %s" (gnus-info-group info)))))) (defun gnus-group-set-method-info (group select-method) (gnus-group-set-info select-method group 'method)) @@ -3329,7 +3605,7 @@ and the second element is the address." (defun gnus-add-marked-articles (group type articles &optional info force) ;; Add ARTICLES of TYPE to the info of GROUP. - ;; If INFO is non-nil, use that info. If FORCE is non-nil, don't + ;; If INFO is non-nil, use that info. If FORCE is non-nil, don't ;; add, but replace marked articles of TYPE with ARTICLES. (let ((info (or info (gnus-get-info group))) marked m) @@ -3373,8 +3649,8 @@ or `gnus-group-catchup-group-hook'." (defun gnus-group-timestamp-delta (group) "Return the offset in seconds from the timestamp for GROUP to the current time, as a floating point number." (let* ((time (or (gnus-group-timestamp group) - (list 0 0))) - (delta (gnus-time-minus (current-time) time))) + (list 0 0))) + (delta (subtract-time (current-time) time))) (+ (* (nth 0 delta) 65536.0) (nth 1 delta)))) @@ -3385,6 +3661,118 @@ or `gnus-group-catchup-group-hook'." "" (gnus-time-iso8601 time)))) +(defun gnus-group-prepare-flat-list-dead-predicate + (groups level mark predicate) + (let (group) + (if predicate + ;; This loop is used when listing groups that match some + ;; regexp. + (while (setq group (pop groups)) + (when (funcall predicate group) + (gnus-add-text-properties + (point) (prog1 (1+ (point)) + (insert " " mark " *: " + (gnus-group-name-decode group + (gnus-group-name-charset + nil group)) + "\n")) + (list 'gnus-group (gnus-intern-safe group gnus-active-hashtb) + 'gnus-unread t + 'gnus-level level))))))) + +(defun gnus-group-prepare-flat-predicate (level predicate &optional lowest + dead-predicate) + "List all newsgroups with unread articles of level LEVEL or lower. +If LOWEST is non-nil, list all newsgroups of level LOWEST or higher. +If PREDICATE, only list groups which PREDICATE returns non-nil. +If DEAD-PREDICATE, list dead groups which DEAD-PREDICATE returns non-nil." + (set-buffer gnus-group-buffer) + (let ((buffer-read-only nil) + (newsrc (cdr gnus-newsrc-alist)) + (lowest (or lowest 1)) + info clevel unread group params) + (erase-buffer) + ;; List living groups. + (while newsrc + (setq info (car newsrc) + group (gnus-info-group info) + params (gnus-info-params info) + newsrc (cdr newsrc) + unread (car (gnus-gethash group gnus-newsrc-hashtb))) + (and unread ; This group might be unchecked + (funcall predicate info) + (<= (setq clevel (gnus-info-level info)) level) + (>= clevel lowest) + (gnus-group-insert-group-line + group (gnus-info-level info) + (gnus-info-marks info) unread (gnus-info-method info)))) + + ;; List dead groups. + (and (>= level gnus-level-zombie) (<= lowest gnus-level-zombie) + (gnus-group-prepare-flat-list-dead-predicate + (setq gnus-zombie-list (sort gnus-zombie-list 'string<)) + gnus-level-zombie ?Z + dead-predicate)) + (and (>= level gnus-level-killed) (<= lowest gnus-level-killed) + (gnus-group-prepare-flat-list-dead-predicate + (setq gnus-killed-list (sort gnus-killed-list 'string<)) + gnus-level-killed ?K dead-predicate)) + + (gnus-group-set-mode-line) + (setq gnus-group-list-mode (cons level t)) + (gnus-run-hooks 'gnus-group-prepare-hook) + t)) + +(defun gnus-group-list-cached (level &optional lowest) + "List all groups with cached articles. +If the prefix LEVEL is non-nil, it should be a number that says which +level to cut off listing groups. +If LOWEST, don't list groups with level lower than LOWEST. + +This command may read the active file." + (interactive "P") + (when level + (setq level (prefix-numeric-value level))) + (when (or (not level) (>= level gnus-level-zombie)) + (gnus-cache-open)) + (gnus-group-prepare-flat-predicate (or level gnus-level-subscribed) + #'(lambda (info) + (let ((marks (gnus-info-marks info))) + (assq 'cache marks))) + lowest + #'(lambda (group) + (or (gnus-gethash group + gnus-cache-active-hashtb) + ;; Cache active file might use "." + ;; instead of ":". + (gnus-gethash + (mapconcat 'identity + (split-string group ":") + ".") + gnus-cache-active-hashtb)))) + (goto-char (point-min)) + (gnus-group-position-point)) + +(defun gnus-group-list-dormant (level &optional lowest) + "List all groups with dormant articles. +If the prefix LEVEL is non-nil, it should be a number that says which +level to cut off listing groups. +If LOWEST, don't list groups with level lower than LOWEST. + +This command may read the active file." + (interactive "P") + (when level + (setq level (prefix-numeric-value level))) + (when (or (not level) (>= level gnus-level-zombie)) + (gnus-cache-open)) + (gnus-group-prepare-flat-predicate (or level gnus-level-subscribed) + #'(lambda (info) + (let ((marks (gnus-info-marks info))) + (assq 'dormant marks))) + lowest) + (goto-char (point-min)) + (gnus-group-position-point)) + (provide 'gnus-group) ;;; gnus-group.el ends here diff --git a/lisp/gnus/gnus-int.el b/lisp/gnus/gnus-int.el index d441a1b6287..bdd0227b848 100644 --- a/lisp/gnus/gnus-int.el +++ b/lisp/gnus/gnus-int.el @@ -1,5 +1,6 @@ ;;; gnus-int.el --- backend interface functions for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (defcustom gnus-open-server-hook nil @@ -93,6 +92,7 @@ If CONFIRM is non-nil, the user will be asked for an NNTP server." ;; gnus-open-server-hook might have opened it (gnus-server-opened gnus-select-method) (gnus-open-server gnus-select-method) + gnus-batch-mode (gnus-y-or-n-p (format "%s (%s) open error: '%s'. Continue? " @@ -220,10 +220,12 @@ If it is down, start it up (again)." (defun gnus-server-opened (gnus-command-method) "Check whether a connection to GNUS-COMMAND-METHOD has been opened." - (when (stringp gnus-command-method) - (setq gnus-command-method (gnus-server-to-method gnus-command-method))) - (funcall (inline (gnus-get-function gnus-command-method 'server-opened)) - (nth 1 gnus-command-method))) + (unless (eq (gnus-server-status gnus-command-method) + 'denied) + (when (stringp gnus-command-method) + (setq gnus-command-method (gnus-server-to-method gnus-command-method))) + (funcall (inline (gnus-get-function gnus-command-method 'server-opened)) + (nth 1 gnus-command-method)))) (defun gnus-status-message (gnus-command-method) "Return the status message from GNUS-COMMAND-METHOD. @@ -270,6 +272,14 @@ this group uses will be queried." (funcall (gnus-get-function gnus-command-method func) (gnus-group-real-name group) (nth 1 gnus-command-method))))) +(defun gnus-request-group-articles (group) + "Request a list of existing articles in GROUP." + (let ((gnus-command-method (gnus-find-method-for-group group)) + (func 'request-group-articles)) + (when (gnus-check-backend-function func group) + (funcall (gnus-get-function gnus-command-method func) + (gnus-group-real-name group) (nth 1 gnus-command-method))))) + (defun gnus-close-group (group) "Request the GROUP be closed." (let ((gnus-command-method (inline (gnus-find-method-for-group group)))) @@ -309,6 +319,16 @@ If FETCH-OLD, retrieve all headers (or some subset thereof) in the group." (funcall (gnus-get-function gnus-command-method 'request-type) (gnus-group-real-name group) article)))) +(defun gnus-request-set-mark (group action) + "Set marks on articles in the backend." + (let ((gnus-command-method (gnus-find-method-for-group group))) + (if (not (gnus-check-backend-function + 'request-set-mark (car gnus-command-method))) + action + (funcall (gnus-get-function gnus-command-method 'request-set-mark) + (gnus-group-real-name group) action + (nth 1 gnus-command-method))))) + (defun gnus-request-update-mark (group article mark) "Allow the backend to change the mark the user tries to put on an article." (let ((gnus-command-method (gnus-find-method-for-group group))) @@ -394,13 +414,14 @@ If BUFFER, insert the article in that group." (defun gnus-request-scan (group gnus-command-method) "Request a SCAN being performed in GROUP from GNUS-COMMAND-METHOD. If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned." - (when gnus-plugged - (let ((gnus-command-method - (if group (gnus-find-method-for-group group) gnus-command-method)) - (gnus-inhibit-demon t)) - (funcall (gnus-get-function gnus-command-method 'request-scan) - (and group (gnus-group-real-name group)) - (nth 1 gnus-command-method))))) + (let ((gnus-command-method + (if group (gnus-find-method-for-group group) gnus-command-method)) + (gnus-inhibit-demon t) + (mail-source-plugged gnus-plugged)) + (if (or gnus-plugged (not (gnus-agent-method-p gnus-command-method))) + (funcall (gnus-get-function gnus-command-method 'request-scan) + (and group (gnus-group-real-name group)) + (nth 1 gnus-command-method))))) (defsubst gnus-request-update-info (info gnus-command-method) "Request that GNUS-COMMAND-METHOD update INFO." @@ -425,7 +446,8 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned." article (gnus-group-real-name group) (nth 1 gnus-command-method) accept-function last))) -(defun gnus-request-accept-article (group &optional gnus-command-method last) +(defun gnus-request-accept-article (group &optional gnus-command-method last + no-encode) ;; Make sure there's a newline at the end of the article. (when (stringp gnus-command-method) (setq gnus-command-method (gnus-server-to-method gnus-command-method))) @@ -435,6 +457,12 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned." (goto-char (point-max)) (unless (bolp) (insert "\n")) + (unless no-encode + (save-restriction + (message-narrow-to-head) + (let ((mail-parse-charset message-default-charset)) + (mail-encode-encoded-word-buffer))) + (message-encode-message-body)) (let ((func (car (or gnus-command-method (gnus-find-method-for-group group))))) (funcall (intern (format "%s-request-accept-article" func)) @@ -442,7 +470,13 @@ If GROUP is nil, all groups on GNUS-COMMAND-METHOD are scanned." (cadr gnus-command-method) last))) -(defun gnus-request-replace-article (article group buffer) +(defun gnus-request-replace-article (article group buffer &optional no-encode) + (unless no-encode + (save-restriction + (message-narrow-to-head) + (let ((mail-parse-charset message-default-charset)) + (mail-encode-encoded-word-buffer))) + (message-encode-message-body)) (let ((func (car (gnus-group-name-to-method group)))) (funcall (intern (format "%s-request-replace-article" func)) article (gnus-group-real-name group) buffer))) diff --git a/lisp/gnus/gnus-kill.el b/lisp/gnus/gnus-kill.el index 3ca8b20f08f..4838ba877a4 100644 --- a/lisp/gnus/gnus-kill.el +++ b/lisp/gnus/gnus-kill.el @@ -1,5 +1,6 @@ ;;; gnus-kill.el --- kill commands for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Masanobu UMEDA ;; Lars Magne Ingebrigtsen @@ -28,8 +29,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-art) (require 'gnus-range) @@ -51,7 +50,8 @@ :type 'boolean) (defcustom gnus-winconf-kill-file nil - "What does this do, Lars?" + "What does this do, Lars? +I don't know, Per." :group 'gnus-score-kill :type 'sexp) @@ -431,7 +431,7 @@ Returns the number of articles marked as read." (defun gnus-score-insert-help (string alist idx) (save-excursion (pop-to-buffer "*Score Help*") - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) (insert string ":\n\n") (while alist @@ -446,7 +446,7 @@ Returns the number of articles marked as read." (setq beg (point)) (setq form (ignore-errors (read (current-buffer))))) (unless (listp form) - (error "Illegal kill entry (possibly rn kill file?): %s" form)) + (error "Invalid kill entry (possibly rn kill file?): %s" form)) (if (or (eq (car form) 'gnus-kill) (eq (car form) 'gnus-raise) (eq (car form) 'gnus-lower)) @@ -526,7 +526,7 @@ COMMAND must be a lisp expression or a string representing a key sequence." ;; It's on the form (regexp . date). (if (zerop (gnus-execute field (car kill-list) command nil (not all))) - (when (> (gnus-days-between date (cdr kill-list)) + (when (> (days-between date (cdr kill-list)) gnus-kill-expiry-days) (setq regexp nil)) (setcdr kill-list date)) @@ -537,7 +537,7 @@ COMMAND must be a lisp expression or a string representing a key sequence." (setq kdate (cdr kill)) (if (zerop (gnus-execute field (car kill) command nil (not all))) - (when (> (gnus-days-between date kdate) + (when (> (days-between date kdate) gnus-kill-expiry-days) ;; Time limit has been exceeded, so we ;; remove the match. @@ -568,7 +568,7 @@ COMMAND must be a lisp expression or a string representing a key sequence." (concat "\n" (gnus-prin1-to-string object)) (save-excursion (set-buffer (gnus-get-buffer-create "*Gnus PP*")) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) (insert (format "\n(%S %S\n '(" (nth 0 object) (nth 1 object))) (let ((klist (cadr (nth 2 object))) @@ -685,6 +685,7 @@ Usage: emacs -batch -l ~/.emacs -l gnus -f gnus-batch-score" (mapconcat 'identity command-line-args-left " ")))) (gnus-expert-user t) (nnmail-spool-file nil) + (mail-sources nil) (gnus-use-dribble-file nil) (gnus-batch-mode t) info group newsrc entry @@ -704,7 +705,8 @@ Usage: emacs -batch -l ~/.emacs -l gnus -f gnus-batch-score" (and (car entry) (or (eq (car entry) t) (not (zerop (car entry)))))) - (gnus-summary-read-group group nil t nil t) + (ignore-errors + (gnus-summary-read-group group nil t nil t)) (when (eq (current-buffer) (get-buffer gnus-summary-buffer)) (gnus-summary-exit)))) ;; Exit Emacs. diff --git a/lisp/gnus/gnus-logic.el b/lisp/gnus/gnus-logic.el index a6028352bf5..56964ff9f46 100644 --- a/lisp/gnus/gnus-logic.el +++ b/lisp/gnus/gnus-logic.el @@ -1,5 +1,6 @@ ;;; gnus-logic.el --- advanced scoring code for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-score) (require 'gnus-util) @@ -173,9 +172,9 @@ ((eq type 'at) (equal date match)) ((eq type 'before) - (gnus-time-less match date)) + (time-less-p match date)) ((eq type 'after) - (gnus-time-less date match)) + (time-less-p date match)) (t (error "No such date score type: %s" type))))) @@ -220,7 +219,7 @@ ((memq type '(s S string String)) 'search-forward) (t - (error "Illegal match type: %s" type))))) + (error "Invalid match type: %s" type))))) (goto-char (point-min)) (prog1 (funcall search-func match nil t) diff --git a/lisp/gnus/gnus-mh.el b/lisp/gnus/gnus-mh.el index 123350c8f12..f7377120a4a 100644 --- a/lisp/gnus/gnus-mh.el +++ b/lisp/gnus/gnus-mh.el @@ -1,5 +1,6 @@ ;;; gnus-mh.el --- mh-e interface for Gnus -;; Copyright (C) 1994,95,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Masanobu UMEDA ;; Lars Magne Ingebrigtsen @@ -66,8 +67,8 @@ Optional argument FOLDER specifies folder name." t)))) (errbuf (gnus-get-buffer-create " *Gnus rcvstore*")) ;; Find the rcvstore program. - (exec-path (if mh-lib-progs (cons mh-lib-progs exec-path) exec-path))) - (gnus-eval-in-buffer-window gnus-original-article-buffer + (exec-path (if mh-lib (cons mh-lib exec-path) exec-path))) + (with-current-buffer gnus-original-article-buffer (save-restriction (widen) (unwind-protect diff --git a/lisp/gnus/gnus-move.el b/lisp/gnus/gnus-move.el index b461952185e..36839c8d07e 100644 --- a/lisp/gnus/gnus-move.el +++ b/lisp/gnus/gnus-move.el @@ -1,5 +1,6 @@ ;;; gnus-move.el --- commands for moving Gnus from one server to another -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-start) (require 'gnus-int) @@ -47,6 +46,7 @@ Update the .newsrc.eld file to reflect the change of nntp server." ;; First start Gnus. (let ((gnus-activate-level 0) + (mail-sources nil) (nnmail-spool-file nil)) (gnus)) @@ -92,6 +92,8 @@ Update the .newsrc.eld file to reflect the change of nntp server." ;; Then we read the headers from the `from-server'. (when (and (gnus-request-group group nil from-server) (gnus-active group) + (gnus-uncompress-range + (gnus-active group)) (setq type (gnus-retrieve-headers (gnus-uncompress-range (gnus-active group)) diff --git a/lisp/gnus/gnus-msg.el b/lisp/gnus/gnus-msg.el index 992eac52c4a..e371db143f8 100644 --- a/lisp/gnus/gnus-msg.el +++ b/lisp/gnus/gnus-msg.el @@ -1,5 +1,6 @@ ;;; gnus-msg.el --- mail and post interface for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Masanobu UMEDA ;; Lars Magne Ingebrigtsen @@ -28,26 +29,24 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-ems) (require 'message) (require 'gnus-art) -(defcustom gnus-post-method nil +(defcustom gnus-post-method 'current "*Preferred method for posting USENET news. If this variable is `current', Gnus will use the \"current\" select method when posting. If it is nil (which is the default), Gnus will -use the native posting method of the server. +use the native select method when posting. This method will not be used in mail groups and the like, only in \"real\" newsgroups. If not nil nor `native', the value must be a valid method as discussed -in the documentation of `gnus-select-method'. It can also be a list of -methods. If that is the case, the user will be queried for what select +in the documentation of `gnus-select-method'. It can also be a list of +methods. If that is the case, the user will be queried for what select method to use when posting." :group 'gnus-group-foreign :type `(choice (const nil) @@ -102,13 +101,37 @@ the second with the current group name.") (defvar gnus-posting-styles nil "*Alist of styles to use when posting.") -(defvar gnus-posting-style-alist - '((organization . message-user-organization) - (signature . message-signature) - (signature-file . message-signature-file) - (address . user-mail-address) - (name . user-full-name)) - "*Mapping from style parameters to variables.") +(defcustom gnus-group-posting-charset-alist + '(("^\\(no\\|fr\\|dk\\)\\.[^,]*\\(,[ \t\n]*\\(no\\|fr\\|dk\\)\\.[^,]*\\)*$" iso-8859-1 (iso-8859-1)) + ("^\\(fido7\\|relcom\\)\\.[^,]*\\(,[ \t\n]*\\(fido7\\|relcom\\)\\.[^,]*\\)*$" koi8-r (koi8-r)) + (message-this-is-mail nil nil) + (message-this-is-news nil t)) + "Alist of regexps and permitted unencoded charsets for posting. +Each element of the alist has the form (TEST HEADER BODY-LIST), where +TEST is either a regular expression matching the newsgroup header or a +variable to query, +HEADER is the charset which may be left unencoded in the header (nil +means encode all charsets), +BODY-LIST is a list of charsets which may be encoded using 8bit +content-transfer encoding in the body, or one of the special values +nil (always encode using quoted-printable) or t (always use 8bit). + +Note that any value other than nil for HEADER infringes some RFCs, so +use this option with care." + :type '(repeat (list :tag "Permitted unencoded charsets" + (choice :tag "Where" + (regexp :tag "Group") + (const :tag "Mail message" :value message-this-is-mail) + (const :tag "News article" :value message-this-is-news)) + (choice :tag "Header" + (const :tag "None" nil) + (symbol :tag "Charset")) + (choice :tag "Body" + (const :tag "Any" :value t) + (const :tag "None" :value nil) + (repeat :tag "Charsets" + (symbol :tag "Charset"))))) + :group 'gnus-charset) ;;; Internal variables. @@ -127,9 +150,10 @@ the second with the current group name.") The buffer below is a mail buffer. When you press `C-c C-c', it will be sent to the Gnus Bug Exterminators. -At the bottom of the buffer you'll see lots of variable settings. -Please do not delete those. They will tell the Bug People what your -environment is, so that it will be easier to locate the bugs. +The thing near the bottom of the buffer is how the environment +settings will be included in the mail. Please do not delete that. +They will tell the Bug People what your environment is, so that it +will be easier to locate the bugs. If you have found a bug that makes Emacs go \"beep\", set debug-on-error to t (`M-x set-variable RET debug-on-error RET t RET') @@ -159,6 +183,7 @@ Thank you for your help in stamping out bugs. "c" gnus-summary-cancel-article "s" gnus-summary-supersede-article "r" gnus-summary-reply + "y" gnus-summary-yank-message "R" gnus-summary-reply-with-original "w" gnus-summary-wide-reply "W" gnus-summary-wide-reply-with-original @@ -177,6 +202,20 @@ Thank you for your help in stamping out bugs. ;; "c" gnus-summary-send-draft "r" gnus-summary-resend-message) +;;;###autoload +(defun gnus-msg-mail (&rest args) + "Start editing a mail message to be sent. +Like `message-mail', but with Gnus paraphernalia, particularly the +the Gcc: header for archiving purposes." + (interactive) + (gnus-setup-message 'message + (apply 'message-mail args))) + +;;;###autoload +(define-mail-user-agent 'gnus-user-agent + 'gnus-msg-mail 'message-send-and-exit + 'message-kill-buffer 'message-send-hook) + ;;; Internal functions. (defvar gnus-article-reply nil) @@ -191,7 +230,9 @@ Thank you for your help in stamping out bugs. (,group gnus-newsgroup-name) (message-header-setup-hook (copy-sequence message-header-setup-hook)) + (mbl mml-buffer-list) (message-mode-hook (copy-sequence message-mode-hook))) + (setq mml-buffer-list nil) (add-hook 'message-header-setup-hook 'gnus-inews-insert-gcc) (add-hook 'message-header-setup-hook 'gnus-inews-insert-archive-gcc) (add-hook 'message-mode-hook 'gnus-configure-posting-styles) @@ -202,12 +243,37 @@ Thank you for your help in stamping out bugs. (setq gnus-message-buffer (current-buffer)) (set (make-local-variable 'gnus-message-group-art) (cons ,group ,article)) - (make-local-variable 'gnus-newsgroup-name) - (gnus-run-hooks 'gnus-message-setup-hook)) + (set (make-local-variable 'gnus-newsgroup-name) ,group) + (gnus-run-hooks 'gnus-message-setup-hook) + (if (eq major-mode 'message-mode) + ;; Make mml-buffer-list local. + ;; Restore global mml-buffer-list value as mbl. + ;; What a hack! -- Shenghuo + (let ((mml-buffer-list mml-buffer-list)) + (setq mml-buffer-list mbl) + (make-local-variable 'mml-buffer-list) + (add-hook 'kill-buffer-hook 'mml-destroy-buffers t t)) + (mml-destroy-buffers) + (setq mml-buffer-list mbl))) (gnus-add-buffer) (gnus-configure-windows ,config t) (set-buffer-modified-p nil)))) +(defun gnus-setup-posting-charset (group) + (let ((alist gnus-group-posting-charset-alist) + (group (or group "")) + elem) + (when group + (catch 'found + (while (setq elem (pop alist)) + (when (or (and (stringp (car elem)) + (string-match (car elem) group)) + (and (gnus-functionp (car elem)) + (funcall (car elem) group)) + (and (symbolp (car elem)) + (symbol-value (car elem)))) + (throw 'found (cons (cadr elem) (caddr elem))))))))) + (defun gnus-inews-add-send-actions (winconf buffer article) (make-local-hook 'message-sent-hook) (add-hook 'message-sent-hook 'gnus-inews-do-gcc nil t) @@ -230,11 +296,29 @@ Thank you for your help in stamping out bugs. ;;; Post news commands of Gnus group mode and summary mode -(defun gnus-group-mail () - "Start composing a mail." - (interactive) - (gnus-setup-message 'message - (message-mail))) +(defun gnus-group-mail (&optional arg) + "Start composing a mail. +If ARG, use the group under the point to find a posting style. +If ARG is 1, prompt for a group name to find the posting style." + (interactive "P") + ;; We can't `let' gnus-newsgroup-name here, since that leads + ;; to local variables leaking. + (let ((group gnus-newsgroup-name) + (buffer (current-buffer))) + (unwind-protect + (progn + (setq gnus-newsgroup-name + (if arg + (if (= 1 (prefix-numeric-value arg)) + (completing-read "Use posting style of group: " + gnus-active-hashtb nil + (gnus-read-active-file-p)) + (gnus-group-group-name)) + "")) + (gnus-setup-message 'message (message-mail))) + (save-excursion + (set-buffer buffer) + (setq gnus-newsgroup-name group))))) (defun gnus-group-post-news (&optional arg) "Start composing a news message. @@ -355,7 +439,9 @@ header line with the old Message-ID." ;; if ARTICLE-BUFFER is nil, gnus-article-buffer is used ;; this buffer should be passed to all mail/news reply/post routines. (setq gnus-article-copy (gnus-get-buffer-create " *gnus article copy*")) - (buffer-disable-undo gnus-article-copy) + (save-excursion + (set-buffer gnus-article-copy) + (mm-enable-multibyte)) (let ((article-buffer (or article-buffer gnus-article-buffer)) end beg) (if (not (and (get-buffer article-buffer) @@ -374,7 +460,7 @@ header line with the old Message-ID." (gnus-remove-text-with-property 'gnus-next) (insert (prog1 - (format "%s" (buffer-string)) + (buffer-substring-no-properties (point-min) (point-max)) (erase-buffer))) ;; Find the original headers. (set-buffer gnus-original-article-buffer) @@ -386,10 +472,10 @@ header line with the old Message-ID." ;; Delete the headers from the displayed articles. (set-buffer gnus-article-copy) (delete-region (goto-char (point-min)) - (or (search-forward "\n\n" nil t) (point))) + (or (search-forward "\n\n" nil t) (point-max))) ;; Insert the original article headers. (insert-buffer-substring gnus-original-article-buffer beg end) - (gnus-article-decode-rfc1522))) + (article-decode-encoded-words))) gnus-article-copy))) (defun gnus-post-news (post &optional group header article-buffer yank subject @@ -402,6 +488,7 @@ header line with the old Message-ID." (article-buffer 'reply) (t 'message)) (let* ((group (or group gnus-newsgroup-name)) + (charset (gnus-group-name-charset nil group)) (pgroup group) to-address to-group mailing-list to-list newsgroup-p) @@ -412,7 +499,8 @@ header line with the old Message-ID." newsgroup-p (gnus-group-find-parameter group 'newsgroup) mailing-list (when gnus-mailing-list-groups (string-match gnus-mailing-list-groups group)) - group (gnus-group-real-name group))) + group (gnus-group-name-decode (gnus-group-real-name group) + charset))) (if (or (and to-group (gnus-news-group-p to-group)) newsgroup-p @@ -464,7 +552,7 @@ If SILENT, don't prompt the user." ;; the default method. ((null group-method) (or (and (null (eq gnus-post-method 'active)) gnus-post-method) - gnus-select-method message-post-method)) + gnus-select-method message-post-method)) ;; We want the inverse of the default ((and arg (not (eq arg 0))) (if (eq gnus-post-method 'active) @@ -485,14 +573,16 @@ If SILENT, don't prompt the user." (list gnus-post-method))) gnus-secondary-select-methods (mapcar 'cdr gnus-server-alist) + (mapcar 'car gnus-opened-servers) (list gnus-select-method) (list group-method))) method-alist post-methods method) ;; Weed out all mail methods. (while methods (setq method (gnus-server-get-method "" (pop methods))) - (when (or (gnus-method-option-p method 'post) - (gnus-method-option-p method 'post-mail)) + (when (and (or (gnus-method-option-p method 'post) + (gnus-method-option-p method 'post-mail)) + (not (member method post-methods))) (push method post-methods))) ;; Create a name-method alist. (setq method-alist @@ -515,8 +605,9 @@ If SILENT, don't prompt the user." ;; Override normal method. ((and (eq gnus-post-method 'current) (not (eq (car group-method) 'nndraft)) + (gnus-get-function group-method 'request-post t) (not arg)) - group-method) + group-method) ((and gnus-post-method (not (eq gnus-post-method 'current))) gnus-post-method) @@ -525,69 +616,32 @@ If SILENT, don't prompt the user." -;; Dummy to avoid byte-compile warning. +;; Dummies to avoid byte-compile warning. (defvar nnspool-rejected-article-hook) (defvar xemacs-codename) -;;; Since the X-Newsreader/X-Mailer are ``vanity'' headers, they might -;;; as well include the Emacs version as well. -;;; The following function works with later GNU Emacs, and XEmacs. (defun gnus-extended-version () "Stringified Gnus version and Emacs version." (interactive) (concat - gnus-version - "/" + "Gnus/" (prin1-to-string (gnus-continuum-version gnus-version) t) + " (" gnus-version ")" + " " (cond - ((string-match "^\\([0-9]+\\.[0-9]+\\)\\.[.0-9]+$" emacs-version) - (concat "Emacs " (substring emacs-version - (match-beginning 1) - (match-end 1)))) + ((string-match "^\\(\\([.0-9]+\\)*\\)\\.[0-9]+$" emacs-version) + (concat "Emacs/" (match-string 1 emacs-version))) ((string-match "\\([A-Z]*[Mm][Aa][Cc][Ss]\\)[^(]*\\(\\((beta.*)\\|'\\)\\)?" emacs-version) - (concat (substring emacs-version - (match-beginning 1) - (match-end 1)) - (format " %d.%d" emacs-major-version emacs-minor-version) + (concat (match-string 1 emacs-version) + (format "/%d.%d" emacs-major-version emacs-minor-version) (if (match-beginning 3) - (substring emacs-version - (match-beginning 3) - (match-end 3)) + (match-string 3 emacs-version) "") (if (boundp 'xemacs-codename) - (concat " - \"" xemacs-codename "\"")))) + (concat " (" xemacs-codename ")") + ""))) (t emacs-version)))) -;; Written by "Mr. Per Persson" . -(defun gnus-inews-insert-mime-headers () - "Insert MIME headers. -Assumes ISO-Latin-1 is used iff 8-bit characters are present." - (goto-char (point-min)) - (let ((mail-header-separator - (progn - (goto-char (point-min)) - (if (and (search-forward (concat "\n" mail-header-separator "\n") - nil t) - (not (search-backward "\n\n" nil t))) - mail-header-separator - "")))) - (or (mail-position-on-field "Mime-Version") - (insert "1.0") - (cond ((save-restriction - (widen) - (goto-char (point-min)) - (re-search-forward "[^\000-\177]" nil t)) - (or (mail-position-on-field "Content-Type") - (insert "text/plain; charset=ISO-8859-1")) - (or (mail-position-on-field "Content-Transfer-Encoding") - (insert "8bit"))) - (t (or (mail-position-on-field "Content-Type") - (insert "text/plain; charset=US-ASCII")) - (or (mail-position-on-field "Content-Transfer-Encoding") - (insert "7bit"))))))) - -(custom-add-option 'message-header-hook 'gnus-inews-insert-mime-headers) - ;;; ;;; Gnus Mail Functions @@ -610,6 +664,10 @@ automatically." (gnus-summary-select-article) (set-buffer (gnus-copy-article-buffer)) (gnus-msg-treat-broken-reply-to) + (save-restriction + (message-narrow-to-head) + (goto-char (point-max))) + (mml-quote-region (point) (point-max)) (message-reply nil wide) (when yank (gnus-inews-yank-articles yank))))) @@ -635,16 +693,51 @@ The original article will be yanked." (interactive "P") (gnus-summary-reply-with-original n t)) -(defun gnus-summary-mail-forward (&optional full-headers post) - "Forward the current message to another user. -If FULL-HEADERS (the prefix), include full headers when forwarding." +(defun gnus-summary-mail-forward (&optional arg post) + "Forward the current message to another user. +If ARG is nil, see `message-forward-as-mime' and `message-forward-show-mml'; +if ARG is 1, decode the message and forward directly inline; +if ARG is 2, foward message as an rfc822 MIME section; +if ARG is 3, decode message and forward as an rfc822 MIME section; +if ARG is 4, foward message directly inline; +otherwise, use flipped `message-forward-as-mime'. +If POST, post instead of mail." (interactive "P") - (gnus-setup-message 'forward - (gnus-summary-select-article) - (set-buffer gnus-original-article-buffer) - (let ((message-included-forward-headers - (if full-headers "" message-included-forward-headers))) - (message-forward post)))) + (let ((message-forward-as-mime message-forward-as-mime) + (message-forward-show-mml message-forward-show-mml)) + (cond + ((null arg)) + ((eq arg 1) (setq message-forward-as-mime nil + message-forward-show-mml t)) + ((eq arg 2) (setq message-forward-as-mime t + message-forward-show-mml nil)) + ((eq arg 3) (setq message-forward-as-mime t + message-forward-show-mml t)) + ((eq arg 4) (setq message-forward-as-mime nil + message-forward-show-mml nil)) + (t (setq message-forward-as-mime (not message-forward-as-mime)))) + (gnus-setup-message 'forward + (gnus-summary-select-article) + (let ((mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets) + text) + (save-excursion + (set-buffer gnus-original-article-buffer) + (mm-with-unibyte-current-buffer + (setq text (buffer-string)))) + (set-buffer + (gnus-get-buffer-create + (generate-new-buffer-name " *Gnus forward*"))) + (erase-buffer) + (mm-disable-multibyte) + (insert text) + (goto-char (point-min)) + (when (looking-at "From ") + (replace-match "X-From-Line: ") ) + (when message-forward-show-mml + (mm-enable-multibyte) + (mime-to-mml)) + (message-forward post))))) (defun gnus-summary-resend-message (address n) "Resend the current article to ADDRESS." @@ -657,11 +750,11 @@ If FULL-HEADERS (the prefix), include full headers when forwarding." (set-buffer gnus-original-article-buffer) (message-resend address))))) -(defun gnus-summary-post-forward (&optional full-headers) +(defun gnus-summary-post-forward (&optional arg) "Forward the current article to a newsgroup. -If FULL-HEADERS (the prefix), include full headers when forwarding." +See `gnus-summary-mail-forward' for ARG." (interactive "P") - (gnus-summary-mail-forward full-headers t)) + (gnus-summary-mail-forward arg t)) (defvar gnus-nastygram-message "The following article was inappropriately posted to %s.\n\n" @@ -694,7 +787,8 @@ The current group name will be inserted at \"%s\".") (gnus-summary-select-article) (set-buffer gnus-original-article-buffer) (if (and (<= (length (message-tokenize-header - (setq newsgroups (mail-fetch-field "newsgroups")) + (setq newsgroups + (mail-fetch-field "newsgroups")) ", ")) 1) (or (not (setq followup-to (mail-fetch-field "followup-to"))) @@ -833,7 +927,12 @@ If YANK is non-nil, include the original article." (stringp nntp-server-type)) (insert nntp-server-type)) (insert "\n\n\n\n\n") - (gnus-debug) + (let (text) + (save-excursion + (set-buffer (gnus-get-buffer-create " *gnus environment info*")) + (gnus-debug) + (setq text (buffer-string))) + (insert "<#part type=application/x-emacs-lisp disposition=inline description=\"User settings\">\n" text "\n<#/part>")) (goto-char (point-min)) (search-forward "Subject: " nil t) (message ""))) @@ -842,6 +941,19 @@ If YANK is non-nil, include the original article." (when (get-buffer "*Gnus Help Bug*") (kill-buffer "*Gnus Help Bug*"))) +(defun gnus-summary-yank-message (buffer n) + "Yank the current article into a composed message." + (interactive + (list (completing-read "Buffer: " (mapcar 'list (message-buffers)) nil t) + current-prefix-arg)) + (gnus-summary-iterate n + (let ((gnus-display-mime-function nil) + (gnus-inhibit-treatment t)) + (gnus-summary-select-article)) + (save-excursion + (set-buffer buffer) + (message-yank-buffer gnus-article-buffer)))) + (defun gnus-debug () "Attempts to go through the Gnus source file and report what variables have been changed. The source file has to be in the Emacs load path." @@ -857,7 +969,6 @@ The source file has to be in the Emacs load path." ;; Go through all the files looking for non-default values for variables. (save-excursion (set-buffer (gnus-get-buffer-create " *gnus bug info*")) - (buffer-disable-undo (current-buffer)) (while files (erase-buffer) (when (and (setq file (locate-library (pop files))) @@ -940,7 +1051,8 @@ this is a reply." (when gcc (message-remove-header "gcc") (widen) - (setq groups (message-tokenize-header gcc " ,")) + (setq groups (message-unquote-tokens + (message-tokenize-header gcc " ,"))) ;; Copy the article over to some group(s). (while (setq group (pop groups)) (gnus-check-server @@ -964,12 +1076,20 @@ this is a reply." (save-excursion (nnheader-set-temp-buffer " *acc*") (insert-buffer-substring cur) + (message-encode-message-body) + (save-restriction + (message-narrow-to-headers) + (let ((mail-parse-charset message-default-charset) + (rfc2047-header-encoding-alist + (cons '("Newsgroups" . default) + rfc2047-header-encoding-alist))) + (mail-encode-encoded-word-buffer))) (goto-char (point-min)) (when (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$") nil t) (replace-match "" t t )) - (unless (gnus-request-accept-article group method t) + (unless (gnus-request-accept-article group method t t) (gnus-message 1 "Couldn't store article in group %s: %s" group (gnus-status-message method)) (sit-for 2)) @@ -998,9 +1118,10 @@ this is a reply." (group (or group gnus-newsgroup-name "")) (gcc-self-val (and gnus-newsgroup-name + (not (equal gnus-newsgroup-name "")) (gnus-group-find-parameter gnus-newsgroup-name 'gcc-self))) - result + result (groups (cond ((null gnus-message-archive-method) @@ -1068,86 +1189,131 @@ this is a reply." ;;; Posting styles. -(defvar gnus-message-style-insertions nil) - (defun gnus-configure-posting-styles () "Configure posting styles according to `gnus-posting-styles'." (unless gnus-inhibit-posting-styles - (let ((styles gnus-posting-styles) - (gnus-newsgroup-name (or gnus-newsgroup-name "")) - style match variable attribute value value-value) - (make-local-variable 'gnus-message-style-insertions) + (let ((group (or gnus-newsgroup-name "")) + (styles gnus-posting-styles) + style match variable attribute value v results + filep name address element) + ;; If the group has a posting-style parameter, add it at the end with a + ;; regexp matching everything, to be sure it takes precedence over all + ;; the others. + (when gnus-newsgroup-name + (let ((tmp-style (gnus-group-find-parameter group 'posting-style t))) + (when tmp-style + (setq styles (append styles (list (cons ".*" tmp-style))))))) ;; Go through all styles and look for matches. - (while styles - (setq style (pop styles) - match (pop style)) - (when (cond ((stringp match) - ;; Regexp string match on the group name. - (string-match match gnus-newsgroup-name)) - ((or (symbolp match) - (gnus-functionp match)) - (cond ((gnus-functionp match) - ;; Function to be called. - (funcall match)) - ((boundp match) - ;; Variable to be checked. - (symbol-value match)))) - ((listp match) - ;; This is a form to be evaled. - (eval match))) + (dolist (style styles) + (setq match (pop style)) + (goto-char (point-min)) + (when (cond + ((stringp match) + ;; Regexp string match on the group name. + (string-match match group)) + ((eq match 'header) + (let ((header (message-fetch-field (pop style)))) + (and header + (string-match (pop style) header)))) + ((or (symbolp match) + (gnus-functionp match)) + (cond + ((gnus-functionp match) + ;; Function to be called. + (funcall match)) + ((boundp match) + ;; Variable to be checked. + (symbol-value match)))) + ((listp match) + ;; This is a form to be evaled. + (eval match))) ;; We have a match, so we set the variables. - (while style - (setq attribute (pop style) - value (cadr attribute) - variable nil) - ;; We find the variable that is to be modified. - (if (and (not (stringp (car attribute))) - (not (eq 'body (car attribute))) - (not (setq variable - (cdr (assq (car attribute) - gnus-posting-style-alist))))) - (message "Couldn't find attribute %s" (car attribute)) - ;; We get the value. - (setq value-value - (cond ((stringp value) - value) - ((or (symbolp value) - (gnus-functionp value)) - (cond ((gnus-functionp value) - (funcall value)) - ((boundp value) - (symbol-value value)))) - ((listp value) - (eval value)))) - (if variable - ;; This is an ordinary variable. - (set (make-local-variable variable) value-value) - ;; This is either a body or a header to be inserted in the - ;; message. - (when value-value - (let ((attr (car attribute))) - (make-local-variable 'message-setup-hook) - (if (eq 'body attr) - (add-hook 'message-setup-hook - `(lambda () - (save-excursion - (message-goto-body) - (insert ,value-value)))) - (add-hook 'message-setup-hook - 'gnus-message-insert-stylings) - (push (cons (if (stringp attr) attr - (symbol-name attr)) - value-value) - gnus-message-style-insertions)))))))))))) - -(defun gnus-message-insert-stylings () - (let (val) - (save-excursion - (message-goto-eoh) - (while (setq val (pop gnus-message-style-insertions)) - (when (cdr val) - (insert (car val) ": " (cdr val) "\n")) - (gnus-pull (car val) gnus-message-style-insertions))))) + (dolist (attribute style) + (setq element (pop attribute) + variable nil + filep nil) + (setq value + (cond + ((eq (car attribute) :file) + (setq filep t) + (cadr attribute)) + ((eq (car attribute) :value) + (cadr attribute)) + (t + (car attribute)))) + ;; We get the value. + (setq v + (cond + ((stringp value) + value) + ((or (symbolp value) + (gnus-functionp value)) + (cond ((gnus-functionp value) + (funcall value)) + ((boundp value) + (symbol-value value)))) + ((listp value) + (eval value)))) + ;; Translate obsolescent value. + (when (eq element 'signature-file) + (setq element 'signature + filep t)) + ;; Get the contents of file elems. + (when (and filep v) + (setq v (with-temp-buffer + (insert-file-contents v) + (buffer-string)))) + (setq results (delq (assoc element results) results)) + (push (cons element v) results)))) + ;; Now we have all the styles, so we insert them. + (setq name (assq 'name results) + address (assq 'address results)) + (setq results (delq name (delq address results))) + (make-local-variable 'message-setup-hook) + (dolist (result results) + (add-hook 'message-setup-hook + (cond + ((eq 'eval (car result)) + 'ignore) + ((eq 'body (car result)) + `(lambda () + (save-excursion + (message-goto-body) + (insert ,(cdr result))))) + ((eq 'signature (car result)) + (set (make-local-variable 'message-signature) nil) + (set (make-local-variable 'message-signature-file) nil) + (if (not (cdr result)) + 'ignore + `(lambda () + (save-excursion + (let ((message-signature ,(cdr result))) + (when message-signature + (message-insert-signature))))))) + (t + (let ((header + (if (symbolp (car result)) + (capitalize (symbol-name (car result))) + (car result)))) + `(lambda () + (save-excursion + (message-remove-header ,header) + (let ((value ,(cdr result))) + (when value + (message-goto-eoh) + (insert ,header ": " value "\n")))))))))) + (when (or name address) + (add-hook 'message-setup-hook + `(lambda () + (set (make-local-variable 'user-mail-address) + ,(or (cdr address) user-mail-address)) + (let ((user-full-name ,(or (cdr name) (user-full-name))) + (user-mail-address + ,(or (cdr address) user-mail-address))) + (save-excursion + (message-remove-header "From") + (message-goto-eoh) + (insert "From: " (message-make-from) "\n"))))))))) ;;; Allow redefinition of functions. diff --git a/lisp/gnus/gnus-nocem.el b/lisp/gnus/gnus-nocem.el index 1020c729880..597228d5f6f 100644 --- a/lisp/gnus/gnus-nocem.el +++ b/lisp/gnus/gnus-nocem.el @@ -1,5 +1,6 @@ ;;; gnus-nocem.el --- NoCeM pseudo-cancellation treatment -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'nnmail) (require 'gnus-art) @@ -52,8 +51,7 @@ "clewis@ferret.ocunix.on.ca" ; Chris Lewis "jem@xpat.com" ; Despammer from Korea "snowhare@xmission.com" ; Benjamin "Snowhare" Franz - "red@redpoll.mrfs.oh.us (Richard E. Depew)" ; ARMM! ARMM! - ) + "red@redpoll.mrfs.oh.us (Richard E. Depew)") ; ARMM! ARMM! "*List of NoCeM issuers to pay attention to. This can also be a list of `(ISSUER CONDITIONS)' elements." @@ -123,7 +121,7 @@ matches an previously scanned and verified nocem message." (interactive) (let ((groups gnus-nocem-groups) (gnus-inhibit-demon t) - group active gactive articles) + group active gactive articles check-headers) (gnus-make-directory gnus-nocem-directory) ;; Load any previous NoCeM headers. (gnus-nocem-load-cache) @@ -148,7 +146,7 @@ matches an previously scanned and verified nocem message." (save-excursion (let ((dependencies (make-vector 10 nil)) headers header) - (nnheader-temp-write nil + (with-temp-buffer (setq headers (if (eq 'nov (gnus-retrieve-headers @@ -175,7 +173,14 @@ matches an previously scanned and verified nocem message." (null (mail-header-references header))) (not (member (mail-header-message-id header) gnus-nocem-seen-message-ids)))) - (gnus-nocem-check-article group header))))))) + (push header check-headers))) + (let ((i 0) + (len (length check-headers))) + (dolist (h check-headers) + (gnus-message + 7 "Checking article %d in %s for NoCeM (%d of %d)..." + (mail-header-number h) group (incf i) len) + (gnus-nocem-check-article group h))))))) (setq gnus-nocem-active (cons (list group gactive) (delq (assoc group gnus-nocem-active) @@ -187,14 +192,12 @@ matches an previously scanned and verified nocem message." (defun gnus-nocem-check-article (group header) "Check whether the current article is an NCM article and that we want it." ;; Get the article. - (gnus-message 7 "Checking article %d in %s for NoCeM..." - (mail-header-number header) group) (let ((date (mail-header-date header)) issuer b e type) (when (or (not date) - (nnmail-time-less - (nnmail-time-since (nnmail-date-to-time date)) - (nnmail-days-to-time gnus-nocem-expiry-wait))) + (time-less-p + (time-since (date-to-time date)) + (days-to-time gnus-nocem-expiry-wait))) (gnus-request-article-this-buffer (mail-header-number header) group) (goto-char (point-min)) (when (re-search-forward "-----BEGIN PGP MESSAGE-----" nil t) @@ -273,7 +276,7 @@ matches an previously scanned and verified nocem message." gnus-nocem-real-group-hashtb) ;; Valid group. (beginning-of-line) - (while (= (following-char) ?\t) + (while (eq (char-after) ?\t) (forward-line -1)) (setq id (buffer-substring (point) (1- (search-forward "\t")))) (unless (gnus-gethash id gnus-nocem-hashtb) @@ -281,7 +284,7 @@ matches an previously scanned and verified nocem message." (gnus-sethash id t gnus-nocem-hashtb) (push id ncm)) (forward-line 1) - (while (= (following-char) ?\t) + (while (eq (char-after) ?\t) (forward-line 1)))))) (when ncm (setq gnus-nocem-touched-alist t) @@ -304,13 +307,13 @@ matches an previously scanned and verified nocem message." "Save the NoCeM cache." (when (and gnus-nocem-alist gnus-nocem-touched-alist) - (nnheader-temp-write (gnus-nocem-cache-file) + (with-temp-file (gnus-nocem-cache-file) (gnus-prin1 `(setq gnus-nocem-alist ',gnus-nocem-alist))) (setq gnus-nocem-touched-alist nil))) (defun gnus-nocem-save-active () "Save the NoCeM active file." - (nnheader-temp-write (gnus-nocem-active-file) + (with-temp-file (gnus-nocem-active-file) (gnus-prin1 `(setq gnus-nocem-active ',gnus-nocem-active)))) (defun gnus-nocem-alist-to-hashtb () @@ -318,11 +321,11 @@ matches an previously scanned and verified nocem message." (let* ((alist gnus-nocem-alist) (pprev (cons nil alist)) (prev pprev) - (expiry (nnmail-days-to-time gnus-nocem-expiry-wait)) + (expiry (days-to-time gnus-nocem-expiry-wait)) entry) (setq gnus-nocem-hashtb (gnus-make-hashtable (* (length alist) 51))) (while (setq entry (car alist)) - (if (not (nnmail-time-less (nnmail-time-since (car entry)) expiry)) + (if (not (time-less-p (time-since (car entry)) expiry)) ;; This entry has expired, so we remove it. (setcdr prev (cdr alist)) (setq prev alist) diff --git a/lisp/gnus/gnus-range.el b/lisp/gnus/gnus-range.el index 71684707de3..223a32e33b3 100644 --- a/lisp/gnus/gnus-range.el +++ b/lisp/gnus/gnus-range.el @@ -1,5 +1,6 @@ ;;; gnus-range.el --- range and sequence functions for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - ;;; List and range functions (defun gnus-last-element (list) @@ -226,13 +225,81 @@ Note: LIST has to be sorted over `<'." (setq ranges (cdr ranges))) out))) -(defun gnus-remove-from-range (ranges list) - "Return a list of ranges that has all articles from LIST removed from RANGES. -Note: LIST has to be sorted over `<'." - ;; !!! This function shouldn't look like this, but I've got a headache. - (gnus-compress-sequence - (gnus-sorted-complement - (gnus-uncompress-range ranges) list))) +(defun gnus-remove-from-range (range1 range2) + "Return a range that has all articles from RANGE2 removed from RANGE1. +The returned range is always a list. RANGE2 can also be a unsorted +list of articles. RANGE1 is modified by side effects, RANGE2 is not +modified." + (if (or (null range1) (null range2)) + range1 + (let (out r1 r2 r1_min r1_max r2_min r2_max + (range2 (gnus-copy-sequence range2))) + (setq range1 (if (listp (cdr range1)) range1 (list range1)) + range2 (sort (if (listp (cdr range2)) range2 (list range2)) + (lambda (e1 e2) + (< (if (consp e1) (car e1) e1) + (if (consp e2) (car e2) e2)))) + r1 (car range1) + r2 (car range2) + r1_min (if (consp r1) (car r1) r1) + r1_max (if (consp r1) (cdr r1) r1) + r2_min (if (consp r2) (car r2) r2) + r2_max (if (consp r2) (cdr r2) r2)) + (while (and range1 range2) + (cond ((< r2_max r1_min) ; r2 < r1 + (pop range2) + (setq r2 (car range2) + r2_min (if (consp r2) (car r2) r2) + r2_max (if (consp r2) (cdr r2) r2))) + ((and (<= r2_min r1_min) (<= r1_max r2_max)) ; r2 overlap r1 + (pop range1) + (setq r1 (car range1) + r1_min (if (consp r1) (car r1) r1) + r1_max (if (consp r1) (cdr r1) r1))) + ((and (<= r2_min r1_min) (<= r2_max r1_max)) ; r2 overlap min r1 + (pop range2) + (setq r1_min (1+ r2_max) + r2 (car range2) + r2_min (if (consp r2) (car r2) r2) + r2_max (if (consp r2) (cdr r2) r2))) + ((and (<= r1_min r2_min) (<= r2_max r1_max)) ; r2 contained in r1 + (if (eq r1_min (1- r2_min)) + (push r1_min out) + (push (cons r1_min (1- r2_min)) out)) + (pop range2) + (if (< r2_max r1_max) ; finished with r1? + (setq r1_min (1+ r2_max)) + (pop range1) + (setq r1 (car range1) + r1_min (if (consp r1) (car r1) r1) + r1_max (if (consp r1) (cdr r1) r1))) + (setq r2 (car range2) + r2_min (if (consp r2) (car r2) r2) + r2_max (if (consp r2) (cdr r2) r2))) + ((and (<= r2_min r1_max) (<= r1_max r2_max)) ; r2 overlap max r1 + (if (eq r1_min (1- r2_min)) + (push r1_min out) + (push (cons r1_min (1- r2_min)) out)) + (pop range1) + (setq r1 (car range1) + r1_min (if (consp r1) (car r1) r1) + r1_max (if (consp r1) (cdr r1) r1))) + ((< r1_max r2_min) ; r2 > r1 + (pop range1) + (if (eq r1_min r1_max) + (push r1_min out) + (push (cons r1_min r1_max) out)) + (setq r1 (car range1) + r1_min (if (consp r1) (car r1) r1) + r1_max (if (consp r1) (cdr r1) r1))))) + (when r1 + (if (eq r1_min r1_max) + (push r1_min out) + (push (cons r1_min r1_max) out)) + (pop range1)) + (while range1 + (push (pop range1) out)) + (nreverse out)))) (defun gnus-member-of-range (number ranges) (if (not (listp (cdr ranges))) @@ -266,19 +333,59 @@ Note: LIST has to be sorted over `<'." sublistp)) (defun gnus-range-add (range1 range2) - "Add RANGE2 to RANGE1 destructively." - (cond - ;; If either are nil, then the job is quite easy. - ((or (null range1) (null range2)) - (or range1 range2)) - (t - ;; I don't like thinking. - (gnus-compress-sequence - (sort - (nconc - (gnus-uncompress-range range1) - (gnus-uncompress-range range2)) - '<))))) + "Add RANGE2 to RANGE1 (nondestructively)." + (unless (listp (cdr range1)) + (setq range1 (list range1))) + (unless (listp (cdr range2)) + (setq range2 (list range2))) + (let ((item1 (pop range1)) + (item2 (pop range2)) + range item selector) + (while (or item1 item2) + (setq selector + (cond + ((null item1) nil) + ((null item2) t) + ((and (numberp item1) (numberp item2)) (< item1 item2)) + ((numberp item1) (< item1 (car item2))) + ((numberp item2) (< (car item1) item2)) + (t (< (car item1) (car item2))))) + (setq item + (or + (let ((tmp1 item) (tmp2 (if selector item1 item2))) + (cond + ((null tmp1) tmp2) + ((null tmp2) tmp1) + ((and (numberp tmp1) (numberp tmp2)) + (cond + ((eq tmp1 tmp2) tmp1) + ((eq (1+ tmp1) tmp2) (cons tmp1 tmp2)) + ((eq (1+ tmp2) tmp1) (cons tmp2 tmp1)) + (t nil))) + ((numberp tmp1) + (cond + ((and (>= tmp1 (car tmp2)) (<= tmp1 (cdr tmp2))) tmp2) + ((eq (1+ tmp1) (car tmp2)) (cons tmp1 (cdr tmp2))) + ((eq (1- tmp1) (cdr tmp2)) (cons (car tmp2) tmp1)) + (t nil))) + ((numberp tmp2) + (cond + ((and (>= tmp2 (car tmp1)) (<= tmp2 (cdr tmp1))) tmp1) + ((eq (1+ tmp2) (car tmp1)) (cons tmp2 (cdr tmp1))) + ((eq (1- tmp2) (cdr tmp1)) (cons (car tmp1) tmp2)) + (t nil))) + ((< (1+ (cdr tmp1)) (car tmp2)) nil) + ((< (1+ (cdr tmp2)) (car tmp1)) nil) + (t (cons (min (car tmp1) (car tmp2)) + (max (cdr tmp1) (cdr tmp2)))))) + (progn + (if item (push item range)) + (if selector item1 item2)))) + (if selector + (setq item1 (pop range1)) + (setq item2 (pop range2)))) + (if item (push item range)) + (reverse range))) (provide 'gnus-range) diff --git a/lisp/gnus/gnus-salt.el b/lisp/gnus/gnus-salt.el index 73d949fc22f..1cdd97be9b7 100644 --- a/lisp/gnus/gnus-salt.el +++ b/lisp/gnus/gnus-salt.el @@ -1,5 +1,6 @@ ;;; gnus-salt.el --- alternate summary mode interfaces for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1996, 1997, 1998, 1999 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-sum) @@ -55,7 +54,7 @@ :group 'gnus-summary-pick) (defcustom gnus-pick-elegant-flow t - "If non-nil, gnus-pick-start-reading will run gnus-summary-next-group when no articles have been picked." + "If non-nil, `gnus-pick-start-reading' runs `gnus-summary-next-group' when no articles have been picked." :type 'boolean :group 'gnus-summary-pick) @@ -78,8 +77,7 @@ It accepts the same format specs that `gnus-summary-line-format' does." "u" gnus-pick-unmark-article-or-thread "." gnus-pick-article-or-thread gnus-down-mouse-2 gnus-pick-mouse-pick-region - "\r" gnus-pick-start-reading - )) + "\r" gnus-pick-start-reading)) (defun gnus-pick-make-menu-bar () (unless (boundp 'gnus-pick-menu) @@ -123,7 +121,8 @@ It accepts the same format specs that `gnus-summary-line-format' does." ;; Set up the menu. (when (gnus-visual-p 'pick-menu 'menu) (gnus-pick-make-menu-bar)) - (gnus-add-minor-mode 'gnus-pick-mode " Pick" gnus-pick-mode-map) + (gnus-add-minor-mode 'gnus-pick-mode " Pick" gnus-pick-mode-map + nil 'gnus-pick-mode) (gnus-run-hooks 'gnus-pick-mode-hook)))) (defun gnus-pick-setup-message () @@ -133,7 +132,8 @@ It accepts the same format specs that `gnus-summary-line-format' does." (set-buffer gnus-summary-buffer) gnus-pick-mode)) (message-add-action - '(gnus-configure-windows 'pick t) 'send 'exit 'postpone 'kill))) + '(gnus-configure-windows ,gnus-current-window-configuration t) + 'send 'exit 'postpone 'kill))) (defvar gnus-pick-line-number 1) (defun gnus-pick-line-number () @@ -164,8 +164,8 @@ If given a prefix, mark all unpicked articles as read." (error "No articles have been picked")))) (defun gnus-pick-goto-article (arg) - "Go to the article number indicated by ARG. If ARG is an invalid -article number, then stay on current line." + "Go to the article number indicated by ARG. +If ARG is an invalid article number, then stay on current line." (let (pos) (save-excursion (goto-char (point-min)) @@ -174,9 +174,9 @@ article number, then stay on current line." (if (not pos) (gnus-error 2 "No such line: %s" arg) (goto-char pos)))) - + (defun gnus-pick-article (&optional arg) - "Pick the article on the current line. + "Pick the article on the current line. If ARG, pick the article on that line instead." (interactive "P") (when arg @@ -184,27 +184,31 @@ If ARG, pick the article on that line instead." (gnus-summary-mark-as-processable 1)) (defun gnus-pick-article-or-thread (&optional arg) - "If gnus-thread-hide-subtree is t, then pick the thread on the current line. + "If `gnus-thread-hide-subtree' is t, then pick the thread on the current line. Otherwise pick the article on the current line. If ARG, pick the article/thread on that line instead." (interactive "P") (when arg (gnus-pick-goto-article arg)) (if gnus-thread-hide-subtree - (gnus-uu-mark-thread) + (progn + (save-excursion + (gnus-uu-mark-thread)) + (forward-line 1)) (gnus-summary-mark-as-processable 1))) - + (defun gnus-pick-unmark-article-or-thread (&optional arg) - "If gnus-thread-hide-subtree is t, then unmark the thread on current line. + "If `gnus-thread-hide-subtree' is t, then unmark the thread on current line. Otherwise unmark the article on current line. If ARG, unmark thread/article on that line instead." (interactive "P") (when arg (gnus-pick-goto-article arg)) (if gnus-thread-hide-subtree - (gnus-uu-unmark-thread) + (save-excursion + (gnus-uu-unmark-thread)) (gnus-summary-unmark-as-processable 1))) - + (defun gnus-pick-mouse-pick (e) (interactive "e") (mouse-set-point e) @@ -242,46 +246,46 @@ This must be bound to a button-down mouse event." ;; (but not outside the window where the drag started). (let (event end end-point (end-of-range (point))) (track-mouse - (while (progn - (setq event (cdr (gnus-read-event-char))) - (or (mouse-movement-p event) - (eq (car-safe event) 'switch-frame))) - (if (eq (car-safe event) 'switch-frame) - nil - (setq end (event-end event) - end-point (posn-point end)) + (while (progn + (setq event (cdr (gnus-read-event-char))) + (or (mouse-movement-p event) + (eq (car-safe event) 'switch-frame))) + (if (eq (car-safe event) 'switch-frame) + nil + (setq end (event-end event) + end-point (posn-point end)) - (cond - ;; Are we moving within the original window? - ((and (eq (posn-window end) start-window) - (integer-or-marker-p end-point)) - ;; Go to START-POINT first, so that when we move to END-POINT, - ;; if it's in the middle of intangible text, - ;; point jumps in the direction away from START-POINT. - (goto-char start-point) - (goto-char end-point) - (gnus-pick-article) - ;; In case the user moved his mouse really fast, pick - ;; articles on the line between this one and the last one. - (let* ((this-line (1+ (count-lines 1 end-point))) - (min-line (min this-line start-line)) - (max-line (max this-line start-line))) - (while (< min-line max-line) - (goto-line min-line) - (gnus-pick-article) - (setq min-line (1+ min-line))) - (setq start-line this-line)) - (when (zerop (% click-count 3)) - (setq end-of-range (point)))) - (t - (let ((mouse-row (cdr (cdr (mouse-position))))) - (cond - ((null mouse-row)) - ((< mouse-row top) - (mouse-scroll-subr start-window (- mouse-row top))) - ((>= mouse-row bottom) - (mouse-scroll-subr start-window - (1+ (- mouse-row bottom))))))))))) + (cond + ;; Are we moving within the original window? + ((and (eq (posn-window end) start-window) + (integer-or-marker-p end-point)) + ;; Go to START-POINT first, so that when we move to END-POINT, + ;; if it's in the middle of intangible text, + ;; point jumps in the direction away from START-POINT. + (goto-char start-point) + (goto-char end-point) + (gnus-pick-article) + ;; In case the user moved his mouse really fast, pick + ;; articles on the line between this one and the last one. + (let* ((this-line (1+ (count-lines 1 end-point))) + (min-line (min this-line start-line)) + (max-line (max this-line start-line))) + (while (< min-line max-line) + (goto-line min-line) + (gnus-pick-article) + (setq min-line (1+ min-line))) + (setq start-line this-line)) + (when (zerop (% click-count 3)) + (setq end-of-range (point)))) + (t + (let ((mouse-row (cdr (cdr (mouse-position))))) + (cond + ((null mouse-row)) + ((< mouse-row top) + (mouse-scroll-subr start-window (- mouse-row top))) + ((>= mouse-row bottom) + (mouse-scroll-subr start-window + (1+ (- mouse-row bottom))))))))))) (when (consp event) (let ((fun (key-binding (vector (car event))))) ;; Run the binding of the terminating up-event, if possible. @@ -323,8 +327,8 @@ This must be bound to a button-down mouse event." (setq gnus-binary-mode-map (make-sparse-keymap)) (gnus-define-keys - gnus-binary-mode-map - "g" gnus-binary-show-article)) + gnus-binary-mode-map + "g" gnus-binary-show-article)) (defun gnus-binary-make-menu-bar () (unless (boundp 'gnus-binary-menu) @@ -350,7 +354,8 @@ This must be bound to a button-down mouse event." ;; Set up the menu. (when (gnus-visual-p 'binary-menu 'menu) (gnus-binary-make-menu-bar)) - (gnus-add-minor-mode 'gnus-binary-mode " Binary" gnus-binary-mode-map) + (gnus-add-minor-mode 'gnus-binary-mode " Binary" + gnus-binary-mode-map nil 'gnus-binary-mode-map) (gnus-run-hooks 'gnus-binary-mode-hook)))) (defun gnus-binary-display-article (article &optional all-header) @@ -432,6 +437,7 @@ Two predefined functions are available: (defvar gnus-selected-tree-overlay nil) (defvar gnus-tree-displayed-thread nil) +(defvar gnus-tree-inhibit nil) (defvar gnus-tree-mode-map nil) (put 'gnus-tree-mode 'mode-class 'special) @@ -440,13 +446,13 @@ Two predefined functions are available: (setq gnus-tree-mode-map (make-keymap)) (suppress-keymap gnus-tree-mode-map) (gnus-define-keys - gnus-tree-mode-map - "\r" gnus-tree-select-article - gnus-mouse-2 gnus-tree-pick-article - "\C-?" gnus-tree-read-summary-keys - "h" gnus-tree-show-summary + gnus-tree-mode-map + "\r" gnus-tree-select-article + gnus-mouse-2 gnus-tree-pick-article + "\C-?" gnus-tree-read-summary-keys + "h" gnus-tree-show-summary - "\C-c\C-i" gnus-info-find-node) + "\C-c\C-i" gnus-info-find-node) (substitute-key-definition 'undefined 'gnus-tree-read-summary-keys gnus-tree-mode-map)) @@ -470,7 +476,7 @@ Two predefined functions are available: (setq mode-name "Tree") (setq major-mode 'gnus-tree-mode) (use-local-map gnus-tree-mode-map) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq buffer-read-only t) (setq truncate-lines t) (save-excursion @@ -482,15 +488,17 @@ Two predefined functions are available: (defun gnus-tree-read-summary-keys (&optional arg) "Read a summary buffer key sequence and execute it." (interactive "P") - (let ((buf (current-buffer)) - win) - (set-buffer gnus-article-buffer) - (gnus-article-read-summary-keys arg nil t) - (when (setq win (get-buffer-window buf)) - (select-window win) - (when gnus-selected-tree-overlay - (goto-char (or (gnus-overlay-end gnus-selected-tree-overlay) 1))) - (gnus-tree-minimize)))) + (unless gnus-tree-inhibit + (let ((buf (current-buffer)) + (gnus-tree-inhibit t) + win) + (set-buffer gnus-article-buffer) + (gnus-article-read-summary-keys arg nil t) + (when (setq win (get-buffer-window buf)) + (select-window win) + (when gnus-selected-tree-overlay + (goto-char (or (gnus-overlay-end gnus-selected-tree-overlay) 1))) + (gnus-tree-minimize))))) (defun gnus-tree-show-summary () "Reconfigure windows to show summary buffer." @@ -521,12 +529,14 @@ Two predefined functions are available: (defun gnus-tree-article-region (article) "Return a cons with BEG and END of the article region." - (let ((pos (text-property-any (point-min) (point-max) 'gnus-number article))) + (let ((pos (text-property-any + (point-min) (point-max) 'gnus-number article))) (when pos (cons pos (next-single-property-change pos 'gnus-number))))) (defun gnus-tree-goto-article (article) - (let ((pos (text-property-any (point-min) (point-max) 'gnus-number article))) + (let ((pos (text-property-any + (point-min) (point-max) 'gnus-number article))) (when pos (goto-char pos)))) @@ -704,7 +714,7 @@ Two predefined functions are available: (while (progn (forward-line -1) (forward-char col) - (= (following-char) ? )) + (eq (char-after) ? )) (delete-char 1) (insert (caddr gnus-tree-parent-child-edges))) (goto-char beg))) @@ -762,7 +772,7 @@ Two predefined functions are available: (forward-char -1) ;; Draw "-" lines leftwards. (while (and (> (point) 1) - (= (char-after (1- (point))) ? )) + (eq (char-after (1- (point))) ? )) (delete-char -1) (insert (car gnus-tree-parent-child-edges)) (forward-char -1)) @@ -969,7 +979,7 @@ The following commands are available: (setq mode-name "Gnus Carpal") (setq mode-line-process nil) (use-local-map gnus-carpal-mode-map) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq buffer-read-only t) (make-local-variable 'gnus-carpal-attached-buffer) (gnus-run-hooks 'gnus-carpal-mode-hook)) diff --git a/lisp/gnus/gnus-score.el b/lisp/gnus/gnus-score.el index 31b3017d833..57c001e39d7 100644 --- a/lisp/gnus/gnus-score.el +++ b/lisp/gnus/gnus-score.el @@ -1,5 +1,6 @@ ;;; gnus-score.el --- scoring code for Gnus -;; Copyright (C) 1995, 1996, 1997 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Per Abrahamsen ;; Lars Magne Ingebrigtsen @@ -28,8 +29,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-sum) (require 'gnus-range) @@ -109,8 +108,8 @@ gnus-score-find-bnews: Apply score files whose names matches. See the documentation to these functions for more information. This variable can also be a list of functions to be called. Each -function should either return a list of score files, or a list of -score alists. +function is given the group name as argument and should either return +a list of score files, or a list of score alists. If functions other than these pre-defined functions are used, the `a' symbolic prefix to the score commands will always use @@ -119,13 +118,17 @@ the `a' symbolic prefix to the score commands will always use :type '(radio (function-item gnus-score-find-single) (function-item gnus-score-find-hierarchical) (function-item gnus-score-find-bnews) - (function :tag "Other"))) + (repeat :tag "List of functions" + (choice (function :tag "Other" :value 'ignore) + (function-item gnus-score-find-single) + (function-item gnus-score-find-hierarchical) + (function-item gnus-score-find-bnews))) + (function :tag "Other" :value 'ignore))) (defcustom gnus-score-interactive-default-score 1000 "*Scoring commands will raise/lower the score with this number as the default." :group 'gnus-score-default - :type '(choice (const nil) - integer)) + :type 'integer) (defcustom gnus-score-expiry-days 7 "*Number of days before unused score file entries are expired. @@ -141,12 +144,6 @@ will be expired along with non-matching score entries." :group 'gnus-score-expire :type 'boolean) -(defcustom gnus-orphan-score nil - "*All orphans get this score added. Set in the score file." - :group 'gnus-score-default - :type '(choice (const nil) - integer)) - (defcustom gnus-decay-scores nil "*If non-nil, decay non-permanent scores." :group 'gnus-score-decay @@ -204,6 +201,8 @@ It can be: (repeat (choice string (cons regexp (repeat file)) (function :value fun))) + (function-item gnus-hierarchial-home-score-file) + (function-item gnus-current-home-score-file) (function :value fun))) (defcustom gnus-home-adapt-file nil @@ -224,14 +223,14 @@ This variable allows the same syntax as `gnus-home-score-file'." (gnus-catchup-mark (subject -10)) (gnus-killed-mark (from -1) (subject -20)) (gnus-del-mark (from -2) (subject -15))) -"*Alist of marks and scores." -:group 'gnus-score-adapt -:type '(repeat (cons (symbol :tag "Mark") - (repeat (list (choice :tag "Header" - (const from) - (const subject) - (symbol :tag "other")) - (integer :tag "Score")))))) + "*Alist of marks and scores." + :group 'gnus-score-adapt + :type '(repeat (cons (symbol :tag "Mark") + (repeat (list (choice :tag "Header" + (const from) + (const subject) + (symbol :tag "other")) + (integer :tag "Score")))))) (defcustom gnus-ignored-adaptive-words nil "List of words to be ignored when doing adaptive word scoring." @@ -262,10 +261,10 @@ This variable allows the same syntax as `gnus-home-score-file'." (,gnus-catchup-mark . -10) (,gnus-killed-mark . -20) (,gnus-del-mark . -15)) -"*Alist of marks and scores." -:group 'gnus-score-adapt -:type '(repeat (cons (character :tag "Mark") - (integer :tag "Score")))) + "*Alist of marks and scores." + :group 'gnus-score-adapt + :type '(repeat (cons (character :tag "Mark") + (integer :tag "Score")))) (defcustom gnus-adaptive-word-minimum nil "If a number, this is the minimum score value that can be assigned to a word." @@ -311,6 +310,7 @@ Should be one of the following symbols. i: message-id t: references x: xref + e: `extra' (non-standard overview) l: lines d: date f: followup @@ -324,6 +324,7 @@ If nil, the user will be asked for a header." (const :tag "message-id" i) (const :tag "references" t) (const :tag "xref" x) + (const :tag "extra" e) (const :tag "lines" l) (const :tag "date" d) (const :tag "followup" f) @@ -388,7 +389,7 @@ If nil, the user will be asked for a duration." (defcustom gnus-score-thread-simplify nil "If non-nil, subjects will simplified as in threading." :group 'gnus-score-various - :type 'boolean) + :type 'boolean) @@ -447,6 +448,7 @@ of the last successful match.") ("chars" 6 gnus-score-integer) ("lines" 7 gnus-score-integer) ("xref" 8 gnus-score-string) + ("extra" 9 gnus-score-string) ("head" -1 gnus-score-body) ("body" -1 gnus-score-body) ("all" -1 gnus-score-body) @@ -480,7 +482,7 @@ The user will be prompted for header to score on, match type, permanence, and the string to be used. The numerical prefix will be used as score." (interactive (gnus-interactive "P\ny")) - (gnus-summary-increase-score (- (gnus-score-default score)) symp)) + (gnus-summary-increase-score (- (gnus-score-delta-default score)) symp)) (defun gnus-score-kill-help-buffer () (when (get-buffer "*Score Help*") @@ -494,7 +496,7 @@ The user will be prompted for header to score on, match type, permanence, and the string to be used. The numerical prefix will be used as score." (interactive (gnus-interactive "P\ny")) - (let* ((nscore (gnus-score-default score)) + (let* ((nscore (gnus-score-delta-default score)) (prefix (if (< nscore 0) ?L ?I)) (increase (> nscore 0)) (char-to-header @@ -502,9 +504,10 @@ used as score." (?s "subject" nil nil string) (?b "body" "" nil body-string) (?h "head" "" nil body-string) - (?i "message-id" nil t string) + (?i "message-id" nil nil string) (?r "references" "message-id" nil string) (?x "xref" nil nil string) + (?e "extra" nil nil string) (?l "lines" nil nil number) (?d "date" nil nil date) (?f "followup" nil nil string) @@ -533,7 +536,7 @@ used as score." (aref (symbol-name gnus-score-default-type) 0))) (pchar (and gnus-score-default-duration (aref (symbol-name gnus-score-default-duration) 0))) - entry temporary type match) + entry temporary type match extra) (unwind-protect (progn @@ -555,7 +558,7 @@ used as score." (gnus-score-kill-help-buffer) (unless (setq entry (assq (downcase hchar) char-to-header)) (if mimic (error "%c %c" prefix hchar) - (error "Illegal header type"))) + (error "Invalid header type"))) (when (/= (downcase hchar) hchar) ;; This was a majuscule, so we end reading and set the defaults. @@ -588,7 +591,7 @@ used as score." (gnus-score-kill-help-buffer) (unless (setq type (nth 1 (assq (downcase tchar) legal-types))) (if mimic (error "%c %c" prefix hchar) - (error "Illegal match type")))) + (error "Invalid match type")))) (when (/= (downcase tchar) tchar) ;; It was a majuscule, so we end reading and use the default. @@ -616,18 +619,35 @@ used as score." ;; Deal with der(r)ided superannuated paradigms. (when (and (eq (1+ prefix) 77) (eq (+ hchar 12) 109) - (eq tchar 114) + (eq (1- tchar) 113) (eq (- pchar 4) 111)) (error "You rang?")) (if mimic (error "%c %c %c %c" prefix hchar tchar pchar) - (error "Illegal match duration")))) + (error "Invalid match duration")))) ;; Always kill the score help buffer. (gnus-score-kill-help-buffer)) + ;; If scoring an extra (non-standard overview) header, + ;; we must find out which header is in question. + (setq extra + (and gnus-extra-headers + (equal (nth 1 entry) "extra") + (intern ; need symbol + (gnus-completing-read + (symbol-name (car gnus-extra-headers)) ; default response + "Score extra header:" ; prompt + (mapcar (lambda (x) ; completion list + (cons (symbol-name x) x)) + gnus-extra-headers) + nil ; no completion limit + t)))) ; require match + ;; extra is now nil or a symbol. + ;; We have all the data, so we enter this score. (setq match (if (string= (nth 2 entry) "") "" - (gnus-summary-header (or (nth 2 entry) (nth 1 entry))))) + (gnus-summary-header (or (nth 2 entry) (nth 1 entry)) + nil extra))) ;; Modify the match, perhaps. (cond @@ -654,7 +674,7 @@ used as score." current-score-file) (t (gnus-score-file-name "all")))))) - + (gnus-summary-score-entry (nth 1 entry) ; Header match ; Match @@ -663,7 +683,9 @@ used as score." (if (eq temporary 'perm) ; Temp nil temporary) - (not (nth 3 entry))) ; Prompt + (not (nth 3 entry)) ; Prompt + nil ; not silent + extra) ; non-standard overview. (when (eq symp 'a) ;; We change the score file back to the previous one. @@ -675,7 +697,7 @@ used as score." (setq gnus-score-help-winconf (current-window-configuration)) (save-excursion (set-buffer (gnus-get-buffer-create "*Score Help*")) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (delete-windows-on (current-buffer)) (erase-buffer) (insert string ":\n\n") @@ -710,16 +732,18 @@ used as score." (pop-to-buffer "*Score Help*") (let ((window-min-height 1)) (shrink-window-if-larger-than-buffer)) - (select-window (get-buffer-window gnus-summary-buffer)))) + (select-window (get-buffer-window gnus-summary-buffer t)))) -(defun gnus-summary-header (header &optional no-err) +(defun gnus-summary-header (header &optional no-err extra) ;; Return HEADER for current articles, or error. (let ((article (gnus-summary-article-number)) headers) (if article (if (and (setq headers (gnus-summary-article-header article)) (vectorp headers)) - (aref headers (nth 1 (assoc header gnus-header-index))) + (if extra ; `header' must be "extra" + (or (cdr (assq extra (mail-header-extra headers))) "") + (aref headers (nth 1 (assoc header gnus-header-index)))) (if no-err nil (error "Pseudo-articles can't be scored"))) @@ -745,7 +769,7 @@ used as score." (gnus-newsgroup-score-alist))))) (defun gnus-summary-score-entry (header match type score date - &optional prompt silent) + &optional prompt silent extra) "Enter score file entry. HEADER is the header being scored. MATCH is the string we are looking for. @@ -753,7 +777,8 @@ TYPE is the match type: substring, regexp, exact, fuzzy. SCORE is the score to add. DATE is the expire date, or nil for no expire, or 'now for immediate expire. If optional argument `PROMPT' is non-nil, allow user to edit match. -If optional argument `SILENT' is nil, show effect of score entry." +If optional argument `SILENT' is nil, show effect of score entry. +If optional argument `EXTRA' is non-nil, it's a non-standard overview header." ;; Regexp is the default type. (when (eq type t) (setq type 'r)) @@ -762,9 +787,10 @@ If optional argument `SILENT' is nil, show effect of score entry." (setq match (if match (gnus-simplify-subject-re match) ""))) ((eq type 'f) (setq match (gnus-simplify-subject-fuzzy match)))) - (let ((score (gnus-score-default score)) - (header (format "%s" (downcase header))) + (let ((score (gnus-score-delta-default score)) + (header (downcase header)) new) + (set-text-properties 0 (length header) nil header) (when prompt (setq match (read-string (format "Match %s on %s, %s: " @@ -779,8 +805,7 @@ If optional argument `SILENT' is nil, show effect of score entry." (int-to-string match) match)))) - ;; Get rid of string props. - (setq match (format "%s" match)) + (set-text-properties 0 (length match) nil match) ;; If this is an integer comparison, we transform from string to int. (when (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-integer) @@ -794,12 +819,17 @@ If optional argument `SILENT' is nil, show effect of score entry." elem) (setq new (cond + (extra + (list match score + (and date (if (numberp date) date + (date-to-day date))) + type (symbol-name extra))) (type (list match score (and date (if (numberp date) date - (gnus-day-number date))) + (date-to-day date))) type)) - (date (list match score (gnus-day-number date))) + (date (list match score (date-to-day date))) (score (list match score)) (t (list match)))) ;; We see whether we can collapse some score entries. @@ -824,18 +854,19 @@ If optional argument `SILENT' is nil, show effect of score entry." (if (and (>= (nth 1 (assoc header gnus-header-index)) 0) (eq (nth 2 (assoc header gnus-header-index)) 'gnus-score-string)) - (gnus-summary-score-effect header match type score) + (gnus-summary-score-effect header match type score extra) (gnus-summary-rescore))) ;; Return the new scoring rule. new)) -(defun gnus-summary-score-effect (header match type score) +(defun gnus-summary-score-effect (header match type score extra) "Simulate the effect of a score file entry. HEADER is the header being scored. MATCH is the string we are looking for. TYPE is the score type. -SCORE is the score to add." +SCORE is the score to add. +EXTRA is the possible non-standard header." (interactive (list (completing-read "Header: " gnus-header-index (lambda (x) (fboundp (nth 2 x))) @@ -856,7 +887,7 @@ SCORE is the score to add." (t (regexp-quote match))))) (while (not (eobp)) - (let ((content (gnus-summary-header header 'noerr)) + (let ((content (gnus-summary-header header 'noerr extra)) (case-fold-search t)) (and content (when (if (eq type 'f) @@ -939,7 +970,7 @@ SCORE is the score to add." (defun gnus-score-followup-article (&optional score) "Add SCORE to all followups to the article in the current buffer." (interactive "P") - (setq score (gnus-score-default score)) + (setq score (gnus-score-delta-default score)) (when (gnus-buffer-live-p gnus-summary-buffer) (save-excursion (save-restriction @@ -954,7 +985,7 @@ SCORE is the score to add." (defun gnus-score-followup-thread (&optional score) "Add SCORE to all later articles in the thread the current buffer is part of." (interactive "P") - (setq score (gnus-score-default score)) + (setq score (gnus-score-delta-default score)) (when (gnus-buffer-live-p gnus-summary-buffer) (save-excursion (save-restriction @@ -999,7 +1030,7 @@ SCORE is the score to add." (let ((buffer-read-only nil)) ;; Set score. (gnus-summary-update-mark - (if (= n (or gnus-summary-default-score 0)) ? + (if (= n (or gnus-summary-default-score 0)) ? ;Whitespace (if (< n (or gnus-summary-default-score 0)) gnus-score-below-mark gnus-score-over-mark)) 'score)) @@ -1124,7 +1155,7 @@ SCORE is the score to add." (or (not decay) (gnus-decay-scores alist decay))) (gnus-score-set 'touched '(t) alist) - (gnus-score-set 'decay (list (gnus-time-to-day (current-time))) alist)) + (gnus-score-set 'decay (list (time-to-days (current-time))) alist)) ;; We do not respect eval and files atoms from global score ;; files. (when (and files (not global)) @@ -1205,9 +1236,9 @@ SCORE is the score to add." ;; Couldn't read file. (setq gnus-score-alist nil) ;; Read file. - (save-excursion - (gnus-set-work-buffer) - (insert-file-contents file) + (with-temp-buffer + (let ((coding-system-for-read score-mode-coding-system)) + (insert-file-contents file)) (goto-char (point-min)) ;; Only do the loading if the score file isn't empty. (when (save-excursion (re-search-forward "[()0-9a-zA-Z]" nil t)) @@ -1247,11 +1278,11 @@ SCORE is the score to add." err (cond ((not (listp (car a))) - (format "Illegal score element %s in %s" (car a) file)) + (format "Invalid score element %s in %s" (car a) file)) ((stringp (caar a)) (cond ((not (listp (setq sr (cdar a)))) - (format "Illegal header match %s in %s" (nth 1 (car a)) file)) + (format "Invalid header match %s in %s" (nth 1 (car a)) file)) (t (setq type (caar a)) (while (and sr (not err)) @@ -1262,7 +1293,7 @@ SCORE is the score to add." ((if (member (downcase type) '("lines" "chars")) (not (numberp (car s))) (not (stringp (car s)))) - (format "Illegal match %s in %s" (car s) file)) + (format "Invalid match %s in %s" (car s) file)) ((and (cadr s) (not (integerp (cadr s)))) (format "Non-integer score %s in %s" (cadr s) file)) ((and (caddr s) (not (integerp (caddr s)))) @@ -1293,7 +1324,7 @@ SCORE is the score to add." (setcar scor (list (caar scor) (nth 2 (car scor)) (and (nth 3 (car scor)) - (gnus-day-number (nth 3 (car scor)))) + (date-to-day (nth 3 (car scor)))) (if (nth 1 (car scor)) 'r 's))) (setq scor (cdr scor)))) (push (if (not (listp (cdr entry))) @@ -1313,7 +1344,7 @@ SCORE is the score to add." (while cache (current-buffer) (setq entry (pop cache) - file (car entry) + file (nnheader-translate-file-chars (car entry) t) score (cdr entry)) (if (or (not (equal (gnus-score-get 'touched score) '(t))) (gnus-score-get 'read-only score) @@ -1340,7 +1371,8 @@ SCORE is the score to add." (delete-file file) ;; There are scores, so we write the file. (when (file-writable-p file) - (gnus-write-buffer file) + (let ((coding-system-for-write score-mode-coding-system)) + (gnus-write-buffer file)) (when gnus-score-after-write-file-function (funcall gnus-score-after-write-file-function file))))) (and gnus-score-uncacheable-files @@ -1388,7 +1420,7 @@ SCORE is the score to add." (when (and gnus-summary-default-score scores) (let* ((entries gnus-header-index) - (now (gnus-day-number (current-time-string))) + (now (date-to-day (current-time-string))) (expire (and gnus-score-expiry-days (- now gnus-score-expiry-days))) (headers gnus-newsgroup-headers) @@ -1407,7 +1439,7 @@ SCORE is the score to add." (save-excursion (set-buffer (gnus-get-buffer-create "*Headers*")) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (when (gnus-buffer-live-p gnus-summary-buffer) (message-clone-locals gnus-summary-buffer)) @@ -1431,6 +1463,10 @@ SCORE is the score to add." (when (setq new (funcall (nth 2 entry) scores header now expire trace)) (push new news)))) + (when (gnus-buffer-live-p gnus-summary-buffer) + (let ((scored gnus-newsgroup-scored)) + (with-current-buffer gnus-summary-buffer + (setq gnus-newsgroup-scored scored)))) ;; Remove the buffer. (kill-buffer (current-buffer))) @@ -1447,85 +1483,56 @@ SCORE is the score to add." (let (score) (while (setq score (pop scores)) (while score - (when (listp (caar score)) + (when (consp (caar score)) (gnus-score-advanced (car score) trace)) (pop score)))) (gnus-message 5 "Scoring...done")))))) +(defun gnus-score-lower-thread (thread score-adjust) + "Lower the socre on THREAD with SCORE-ADJUST. +THREAD is expected to contain a list of the form `(PARENT [CHILD1 +CHILD2 ...])' where PARENT is a header array and each CHILD is a list +of the same form as THREAD. The empty list `nil' is valid. For each +article in the tree, the score of the corresponding entry in +GNUS-NEWSGROUP-SCORED is adjusted by SCORE-ADJUST." + (while thread + (let ((head (car thread))) + (if (listp head) + ;; handle a child and its descendants + (gnus-score-lower-thread head score-adjust) + ;; handle the parent + (let* ((article (mail-header-number head)) + (score (assq article gnus-newsgroup-scored))) + (if score (setcdr score (+ (cdr score) score-adjust)) + (push (cons article score-adjust) gnus-newsgroup-scored))))) + (setq thread (cdr thread)))) -(defun gnus-get-new-thread-ids (articles) - (let ((index (nth 1 (assoc "message-id" gnus-header-index))) - (refind gnus-score-index) - id-list art this tref) - (while articles - (setq art (car articles) - this (aref (car art) index) - tref (aref (car art) refind) - articles (cdr articles)) - (when (string-equal tref "") ;no references line - (push this id-list))) - id-list)) - -;; Orphan functions written by plm@atcmp.nl (Peter Mutsaers). (defun gnus-score-orphans (score) - (let ((new-thread-ids (gnus-get-new-thread-ids gnus-scores-articles)) - alike articles art arts this last this-id) - - (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<) - articles gnus-scores-articles) - - ;;more or less the same as in gnus-score-string - (erase-buffer) - (while articles - (setq art (car articles) - this (aref (car art) gnus-score-index) - articles (cdr articles)) - ;;completely skip if this is empty (not a child, so not an orphan) - (when (not (string= this "")) - (if (equal last this) - ;; O(N*H) cons-cells used here, where H is the number of - ;; headers. - (push art alike) - (when last - ;; Insert the line, with a text property on the - ;; terminating newline referring to the articles with - ;; this line. - (insert last ?\n) - (put-text-property (1- (point)) (point) 'articles alike)) - (setq alike (list art) - last this)))) - (when last ; Bwadr, duplicate code. - (insert last ?\n) - (put-text-property (1- (point)) (point) 'articles alike)) - - ;; PLM: now delete those lines that contain an entry from new-thread-ids - (while new-thread-ids - (setq this-id (car new-thread-ids) - new-thread-ids (cdr new-thread-ids)) - (goto-char (point-min)) - (while (search-forward this-id nil t) - ;; found a match. remove this line - (beginning-of-line) - (kill-line 1))) - - ;; now for each line: update its articles with score by moving to - ;; every end-of-line in the buffer and read the articles property - (goto-char (point-min)) - (while (eq 0 (progn - (end-of-line) - (setq arts (get-text-property (point) 'articles)) - (while arts - (setq art (car arts) - arts (cdr arts)) - (setcdr art (+ score (cdr art)))) - (forward-line)))))) - + "Score orphans. +A root is an article with no references. An orphan is an article +which has references, but is not connected via its references to a +root article. This function finds all the orphans, and adjusts their +score in GNUS-NEWSGROUP-SCORED by SCORE." + (let ((threads (gnus-make-threads))) + ;; gnus-make-threads produces a list, where each entry is a "thread" + ;; as described in the gnus-score-lower-thread docs. This function + ;; will be called again (after limiting has been done) if the display + ;; is threaded. It would be nice to somehow save this info and use + ;; it later. + (while threads + (let* ((thread (car threads)) + (id (aref (car thread) gnus-score-index))) + ;; If the parent of the thread is not a root, lower the score of + ;; it and its descendants. Note that some roots seem to satisfy + ;; (eq id nil) and some (eq id ""); not sure why. + (if (and id (not (string= id ""))) + (gnus-score-lower-thread thread score))) + (setq threads (cdr threads))))) (defun gnus-score-integer (scores header now expire &optional trace) (let ((gnus-score-index (nth 1 (assoc header gnus-header-index))) entries alist) - ;; Find matches. (while scores (setq alist (car scores) @@ -1542,7 +1549,7 @@ SCORE is the score to add." (match-func (if (or (eq type '>) (eq type '<) (eq type '<=) (eq type '>=) (eq type '=)) type - (error "Illegal match type: %s" type))) + (error "Invalid match type: %s" type))) (articles gnus-scores-articles)) ;; Instead of doing all the clever stuff that ;; `gnus-score-string' does to minimize searches and stuff, @@ -1574,7 +1581,6 @@ SCORE is the score to add." (defun gnus-score-date (scores header now expire &optional trace) (let ((gnus-score-index (nth 1 (assoc header gnus-header-index))) entries alist match match-func article) - ;; Find matches. (while scores (setq alist (car scores) @@ -1602,7 +1608,7 @@ SCORE is the score to add." ((eq type 'regexp) (setq match-func 'string-match match (nth 0 kill))) - (t (error "Illegal match type: %s" type))) + (t (error "Invalid match type: %s" type))) ;; Instead of doing all the clever stuff that ;; `gnus-score-string' does to minimize searches and stuff, ;; I will assume that people generally will put so few @@ -1661,8 +1667,8 @@ SCORE is the score to add." (while articles (setq article (mail-header-number (caar articles))) (gnus-message 7 "Scoring article %s of %s..." article last) + (widen) (when (funcall request-func article gnus-newsgroup-name) - (widen) (goto-char (point-min)) ;; If just parts of the article is to be searched, but the ;; backend didn't support partial fetching, we just narrow @@ -1700,7 +1706,7 @@ SCORE is the score to add." (eq type 'string) (eq type 'String)) 'search-forward) (t - (error "Illegal match type: %s" type))))) + (error "Invalid match type: %s" type))))) (goto-char (point-min)) (when (funcall search-func match nil t) ;; Found a match, update scores. @@ -1786,7 +1792,7 @@ SCORE is the score to add." (search-func (cond ((= dmt ?r) 're-search-forward) ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward) - (t (error "Illegal match type: %s" type)))) + (t (error "Invalid match type: %s" type)))) arts art) (goto-char (point-min)) (if (= dmt ?e) @@ -1867,12 +1873,23 @@ SCORE is the score to add." ;; and U is the number of unique headers. It is assumed (but ;; untested) this will be a net win because of the large constant ;; factor involved with string matching. - (setq gnus-scores-articles (sort gnus-scores-articles 'gnus-score-string<) + (setq gnus-scores-articles + ;; We cannot string-sort the extra headers list. *sigh* + (if (= gnus-score-index 9) + gnus-scores-articles + (sort gnus-scores-articles 'gnus-score-string<)) articles gnus-scores-articles) (erase-buffer) (while (setq art (pop articles)) (setq this (aref (car art) gnus-score-index)) + + ;; If we're working with non-standard headers, we are stuck + ;; with working on them as a group. What a hassle. + ;; Just wait 'til you see what horrors we commit against `match'... + (if (= gnus-score-index 9) + (setq this (prin1-to-string this))) ; ick. + (if simplify (setq this (gnus-map-function gnus-simplify-subject-functions this))) (if (equal last this) @@ -1903,11 +1920,12 @@ SCORE is the score to add." (type (or (nth 3 kill) 's)) (score (or (nth 1 kill) gnus-score-interactive-default-score)) (date (nth 2 kill)) + (extra (nth 4 kill)) ; non-standard header; string. (found nil) (mt (aref (symbol-name type) 0)) (case-fold-search (not (memq mt '(?R ?S ?E ?F)))) (dmt (downcase mt)) - ; Assume user already simplified regexp and fuzzies + ;; Assume user already simplified regexp and fuzzies (match (if (and simplify (not (memq dmt '(?f ?r)))) (gnus-map-function gnus-simplify-subject-functions @@ -1917,7 +1935,14 @@ SCORE is the score to add." (cond ((= dmt ?r) 're-search-forward) ((or (= dmt ?e) (= dmt ?s) (= dmt ?f)) 'search-forward) ((= dmt ?w) nil) - (t (error "Illegal match type: %s" type))))) + (t (error "Invalid match type: %s" type))))) + + ;; Evil hackery to make match usable in non-standard headers. + (when extra + (setq match (concat "[ (](" extra " \\. \"[^)]*" + match "[^(]*\")[ )]") + search-func 're-search-forward)) ; XXX danger?!? + (cond ;; Fuzzy matches. We save these for later. ((= dmt ?f) @@ -2044,6 +2069,7 @@ SCORE is the score to add." (cond ;; Permanent. ((null date) + ;; Do nothing. ) ;; Match, update date. ((and found gnus-update-score-entry-dates) @@ -2082,6 +2108,7 @@ SCORE is the score to add." (cond ;; Permanent. ((null date) + ;; Do nothing. ) ;; Match, update date. ((and found gnus-update-score-entry-dates) @@ -2212,9 +2239,9 @@ SCORE is the score to add." ;; Perform adaptive word scoring. (when (and (listp gnus-newsgroup-adaptive) (memq 'word gnus-newsgroup-adaptive)) - (nnheader-temp-write nil + (with-temp-buffer (let* ((hashtb (gnus-make-hashtable 1000)) - (date (gnus-day-number (current-time-string))) + (date (date-to-day (current-time-string))) (data gnus-newsgroup-data) (syntab (syntax-table)) word d score val) @@ -2250,7 +2277,7 @@ SCORE is the score to add." (let ((ignored (append gnus-ignored-adaptive-words (if gnus-adaptive-word-no-group-words (message-tokenize-header - (gnus-group-real-name + (gnus-group-real-name gnus-newsgroup-name) ".")) gnus-default-ignored-adaptive-words))) @@ -2292,11 +2319,10 @@ SCORE is the score to add." 1 "No score rules apply to the current article (default score %d)." gnus-summary-default-score) (set-buffer "*Score Trace*") + (setq truncate-lines t) (while trace (insert (format "%S -> %s\n" (cdar trace) - (if (caar trace) - (file-name-nondirectory (caar trace)) - "(non-file rule)"))) + (or (caar trace) "(non-file rule)"))) (setq trace (cdr trace))) (goto-char (point-min)) (gnus-configure-windows 'score-trace))) @@ -2389,14 +2415,14 @@ SCORE is the score to add." (gnus-summary-raise-score score)) (gnus-summary-next-subject 1 t))) -(defun gnus-score-default (level) +(defun gnus-score-delta-default (level) (if level (prefix-numeric-value level) gnus-score-interactive-default-score)) (defun gnus-summary-raise-thread (&optional score) "Raise the score of the articles in the current thread with SCORE." (interactive "P") - (setq score (gnus-score-default score)) + (setq score (gnus-score-delta-default score)) (let (e) (save-excursion (let ((articles (gnus-summary-articles-in-thread))) @@ -2425,7 +2451,7 @@ SCORE is the score to add." (defun gnus-summary-lower-thread (&optional score) "Lower score of articles in the current thread with SCORE." (interactive "P") - (gnus-summary-raise-thread (- (1- (gnus-score-default score))))) + (gnus-summary-raise-thread (- (1- (gnus-score-delta-default score))))) ;;; Finding score files. @@ -2474,8 +2500,8 @@ SCORE is the score to add." seen out file) (while (setq file (pop files)) (cond - ;; Ignore "." and "..". - ((member (file-name-nondirectory file) '("." "..")) + ;; Ignore files that start with a dot. + ((string-match "^\\." (file-name-nondirectory file)) nil) ;; Add subtrees of directory to also be searched. ((and (file-directory-p file) @@ -2505,10 +2531,11 @@ GROUP using BNews sys file syntax." (klen (length kill-dir)) (score-regexp (gnus-score-file-regexp)) (trans (cdr (assq ?: nnheader-file-name-translation-alist))) + (group-trans (nnheader-translate-file-chars group t)) ofiles not-match regexp) (save-excursion (set-buffer (gnus-get-buffer-create "*gnus score files*")) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) ;; Go through all score file names and create regexp with them ;; as the source. (while sfiles @@ -2551,16 +2578,18 @@ GROUP using BNews sys file syntax." (if (looking-at "not.") (progn (setq not-match t) - (setq regexp (concat "^" (buffer-substring 5 (point-max)) "$"))) + (setq regexp + (concat "^" (buffer-substring 5 (point-max)) "$"))) (setq regexp (concat "^" (buffer-substring 1 (point-max)) "$")) (setq not-match nil)) ;; Finally - if this resulting regexp matches the group name, ;; we add this score file to the list of score files ;; applicable to this group. (when (or (and not-match - (not (string-match regexp group))) - (and (not not-match) - (string-match regexp group))) + (ignore-errors + (not (string-match regexp group-trans)))) + (and (not not-match) + (ignore-errors (string-match regexp group-trans)))) (push (car sfiles) ofiles))) (setq sfiles (cdr sfiles))) (kill-buffer (current-buffer)) @@ -2628,7 +2657,7 @@ Destroys the current buffer." (defun gnus-sort-score-files (files) "Sort FILES so that the most general files come first." - (nnheader-temp-write nil + (with-temp-buffer (let ((alist (mapcar (lambda (file) @@ -2797,12 +2826,14 @@ If ADAPT, return the home adaptive file instead." ;; Function. ((gnus-functionp elem) (funcall elem group)) - ;; Regexp-file cons + ;; Regexp-file cons. ((consp elem) (when (string-match (gnus-globalify-regexp (car elem)) group) - (replace-match (cadr elem) t nil group )))))) + (replace-match (cadr elem) t nil group)))))) (when found - (nnheader-concat gnus-kill-files-directory found)))) + (if (file-name-absolute-p found) + found + (nnheader-concat gnus-kill-files-directory found))))) (defun gnus-hierarchial-home-score-file (group) "Return the score file of the top-level hierarchy of GROUP." @@ -2840,7 +2871,7 @@ If ADAPT, return the home adaptive file instead." (defun gnus-decay-scores (alist day) "Decay non-permanent scores in ALIST." - (let ((times (- (gnus-time-to-day (current-time)) day)) + (let ((times (- (time-to-days (current-time)) day)) kill entry updated score n) (unless (zerop times) ;Done decays today already? (while (setq entry (pop alist)) @@ -2854,7 +2885,7 @@ If ADAPT, return the home adaptive file instead." n times) (while (natnump (decf n)) (setq score (funcall gnus-decay-score-function score))) - (setcdr kill (cons score + (setcdr kill (cons score (cdr (cdr kill))))))))) ;; Return whether this score file needs to be saved. By Je-haysuss! updated)) @@ -2913,8 +2944,7 @@ See `(Gnus)Scoring Tips' for examples of good regular expressions." (cond (bad (cons 'bad bad)) (new (cons 'new new)) - ;; or nil - ))))) + (t nil)))))) (provide 'gnus-score) diff --git a/lisp/gnus/gnus-setup.el b/lisp/gnus/gnus-setup.el index ae9909b2d9d..ba770a0f6af 100644 --- a/lisp/gnus/gnus-setup.el +++ b/lisp/gnus/gnus-setup.el @@ -1,5 +1,6 @@ ;;; gnus-setup.el --- Initialization & Setup for Gnus 5 -;; Copyright (C) 1995, 96 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 2000 Free Software Foundation, Inc. ;; Author: Steven L. Baur ;; Keywords: news @@ -65,22 +66,20 @@ "site-lisp/bbdb-1.51/") "Directory where Big Brother Database is found.") -(defvar gnus-use-tm running-xemacs - "Set this if you want MIME support for Gnus") (defvar gnus-use-mhe nil - "Set this if you want to use MH-E for mail reading") + "Set this if you want to use MH-E for mail reading.") (defvar gnus-use-rmail nil - "Set this if you want to use RMAIL for mail reading") + "Set this if you want to use RMAIL for mail reading.") (defvar gnus-use-sendmail t - "Set this if you want to use SENDMAIL for mail reading") + "Set this if you want to use SENDMAIL for mail reading.") (defvar gnus-use-vm nil - "Set this if you want to use the VM package for mail reading") + "Set this if you want to use the VM package for mail reading.") (defvar gnus-use-sc nil - "Set this if you want to use Supercite") + "Set this if you want to use Supercite.") (defvar gnus-use-mailcrypt t - "Set this if you want to use Mailcrypt for dealing with PGP messages") + "Set this if you want to use Mailcrypt for dealing with PGP messages.") (defvar gnus-use-bbdb nil - "Set this if you want to use the Big Brother DataBase") + "Set this if you want to use the Big Brother DataBase.") (when (and (not gnus-use-installed-gnus) (null (member gnus-gnus-lisp-directory load-path))) @@ -89,19 +88,6 @@ ;;; We can't do this until we know where Gnus is. (require 'message) -;;; Tools for MIME by -;;; UMEDA Masanobu -;;; MORIOKA Tomohiko - -(when gnus-use-tm - (when (and (not gnus-use-installed-tm) - (null (member gnus-tm-lisp-directory load-path))) - (setq load-path (cons gnus-tm-lisp-directory load-path))) - ;; tm may or may not be dumped with XEmacs. In Sunpro it is, otherwise - ;; it isn't. - (unless (featurep 'mime-setup) - (load "mime-setup"))) - ;;; Mailcrypt by ;;; Jin Choi ;;; Patrick LoPresti diff --git a/lisp/gnus/gnus-soup.el b/lisp/gnus/gnus-soup.el index 09b58a7c8a3..1f430686948 100644 --- a/lisp/gnus/gnus-soup.el +++ b/lisp/gnus/gnus-soup.el @@ -1,5 +1,7 @@ ;;; gnus-soup.el --- SOUP packet writing support for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Per Abrahamsen ;; Lars Magne Ingebrigtsen @@ -28,8 +30,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-art) (require 'message) @@ -69,9 +69,9 @@ The SOUP packet file name will be inserted at the %s.") ;;; Internal Variables: -(defvar gnus-soup-encoding-type ?n +(defvar gnus-soup-encoding-type ?u "*Soup encoding type. -`n' is news format, `m' is Unix mbox format, and `M' is MMDF mailbox +`u' is USENET news format, `m' is Unix mbox format, and `M' is MMDF mailbox format.") (defvar gnus-soup-index-type ?c @@ -142,21 +142,19 @@ move those articles instead." (buffer-disable-undo tmp-buf) (save-excursion (while articles - ;; Find the header of the article. - (set-buffer gnus-summary-buffer) - (when (setq headers (gnus-summary-article-header (car articles))) - ;; Put the article in a buffer. - (set-buffer tmp-buf) - (when (gnus-request-article-this-buffer - (car articles) gnus-newsgroup-name) - (save-restriction - (message-narrow-to-head) - (message-remove-header gnus-soup-ignored-headers t)) - (gnus-soup-store gnus-soup-directory prefix headers - gnus-soup-encoding-type - gnus-soup-index-type) - (gnus-soup-area-set-number - area (1+ (or (gnus-soup-area-number area) 0))))) + ;; Put the article in a buffer. + (set-buffer tmp-buf) + (when (gnus-request-article-this-buffer + (car articles) gnus-newsgroup-name) + (setq headers (nnheader-parse-head t)) + (save-restriction + (message-narrow-to-head) + (message-remove-header gnus-soup-ignored-headers t)) + (gnus-soup-store gnus-soup-directory prefix headers + gnus-soup-encoding-type + gnus-soup-index-type) + (gnus-soup-area-set-number + area (1+ (or (gnus-soup-area-number area) 0)))) ;; Mark article as read. (set-buffer gnus-summary-buffer) (gnus-summary-remove-process-mark (car articles)) @@ -170,11 +168,11 @@ move those articles instead." "Make a SOUP packet from the SOUP areas." (interactive) (gnus-soup-read-areas) - (unless (file-exists-p gnus-soup-directory) - (message "No such directory: %s" gnus-soup-directory)) - (when (null (directory-files gnus-soup-directory nil "\\.MSG$")) - (message "No files to pack.")) - (gnus-soup-pack gnus-soup-directory gnus-soup-packer)) + (if (file-exists-p gnus-soup-directory) + (if (directory-files gnus-soup-directory nil "\\.MSG$") + (gnus-soup-pack gnus-soup-directory gnus-soup-packer) + (message "No files to pack.")) + (message "No such directory: %s" gnus-soup-directory))) (defun gnus-group-brew-soup (n) "Make a soup packet from the current group. @@ -249,7 +247,8 @@ Note -- this function hasn't been implemented yet." ;; a soup header. (setq head-line (cond - ((= gnus-soup-encoding-type ?n) + ((or (= gnus-soup-encoding-type ?u) + (= gnus-soup-encoding-type ?n)) ;;Gnus back compatibility. (format "#! rnews %d\n" (buffer-size))) ((= gnus-soup-encoding-type ?m) (while (search-forward "\nFrom " nil t) @@ -339,7 +338,8 @@ If NOT-ALL, don't pack ticked articles." (while (setq prefix (pop prefixes)) (erase-buffer) (insert (format "(setq gnus-soup-prev-prefix %d)\n" (cdr prefix))) - (gnus-write-buffer (concat (car prefix) gnus-soup-prefix-file)))))) + (let ((coding-system-for-write mm-text-coding-system)) + (gnus-write-buffer (concat (car prefix) gnus-soup-prefix-file))))))) (defun gnus-soup-pack (dir packer) (let* ((files (mapconcat 'identity @@ -376,7 +376,7 @@ though the two last may be nil if they are missing." (when (file-exists-p file) (save-excursion (set-buffer (nnheader-find-file-noselect file 'force)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (goto-char (point-min)) (while (not (eobp)) (push (vector (gnus-soup-field) @@ -399,7 +399,7 @@ file. The vector contain three strings, [prefix name encoding]." (let (replies) (save-excursion (set-buffer (nnheader-find-file-noselect file)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (goto-char (point-min)) (while (not (eobp)) (push (vector (gnus-soup-field) (gnus-soup-field) @@ -424,7 +424,7 @@ file. The vector contain three strings, [prefix name encoding]." "Write the AREAS file." (interactive) (when gnus-soup-areas - (nnheader-temp-write (concat gnus-soup-directory "AREAS") + (with-temp-file (concat gnus-soup-directory "AREAS") (let ((areas gnus-soup-areas) area) (while (setq area (pop areas)) @@ -445,7 +445,7 @@ file. The vector contain three strings, [prefix name encoding]." (defun gnus-soup-write-replies (dir areas) "Write a REPLIES file in DIR containing AREAS." - (nnheader-temp-write (concat dir "REPLIES") + (with-temp-file (concat dir "REPLIES") (let (area) (while (setq area (pop areas)) (insert (format "%s\t%s\t%s\n" @@ -517,9 +517,12 @@ Return whether the unpacking was successful." (tmp-buf (gnus-get-buffer-create " *soup send*")) beg end) (cond - ((/= (gnus-soup-encoding-format - (gnus-soup-reply-encoding (car replies))) - ?n) + ((and (/= (gnus-soup-encoding-format + (gnus-soup-reply-encoding (car replies))) + ?u) + (/= (gnus-soup-encoding-format + (gnus-soup-reply-encoding (car replies))) + ?n)) ;; Gnus back compatibility. (error "Unsupported encoding")) ((null msg-buf) t) diff --git a/lisp/gnus/gnus-spec.el b/lisp/gnus/gnus-spec.el index 403b5169583..07d1a87df2f 100644 --- a/lisp/gnus/gnus-spec.el +++ b/lisp/gnus/gnus-spec.el @@ -1,5 +1,6 @@ ;;; gnus-spec.el --- format spec functions for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) ;;; Internal variables. @@ -203,9 +202,7 @@ (gnus-parse-format new-format (symbol-value - (intern (format "gnus-%s-line-format-alist" - (if (eq type 'article-mode) - 'summary-mode type)))) + (intern (format "gnus-%s-line-format-alist" type))) (not (string-match "mode$" (symbol-name type)))))) ;; Enter the new format spec into the list. (if entry @@ -243,6 +240,12 @@ (point) (progn ,@form (point)) '(gnus-face t face ,(symbol-value (intern (format "gnus-face-%d" type)))))) +(defun gnus-balloon-face-function (form type) + `(gnus-put-text-property + (point) (progn ,@form (point)) + 'balloon-help + ,(intern (format "gnus-balloon-face-%d" type)))) + (defun gnus-tilde-max-form (el max-width) "Return a form that limits EL to MAX-WIDTH." (let ((max (abs max-width))) @@ -289,8 +292,10 @@ ;; SPEC-ALIST and returns a list that can be eval'ed to return the ;; string. If the FORMAT string contains the specifiers %( and %) ;; the text between them will have the mouse-face text property. + ;; If the FORMAT string contains the specifiers %[ and %], the text between + ;; them will have the balloon-help text property. (if (string-match - "\\`\\(.*\\)%[0-9]?[{(]\\(.*\\)%[0-9]?[})]\\(.*\n?\\)\\'" + "\\`\\(.*\\)%[0-9]?[{(«]\\(.*\\)%[0-9]?[»})]\\(.*\n?\\)\\'" format) (gnus-parse-complex-format format spec-alist) ;; This is a simple format. @@ -305,13 +310,17 @@ (replace-match "\\\"" nil t)) (goto-char (point-min)) (insert "(\"") - (while (re-search-forward "%\\([0-9]+\\)?\\([{}()]\\)" nil t) + (while (re-search-forward "%\\([0-9]+\\)?\\([«»{}()]\\)" nil t) (let ((number (if (match-beginning 1) (match-string 1) "0")) (delim (aref (match-string 2) 0))) (if (or (= delim ?\() - (= delim ?\{)) - (replace-match (concat "\"(" (if (= delim ?\() "mouse" "face") + (= delim ?\{) + (= delim ?\«)) + (replace-match (concat "\"(" + (cond ((= delim ?\() "mouse") + ((= delim ?\{) "face") + (t "balloon")) " " number " \"")) (replace-match "\")\"")))) (goto-char (point-max)) @@ -392,9 +401,9 @@ (t nil))) ;; User-defined spec -- find the spec name. - (when (= (setq spec (following-char)) ?u) + (when (eq (setq spec (char-after)) ?u) (forward-char 1) - (setq user-defined (following-char))) + (setq user-defined (char-after))) (forward-char 1) (delete-region spec-beg (point)) @@ -521,7 +530,7 @@ If PROPS, insert the result." (not (eq 'byte-code (car form))) ;; Under XEmacs, it's (funcall #) (not (and (eq 'funcall (car form)) - (compiled-function-p (cadr form))))) + (byte-code-function-p (cadr form))))) (fset 'gnus-tmp-func `(lambda () ,form)) (byte-compile 'gnus-tmp-func) (setcar (cddr entry) (gnus-byte-code 'gnus-tmp-func)))))) @@ -537,8 +546,11 @@ If PROPS, insert the result." (symbol-value (intern (format "gnus-%s-line-format" type))) (symbol-value (intern (format "gnus-%s-line-format-alist" type))) insertable))) - (provide 'gnus-spec) +;; Local Variables: +;; coding: iso-8859-1 +;; End: + ;;; gnus-spec.el ends here diff --git a/lisp/gnus/gnus-srvr.el b/lisp/gnus/gnus-srvr.el index dc3dd1a6fdb..f1224c9913d 100644 --- a/lisp/gnus/gnus-srvr.el +++ b/lisp/gnus/gnus-srvr.el @@ -1,5 +1,6 @@ ;;; gnus-srvr.el --- virtual server support for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-spec) (require 'gnus-group) @@ -137,6 +136,9 @@ The following specs are understood: "D" gnus-server-deny-server "R" gnus-server-remove-denials + "n" next-line + "p" previous-line + "g" gnus-server-regenerate-server "\C-c\C-i" gnus-info-find-node @@ -163,7 +165,7 @@ The following commands are available: (gnus-set-default-directory) (setq mode-line-process nil) (use-local-map gnus-server-mode-map) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq truncate-lines t) (setq buffer-read-only t) (gnus-run-hooks 'gnus-server-mode-hook)) @@ -173,12 +175,12 @@ The following commands are available: (gnus-tmp-where (nth 1 method)) (elem (assoc method gnus-opened-servers)) (gnus-tmp-status (cond ((eq (nth 1 elem) 'denied) - "(denied)") - ((or (gnus-server-opened method) - (eq (nth 1 elem) 'ok)) - "(opened)") - (t - "(closed)")))) + "(denied)") + ((or (gnus-server-opened method) + (eq (nth 1 elem) 'ok)) + "(opened)") + (t + "(closed)")))) (beginning-of-line) (gnus-add-text-properties (point) @@ -295,6 +297,18 @@ The following commands are available: (push (assoc server gnus-server-alist) gnus-server-killed-servers) (setq gnus-server-alist (delq (car gnus-server-killed-servers) gnus-server-alist)) + (let ((groups (gnus-groups-from-server server))) + (when (and groups + (gnus-yes-or-no-p + (format "Kill all %s groups from this server? " + (length groups)))) + (dolist (group groups) + (setq gnus-newsrc-alist + (delq (assoc group gnus-newsrc-alist) + gnus-newsrc-alist)) + (when gnus-group-change-level-function + (funcall gnus-group-change-level-function + group gnus-level-killed 3))))) (gnus-server-position-point)) (defun gnus-server-yank-server () @@ -508,28 +522,28 @@ The following commands are available: (suppress-keymap gnus-browse-mode-map) (gnus-define-keys - gnus-browse-mode-map - " " gnus-browse-read-group - "=" gnus-browse-select-group - "n" gnus-browse-next-group - "p" gnus-browse-prev-group - "\177" gnus-browse-prev-group - [delete] gnus-browse-prev-group - "N" gnus-browse-next-group - "P" gnus-browse-prev-group - "\M-n" gnus-browse-next-group - "\M-p" gnus-browse-prev-group - "\r" gnus-browse-select-group - "u" gnus-browse-unsubscribe-current-group - "l" gnus-browse-exit - "L" gnus-browse-exit - "q" gnus-browse-exit - "Q" gnus-browse-exit - "\C-c\C-c" gnus-browse-exit - "?" gnus-browse-describe-briefly + gnus-browse-mode-map + " " gnus-browse-read-group + "=" gnus-browse-select-group + "n" gnus-browse-next-group + "p" gnus-browse-prev-group + "\177" gnus-browse-prev-group + [delete] gnus-browse-prev-group + "N" gnus-browse-next-group + "P" gnus-browse-prev-group + "\M-n" gnus-browse-next-group + "\M-p" gnus-browse-prev-group + "\r" gnus-browse-select-group + "u" gnus-browse-unsubscribe-current-group + "l" gnus-browse-exit + "L" gnus-browse-exit + "q" gnus-browse-exit + "Q" gnus-browse-exit + "\C-c\C-c" gnus-browse-exit + "?" gnus-browse-describe-briefly - "\C-c\C-i" gnus-info-find-node - "\C-c\C-b" gnus-bug)) + "\C-c\C-i" gnus-info-find-node + "\C-c\C-b" gnus-bug)) (defun gnus-browse-make-menu-bar () (gnus-turn-off-edit-menu 'browse) @@ -552,9 +566,9 @@ The following commands are available: (defun gnus-browse-foreign-server (server &optional return-buffer) "Browse the server SERVER." - (setq gnus-browse-current-method server) + (setq gnus-browse-current-method (gnus-server-to-method server)) (setq gnus-browse-return-buffer return-buffer) - (let* ((method (gnus-server-to-method server)) + (let* ((method gnus-browse-current-method) (gnus-select-method method) groups group) (gnus-message 5 "Connecting to %s..." (nth 1 method)) @@ -577,7 +591,7 @@ The following commands are available: (when gnus-carpal (gnus-carpal-setup-buffer 'browse)) (gnus-configure-windows 'browse) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (let ((buffer-read-only nil)) (erase-buffer)) (gnus-browse-mode) @@ -591,22 +605,38 @@ The following commands are available: (goto-char (point-min)) (unless (string= gnus-ignored-newsgroups "") (delete-matching-lines gnus-ignored-newsgroups)) - (while (re-search-forward - "\\(^[^ \t]+\\)[ \t]+[0-9]+[ \t]+[0-9]+" nil t) - (goto-char (match-end 1)) - (condition-case () - (push (cons (match-string 1) - (max 0 (- (1+ (read cur)) (read cur)))) - groups) - (error nil))))) + (while (not (eobp)) + (ignore-errors + (push (cons + (if (eq (char-after) ?\") + (read cur) + (let ((p (point)) (name "")) + (skip-chars-forward "^ \t\\\\") + (setq name (buffer-substring p (point))) + (while (eq (char-after) ?\\) + (setq p (1+ (point))) + (forward-char 2) + (skip-chars-forward "^ \t\\\\") + (setq name (concat name (buffer-substring + p (point))))) + name)) + (max 0 (- (1+ (read cur)) (read cur)))) + groups)) + (forward-line)))) (setq groups (sort groups (lambda (l1 l2) (string< (car l1) (car l2))))) - (let ((buffer-read-only nil)) + (let ((buffer-read-only nil) charset) (while groups (setq group (car groups)) - (insert - (format "K%7d: %s\n" (cdr group) (car group))) + (setq charset (gnus-group-name-charset method group)) + (gnus-add-text-properties + (point) + (prog1 (1+ (point)) + (insert + (format "K%7d: %s\n" (cdr group) + (gnus-group-name-decode (car group) charset)))) + (list 'gnus-group (car group))) (setq groups (cdr groups)))) (switch-to-buffer (current-buffer)) (goto-char (point-min)) @@ -638,7 +668,7 @@ buffer. (setq mode-name "Browse Server") (setq mode-line-process nil) (use-local-map gnus-browse-mode-map) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq truncate-lines t) (gnus-set-default-directory) (setq buffer-read-only t) @@ -651,12 +681,12 @@ buffer. (if (or (not (gnus-get-info group)) (gnus-ephemeral-group-p group)) (unless (gnus-group-read-ephemeral-group - group gnus-browse-current-method nil + (gnus-group-real-name group) gnus-browse-current-method nil (cons (current-buffer) 'browse)) (error "Couldn't enter %s" group)) (unless (gnus-group-read-group nil no-article group) (error "Couldn't enter %s" group))))) - + (defun gnus-browse-select-group () "Select the current group." (interactive) @@ -694,11 +724,12 @@ buffer. (defun gnus-browse-group-name () (save-excursion (beginning-of-line) - (when (re-search-forward ": \\(.*\\)$" (gnus-point-at-eol) t) - (gnus-group-prefixed-name - ;; Remove text props. - (format "%s" (match-string 1)) - gnus-browse-current-method)))) + (let ((name (get-text-property (point) 'gnus-group))) + (when (re-search-forward ": \\(.*\\)$" (gnus-point-at-eol) t) + (gnus-group-prefixed-name + (or name + (match-string-no-properties 1)) + gnus-browse-current-method))))) (defun gnus-browse-unsubscribe-group () "Toggle subscription of the current group in the browse buffer." @@ -708,7 +739,7 @@ buffer. (save-excursion (beginning-of-line) ;; If this group it killed, then we want to subscribe it. - (when (= (following-char) ?K) + (when (eq (char-after) ?K) (setq sub t)) (setq group (gnus-browse-group-name)) (when (and sub @@ -725,7 +756,8 @@ buffer. nil nil (if (gnus-server-equal gnus-browse-current-method "native") nil - gnus-browse-current-method)) + (gnus-method-simplify + gnus-browse-current-method))) gnus-level-default-subscribed gnus-level-killed (and (car (nth 1 gnus-newsrc-alist)) (gnus-gethash (car (nth 1 gnus-newsrc-alist)) diff --git a/lisp/gnus/gnus-start.el b/lisp/gnus/gnus-start.el index f4d8bc20103..078233373fc 100644 --- a/lisp/gnus/gnus-start.el +++ b/lisp/gnus/gnus-start.el @@ -1,5 +1,6 @@ ;;; gnus-start.el --- startup functions for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -52,7 +53,7 @@ If a file with the `.el' or `.elc' suffixes exists, it will be read instead." (directory-file-name installation-directory)) "site-lisp/gnus-init") (error nil)) - "*The site-wide Gnus Emacs-Lisp startup file name, or nil if none. + "The site-wide Gnus Emacs-Lisp startup file name, or nil if none. If a file with the `.el' or `.elc' suffixes exists, it will be read instead." :group 'gnus-start :type '(choice file (const nil))) @@ -142,27 +143,19 @@ properly with all servers." (const some) (const t))) -(defcustom gnus-level-subscribed 5 - "*Groups with levels less than or equal to this variable are subscribed." - :group 'gnus-group-levels - :type 'integer) +(defconst gnus-level-subscribed 5 + "Groups with levels less than or equal to this variable are subscribed.") -(defcustom gnus-level-unsubscribed 7 - "*Groups with levels less than or equal to this variable are unsubscribed. +(defconst gnus-level-unsubscribed 7 + "Groups with levels less than or equal to this variable are unsubscribed. Groups with levels less than `gnus-level-subscribed', which should be -less than this variable, are subscribed." - :group 'gnus-group-levels - :type 'integer) +less than this variable, are subscribed.") -(defcustom gnus-level-zombie 8 - "*Groups with this level are zombie groups." - :group 'gnus-group-levels - :type 'integer) +(defconst gnus-level-zombie 8 + "Groups with this level are zombie groups.") -(defcustom gnus-level-killed 9 - "*Groups with this level are killed." - :group 'gnus-group-levels - :type 'integer) +(defconst gnus-level-killed 9 + "Groups with this level are killed.") (defcustom gnus-level-default-subscribed 3 "*New subscribed groups will be subscribed at this level." @@ -197,6 +190,16 @@ groups." :type '(choice integer (const :tag "none" nil))) +(defcustom gnus-read-newsrc-file t + "*Non-nil means that Gnus will read the `.newsrc' file. +Gnus always reads its own startup file, which is called +\".newsrc.eld\". The file called \".newsrc\" is in a format that can +be readily understood by other newsreaders. If you don't plan on +using other newsreaders, set this variable to nil to save some time on +entry." + :group 'gnus-newsrc + :type 'boolean) + (defcustom gnus-save-newsrc-file t "*Non-nil means that Gnus will save the `.newsrc' file. Gnus always saves its own startup file, which is called @@ -223,12 +226,12 @@ not match this regexp will be removed before saving the list." :type 'boolean) (defcustom gnus-ignored-newsgroups - (purecopy (mapconcat 'identity - '("^to\\." ; not "real" groups - "^[0-9. \t]+ " ; all digits in name - "[][\"#'()]" ; bogus characters - ) - "\\|")) + (mapconcat 'identity + '("^to\\." ; not "real" groups + "^[0-9. \t]+ " ; all digits in name + "^[\"][]\"[#'()]" ; bogus characters + ) + "\\|") "*A regexp to match uninteresting newsgroups in the active file. Any lines in the active file matching this regular expression are removed from the newsgroup list before anything else is done to it, @@ -244,7 +247,9 @@ inserts new groups at the beginning of the list of groups; alphabetic order; `gnus-subscribe-hierarchically' inserts new groups in hierarchical newsgroup order; `gnus-subscribe-interactively' asks for your decision; `gnus-subscribe-killed' kills all new groups; -`gnus-subscribe-zombies' will make all new groups into zombies." +`gnus-subscribe-zombies' will make all new groups into zombies; +`gnus-subscribe-topics' will enter groups into the topics that +claim them." :group 'gnus-group-new :type '(radio (function-item gnus-subscribe-randomly) (function-item gnus-subscribe-alphabetically) @@ -252,6 +257,7 @@ for your decision; `gnus-subscribe-killed' kills all new groups; (function-item gnus-subscribe-interactively) (function-item gnus-subscribe-killed) (function-item gnus-subscribe-zombies) + (function-item gnus-subscribe-topics) function)) (defcustom gnus-subscribe-options-newsgroup-method @@ -360,7 +366,7 @@ This hook is called as the first thing when Gnus is started." (defcustom gnus-after-getting-new-news-hook (when (gnus-boundp 'display-time-timer) '(display-time-event-handler)) - "*A hook run after Gnus checks for new news." + "*A hook run after Gnus checks for new news when Gnus is already running." :group 'gnus-group-new :type 'hook) @@ -382,16 +388,13 @@ Can be used to turn version control on or off." :type 'hook) (defcustom gnus-always-read-dribble-file nil - "Uncoditionally read the dribble file." + "Unconditionally read the dribble file." :group 'gnus-newsrc :type 'boolean) (defvar gnus-startup-file-coding-system 'binary "*Coding system for startup file.") -(defvar gnus-startup-file-coding-system 'binary - "*Coding system for startup file.") - ;;; Internal variables (defvar gnus-newsrc-file-version nil) @@ -618,6 +621,7 @@ the first newsgroup." gnus-newsgroup-unreads nil nnoo-state-alist nil gnus-current-select-method nil + nnmail-split-history nil gnus-ephemeral-servers nil) (gnus-shutdown 'gnus) ;; Kill the startup file. @@ -729,17 +733,14 @@ prompt the user for the name of an NNTP server to use." ;;;###autoload (defun gnus-unload () - "Unload all Gnus features." + "Unload all Gnus features. +\(For some value of `all' or `Gnus'.) Currently, features whose names +have prefixes `gnus-', `nn', `mm-' or `rfc' are unloaded. Use +cautiously -- unloading may cause trouble." (interactive) - (unless (boundp 'load-history) - (error "Sorry, `gnus-unload' is not implemented in this Emacs version")) - (let ((history load-history) - feature) - (while history - (and (string-match "^\\(gnus\\|nn\\)" (caar history)) - (setq feature (cdr (assq 'provide (car history)))) - (unload-feature feature 'force)) - (setq history (cdr history))))) + (dolist (feature features) + (if (string-match "^\\(gnus-\\|nn\\|mm-\\|rfc\\)" (symbol-name feature)) + (unload-feature feature 'force)))) ;;; @@ -788,7 +789,7 @@ prompt the user for the name of an NNTP server to use." (erase-buffer) (setq buffer-file-name dribble-file) (auto-save-mode t) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (bury-buffer (current-buffer)) (set-buffer-modified-p nil) (let ((auto (make-auto-save-file-name)) @@ -858,7 +859,11 @@ prompt the user for the name of an NNTP server to use." "Setup news information. If RAWFILE is non-nil, the .newsrc file will also be read. If LEVEL is non-nil, the news will be set up at level LEVEL." - (let ((init (not (and gnus-newsrc-alist gnus-active-hashtb (not rawfile))))) + (require 'nnmail) + (let ((init (not (and gnus-newsrc-alist gnus-active-hashtb (not rawfile)))) + ;; Binding this variable will inhibit multiple fetchings + ;; of the same mail source. + (nnmail-fetched-sources (list t))) (when init ;; Clear some variables to re-initialize news information. @@ -942,7 +947,7 @@ If LEVEL is non-nil, the news will be set up at level LEVEL." (defun gnus-find-new-newsgroups (&optional arg) "Search for new newsgroups and add them. -Each new newsgroup will be treated with `gnus-subscribe-newsgroup-method.' +Each new newsgroup will be treated with `gnus-subscribe-newsgroup-method'. The `-n' option line from .newsrc is respected. With 1 C-u, use the `ask-server' method to query the server for new @@ -953,16 +958,16 @@ for new groups, and subscribe the new groups as zombies." (let* ((gnus-subscribe-newsgroup-method gnus-subscribe-newsgroup-method) (check (cond - ((or (and (= (or arg 1) 4) - (not (listp gnus-check-new-newsgroups))) - (null gnus-read-active-file) - (eq gnus-read-active-file 'some)) - 'ask-server) - ((= (or arg 1) 16) - (setq gnus-subscribe-newsgroup-method - 'gnus-subscribe-zombies) - t) - (t gnus-check-new-newsgroups)))) + ((or (and (= (or arg 1) 4) + (not (listp gnus-check-new-newsgroups))) + (null gnus-read-active-file) + (eq gnus-read-active-file 'some)) + 'ask-server) + ((= (or arg 1) 16) + (setq gnus-subscribe-newsgroup-method + 'gnus-subscribe-zombies) + t) + (t gnus-check-new-newsgroups)))) (unless (gnus-check-first-time-used) (if (or (consp check) (eq check 'ask-server)) @@ -1097,34 +1102,40 @@ for new groups, and subscribe the new groups as zombies." hashtb)) (when new-newsgroups (gnus-subscribe-hierarchical-interactive new-newsgroups))) - (if (> groups 0) - (gnus-message 5 "%d new newsgroup%s arrived" - groups (if (> groups 1) "s have" " has")) - (gnus-message 5 "No new newsgroups")) + (if (> groups 0) + (gnus-message 5 "%d new newsgroup%s arrived" + groups (if (> groups 1) "s have" " has")) + (gnus-message 5 "No new newsgroups")) (when got-new (setq gnus-newsrc-last-checked-date new-date)) got-new)) (defun gnus-check-first-time-used () - (if (or (> (length gnus-newsrc-alist) 1) - (file-exists-p gnus-startup-file) - (file-exists-p (concat gnus-startup-file ".el")) - (file-exists-p (concat gnus-startup-file ".eld"))) - nil + (catch 'ended + ;; First check if any of the following files exist. If they do, + ;; it's not the first time the user has used Gnus. + (dolist (file (list gnus-current-startup-file + (concat gnus-current-startup-file ".el") + (concat gnus-current-startup-file ".eld") + gnus-startup-file + (concat gnus-startup-file ".el") + (concat gnus-startup-file ".eld"))) + (when (file-exists-p file) + (throw 'ended nil))) (gnus-message 6 "First time user; subscribing you to default groups") (unless (gnus-read-active-file-p) (let ((gnus-read-active-file t)) (gnus-read-active-file))) (setq gnus-newsrc-last-checked-date (current-time-string)) - (let ((groups gnus-default-subscribed-newsgroups) + ;; Subscribe to the default newsgroups. + (let ((groups (or gnus-default-subscribed-newsgroups + gnus-backup-default-subscribed-newsgroups)) group) - (if (eq groups t) - nil - (setq groups (or groups gnus-backup-default-subscribed-newsgroups)) + (when (eq groups t) + ;; If t, we subscribe (or not) all groups as if they were new. (mapatoms (lambda (sym) - (if (null (setq group (symbol-name sym))) - () + (when (setq group (symbol-name sym)) (let ((do-sub (gnus-matches-options-n group))) (cond ((eq do-sub 'subscribe) @@ -1135,23 +1146,25 @@ for new groups, and subscribe the new groups as zombies." (t (push group gnus-killed-list)))))) gnus-active-hashtb) - (while groups - (when (gnus-active (car groups)) + (dolist (group groups) + ;; Only subscribe the default groups that are activated. + (when (gnus-active group) (gnus-group-change-level - (car groups) gnus-level-default-subscribed gnus-level-killed)) - (setq groups (cdr groups))) + group gnus-level-default-subscribed gnus-level-killed))) (save-excursion (set-buffer gnus-group-buffer) (gnus-group-make-help-group)) (when gnus-novice-user (gnus-message 7 "`A k' to list killed groups")))))) -(defun gnus-subscribe-group (group previous &optional method) +(defun gnus-subscribe-group (group &optional previous method) + "Subcribe GROUP and put it after PREVIOUS." (gnus-group-change-level (if method (list t group gnus-level-default-subscribed nil nil method) group) - gnus-level-default-subscribed gnus-level-killed previous t)) + gnus-level-default-subscribed gnus-level-killed previous t) + t) ;; `gnus-group-change-level' is the fundamental function for changing ;; subscription levels of newsgroups. This might mean just changing @@ -1246,14 +1259,14 @@ for new groups, and subscribe the new groups as zombies." (setq active (gnus-active group)) (setq num (if active (- (1+ (cdr active)) (car active)) t)) - ;; Check whether the group is foreign. If so, the - ;; foreign select method has to be entered into the - ;; info. - (let ((method (or gnus-override-subscribe-method - (gnus-group-method group)))) - (if (eq method gnus-select-method) - (setq info (list group level nil)) - (setq info (list group level nil nil method))))) + ;; Shorten the select method if possible, if we need to + ;; store it at all (native groups). + (let ((method (gnus-method-simplify + (or gnus-override-subscribe-method + (gnus-group-method group))))) + (if method + (setq info (list group level nil nil method)) + (setq info (list group level nil))))) (unless previous (setq previous (let ((p gnus-newsrc-alist)) @@ -1371,7 +1384,7 @@ newsgroup." t) (condition-case () (inline (gnus-request-group group dont-check method)) - (error nil) + ;;(error nil) (quit nil)) (setq active (gnus-parse-active)) ;; If there are no articles in the group, the GROUP @@ -1443,7 +1456,7 @@ newsgroup." ;; Then we want to peel off any elements that are higher ;; than the upper active limit. (let ((srange range)) - ;; Go past all legal elements. + ;; Go past all valid elements. (while (and (cdr srange) (<= (or (and (atom (cadr srange)) (cadr srange)) @@ -1451,7 +1464,7 @@ newsgroup." (cdr active))) (setq srange (cdr srange))) (when (cdr srange) - ;; Nuke all remaining illegal elements. + ;; Nuke all remaining invalid elements. (setcdr srange nil)) ;; Adjust the final element. @@ -1485,7 +1498,7 @@ newsgroup." gnus-activate-foreign-newsgroups) (t 0)) level)) - info group active method) + scanned-methods info group active method retrievegroups) (gnus-message 5 "Checking new news...") (while newsrc @@ -1497,6 +1510,13 @@ newsgroup." ;; be reached) we just set the number of unread articles in this ;; newsgroup to t. This means that Gnus thinks that there are ;; unread articles, but it has no idea how many. + + ;; To be more explicit: + ;; >0 for an active group with messages + ;; 0 for an active group with no unread messages + ;; nil for non-foreign groups that the user has requested not be checked + ;; t for unchecked foreign groups or bogus groups, or groups that can't + ;; be checked, for one reason or other. (if (and (setq method (gnus-info-method info)) (not (inline (gnus-server-equal @@ -1504,8 +1524,8 @@ newsgroup." (setq method (gnus-server-get-method nil method))))) (not (gnus-secondary-method-p method))) ;; These groups are foreign. Check the level. - (when (<= (gnus-info-level info) foreign-level) - (setq active (gnus-activate-group group 'scan)) + (when (and (<= (gnus-info-level info) foreign-level) + (setq active (gnus-activate-group group 'scan))) ;; Let the Gnus agent save the active file. (when (and gnus-agent gnus-plugged active) (gnus-agent-save-group-info @@ -1516,18 +1536,76 @@ newsgroup." "-request-update-info"))) (inline (gnus-request-update-info info method)))) ;; These groups are native or secondary. - (when (and (<= (gnus-info-level info) level) - (not gnus-read-active-file)) - (setq active (gnus-activate-group group 'scan)) - (inline (gnus-close-group group)))) + (cond + ;; We don't want these groups. + ((> (gnus-info-level info) level) + (setq active 'ignore)) + ;; Activate groups. + ((not gnus-read-active-file) + (if (gnus-check-backend-function 'retrieve-groups group) + ;; if server support gnus-retrieve-groups we push + ;; the group onto retrievegroups for later checking + (if (assoc method retrievegroups) + (setcdr (assoc method retrievegroups) + (cons group (cdr (assoc method retrievegroups)))) + (push (list method group) retrievegroups)) + ;; hack: `nnmail-get-new-mail' changes the mail-source depending + ;; on the group, so we must perform a scan for every group + ;; if the users has any directory mail sources. + ;; hack: if `nnmail-scan-directory-mail-source-once' is non-nil, + ;; for it scan all spool files even when the groups are + ;; not required. + (if (and + (or nnmail-scan-directory-mail-source-once + (null (assq 'directory + (or mail-sources + (if (listp nnmail-spool-file) + nnmail-spool-file + (list nnmail-spool-file)))))) + (member method scanned-methods)) + (setq active (gnus-activate-group group)) + (setq active (gnus-activate-group group 'scan)) + (push method scanned-methods)) + (when active + (gnus-close-group group)))))) ;; Get the number of unread articles in the group. - (if active - (inline (gnus-get-unread-articles-in-group info active t)) + (cond + ((eq active 'ignore) + ;; Don't do anything. + ) + (active + (inline (gnus-get-unread-articles-in-group info active t))) + (t ;; The group couldn't be reached, so we nix out the number of ;; unread articles and stuff. (gnus-set-active group nil) - (setcar (gnus-gethash group gnus-newsrc-hashtb) t))) + (let ((tmp (gnus-gethash group gnus-newsrc-hashtb))) + (if tmp (setcar tmp t)))))) + + ;; iterate through groups on methods which support gnus-retrieve-groups + ;; and fetch a partial active file and use it to find new news. + (while retrievegroups + (let* ((mg (pop retrievegroups)) + (method (or (car mg) gnus-select-method)) + (groups (cdr mg))) + (when (gnus-check-server method) + ;; Request that the backend scan its incoming messages. + (when (gnus-check-backend-function 'request-scan (car method)) + (gnus-request-scan nil method)) + (gnus-read-active-file-2 (mapcar (lambda (group) + (gnus-group-real-name group)) + groups) method) + (dolist (group groups) + (cond + ((setq active (gnus-active (gnus-info-group + (setq info (gnus-get-info group))))) + (inline (gnus-get-unread-articles-in-group info active t))) + (t + ;; The group couldn't be reached, so we nix out the number of + ;; unread articles and stuff. + (gnus-set-active group nil) + (setcar (gnus-gethash group gnus-newsrc-hashtb) t))))))) (gnus-message 5 "Checking new news...done"))) @@ -1635,85 +1713,90 @@ newsgroup." (defun gnus-read-active-file (&optional force not-native) (gnus-group-set-mode-line) (let ((methods - (append - (if (and (not not-native) - (gnus-check-server gnus-select-method)) - ;; The native server is available. - (cons gnus-select-method gnus-secondary-select-methods) - ;; The native server is down, so we just do the - ;; secondary ones. - gnus-secondary-select-methods) - ;; Also read from the archive server. - (when (gnus-archive-server-wanted-p) - (list "archive")))) - list-type) + (mapcar + (lambda (m) (if (stringp m) (gnus-server-get-method nil m) m)) + (append + (if (and (not not-native) + (gnus-check-server gnus-select-method)) + ;; The native server is available. + (cons gnus-select-method gnus-secondary-select-methods) + ;; The native server is down, so we just do the + ;; secondary ones. + gnus-secondary-select-methods) + ;; Also read from the archive server. + (when (gnus-archive-server-wanted-p) + (list "archive"))))) + method) (setq gnus-have-read-active-file nil) (save-excursion (set-buffer nntp-server-buffer) - (while methods - (let* ((method (if (stringp (car methods)) - (gnus-server-get-method nil (car methods)) - (car methods))) - (where (nth 1 method)) - (mesg (format "Reading active file%s via %s..." - (if (and where (not (zerop (length where)))) - (concat " from " where) "") - (car method)))) + (while (setq method (pop methods)) + ;; Only do each method once, in case the methods appear more + ;; than once in this list. + (unless (member method methods) + (condition-case () + (gnus-read-active-file-1 method force) + ;; We catch C-g so that we can continue past servers + ;; that do not respond. + (quit nil))))))) + +(defun gnus-read-active-file-1 (method force) + (let (where mesg) + (setq where (nth 1 method) + mesg (format "Reading active file%s via %s..." + (if (and where (not (zerop (length where)))) + (concat " from " where) "") + (car method))) + (gnus-message 5 mesg) + (when (gnus-check-server method) + ;; Request that the backend scan its incoming messages. + (when (gnus-check-backend-function 'request-scan (car method)) + (gnus-request-scan nil method)) + (cond + ((and (eq gnus-read-active-file 'some) + (gnus-check-backend-function 'retrieve-groups (car method)) + (not force)) + (let ((newsrc (cdr gnus-newsrc-alist)) + (gmethod (gnus-server-get-method nil method)) + groups info) + (while (setq info (pop newsrc)) + (when (inline + (gnus-server-equal + (inline + (gnus-find-method-for-group + (gnus-info-group info) info)) + gmethod)) + (push (gnus-group-real-name (gnus-info-group info)) + groups))) + (gnus-read-active-file-2 groups method))) + ((null method) + t) + (t + (if (not (gnus-request-list method)) + (unless (equal method gnus-message-archive-method) + (gnus-error 1 "Cannot read active file from %s server" + (car method))) (gnus-message 5 mesg) - (when (gnus-check-server method) - ;; Request that the backend scan its incoming messages. - (when (gnus-check-backend-function 'request-scan (car method)) - (gnus-request-scan nil method)) - (cond - ((and (eq gnus-read-active-file 'some) - (gnus-check-backend-function 'retrieve-groups (car method)) - (not force)) - (let ((newsrc (cdr gnus-newsrc-alist)) - (gmethod (gnus-server-get-method nil method)) - groups info) - (while (setq info (pop newsrc)) - (when (inline - (gnus-server-equal - (inline - (gnus-find-method-for-group - (gnus-info-group info) info)) - gmethod)) - (push (gnus-group-real-name (gnus-info-group info)) - groups))) - (when groups - (gnus-check-server method) - (setq list-type (gnus-retrieve-groups groups method)) - (cond - ((not list-type) - (gnus-error - 1.2 "Cannot read partial active file from %s server." - (car method))) - ((eq list-type 'active) - (gnus-active-to-gnus-format - method gnus-active-hashtb nil t)) - (t - (gnus-groups-to-gnus-format - method gnus-active-hashtb t)))))) - ((null method) - t) - (t - (if (not (gnus-request-list method)) - (unless (equal method gnus-message-archive-method) - (gnus-error 1 "Cannot read active file from %s server" - (car method))) - (gnus-message 5 mesg) - (gnus-active-to-gnus-format method gnus-active-hashtb nil t) - ;; We mark this active file as read. - (push method gnus-have-read-active-file) - (gnus-message 5 "%sdone" mesg)))))) - (setq methods (cdr methods)))))) + (gnus-active-to-gnus-format method gnus-active-hashtb nil t) + ;; We mark this active file as read. + (push method gnus-have-read-active-file) + (gnus-message 5 "%sdone" mesg))))))) - -(defun gnus-ignored-newsgroups-has-to-p () - "Non-nil iff gnus-ignored-newsgroups includes \"^to\\\\.\" as an element." - ;; note this regexp is the same as: - ;; (concat (regexp-quote "^to\\.") "\\($\\|" (regexp-quote "\\|") "\\)") - (string-match "\\^to\\\\\\.\\($\\|\\\\|\\)" gnus-ignored-newsgroups)) +(defun gnus-read-active-file-2 (groups method) + "Read an active file for GROUPS in METHOD using gnus-retrieve-groups." + (when groups + (save-excursion + (set-buffer nntp-server-buffer) + (gnus-check-server method) + (let ((list-type (gnus-retrieve-groups groups method))) + (cond ((not list-type) + (gnus-error + 1.2 "Cannot read partial active file from %s server." + (car method))) + ((eq list-type 'active) + (gnus-active-to-gnus-format method gnus-active-hashtb nil t)) + (t + (gnus-groups-to-gnus-format method gnus-active-hashtb t))))))) ;; Read an active file and place the results in `gnus-active-hashtb'. (defun gnus-active-to-gnus-format (&optional method hashtb ignore-errors @@ -1732,22 +1815,22 @@ newsgroup." (gnus-make-hashtable 4096))))))) ;; Delete unnecessary lines. (goto-char (point-min)) - (cond ((gnus-ignored-newsgroups-has-to-p) - (delete-matching-lines gnus-ignored-newsgroups)) - ((string= gnus-ignored-newsgroups "") - (delete-matching-lines "^to\\.")) - (t - (delete-matching-lines (concat "^to\\.\\|" - gnus-ignored-newsgroups)))) + (cond + ((string= gnus-ignored-newsgroups "") + (delete-matching-lines "^to\\.")) + (t + (delete-matching-lines (concat "^to\\.\\|" gnus-ignored-newsgroups)))) - ;; Make the group names readable as a lisp expression even if they - ;; contain special characters. - (goto-char (point-max)) - (while (re-search-backward "[][';?()#]" nil t) - (insert ?\\)) + (goto-char (point-min)) + (unless (re-search-forward "[\\\"]" nil t) + ;; Make the group names readable as a lisp expression even if they + ;; contain special characters. + (goto-char (point-max)) + (while (re-search-backward "[][';?()#]" nil t) + (insert ?\\))) ;; Let the Gnus agent save the active file. - (when (and gnus-agent real-active) + (when (and gnus-agent real-active gnus-plugged) (gnus-agent-save-active method)) ;; If these are groups from a foreign select method, we insert the @@ -1758,30 +1841,37 @@ newsgroup." (let ((prefix (gnus-group-prefixed-name "" method))) (goto-char (point-min)) (while (and (not (eobp)) - (progn (insert prefix) - (zerop (forward-line 1))))))) + (progn + (when (= (following-char) ?\") + (forward-char 1)) + (insert prefix) + (zerop (forward-line 1))))))) ;; Store the active file in a hash table. (goto-char (point-min)) (let (group max min) (while (not (eobp)) - (condition-case () + (condition-case err (progn (narrow-to-region (point) (gnus-point-at-eol)) ;; group gets set to a symbol interned in the hash table ;; (what a hack!!) - jwz (setq group (let ((obarray hashtb)) (read cur))) + ;; ### The extended group name scheme makes + ;; the previous optimization strategy sort of pointless... + (when (stringp group) + (setq group (intern group hashtb))) (if (and (numberp (setq max (read cur))) (numberp (setq min (read cur))) (progn (skip-chars-forward " \t") (not - (or (= (following-char) ?=) - (= (following-char) ?x) - (= (following-char) ?j))))) + (or (eq (char-after) ?=) + (eq (char-after) ?x) + (eq (char-after) ?j))))) (progn (set group (cons min max)) ;; if group is moderated, stick in moderation table - (when (= (following-char) ?m) + (when (eq (char-after) ?m) (unless gnus-moderated-hashtb (setq gnus-moderated-hashtb (gnus-make-hashtable))) (gnus-sethash (symbol-name group) t @@ -1792,7 +1882,7 @@ newsgroup." (symbolp group) (set group nil)) (unless ignore-errors - (gnus-message 3 "Warning - illegal active: %s" + (gnus-message 3 "Warning - invalid active: %s" (buffer-substring (gnus-point-at-bol) (gnus-point-at-eol)))))) (widen) @@ -1814,39 +1904,44 @@ newsgroup." (gnus-group-prefixed-name "" method)))) ;; Let the Gnus agent save the active file. - (when (and gnus-agent real-active) - (gnus-agent-save-groups method)) - - (goto-char (point-min)) - ;; We split this into to separate loops, one with the prefix - ;; and one without to speed the reading up somewhat. - (if prefix - (let (min max opoint group) + (if (and gnus-agent + real-active + gnus-plugged + (gnus-agent-method-p method)) + (progn + (gnus-agent-save-groups method) + (gnus-active-to-gnus-format method hashtb nil real-active)) + + (goto-char (point-min)) + ;; We split this into to separate loops, one with the prefix + ;; and one without to speed the reading up somewhat. + (if prefix + (let (min max opoint group) + (while (not (eobp)) + (condition-case () + (progn + (read cur) (read cur) + (setq min (read cur) + max (read cur) + opoint (point)) + (skip-chars-forward " \t") + (insert prefix) + (goto-char opoint) + (set (let ((obarray hashtb)) (read cur)) + (cons min max))) + (error (and group (symbolp group) (set group nil)))) + (forward-line 1))) + (let (min max group) (while (not (eobp)) (condition-case () - (progn + (when (eq (char-after) ?2) (read cur) (read cur) (setq min (read cur) - max (read cur) - opoint (point)) - (skip-chars-forward " \t") - (insert prefix) - (goto-char opoint) - (set (let ((obarray hashtb)) (read cur)) + max (read cur)) + (set (setq group (let ((obarray hashtb)) (read cur))) (cons min max))) (error (and group (symbolp group) (set group nil)))) - (forward-line 1))) - (let (min max group) - (while (not (eobp)) - (condition-case () - (when (= (following-char) ?2) - (read cur) (read cur) - (setq min (read cur) - max (read cur)) - (set (setq group (let ((obarray hashtb)) (read cur))) - (cons min max))) - (error (and group (symbolp group) (set group nil)))) - (forward-line 1)))))) + (forward-line 1))))))) (defun gnus-read-newsrc-file (&optional force) "Read startup file. @@ -1864,7 +1959,8 @@ If FORCE is non-nil, the .newsrc file is read." ;; file (ticked articles, killed groups, foreign methods, etc.) (gnus-read-newsrc-el-file quick-file) - (when (and (file-exists-p gnus-current-startup-file) + (when (and gnus-read-newsrc-file + (file-exists-p gnus-current-startup-file) (or force (and (file-newer-than-file-p newsrc-file quick-file) (file-newer-than-file-p newsrc-file @@ -1880,7 +1976,7 @@ If FORCE is non-nil, the .newsrc file is read." (save-excursion (gnus-message 5 "Reading %s..." newsrc-file) (set-buffer (nnheader-find-file-noselect newsrc-file)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (gnus-newsrc-to-gnus-format) (kill-buffer (current-buffer)) (gnus-message 5 "Reading %s...done" newsrc-file))) @@ -2056,7 +2152,7 @@ If FORCE is non-nil, the .newsrc file is read." (unless (boundp symbol) (set symbol nil)) ;; It was a group name. - (setq subscribed (= (following-char) ?:) + (setq subscribed (eq (char-after) ?:) group (symbol-name symbol) reads nil) (if (eolp) @@ -2080,7 +2176,7 @@ If FORCE is non-nil, the .newsrc file is read." (read buf))) (widen) ;; If the next character is a dash, then this is a range. - (if (= (following-char) ?-) + (if (eq (char-after) ?-) (progn ;; We read the upper bound of the range. (forward-char 1) @@ -2102,8 +2198,8 @@ If FORCE is non-nil, the .newsrc file is read." (push num1 reads)) ;; If the next char in ?\n, then we have reached the end ;; of the line and return nil. - (/= (following-char) ?\n)) - ((= (following-char) ?\n) + (not (eq (char-after) ?\n))) + ((eq (char-after) ?\n) ;; End of line, so we end. nil) (t @@ -2117,7 +2213,7 @@ If FORCE is non-nil, the .newsrc file is read." (buffer-substring (gnus-point-at-bol) (gnus-point-at-eol)))) nil)) - ;; Skip past ", ". Spaces are illegal in these ranges, but + ;; Skip past ", ". Spaces are invalid in these ranges, but ;; we allow them, because it's a common mistake to put a ;; space after the comma. (skip-chars-forward ", ")) @@ -2229,7 +2325,7 @@ If FORCE is non-nil, the .newsrc file is read." (gnus-point-at-eol))) ;; Search for all "words"... (while (re-search-forward "[^ \t,\n]+" eol t) - (if (= (char-after (match-beginning 0)) ?!) + (if (eq (char-after (match-beginning 0)) ?!) ;; If the word begins with a bang (!), this is a "not" ;; spec. We put this spec (minus the bang) and the ;; symbol `ignore' into the list. @@ -2277,7 +2373,7 @@ If FORCE is non-nil, the .newsrc file is read." (setq buffer-file-name (concat gnus-current-startup-file ".eld")) (setq default-directory (file-name-directory buffer-file-name)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) (gnus-message 5 "Saving %s.eld..." gnus-current-startup-file) (gnus-gnus-to-quick-newsrc-format) @@ -2294,6 +2390,7 @@ If FORCE is non-nil, the .newsrc file is read." "Insert Gnus variables such as gnus-newsrc-alist in lisp format." (let ((print-quoted t) (print-escape-newlines t)) + (insert ";; -*- emacs-lisp -*-\n") (insert ";; Gnus startup file.\n") (insert "\ @@ -2341,7 +2438,7 @@ If FORCE is non-nil, the .newsrc file is read." info ranges range method) (setq buffer-file-name gnus-current-startup-file) (setq default-directory (file-name-directory buffer-file-name)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) ;; Write options. (when gnus-newsrc-options @@ -2404,12 +2501,13 @@ If FORCE is non-nil, the .newsrc file is read." (save-excursion (set-buffer gnus-dribble-buffer) (let ((slave-name - (make-temp-file (concat gnus-current-startup-file "-slave-"))) + (make-temp-name (concat gnus-current-startup-file "-slave-"))) (modes (ignore-errors (file-modes (concat gnus-current-startup-file ".eld"))))) + (let ((coding-system-for-write gnus-startup-file-coding-system)) + (gnus-write-buffer slave-name)) (when modes - (set-file-modes slave-name modes)) - (gnus-write-buffer slave-name)))) + (set-file-modes slave-name modes))))) (defun gnus-master-read-slave-newsrc () (let ((slave-files @@ -2427,7 +2525,6 @@ If FORCE is non-nil, the .newsrc file is read." (gnus-message 7 "Reading slave newsrcs...") (save-excursion (set-buffer (gnus-get-buffer-create " *gnus slave*")) - (buffer-disable-undo (current-buffer)) (setq slave-files (sort (mapcar (lambda (file) (list (nth 5 (file-attributes file)) file)) @@ -2438,7 +2535,7 @@ If FORCE is non-nil, the .newsrc file is read." (while slave-files (erase-buffer) (setq file (nth 1 (car slave-files))) - (insert-file-contents file) + (nnheader-insert-file-contents file) (when (condition-case () (progn (eval-buffer (current-buffer)) @@ -2485,6 +2582,8 @@ If FORCE is non-nil, the .newsrc file is read." (gnus-message 5 "Reading descriptions file via %s..." (car method)) (cond + ((null (gnus-get-function method 'request-list-newsgroups t)) + t) ((not (gnus-check-server method)) (gnus-message 1 "Couldn't open server") nil) @@ -2529,12 +2628,13 @@ If FORCE is non-nil, the .newsrc file is read." (let ((str (buffer-substring (point) (progn (end-of-line) (point)))) (coding - (and (boundp 'enable-multibyte-characters) - enable-multibyte-characters + (and (or gnus-xemacs + (and (boundp 'enable-multibyte-characters) + enable-multibyte-characters)) (fboundp 'gnus-mule-get-coding-system) (gnus-mule-get-coding-system (symbol-name group))))) - (if coding - (setq str (gnus-decode-coding-string str (car coding)))) + (when coding + (setq str (mm-decode-coding-string str (car coding)))) (set group str))) (forward-line 1)))) (gnus-message 5 "Reading descriptions file...done") @@ -2554,7 +2654,8 @@ If FORCE is non-nil, the .newsrc file is read." "Declare backend NAME with ABILITIES as a Gnus backend." (setq gnus-valid-select-methods (nconc gnus-valid-select-methods - (list (apply 'list name abilities))))) + (list (apply 'list name abilities)))) + (gnus-redefine-select-method-widget)) (defun gnus-set-default-directory () "Set the default directory in the current buffer to `gnus-default-directory'. diff --git a/lisp/gnus/gnus-sum.el b/lisp/gnus/gnus-sum.el index 45658018139..52d93cb3c18 100644 --- a/lisp/gnus/gnus-sum.el +++ b/lisp/gnus/gnus-sum.el @@ -1,5 +1,6 @@ ;;; gnus-sum.el --- summary mode commands for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-group) (require 'gnus-spec) @@ -36,6 +35,7 @@ (require 'gnus-int) (require 'gnus-undo) (require 'gnus-util) +(require 'mm-decode) (autoload 'gnus-summary-limit-include-cached "gnus-cache" nil t) (defcustom gnus-kill-summary-on-exit t @@ -169,10 +169,15 @@ This variable will only be used if the value of :type 'string) (defcustom gnus-summary-goto-unread t - "*If t, marking commands will go to the next unread article. -If `never', commands that usually go to the next unread article, will -go to the next article, whether it is read or not. -If nil, only the marking commands will go to the next (un)read article." + "*If t, many commands will go to the next unread article. +This applies to marking commands as well as other commands that +\"naturally\" select the next article, like, for instance, `SPC' at +the end of an article. + +If nil, the marking commands do NOT go to the next unread article +(they go to the next article instead). If `never', commands that +usually go to the next unread article, will go to the next article, +whether it is read or not." :group 'gnus-summary-marks :link '(custom-manual "(gnus)Setting Marks") :type '(choice (const :tag "off" nil) @@ -254,8 +259,12 @@ equal will be included." (defcustom gnus-auto-select-first t "*If nil, don't select the first unread article when entering a group. If this variable is `best', select the highest-scored unread article -in the group. If neither nil nor `best', select the first unread -article. +in the group. If t, select the first unread article. + +This variable can also be a function to place point on a likely +subject line. Useful values include `gnus-summary-first-unread-subject', +`gnus-summary-first-unread-article' and +`gnus-summary-best-unread-article'. If you want to prevent automatic selection of the first unread article in some newsgroups, set the variable to nil in @@ -263,7 +272,10 @@ in some newsgroups, set the variable to nil in :group 'gnus-group-select :type '(choice (const :tag "none" nil) (const best) - (sexp :menu-tag "first" t))) + (sexp :menu-tag "first" t) + (function-item gnus-summary-first-unread-subject) + (function-item gnus-summary-first-unread-article) + (function-item gnus-summary-best-unread-article))) (defcustom gnus-auto-select-next t "*If non-nil, offer to go to the next group from the end of the previous. @@ -304,6 +316,7 @@ and non-`vertical', do both horizontal and vertical recentering." :group 'gnus-summary-maneuvering :type '(choice (const :tag "none" nil) (const vertical) + (integer :tag "height") (sexp :menu-tag "both" t))) (defcustom gnus-show-all-headers nil @@ -330,13 +343,6 @@ variable." :group 'gnus-article-various :type 'boolean) -(defcustom gnus-show-mime nil - "*If non-nil, do mime processing of articles. -The articles will simply be fed to the function given by -`gnus-show-mime-method'." - :group 'gnus-article-mime - :type 'boolean) - (defcustom gnus-move-split-methods nil "*Variable used to suggest where articles are to be moved to. It uses the same syntax as the `gnus-split-methods' variable." @@ -345,7 +351,7 @@ It uses the same syntax as the `gnus-split-methods' variable." (cons :value ("" "") regexp (repeat string)) (sexp :value nil)))) -(defcustom gnus-unread-mark ? ;space +(defcustom gnus-unread-mark ? ;Whitespace "*Mark used for unread articles." :group 'gnus-summary-marks :type 'character) @@ -460,7 +466,7 @@ It uses the same syntax as the `gnus-split-methods' variable." :group 'gnus-summary-marks :type 'character) -(defcustom gnus-empty-thread-mark ? ;space +(defcustom gnus-empty-thread-mark ? ;Whitespace "*There is no thread under the article." :group 'gnus-summary-marks :type 'character) @@ -475,6 +481,19 @@ It uses the same syntax as the `gnus-split-methods' variable." :group 'gnus-extract-view :type 'boolean) +(defcustom gnus-auto-expirable-marks + (list gnus-killed-mark gnus-del-mark gnus-catchup-mark + gnus-low-score-mark gnus-ancient-mark gnus-read-mark + gnus-souped-mark gnus-duplicate-mark) + "*The list of marks converted into expiration if a group is auto-expirable." + :group 'gnus-summary + :type '(repeat character)) + +(defcustom gnus-inhibit-user-auto-expire t + "*If non-nil, user marking commands will not mark an article as expirable, even if the group has auto-expire turned on." + :group 'gnus-summary + :type 'boolean) + (defcustom gnus-view-pseudos nil "*If `automatic', pseudo-articles will be viewed automatically. If `not-confirm', pseudos will be viewed automatically, and the user @@ -506,7 +525,7 @@ with some simple extensions. :group 'gnus-threading :type 'string) -(defcustom gnus-summary-mode-line-format "Gnus: %%b [%A] %Z" +(defcustom gnus-summary-mode-line-format "Gnus: %g [%A] %Z" "*The format specification for the summary mode line. It works along the same lines as a normal formatting string, with some simple extensions: @@ -529,6 +548,15 @@ with some simple extensions: :group 'gnus-summary-format :type 'string) +(defcustom gnus-list-identifiers nil + "Regexp that matches list identifiers to be removed from subject. +This can also be a list of regexps." + :group 'gnus-summary-format + :group 'gnus-article-hiding + :type '(choice (const :tag "none" nil) + (regexp :value ".*") + (repeat :value (".*") regexp))) + (defcustom gnus-summary-mark-below 0 "*Mark all articles with a score below this variable as read. This variable is local to each summary buffer and usually set by the @@ -593,7 +621,7 @@ See `gnus-thread-score-function' for en explanation of what a \"thread score\" is. This variable is local to the summary buffers." - :group 'gnus-treading + :group 'gnus-threading :group 'gnus-score-default :type '(choice (const :tag "off" nil) integer)) @@ -665,38 +693,14 @@ is not run if `gnus-visual' is nil." :group 'gnus-summary-visual :type 'hook) -(defcustom gnus-structured-field-decoder - (if (and (featurep 'mule) - (boundp 'enable-multibyte-characters)) - (lambda (string) - (if (and enable-multibyte-characters gnus-mule-coding-system) - (decode-coding-string string gnus-mule-coding-system) - string)) - 'identity) - "Function to decode non-ASCII characters in structured field for summary." - :group 'gnus-various - :type 'function) - -(defcustom gnus-unstructured-field-decoder - (if (and (featurep 'mule) - (boundp 'enable-multibyte-characters)) - (lambda (string) - (if (and enable-multibyte-characters gnus-mule-coding-system) - (decode-coding-string string gnus-mule-coding-system) - string)) - 'identity) - "Function to decode non-ASCII characters in unstructured field for summary." - :group 'gnus-various - :type 'function) - -(defcustom gnus-parse-headers-hook - (list 'gnus-hack-decode-rfc1522 'gnus-decode-rfc1522) +(defcustom gnus-parse-headers-hook nil "*A hook called before parsing the headers." :group 'gnus-various :type 'hook) (defcustom gnus-exit-group-hook nil - "*A hook called when exiting (not quitting) summary mode." + "*A hook called when exiting summary mode. +This hook is not called from the non-updating exit commands like `Q'." :group 'gnus-various :type 'hook) @@ -795,10 +799,99 @@ mark: The articles mark." The function is called with one parameter, the article header vector, which it may alter in any way.") +(defvar gnus-decode-encoded-word-function 'mail-decode-encoded-word-string + "Variable that says which function should be used to decode a string with encoded words.") + +(defcustom gnus-extra-headers nil + "*Extra headers to parse." + :group 'gnus-summary + :type '(repeat symbol)) + +(defcustom gnus-ignored-from-addresses + (and user-mail-address (regexp-quote user-mail-address)) + "*Regexp of From headers that may be suppressed in favor of To headers." + :group 'gnus-summary + :type 'regexp) + +(defcustom gnus-group-charset-alist + '(("^hk\\>\\|^tw\\>\\|\\" cn-big5) + ("^cn\\>\\|\\" cn-gb-2312) + ("^fj\\>\\|^japan\\>" iso-2022-jp-2) + ("^tnn\\>\\|^pin\\>\\|^sci.lang.japan" iso-2022-7bit) + ("^relcom\\>" koi8-r) + ("^fido7\\>" koi8-r) + ("^\\(cz\\|hun\\|pl\\|sk\\|hr\\)\\>" iso-8859-2) + ("^israel\\>" iso-8859-1) + ("^han\\>" euc-kr) + ("^alt.chinese.text.big5\\>" chinese-big5) + ("^soc.culture.vietnamese\\>" vietnamese-viqr) + ("^\\(comp\\|rec\\|alt\\|sci\\|soc\\|news\\|gnu\\|bofh\\)\\>" iso-8859-1) + (".*" iso-8859-1)) + "Alist of regexps (to match group names) and default charsets to be used when reading." + :type '(repeat (list (regexp :tag "Group") + (symbol :tag "Charset"))) + :group 'gnus-charset) + +(defcustom gnus-newsgroup-ignored-charsets '(unknown-8bit x-unknown) + "List of charsets that should be ignored. +When these charsets are used in the \"charset\" parameter, the +default charset will be used instead." + :type '(repeat symbol) + :group 'gnus-charset) + +(defcustom gnus-group-ignored-charsets-alist + '(("alt\\.chinese\\.text" iso-8859-1)) + "Alist of regexps (to match group names) and charsets that should be ignored. +When these charsets are used in the \"charset\" parameter, the +default charset will be used instead." + :type '(repeat (cons (regexp :tag "Group") + (repeat symbol))) + :group 'gnus-charset) + +(defcustom gnus-group-highlight-words-alist nil + "Alist of group regexps and highlight regexps. +This variable uses the same syntax as `gnus-emphasis-alist'." + :type '(repeat (cons (regexp :tag "Group") + (repeat (list (regexp :tag "Highlight regexp") + (number :tag "Group for entire word" 0) + (number :tag "Group for displayed part" 0) + (symbol :tag "Face" + gnus-emphasis-highlight-words))))) + :group 'gnus-summary-visual) + +(defcustom gnus-summary-show-article-charset-alist + nil + "Alist of number and charset. +The article will be shown with the charset corresponding to the +numbered argument. +For example: ((1 . cn-gb-2312) (2 . big5))." + :type '(repeat (cons (number :tag "Argument" 1) + (symbol :tag "Charset"))) + :group 'gnus-charset) + +(defcustom gnus-preserve-marks t + "Whether marks are preserved when moving, copying and respooling messages." + :type 'boolean + :group 'gnus-summary-marks) + +(defcustom gnus-alter-articles-to-read-function nil + "Function to be called to alter the list of articles to be selected." + :type 'function + :group 'gnus-summary) + +(defcustom gnus-orphan-score nil + "*All orphans get this score added. Set in the score file." + :group 'gnus-score-default + :type '(choice (const nil) + integer)) + ;;; Internal variables +(defvar gnus-article-mime-handles nil) +(defvar gnus-article-decoded-p nil) (defvar gnus-scores-exclude-files nil) (defvar gnus-page-broken nil) +(defvar gnus-inhibit-mime-unbuttonizing nil) (defvar gnus-original-article nil) (defvar gnus-article-internal-prepare-hook nil) @@ -806,6 +899,8 @@ which it may alter in any way.") (defvar gnus-thread-indent-array nil) (defvar gnus-thread-indent-array-level gnus-thread-indent-level) +(defvar gnus-sort-gathered-threads-function 'gnus-thread-sort-by-number + "Function called to sort the articles within a thread after it has been gathered together.") ;; Avoid highlighting in kill files. (defvar gnus-summary-inhibit-highlight nil) @@ -853,6 +948,7 @@ which it may alter in any way.") (?l (bbb-grouplens-score gnus-tmp-header) ?s) (?V (gnus-thread-total-score (and (boundp 'thread) (car thread))) ?d) (?U gnus-tmp-unread ?c) + (?f (gnus-summary-from-or-to-or-newsgroups gnus-tmp-header) ?s) (?t (gnus-summary-number-of-articles-in-thread (and (boundp 'thread) (car thread)) gnus-tmp-level) ?d) @@ -861,9 +957,9 @@ which it may alter in any way.") ?c) (?u gnus-tmp-user-defined ?s) (?P (gnus-pick-line-number) ?d)) - "An alist of format specifications that can appear in summary lines, -and what variables they correspond with, along with the type of the -variable (string, integer, character, etc).") + "An alist of format specifications that can appear in summary lines. +These are paired with what variables they correspond with, along with +the type of the variable (string, integer, character, etc).") (defvar gnus-summary-dummy-line-format-alist `((?S gnus-tmp-subject ?s) @@ -979,6 +1075,9 @@ variable (string, integer, character, etc).") (defvar gnus-have-all-headers nil) (defvar gnus-last-article nil) (defvar gnus-newsgroup-history nil) +(defvar gnus-newsgroup-charset nil) +(defvar gnus-newsgroup-ephemeral-charset nil) +(defvar gnus-newsgroup-ephemeral-ignored-charsets nil) (defconst gnus-summary-local-variables '(gnus-newsgroup-name @@ -1000,8 +1099,10 @@ variable (string, integer, character, etc).") gnus-newsgroup-dependencies gnus-newsgroup-selected-overlay gnus-newsgroup-scored gnus-newsgroup-kill-headers gnus-thread-expunge-below - gnus-score-alist gnus-current-score-file gnus-summary-expunge-below + gnus-score-alist gnus-current-score-file + (gnus-summary-expunge-below . global) (gnus-summary-mark-below . global) + (gnus-orphan-score . global) gnus-newsgroup-active gnus-scores-exclude-files gnus-newsgroup-history gnus-newsgroup-ancient gnus-newsgroup-sparse gnus-newsgroup-process-stack @@ -1010,16 +1111,55 @@ variable (string, integer, character, etc).") (gnus-newsgroup-expunged-tally . 0) gnus-cache-removable-articles gnus-newsgroup-cached gnus-newsgroup-data gnus-newsgroup-data-reverse - gnus-newsgroup-limit gnus-newsgroup-limits) + gnus-newsgroup-limit gnus-newsgroup-limits + gnus-newsgroup-charset) "Variables that are buffer-local to the summary buffers.") ;; Byte-compiler warning. (defvar gnus-article-mode-map) +;; MIME stuff. + +(defvar gnus-decode-encoded-word-methods + '(mail-decode-encoded-word-string) + "List of methods used to decode encoded words. + +This variable is a list of FUNCTION or (REGEXP . FUNCTION). If item is +FUNCTION, FUNCTION will be apply to all newsgroups. If item is a +(REGEXP . FUNCTION), FUNCTION will be only apply to thes newsgroups +whose names match REGEXP. + +For example: +((\"chinese\" . gnus-decode-encoded-word-string-by-guess) + mail-decode-encoded-word-string + (\"chinese\" . rfc1843-decode-string))") + +(defvar gnus-decode-encoded-word-methods-cache nil) + +(defun gnus-multi-decode-encoded-word-string (string) + "Apply the functions from `gnus-encoded-word-methods' that match." + (unless (and gnus-decode-encoded-word-methods-cache + (eq gnus-newsgroup-name + (car gnus-decode-encoded-word-methods-cache))) + (setq gnus-decode-encoded-word-methods-cache (list gnus-newsgroup-name)) + (mapcar (lambda (x) + (if (symbolp x) + (nconc gnus-decode-encoded-word-methods-cache (list x)) + (if (and gnus-newsgroup-name + (string-match (car x) gnus-newsgroup-name)) + (nconc gnus-decode-encoded-word-methods-cache + (list (cdr x)))))) + gnus-decode-encoded-word-methods)) + (let ((xlist gnus-decode-encoded-word-methods-cache)) + (pop xlist) + (while xlist + (setq string (funcall (pop xlist) string)))) + string) + ;; Subject simplification. (defun gnus-simplify-whitespace (str) - "Remove excessive whitespace." + "Remove excessive whitespace from STR." (let ((mystr str)) ;; Multiple spaces. (while (string-match "[ \t][ \t]+" mystr) @@ -1064,7 +1204,7 @@ If RE-ONLY is non-nil, strip leading `Re:'s only." (defsubst gnus-simplify-buffer-fuzzy-step (regexp &optional newtext) (goto-char (point-min)) (while (re-search-forward regexp nil t) - (replace-match (or newtext "")))) + (replace-match (or newtext "")))) (defun gnus-simplify-buffer-fuzzy () "Simplify string in the buffer fuzzily. @@ -1072,7 +1212,7 @@ The string in the accessible portion of the current buffer is simplified. It is assumed to be a single-line subject. Whitespace is generally cleaned up, and miscellaneous leading/trailing matter is removed. Additional things can be deleted by setting -gnus-simplify-subject-fuzzy-regexp." +`gnus-simplify-subject-fuzzy-regexp'." (let ((case-fold-search t) (modified-tick)) (gnus-simplify-buffer-fuzzy-step "\t" " ") @@ -1196,6 +1336,8 @@ increase the score of each group you read." "\M-\C-h" gnus-summary-hide-thread "\M-\C-f" gnus-summary-next-thread "\M-\C-b" gnus-summary-prev-thread + [(meta down)] gnus-summary-next-thread + [(meta up)] gnus-summary-prev-thread "\M-\C-u" gnus-summary-up-thread "\M-\C-d" gnus-summary-down-thread "&" gnus-summary-execute-command @@ -1206,6 +1348,7 @@ increase the score of each group you read." "\C-c\M-\C-s" gnus-summary-limit-include-expunged "\C-c\C-s\C-n" gnus-summary-sort-by-number "\C-c\C-s\C-l" gnus-summary-sort-by-lines + "\C-c\C-s\C-c" gnus-summary-sort-by-chars "\C-c\C-s\C-a" gnus-summary-sort-by-author "\C-c\C-s\C-s" gnus-summary-sort-by-subject "\C-c\C-s\C-d" gnus-summary-sort-by-date @@ -1215,7 +1358,6 @@ increase the score of each group you read." "\M-g" gnus-summary-rescan-group "w" gnus-summary-stop-page-breaking "\C-c\C-r" gnus-summary-caesar-message - "\M-t" gnus-summary-toggle-mime "f" gnus-summary-followup "F" gnus-summary-followup-with-original "C" gnus-summary-cancel-article @@ -1237,13 +1379,14 @@ increase the score of each group you read." "a" gnus-summary-post-news "x" gnus-summary-limit-to-unread "s" gnus-summary-isearch-article - "t" gnus-article-hide-headers + "t" gnus-summary-toggle-header "g" gnus-summary-show-article "l" gnus-summary-goto-last-article "\C-c\C-v\C-v" gnus-uu-decode-uu-view "\C-d" gnus-summary-enter-digest-group "\M-\C-d" gnus-summary-read-document "\M-\C-e" gnus-summary-edit-parameters + "\M-\C-a" gnus-summary-customize-parameters "\C-c\C-b" gnus-bug "*" gnus-cache-enter-article "\M-*" gnus-cache-remove-article @@ -1254,6 +1397,9 @@ increase the score of each group you read." "\M-i" gnus-symbolic-argument "h" gnus-summary-select-article-buffer + "b" gnus-article-view-part + "\M-t" gnus-summary-toggle-display-buttonized + "V" gnus-summary-score-map "X" gnus-uu-extract-map "S" gnus-summary-send-map) @@ -1295,12 +1441,14 @@ increase the score of each group you read." "a" gnus-summary-limit-to-author "u" gnus-summary-limit-to-unread "m" gnus-summary-limit-to-marks + "M" gnus-summary-limit-exclude-marks "v" gnus-summary-limit-to-score "*" gnus-summary-limit-include-cached "D" gnus-summary-limit-include-dormant "T" gnus-summary-limit-include-thread "d" gnus-summary-limit-exclude-dormant "t" gnus-summary-limit-to-age + "x" gnus-summary-limit-to-extra "E" gnus-summary-limit-include-expunged "c" gnus-summary-limit-exclude-childless-dormant "C" gnus-summary-limit-mark-excluded-as-read) @@ -1371,11 +1519,13 @@ increase the score of each group you read." "e" gnus-summary-end-of-article "^" gnus-summary-refer-parent-article "r" gnus-summary-refer-parent-article + "D" gnus-summary-enter-digest-group "R" gnus-summary-refer-references "T" gnus-summary-refer-thread "g" gnus-summary-show-article "s" gnus-summary-isearch-article - "P" gnus-summary-print-article) + "P" gnus-summary-print-article + "t" gnus-article-babel) (gnus-define-keys (gnus-summary-wash-map "W" gnus-summary-mode-map) "b" gnus-article-add-buttons @@ -1383,15 +1533,19 @@ increase the score of each group you read." "o" gnus-article-treat-overstrike "e" gnus-article-emphasize "w" gnus-article-fill-cited-article + "Q" gnus-article-fill-long-lines + "C" gnus-article-capitalize-sentences "c" gnus-article-remove-cr "q" gnus-article-de-quoted-unreadable + "6" gnus-article-de-base64-unreadable + "Z" gnus-article-decode-HZ + "h" gnus-article-wash-html "f" gnus-article-display-x-face "l" gnus-summary-stop-page-breaking "r" gnus-summary-caesar-message - "t" gnus-article-hide-headers + "t" gnus-summary-toggle-header "v" gnus-summary-verbose-headers - "m" gnus-summary-toggle-mime - "h" gnus-article-treat-html + "H" gnus-article-strip-headers-in-body "d" gnus-article-treat-dumbquotes) (gnus-define-keys (gnus-summary-wash-hide-map "W" gnus-summary-wash-map) @@ -1401,7 +1555,9 @@ increase the score of each group you read." "s" gnus-article-hide-signature "c" gnus-article-hide-citation "C" gnus-article-hide-citation-in-followups + "l" gnus-article-hide-list-identifiers "p" gnus-article-hide-pgp + "B" gnus-article-strip-banner "P" gnus-article-hide-pem "\C-c" gnus-article-hide-citation-maybe) @@ -1411,6 +1567,12 @@ increase the score of each group you read." "c" gnus-article-highlight-citation "s" gnus-article-highlight-signature) + (gnus-define-keys (gnus-summary-wash-mime-map "M" gnus-summary-wash-map) + "w" gnus-article-decode-mime-words + "c" gnus-article-decode-charset + "v" gnus-mime-view-all-parts + "b" gnus-article-view-part) + (gnus-define-keys (gnus-summary-wash-time-map "T" gnus-summary-wash-map) "z" gnus-article-date-ut "u" gnus-article-date-ut @@ -1426,7 +1588,8 @@ increase the score of each group you read." "m" gnus-article-strip-multiple-blank-lines "a" gnus-article-strip-blank-lines "A" gnus-article-strip-all-blank-lines - "s" gnus-article-strip-leading-space) + "s" gnus-article-strip-leading-space + "e" gnus-article-strip-trailing-space) (gnus-define-keys (gnus-summary-help-map "H" gnus-summary-mode-map) "v" gnus-version @@ -1440,6 +1603,7 @@ increase the score of each group you read." "\M-\C-e" gnus-summary-expire-articles-now "\177" gnus-summary-delete-article [delete] gnus-summary-delete-article + [backspace] gnus-summary-delete-article "m" gnus-summary-move-article "r" gnus-summary-respool-article "w" gnus-summary-edit-article @@ -1460,7 +1624,17 @@ increase the score of each group you read." "h" gnus-summary-save-article-folder "v" gnus-summary-save-article-vm "p" gnus-summary-pipe-output - "s" gnus-soup-add-article)) + "s" gnus-soup-add-article) + + (gnus-define-keys (gnus-summary-mime-map "K" gnus-summary-mode-map) + "b" gnus-summary-display-buttonized + "m" gnus-summary-repair-multipart + "v" gnus-article-view-part + "o" gnus-article-save-part + "c" gnus-article-copy-part + "e" gnus-article-externalize-part + "i" gnus-article-inline-part + "|" gnus-article-pipe-part)) (defun gnus-summary-make-menu-bar () (gnus-turn-off-edit-menu 'summary) @@ -1503,13 +1677,21 @@ increase the score of each group you read." ["Headers" gnus-article-hide-headers t] ["Signature" gnus-article-hide-signature t] ["Citation" gnus-article-hide-citation t] + ["List identifiers" gnus-article-hide-list-identifiers t] ["PGP" gnus-article-hide-pgp t] + ["Banner" gnus-article-strip-banner t] ["Boring headers" gnus-article-hide-boring-headers t]) ("Highlight" ["All" gnus-article-highlight t] ["Headers" gnus-article-highlight-headers t] ["Signature" gnus-article-highlight-signature t] ["Citation" gnus-article-highlight-citation t]) + ("MIME" + ["Words" gnus-article-decode-mime-words t] + ["Charset" gnus-article-decode-charset t] + ["QP" gnus-article-de-quoted-unreadable t] + ["Base64" gnus-article-de-base64-unreadable t] + ["View all" gnus-mime-view-all-parts t]) ("Date" ["Local" gnus-article-date-local t] ["ISO8601" gnus-article-date-iso8601 t] @@ -1524,23 +1706,27 @@ increase the score of each group you read." ["Trailing" gnus-article-remove-trailing-blank-lines t] ["All of the above" gnus-article-strip-blank-lines t] ["All" gnus-article-strip-all-blank-lines t] - ["Leading space" gnus-article-strip-leading-space t]) + ["Leading space" gnus-article-strip-leading-space t] + ["Trailing space" gnus-article-strip-trailing-space t]) ["Overstrike" gnus-article-treat-overstrike t] ["Dumb quotes" gnus-article-treat-dumbquotes t] ["Emphasis" gnus-article-emphasize t] ["Word wrap" gnus-article-fill-cited-article t] + ["Fill long lines" gnus-article-fill-long-lines t] + ["Capitalize sentences" gnus-article-capitalize-sentences t] ["CR" gnus-article-remove-cr t] ["Show X-Face" gnus-article-display-x-face t] ["Quoted-Printable" gnus-article-de-quoted-unreadable t] - ["UnHTMLize" gnus-article-treat-html t] + ["Base64" gnus-article-de-base64-unreadable t] ["Rot 13" gnus-summary-caesar-message t] ["Unix pipe" gnus-summary-pipe-message t] ["Add buttons" gnus-article-add-buttons t] ["Add buttons to head" gnus-article-add-buttons-to-head t] ["Stop page breaking" gnus-summary-stop-page-breaking t] - ["Toggle MIME" gnus-summary-toggle-mime t] ["Verbose header" gnus-summary-verbose-headers t] - ["Toggle header" gnus-summary-toggle-header t]) + ["Toggle header" gnus-summary-toggle-header t] + ["Html" gnus-article-wash-html t] + ["HZ" gnus-article-decode-HZ t]) ("Output" ["Save in default format" gnus-summary-save-article t] ["Save in file" gnus-summary-save-article-file t] @@ -1584,6 +1770,7 @@ increase the score of each group you read." ("Cache" ["Enter article" gnus-cache-enter-article t] ["Remove article" gnus-cache-remove-article t]) + ["Translate" gnus-article-babel t] ["Select article buffer" gnus-summary-select-article-buffer t] ["Enter digest buffer" gnus-summary-enter-digest-group t] ["Isearch article..." gnus-summary-isearch-article t] @@ -1618,8 +1805,7 @@ increase the score of each group you read." ["Mark thread as read" gnus-summary-kill-thread t] ["Lower thread score" gnus-summary-lower-thread t] ["Raise thread score" gnus-summary-raise-thread t] - ["Rethread current" gnus-summary-rethread-current t] - )) + ["Rethread current" gnus-summary-rethread-current t])) (easy-menu-define gnus-summary-post-menu gnus-summary-mode-map "" @@ -1674,6 +1860,7 @@ increase the score of each group you read." ["Subject..." gnus-summary-limit-to-subject t] ["Author..." gnus-summary-limit-to-author t] ["Age..." gnus-summary-limit-to-age t] + ["Extra..." gnus-summary-limit-to-extra t] ["Score" gnus-summary-limit-to-score t] ["Unread" gnus-summary-limit-to-unread t] ["Non-dormant" gnus-summary-limit-exclude-dormant t] @@ -1683,6 +1870,7 @@ increase the score of each group you read." ["Hide childless dormant" gnus-summary-limit-exclude-childless-dormant t] ;;["Hide thread" gnus-summary-limit-exclude-thread t] + ["Hide marked" gnus-summary-limit-exclude-marks t] ["Show expunged" gnus-summary-show-all-expunged t]) ("Process Mark" ["Set mark" gnus-summary-mark-as-processable t] @@ -1729,7 +1917,8 @@ increase the score of each group you read." ["Sort by subject" gnus-summary-sort-by-subject t] ["Sort by date" gnus-summary-sort-by-date t] ["Sort by score" gnus-summary-sort-by-score t] - ["Sort by lines" gnus-summary-sort-by-lines t]) + ["Sort by lines" gnus-summary-sort-by-lines t] + ["Sort by characters" gnus-summary-sort-by-chars t]) ("Help" ["Fetch group FAQ" gnus-summary-fetch-faq t] ["Describe group" gnus-summary-describe-group t] @@ -1753,6 +1942,7 @@ increase the score of each group you read." ["Edit local kill file" gnus-summary-edit-local-kill t] ["Edit main kill file" gnus-summary-edit-global-kill t] ["Edit group parameters" gnus-summary-edit-parameters t] + ["Customize group parameters" gnus-summary-customize-parameters t] ["Send a bug report" gnus-bug t] ("Exit" ["Catchup and exit" gnus-summary-catchup-and-exit t] @@ -1783,6 +1973,7 @@ increase the score of each group you read." ("article body" "body" string) ("article head" "head" string) ("xref" "xref" string) + ("extra header" "extra" string) ("lines" "lines" number) ("followups to author" "followup" string))) (types '((number ("less than" <) @@ -1837,7 +2028,8 @@ increase the score of each group you read." (list 'gnus-summary-header (nth 1 header))) (list 'quote (nth 1 (car ts))) - (list 'gnus-score-default nil) + (list 'gnus-score-delta-default + nil) (nth 1 (car ps)) t) t) @@ -1884,7 +2076,7 @@ The following commands are available: (setq mode-name "Summary") (make-local-variable 'minor-mode-alist) (use-local-map gnus-summary-mode-map) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq buffer-read-only t) ;Disable modification (setq truncate-lines t) (setq selective-display t) @@ -1897,19 +2089,17 @@ The following commands are available: (make-local-variable 'gnus-summary-dummy-line-format) (make-local-variable 'gnus-summary-dummy-line-format-spec) (make-local-variable 'gnus-summary-mark-positions) - (make-local-hook 'post-command-hook) - (add-hook 'post-command-hook 'gnus-clear-inboxes-moved nil t) (make-local-hook 'pre-command-hook) (add-hook 'pre-command-hook 'gnus-set-global-variables nil t) (gnus-run-hooks 'gnus-summary-mode-hook) + (mm-enable-multibyte) (gnus-update-format-specifications nil 'summary 'summary-mode 'summary-dummy) (gnus-update-summary-mark-positions)) (defun gnus-summary-make-local-variables () "Make all the local summary buffer variables." - (let ((locals gnus-summary-local-variables) - global local) - (while (setq local (pop locals)) + (let (global) + (dolist (local gnus-summary-local-variables) (if (consp local) (progn (if (eq (cdr local) 'global) @@ -1917,11 +2107,9 @@ The following commands are available: (setq global (symbol-value (car local))) ;; Use the value from the list. (setq global (eval (cdr local)))) - (make-local-variable (car local)) - (set (car local) global)) + (set (make-local-variable (car local)) global)) ;; Simple nil-valued local variable. - (make-local-variable local) - (set local nil))))) + (set (make-local-variable local) nil))))) (defun gnus-summary-clear-local-variables () (let ((locals gnus-summary-local-variables)) @@ -2215,26 +2403,6 @@ marks of articles." ,@forms) (gnus-restore-hidden-threads-configuration ,config))))) -(defun gnus-hidden-threads-configuration () - "Return the current hidden threads configuration." - (save-excursion - (let (config) - (goto-char (point-min)) - (while (search-forward "\r" nil t) - (push (1- (point)) config)) - config))) - -(defun gnus-restore-hidden-threads-configuration (config) - "Restore hidden threads configuration from CONFIG." - (let (point buffer-read-only) - (while (setq point (pop config)) - (when (and (< point (point-max)) - (goto-char point) - (= (following-char) ?\n)) - (subst-char-in-region point (1+ point) ?\n ?\r))))) - -;; This needs to be put here because it uses the -;; gnus-save-hidden-threads macro (defun gnus-data-compute-positions () "Compute the positions of all articles." (setq gnus-newsgroup-data-reverse nil) @@ -2250,6 +2418,25 @@ marks of articles." (setq data (cdr data)) (forward-line 1)))))) +(defun gnus-hidden-threads-configuration () + "Return the current hidden threads configuration." + (save-excursion + (let (config) + (goto-char (point-min)) + (while (search-forward "\r" nil t) + (push (1- (point)) config)) + config))) + +(defun gnus-restore-hidden-threads-configuration (config) + "Restore hidden threads configuration from CONFIG." + (save-excursion + (let (point buffer-read-only) + (while (setq point (pop config)) + (when (and (< point (point-max)) + (goto-char point) + (eq (char-after) ?\n)) + (subst-char-in-region point (1+ point) ?\n ?\r)))))) + ;; Various summary mode internalish functions. (defun gnus-mouse-pick-article (e) @@ -2258,9 +2445,10 @@ marks of articles." (gnus-summary-next-page nil t)) (defun gnus-summary-set-display-table () - ;; Change the display table. Odd characters have a tendency to mess - ;; up nicely formatted displays - we make all possible glyphs - ;; display only a single character. + "Change the display table. +Odd characters have a tendency to mess +up nicely formatted displays - we make all possible glyphs +display only a single character." ;; We start from the standard display table, if any. (let ((table (or (copy-sequence standard-display-table) @@ -2304,9 +2492,9 @@ marks of articles." t))) (defun gnus-set-global-variables () - ;; Set the global equivalents of the summary buffer-local variables - ;; to the latest values they had. These reflect the summary buffer - ;; that was in action when the last article was fetched. + "Set the global equivalents of the buffer-local variables. +They are set to the latest values they had. These reflect the summary +buffer that was in action when the last article was fetched." (when (eq major-mode 'gnus-summary-mode) (setq gnus-summary-buffer (current-buffer)) (let ((name gnus-newsgroup-name) @@ -2319,7 +2507,8 @@ marks of articles." (original gnus-original-article-buffer) (gac gnus-article-current) (reffed gnus-reffed-article-number) - (score-file gnus-current-score-file)) + (score-file gnus-current-score-file) + (default-charset gnus-newsgroup-charset)) (save-excursion (set-buffer gnus-group-buffer) (setq gnus-newsgroup-name name @@ -2332,7 +2521,8 @@ marks of articles." gnus-article-buffer article-buffer gnus-original-article-buffer original gnus-reffed-article-number reffed - gnus-current-score-file score-file) + gnus-current-score-file score-file + gnus-newsgroup-charset default-charset) ;; The article buffer also has local variables. (when (gnus-buffer-live-p gnus-article-buffer) (set-buffer gnus-article-buffer) @@ -2351,7 +2541,8 @@ marks of articles." (defun gnus-summary-last-article-p (&optional article) "Return whether ARTICLE is the last article in the buffer." (if (not (setq article (or article (gnus-summary-article-number)))) - t ; All non-existent numbers are the last article. :-) + ;; All non-existent numbers are the last article. :-) + t (not (cdr (gnus-data-find-list article))))) (defun gnus-make-thread-indent-array () @@ -2381,7 +2572,7 @@ marks of articles." (let ((gnus-summary-line-format-spec spec) (gnus-newsgroup-downloadable '((0 . t)))) (gnus-summary-insert-line - [0 "" "" "" "" "" 0 0 ""] 0 nil 128 t nil "" nil 1) + [0 "" "" "" "" "" 0 0 "" nil] 0 nil 128 t nil "" nil 1) (goto-char (point-min)) (setq pos (list (cons 'unread (and (search-forward "\200" nil t) (- (point) 2))))) @@ -2405,6 +2596,33 @@ marks of articles." (point) (progn (eval gnus-summary-dummy-line-format-spec) (point)) (list 'gnus-number gnus-tmp-number 'gnus-intangible gnus-tmp-number))) +(defun gnus-summary-from-or-to-or-newsgroups (header) + (let ((to (cdr (assq 'To (mail-header-extra header)))) + (newsgroups (cdr (assq 'Newsgroups (mail-header-extra header)))) + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (set-buffer gnus-summary-buffer) + gnus-newsgroup-ignored-charsets))) + (cond + ((and to + gnus-ignored-from-addresses + (string-match gnus-ignored-from-addresses + (mail-header-from header))) + (concat "-> " + (or (car (funcall gnus-extract-address-components + (funcall + gnus-decode-encoded-word-function to))) + (funcall gnus-decode-encoded-word-function to)))) + ((and newsgroups + gnus-ignored-from-addresses + (string-match gnus-ignored-from-addresses + (mail-header-from header))) + (concat "=> " newsgroups)) + (t + (or (car (funcall gnus-extract-address-components + (mail-header-from header))) + (mail-header-from header)))))) + (defun gnus-summary-insert-line (gnus-tmp-header gnus-tmp-level gnus-tmp-current gnus-tmp-unread gnus-tmp-replied @@ -2418,7 +2636,7 @@ marks of articles." (if (or (null gnus-summary-default-score) (<= (abs (- gnus-tmp-score gnus-summary-default-score)) gnus-summary-zcore-fuzz)) - ? ;space + ? ;Whitespace (if (< gnus-tmp-score gnus-summary-default-score) gnus-score-below-mark gnus-score-over-mark))) (gnus-tmp-replied @@ -2451,7 +2669,7 @@ marks of articles." (setq gnus-tmp-name gnus-tmp-from)) (unless (numberp gnus-tmp-lines) (setq gnus-tmp-lines 0)) - (gnus-put-text-property-excluding-characters-with-faces + (gnus-put-text-property (point) (progn (eval gnus-summary-line-format-spec) (point)) 'gnus-number gnus-tmp-number) @@ -2461,7 +2679,7 @@ marks of articles." (forward-line 1)))) (defun gnus-summary-update-line (&optional dont-update) - ;; Update summary line after change. + "Update summary line after change." (when (and gnus-summary-default-score (not gnus-summary-inhibit-highlight)) (let* ((gnus-summary-inhibit-highlight t) ; Prevent recursion. @@ -2483,7 +2701,7 @@ marks of articles." (if (or (null gnus-summary-default-score) (<= (abs (- score gnus-summary-default-score)) gnus-summary-zcore-fuzz)) - ? ;space + ? ;Whitespace (if (< score gnus-summary-default-score) gnus-score-below-mark gnus-score-over-mark)) 'score)) @@ -2552,7 +2770,7 @@ If NO-DISPLAY, don't generate a summary buffer." kill-buffer no-display select-articles) (setq show-all nil - select-articles nil))))) + select-articles nil))))) (eq gnus-auto-select-next 'quietly)) (set-buffer gnus-group-buffer) ;; The entry function called above goes to the next @@ -2634,6 +2852,7 @@ If NO-DISPLAY, don't generate a summary buffer." (gnus-summary-set-local-parameters gnus-newsgroup-name) (gnus-update-format-specifications nil 'summary 'summary-mode 'summary-dummy) + (gnus-update-summary-mark-positions) ;; Do score processing. (when gnus-use-scoring (gnus-possibly-score-headers)) @@ -2646,6 +2865,7 @@ If NO-DISPLAY, don't generate a summary buffer." (let ((gnus-newsgroup-dormant nil)) (gnus-summary-initial-limit show-all)) (gnus-summary-initial-limit show-all)) + ;; When untreaded, all articles are always shown. (setq gnus-newsgroup-limit (mapcar (lambda (header) (mail-header-number header)) @@ -2691,10 +2911,15 @@ If NO-DISPLAY, don't generate a summary buffer." (not no-display) gnus-newsgroup-unreads gnus-auto-select-first) - (unless (if (eq gnus-auto-select-first 'best) - (gnus-summary-best-unread-article) - (gnus-summary-first-unread-article)) - (gnus-configure-windows 'summary)) + (progn + (gnus-configure-windows 'summary) + (cond + ((eq gnus-auto-select-first 'best) + (gnus-summary-best-unread-article)) + ((eq gnus-auto-select-first t) + (gnus-summary-first-unread-article)) + ((gnus-functionp gnus-auto-select-first) + (funcall gnus-auto-select-first)))) ;; Don't select any articles, just move point to the first ;; article in the group. (goto-char (point-min)) @@ -2839,12 +3064,12 @@ If NO-DISPLAY, don't generate a summary buffer." result)) (defun gnus-sort-gathered-threads (threads) - "Sort subtreads inside each gathered thread by article number." + "Sort subtreads inside each gathered thread by `gnus-sort-gathered-threads-function'." (let ((result threads)) (while threads (when (stringp (caar threads)) (setcdr (car threads) - (sort (cdar threads) 'gnus-thread-sort-by-number))) + (sort (cdar threads) gnus-sort-gathered-threads-function))) (setq threads (cdr threads))) result)) @@ -2900,7 +3125,7 @@ If NO-DISPLAY, don't generate a summary buffer." threads)) ;; Build the thread tree. -(defun gnus-dependencies-add-header (header dependencies force-new) +(defsubst gnus-dependencies-add-header (header dependencies force-new) "Enter HEADER into the DEPENDENCIES table if it is not already there. If FORCE-NEW is not nil, enter HEADER into the DEPENDENCIES table even @@ -2979,6 +3204,7 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise." (defun gnus-build-sparse-threads () (let ((headers gnus-newsgroup-headers) + (mail-parse-charset gnus-newsgroup-charset) (gnus-summary-ignore-duplicates t) header references generation relations subject child end new-child date) @@ -3031,7 +3257,8 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise." ;; fetch the headers for the articles that aren't there. This will ;; build complete threads - if the roots haven't been expired by the ;; server, that is. - (let (id heads) + (let ((mail-parse-charset gnus-newsgroup-charset) + id heads) (mapatoms (lambda (refs) (when (not (car (symbol-value refs))) @@ -3046,24 +3273,6 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise." (setq heads nil))))) gnus-newsgroup-dependencies))) -;; The following macros and functions were written by Felix Lee -;; . - -(defmacro gnus-nov-read-integer () - '(prog1 - (if (= (following-char) ?\t) - 0 - (let ((num (ignore-errors (read buffer)))) - (if (numberp num) num 0))) - (unless (eobp) - (search-forward "\t" eol 'move)))) - -(defmacro gnus-nov-skip-field () - '(search-forward "\t" eol 'move)) - -(defmacro gnus-nov-field () - '(buffer-substring (point) (if (gnus-nov-skip-field) (1- (point)) eol))) - ;; This function has to be called with point after the article number ;; on the beginning of the line. (defsubst gnus-nov-parse-line (number dependencies &optional force-new) @@ -3081,18 +3290,18 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise." (setq header (make-full-mail-header number ; number - (funcall - gnus-unstructured-field-decoder (gnus-nov-field)) ; subject - (funcall - gnus-structured-field-decoder (gnus-nov-field)) ; from - (gnus-nov-field) ; date - (or (gnus-nov-field) - (nnheader-generate-fake-message-id)) ; id - (gnus-nov-field) ; refs - (gnus-nov-read-integer) ; chars - (gnus-nov-read-integer) ; lines - (unless (= (following-char) ?\n) - (gnus-nov-field))))) ; misc + (funcall gnus-decode-encoded-word-function + (nnheader-nov-field)) ; subject + (funcall gnus-decode-encoded-word-function + (nnheader-nov-field)) ; from + (nnheader-nov-field) ; date + (nnheader-nov-read-message-id) ; id + (nnheader-nov-field) ; refs + (nnheader-nov-read-integer) ; chars + (nnheader-nov-read-integer) ; lines + (unless (eobp) + (nnheader-nov-field)) ; misc + (nnheader-nov-parse-extra)))) ; extra (widen)) @@ -3101,9 +3310,9 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise." (gnus-dependencies-add-header header dependencies force-new))) (defun gnus-build-get-header (id) - ;; Look through the buffer of NOV lines and find the header to - ;; ID. Enter this line into the dependencies hash table, and return - ;; the id of the parent article (if any). + "Look through the buffer of NOV lines and find the header to ID. +Enter this line into the dependencies hash table, and return +the id of the parent article (if any)." (let ((deps gnus-newsgroup-dependencies) found header) (prog1 @@ -3138,6 +3347,7 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise." (defun gnus-build-all-threads () "Read all the headers." (let ((gnus-summary-ignore-duplicates t) + (mail-parse-charset gnus-newsgroup-charset) (dependencies gnus-newsgroup-dependencies) header article) (save-excursion @@ -3147,8 +3357,7 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise." (while (not (eobp)) (ignore-errors (setq article (read (current-buffer)) - header (gnus-nov-parse-line - article dependencies))) + header (gnus-nov-parse-line article dependencies))) (when header (save-excursion (set-buffer gnus-summary-buffer) @@ -3185,17 +3394,19 @@ Returns HEADER if it was entered in the DEPENDENCIES. Returns nil otherwise." (memq article gnus-newsgroup-expirable) ;; Only insert the Subject string when it's different ;; from the previous Subject string. - (if (gnus-subject-equal - (condition-case () - (mail-header-subject - (gnus-data-header - (cadr - (gnus-data-find-list - article - (gnus-data-list t))))) - ;; Error on the side of excessive subjects. - (error "")) - (mail-header-subject header)) + (if (and + gnus-show-threads + (gnus-subject-equal + (condition-case () + (mail-header-subject + (gnus-data-header + (cadr + (gnus-data-find-list + article + (gnus-data-list t))))) + ;; Error on the side of excessive subjects. + (error "")) + (mail-header-subject header))) "" (mail-header-subject header)) nil (cdr (assq article gnus-newsgroup-scored)) @@ -3409,7 +3620,6 @@ If LINE, insert the rebuilt thread starting on line LINE." (while thread (gnus-remove-thread-1 (car thread)) (setq thread (cdr thread)))) - (gnus-summary-show-all-threads) (gnus-remove-thread-1 thread)))))))) (defun gnus-remove-thread-1 (thread) @@ -3421,6 +3631,7 @@ If LINE, insert the rebuilt thread starting on line LINE." (gnus-remove-thread-1 (pop thread))) (when (setq d (gnus-data-find number)) (goto-char (gnus-data-pos d)) + (gnus-summary-show-thread) (gnus-data-remove number (- (gnus-point-at-bol) @@ -3428,13 +3639,22 @@ If LINE, insert the rebuilt thread starting on line LINE." (1+ (gnus-point-at-eol)) (gnus-delete-line))))))) +(defun gnus-sort-threads-1 (threads func) + (sort (mapcar (lambda (thread) + (cons (car thread) + (and (cdr thread) + (gnus-sort-threads-1 (cdr thread) func)))) + threads) func)) + (defun gnus-sort-threads (threads) "Sort THREADS." (if (not gnus-thread-sort-functions) threads (gnus-message 8 "Sorting threads...") (prog1 - (sort threads (gnus-make-sort-function gnus-thread-sort-functions)) + (gnus-sort-threads-1 + threads + (gnus-make-sort-function gnus-thread-sort-functions)) (gnus-message 8 "Sorting threads...done")))) (defun gnus-sort-articles (articles) @@ -3449,12 +3669,12 @@ If LINE, insert the rebuilt thread starting on line LINE." ;; Written by Hallvard B Furuseth . (defmacro gnus-thread-header (thread) - ;; Return header of first article in THREAD. - ;; Note that THREAD must never, ever be anything else than a variable - - ;; using some other form will lead to serious barfage. + "Return header of first article in THREAD. +Note that THREAD must never, ever be anything else than a variable - +using some other form will lead to serious barfage." (or (symbolp thread) (signal 'wrong-type-argument '(symbolp thread))) ;; (8% speedup to gnus-summary-prepare, just for fun :-) - (list 'byte-code "\10\211:\203\17\0\211@;\203\16\0A@@\207" ; + (list 'byte-code "\10\211:\203\17\0\211@;\203\16\0A@@\207" (vector thread) 2)) (defsubst gnus-article-sort-by-number (h1 h2) @@ -3477,6 +3697,16 @@ If LINE, insert the rebuilt thread starting on line LINE." (gnus-article-sort-by-lines (gnus-thread-header h1) (gnus-thread-header h2))) +(defsubst gnus-article-sort-by-chars (h1 h2) + "Sort articles by octet length." + (< (mail-header-chars h1) + (mail-header-chars h2))) + +(defun gnus-thread-sort-by-chars (h1 h2) + "Sort threads by root article octet length." + (gnus-article-sort-by-chars + (gnus-thread-header h1) (gnus-thread-header h2))) + (defsubst gnus-article-sort-by-author (h1 h2) "Sort articles by root author." (string-lessp @@ -3507,7 +3737,7 @@ If LINE, insert the rebuilt thread starting on line LINE." (defsubst gnus-article-sort-by-date (h1 h2) "Sort articles by root article date." - (gnus-time-less + (time-less-p (gnus-date-get-time (mail-header-date h1)) (gnus-date-get-time (mail-header-date h2)))) @@ -3537,7 +3767,7 @@ Unscored articles will be counted as having a score of zero." (> (gnus-thread-total-score h1) (gnus-thread-total-score h2))) (defun gnus-thread-total-score (thread) - ;; This function find the total score of THREAD. + ;; This function find the total score of THREAD. (cond ((null thread) 0) ((consp thread) @@ -3568,6 +3798,12 @@ Unscored articles will be counted as having a score of zero." (defvar gnus-tmp-root-expunged nil) (defvar gnus-tmp-dummy-line nil) +(defvar gnus-tmp-header) +(defun gnus-extra-header (type &optional header) + "Return the extra header of TYPE." + (or (cdr (assq type (mail-header-extra (or header gnus-tmp-header)))) + "")) + (defun gnus-summary-prepare-threads (threads) "Prepare summary buffer from THREADS and indentation LEVEL. THREADS is either a list of `(PARENT [(CHILD1 [(GRANDCHILD ...]...) ...])' @@ -3765,7 +4001,7 @@ or a straight list of headers." (if (or (null gnus-summary-default-score) (<= (abs (- gnus-tmp-score gnus-summary-default-score)) gnus-summary-zcore-fuzz)) - ? ;space + ? ;Whitespace (if (< gnus-tmp-score gnus-summary-default-score) gnus-score-below-mark gnus-score-over-mark)) gnus-tmp-replied @@ -3795,7 +4031,7 @@ or a straight list of headers." (setq gnus-tmp-name gnus-tmp-from)) (unless (numberp gnus-tmp-lines) (setq gnus-tmp-lines 0)) - (gnus-put-text-property-excluding-characters-with-faces + (gnus-put-text-property (point) (progn (eval gnus-summary-line-format-spec) (point)) 'gnus-number number) @@ -3849,6 +4085,24 @@ or a straight list of headers." (cdr (assq number gnus-newsgroup-scored)) (memq number gnus-newsgroup-processable)))))) +(defun gnus-summary-remove-list-identifiers () + "Remove list identifiers in `gnus-list-identifiers' from articles in the current group." + (let ((regexp (if (stringp gnus-list-identifiers) + gnus-list-identifiers + (mapconcat 'identity gnus-list-identifiers " *\\|")))) + (dolist (header gnus-newsgroup-headers) + (when (string-match (concat "\\(\\(\\(Re: +\\)?\\(" regexp + " *\\)\\)+\\(Re: +\\)?\\)") + (mail-header-subject header)) + (mail-header-set-subject + header (concat (substring (mail-header-subject header) + 0 (match-beginning 1)) + (or + (match-string 3 (mail-header-subject header)) + (match-string 5 (mail-header-subject header))) + (substring (mail-header-subject header) + (match-end 1)))))))) + (defun gnus-select-newsgroup (group &optional read-all select-articles) "Select newsgroup GROUP. If READ-ALL is non-nil, all articles in the group are selected. @@ -3884,6 +4138,7 @@ If SELECT-ARTICLES, only select those articles from GROUP." (setq gnus-newsgroup-name group) (setq gnus-newsgroup-unselected nil) (setq gnus-newsgroup-unreads (gnus-list-of-unread-articles group)) + (gnus-summary-setup-default-charset) ;; Adjust and set lists of article marks. (when info @@ -3918,6 +4173,7 @@ If SELECT-ARTICLES, only select those articles from GROUP." ;; Init the dependencies hash table. (setq gnus-newsgroup-dependencies (gnus-make-hashtable (length articles))) + (gnus-set-global-variables) ;; Retrieve the headers and read them in. (gnus-message 5 "Fetching headers for %s..." gnus-newsgroup-name) (setq gnus-newsgroup-headers @@ -3966,6 +4222,9 @@ If SELECT-ARTICLES, only select those articles from GROUP." ;; Let the Gnus agent mark articles as read. (when gnus-agent (gnus-agent-get-undownloaded-list)) + ;; Remove list identifiers from subject + (when gnus-list-identifiers + (gnus-summary-remove-list-identifiers)) ;; Check whether auto-expire is to be done in this group. (setq gnus-newsgroup-auto-expire (gnus-group-auto-expirable-p group)) @@ -3983,7 +4242,7 @@ If SELECT-ARTICLES, only select those articles from GROUP." (or gnus-newsgroup-headers t))))) (defun gnus-articles-to-read (group &optional read-all) - ;; Find out what articles the user wants to read. + "Find out what articles the user wants to read." (let* ((articles ;; Select all articles if `read-all' is non-nil, or if there ;; are no unread articles. @@ -3992,7 +4251,9 @@ If SELECT-ARTICLES, only select those articles from GROUP." (zerop (length gnus-newsgroup-unreads))) (eq (gnus-group-find-parameter group 'display) 'all)) - (gnus-uncompress-range (gnus-active group)) + (or + (gnus-uncompress-range (gnus-active group)) + (gnus-cache-articles-in-group group)) (sort (append gnus-newsgroup-dormant gnus-newsgroup-marked (copy-sequence gnus-newsgroup-unreads)) '<))) @@ -4048,6 +4309,12 @@ If SELECT-ARTICLES, only select those articles from GROUP." (gnus-sorted-intersection gnus-newsgroup-unreads (gnus-sorted-complement gnus-newsgroup-unreads articles))) + (when gnus-alter-articles-to-read-function + (setq gnus-newsgroup-unreads + (sort + (funcall gnus-alter-articles-to-read-function + gnus-newsgroup-name gnus-newsgroup-unreads) + '<))) articles))) (defun gnus-killed-articles (killed articles) @@ -4070,7 +4337,7 @@ If SELECT-ARTICLES, only select those articles from GROUP." out)) (defun gnus-adjust-marked-articles (info) - "Set all article lists and remove all marks that are no longer legal." + "Set all article lists and remove all marks that are no longer valid." (let* ((marked-lists (gnus-info-marks info)) (active (gnus-active (gnus-info-group info))) (min (car active)) @@ -4128,15 +4395,16 @@ If SELECT-ARTICLES, only select those articles from GROUP." (let ((types gnus-article-mark-lists) (info (gnus-get-info gnus-newsgroup-name)) (uncompressed '(score bookmark killed)) - type list newmarked symbol) + type list newmarked symbol delta-marks) (when info - ;; Add all marks lists that are non-nil to the list of marks lists. + ;; Add all marks lists to the list of marks lists. (while (setq type (pop types)) - (when (setq list (symbol-value - (setq symbol - (intern (format "gnus-newsgroup-%s" - (car type)))))) + (setq list (symbol-value + (setq symbol + (intern (format "gnus-newsgroup-%s" + (car type)))))) + (when list ;; Get rid of the entries of the articles that have the ;; default score. (when (and (eq (cdr type) 'score) @@ -4151,14 +4419,38 @@ If SELECT-ARTICLES, only select those articles from GROUP." (setcdr prev (cdr arts)) (setq prev arts)) (setq arts (cdr arts))) - (setq list (cdr all)))) + (setq list (cdr all))))) - (push (cons (cdr type) - (if (memq (cdr type) uncompressed) list - (gnus-compress-sequence - (set symbol (sort list '<)) t))) - newmarked))) + (unless (memq (cdr type) uncompressed) + (setq list (gnus-compress-sequence (set symbol (sort list '<)) t))) + + (when (gnus-check-backend-function + 'request-set-mark gnus-newsgroup-name) + ;; propagate flags to server, with the following exceptions: + ;; uncompressed:s are not proper flags (they are cons cells) + ;; cache is a internal gnus flag + ;; download are local to one gnus installation (well) + ;; unsend are for nndraft groups only + ;; xxx: generality of this? this suits nnimap anyway + (unless (memq (cdr type) (append '(cache download unsend) + uncompressed)) + (let* ((old (cdr (assq (cdr type) (gnus-info-marks info)))) + (del (gnus-remove-from-range (gnus-copy-sequence old) list)) + (add (gnus-remove-from-range + (gnus-copy-sequence list) old))) + (when add + (push (list add 'add (list (cdr type))) delta-marks)) + (when del + (push (list del 'del (list (cdr type))) delta-marks))))) + + (when list + (push (cons (cdr type) list) newmarked))) + (when delta-marks + (unless (gnus-check-group gnus-newsgroup-name) + (error "Can't open server for %s" gnus-newsgroup-name)) + (gnus-request-set-mark gnus-newsgroup-name delta-marks)) + ;; Enter these new marks into the info of the group. (if (nthcdr 3 info) (setcar (nthcdr 3 info) newmarked) @@ -4174,10 +4466,12 @@ If SELECT-ARTICLES, only select those articles from GROUP." (setcdr (nthcdr i info) nil))))))) (defun gnus-set-mode-line (where) - "This function sets the mode line of the article or summary buffers. + "Set the mode line of the article or summary buffers. If WHERE is `summary', the summary mode line format will be used." ;; Is this mode line one we keep updated? - (when (memq where gnus-updated-mode-lines) + (when (and (memq where gnus-updated-mode-lines) + (symbol-value + (intern (format "gnus-%s-mode-line-format-spec" where)))) (let (mode-string) (save-excursion ;; We evaluate this in the summary buffer since these @@ -4188,7 +4482,11 @@ If WHERE is `summary', the summary mode line format will be used." (let* ((mformat (symbol-value (intern (format "gnus-%s-mode-line-format-spec" where)))) - (gnus-tmp-group-name gnus-newsgroup-name) + (gnus-tmp-group-name (gnus-group-name-decode + gnus-newsgroup-name + (gnus-group-name-charset + nil + gnus-newsgroup-name))) (gnus-tmp-article-number (or gnus-current-article 0)) (gnus-tmp-unread gnus-newsgroup-unreads) (gnus-tmp-unread-and-unticked (length gnus-newsgroup-unreads)) @@ -4227,7 +4525,7 @@ If WHERE is `summary', the summary mode line format will be used." ;; We might have to chop a bit of the string off... (when (> (length mode-string) max-len) (setq mode-string - (concat (gnus-truncate-string mode-string (- max-len 3)) + (concat (truncate-string-to-width mode-string (- max-len 3)) "..."))) ;; Pad the mode string a bit. (setq mode-string (format (format "%%-%ds" max-len) mode-string)))) @@ -4305,7 +4603,7 @@ The resulting hash table is returned, or nil if no Xrefs were found." (active (gnus-active group)) ninfo) (when entry - ;; First peel off all illegal article numbers. + ;; First peel off all invalid article numbers. (when active (let ((ids articles) id first) @@ -4374,15 +4672,6 @@ The resulting hash table is returned, or nil if no Xrefs were found." ;; Update the group buffer. (gnus-group-update-group group t))))) -(defun gnus-methods-equal-p (m1 m2) - (let ((m1 (or m1 gnus-select-method)) - (m2 (or m2 gnus-select-method))) - (or (equal m1 m2) - (and (eq (car m1) (car m2)) - (or (not (memq 'address (assoc (symbol-name (car m1)) - gnus-valid-select-methods))) - (equal (nth 1 m1) (nth 1 m2))))))) - (defvar gnus-newsgroup-none-id 0) (defun gnus-get-newsgroup-headers (&optional dependencies force-new) @@ -4391,11 +4680,18 @@ The resulting hash table is returned, or nil if no Xrefs were found." (or dependencies (save-excursion (set-buffer gnus-summary-buffer) gnus-newsgroup-dependencies))) - headers id end ref) + headers id end ref + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets + (save-excursion (condition-case nil + (set-buffer gnus-summary-buffer) + (error)) + gnus-newsgroup-ignored-charsets))) (save-excursion (set-buffer nntp-server-buffer) ;; Translate all TAB characters into SPACE characters. (subst-char-in-region (point-min) (point-max) ?\t ? t) + (subst-char-in-region (point-min) (point-max) ?\r ? t) (gnus-run-hooks 'gnus-parse-headers-hook) (let ((case-fold-search t) in-reply-to header p lines chars) @@ -4427,15 +4723,15 @@ The resulting hash table is returned, or nil if no Xrefs were found." (progn (goto-char p) (if (search-forward "\nsubject: " nil t) - (funcall - gnus-unstructured-field-decoder (nnheader-header-value)) + (funcall gnus-decode-encoded-word-function + (nnheader-header-value)) "(none)")) ;; From. (progn (goto-char p) (if (search-forward "\nfrom: " nil t) - (funcall - gnus-structured-field-decoder (nnheader-header-value)) + (funcall gnus-decode-encoded-word-function + (nnheader-header-value)) "(nobody)")) ;; Date. (progn @@ -4505,7 +4801,19 @@ The resulting hash table is returned, or nil if no Xrefs were found." (progn (goto-char p) (and (search-forward "\nxref: " nil t) - (nnheader-header-value))))) + (nnheader-header-value))) + ;; Extra. + (when gnus-extra-headers + (let ((extra gnus-extra-headers) + out) + (while extra + (goto-char p) + (when (search-forward + (concat "\n" (symbol-name (car extra)) ": ") nil t) + (push (cons (car extra) (nnheader-header-value)) + out)) + (pop extra)) + out)))) (when (equal id ref) (setq ref nil)) @@ -4526,16 +4834,20 @@ The resulting hash table is returned, or nil if no Xrefs were found." (defun gnus-get-newsgroup-headers-xover (sequence &optional force-new dependencies group also-fetch-heads) - "Parse the news overview data in the server buffer, and return a -list of headers that match SEQUENCE (see `nntp-retrieve-headers')." + "Parse the news overview data in the server buffer. +Return a list of headers that match SEQUENCE (see +`nntp-retrieve-headers')." ;; Get the Xref when the users reads the articles since most/some ;; NNTP servers do not include Xrefs when using XOVER. (setq gnus-article-internal-prepare-hook '(gnus-article-get-xrefs)) - (let ((cur nntp-server-buffer) + (let ((mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets) + (cur nntp-server-buffer) (dependencies (or dependencies gnus-newsgroup-dependencies)) number headers header) (save-excursion (set-buffer nntp-server-buffer) + (subst-char-in-region (point-min) (point-max) ?\r ? t) ;; Allow the user to mangle the headers before parsing them. (gnus-run-hooks 'gnus-parse-headers-hook) (goto-char (point-min)) @@ -4589,7 +4901,8 @@ This is meant to be called in `gnus-article-internal-prepare-hook'." (save-restriction (nnheader-narrow-to-headers) (goto-char (point-min)) - (when (or (and (eq (downcase (following-char)) ?x) + (when (or (and (not (eobp)) + (eq (downcase (char-after)) ?x) (looking-at "Xref:")) (search-forward "\nXref:" nil t)) (goto-char (1+ (match-end 0))) @@ -4604,14 +4917,14 @@ the subject line on." (let* ((line (and (numberp old-header) old-header)) (old-header (and (vectorp old-header) old-header)) (header (cond ((and old-header use-old-header) - old-header) - ((and (numberp id) - (gnus-number-to-header id)) - (gnus-number-to-header id)) - (t - (gnus-read-header id)))) - (number (and (numberp id) id)) - d) + old-header) + ((and (numberp id) + (gnus-number-to-header id)) + (gnus-number-to-header id)) + (t + (gnus-read-header id)))) + (number (and (numberp id) id)) + d) (when header ;; Rebuild the thread that this article is part of and go to the ;; article we have fetched. @@ -4706,7 +5019,8 @@ executed with point over the summary line of the articles." `(let ((,articles (gnus-summary-work-articles ,arg))) (while ,articles (gnus-summary-goto-subject (car ,articles)) - ,@forms)))) + ,@forms + (pop ,articles))))) (put 'gnus-summary-iterate 'lisp-indent-function 1) (put 'gnus-summary-iterate 'edebug-form-spec '(form body)) @@ -4851,9 +5165,12 @@ If `gnus-auto-center-summary' is nil, or the article buffer isn't displayed, no centering will be performed." ;; Suggested by earle@mahendo.JPL.NASA.GOV (Greg Earle). ;; Recenter only when requested. Suggested by popovich@park.cs.columbia.edu. + (interactive) (let* ((top (cond ((< (window-height) 4) 0) ((< (window-height) 7) 1) - (t 2))) + (t (if (numberp gnus-auto-center-summary) + gnus-auto-center-summary + 2)))) (height (1- (window-height))) (bottom (save-excursion (goto-char (point-max)) (forward-line (- height)) @@ -4868,7 +5185,8 @@ displayed, no centering will be performed." ;; whichever is the least. (set-window-start window (min bottom (save-excursion - (forward-line (- top)) (point))))) + (forward-line (- top)) (point))) + t)) ;; Do horizontal recentering while we're at it. (when (and (get-buffer-window (current-buffer) t) (not (eq gnus-auto-center-summary 'vertical))) @@ -4908,7 +5226,10 @@ displayed, no centering will be performed." ;; If the range of read articles is a single range, then the ;; first unread article is the article after the last read ;; article. Sounds logical, doesn't it? - (if (not (listp (cdr read))) + (if (and (not (listp (cdr read))) + (or (< (car read) (car active)) + (progn (setq read (list read)) + nil))) (setq first (max (car active) (1+ (cdr read)))) ;; `read' is a list of ranges. (when (/= (setq nlast (or (and (numberp (car read)) (car read)) @@ -4965,8 +5286,7 @@ displayed, no centering will be performed." (key-binding (read-key-sequence (substitute-command-keys - "\\\\[gnus-summary-universal-argument]" - )))) + "\\\\[gnus-summary-universal-argument]")))) 'undefined) (gnus-error 1 "Undefined key") (save-excursion @@ -5062,9 +5382,16 @@ If FORCE (the prefix), also save the .newsrc file(s)." (defun gnus-summary-exit (&optional temporary) "Exit reading current newsgroup, and then return to group selection mode. -gnus-exit-group-hook is called with no arguments if that value is non-nil." +`gnus-exit-group-hook' is called with no arguments if that value is non-nil." (interactive) (gnus-set-global-variables) + (when (gnus-buffer-live-p gnus-article-buffer) + (save-excursion + (set-buffer gnus-article-buffer) + (mm-destroy-parts gnus-article-mime-handles) + ;; Set it to nil for safety reason. + (setq gnus-article-mime-handle-alist nil) + (setq gnus-article-mime-handles nil))) (gnus-kill-save-kill-buffer) (gnus-async-halt-prefetch) (let* ((group gnus-newsgroup-name) @@ -5072,6 +5399,12 @@ gnus-exit-group-hook is called with no arguments if that value is non-nil." (mode major-mode) (group-point nil) (buf (current-buffer))) + (unless quit-config + ;; Do adaptive scoring, and possibly save score files. + (when gnus-newsgroup-adaptive + (gnus-score-adaptive)) + (when gnus-use-scoring + (gnus-score-save))) (gnus-run-hooks 'gnus-summary-prepare-exit-hook) ;; If we have several article buffers, we kill them at exit. (unless gnus-single-article-buffer @@ -5085,17 +5418,14 @@ gnus-exit-group-hook is called with no arguments if that value is non-nil." (gnus-dup-enter-articles)) (when gnus-use-trees (gnus-tree-close group)) + (when gnus-use-cache + (gnus-cache-write-active)) ;; Remove entries for this group. (nnmail-purge-split-history (gnus-group-real-name group)) ;; Make all changes in this group permanent. (unless quit-config (gnus-run-hooks 'gnus-exit-group-hook) - (gnus-summary-update-info) - ;; Do adaptive scoring, and possibly save score files. - (when gnus-newsgroup-adaptive - (gnus-score-adaptive)) - (when gnus-use-scoring - (gnus-score-save))) + (gnus-summary-update-info)) (gnus-close-group group) ;; Make sure where we were, and go to next newsgroup. (set-buffer gnus-group-buffer) @@ -5153,7 +5483,16 @@ gnus-exit-group-hook is called with no arguments if that value is non-nil." gnus-expert-user (gnus-y-or-n-p "Discard changes to this group and exit? ")) (gnus-async-halt-prefetch) - (gnus-run-hooks 'gnus-summary-prepare-exit-hook) + (mapcar 'funcall + (delq 'gnus-summary-expire-articles + (copy-sequence gnus-summary-prepare-exit-hook))) + (when (gnus-buffer-live-p gnus-article-buffer) + (save-excursion + (set-buffer gnus-article-buffer) + (mm-destroy-parts gnus-article-mime-handles) + ;; Set it to nil for safety reason. + (setq gnus-article-mime-handle-alist nil) + (setq gnus-article-mime-handles nil))) ;; If we have several article buffers, we kill them at exit. (unless gnus-single-article-buffer (gnus-kill-buffer gnus-article-buffer) @@ -5261,7 +5600,8 @@ The state which existed when entering the ephemeral is reset." (rename-buffer (concat (substring name 0 (match-beginning 0)) "Dead " (substring name (match-beginning 0))) - t)))) + t) + (bury-buffer)))) (defun gnus-kill-or-deaden-summary (buffer) "Kill or deaden the summary BUFFER." @@ -5322,8 +5662,7 @@ in." (defun gnus-summary-describe-briefly () "Describe summary mode commands briefly." (interactive) - (gnus-message 6 - (substitute-command-keys "\\\\[gnus-summary-next-page]:Select \\[gnus-summary-next-unread-article]:Forward \\[gnus-summary-prev-unread-article]:Backward \\[gnus-summary-exit]:Exit \\[gnus-info-find-node]:Run Info \\[gnus-summary-describe-briefly]:This help"))) + (gnus-message 6 (substitute-command-keys "\\\\[gnus-summary-next-page]:Select \\[gnus-summary-next-unread-article]:Forward \\[gnus-summary-prev-unread-article]:Backward \\[gnus-summary-exit]:Exit \\[gnus-info-find-node]:Run Info \\[gnus-summary-describe-briefly]:This help"))) ;; Walking around group mode buffer from summary mode. @@ -5429,8 +5768,8 @@ returned." (if backward (gnus-summary-find-prev unread) (gnus-summary-find-next unread))) - (gnus-summary-show-thread) - (setq n (1- n))) + (unless (zerop (setq n (1- n))) + (gnus-summary-show-thread))) (when (/= 0 n) (gnus-message 7 "No more%s articles" (if unread " unread" ""))) @@ -5521,35 +5860,41 @@ be displayed." (set-buffer gnus-summary-buffer)) (let ((article (or article (gnus-summary-article-number))) (all-headers (not (not all-headers))) ;Must be T or NIL. - gnus-summary-display-article-function - did) + gnus-summary-display-article-function) (and (not pseudo) (gnus-summary-article-pseudo-p article) (error "This is a pseudo-article")) - (prog1 - (save-excursion - (set-buffer gnus-summary-buffer) - (if (or (and gnus-single-article-buffer - (or (null gnus-current-article) - (null gnus-article-current) - (null (get-buffer gnus-article-buffer)) - (not (eq article (cdr gnus-article-current))) - (not (equal (car gnus-article-current) - gnus-newsgroup-name)))) - (and (not gnus-single-article-buffer) - (or (null gnus-current-article) - (not (eq gnus-current-article article)))) - force) - ;; The requested article is different from the current article. - (prog1 - (gnus-summary-display-article article all-headers) - (setq did article)) + (save-excursion + (set-buffer gnus-summary-buffer) + (if (or (and gnus-single-article-buffer + (or (null gnus-current-article) + (null gnus-article-current) + (null (get-buffer gnus-article-buffer)) + (not (eq article (cdr gnus-article-current))) + (not (equal (car gnus-article-current) + gnus-newsgroup-name)))) + (and (not gnus-single-article-buffer) + (or (null gnus-current-article) + (not (eq gnus-current-article article)))) + force) + ;; The requested article is different from the current article. + (progn + (when (gnus-buffer-live-p gnus-article-buffer) + (with-current-buffer gnus-article-buffer + (mm-enable-multibyte))) + (gnus-summary-display-article article all-headers) + (when (gnus-buffer-live-p gnus-article-buffer) + (with-current-buffer gnus-article-buffer + (if (not gnus-article-decoded-p) ;; a local variable + (mm-disable-multibyte)))) (when (or all-headers gnus-show-all-headers) (gnus-article-show-all-headers)) - 'old)) - (when did - (gnus-article-set-window-start - (cdr (assq article gnus-newsgroup-bookmarks))))))) + (gnus-article-set-window-start + (cdr (assq article gnus-newsgroup-bookmarks))) + article) + (when (or all-headers gnus-show-all-headers) + (gnus-article-show-all-headers)) + 'old)))) (defun gnus-summary-set-current-mark (&optional current-mark) "Obsolete function." @@ -5821,15 +6166,25 @@ Return nil if there are no unread articles." (gnus-summary-display-article (gnus-summary-article-number))) (gnus-summary-position-point))) +(defun gnus-summary-first-unread-subject () + "Place the point on the subject line of the first unread article. +Return nil if there are no unread articles." + (interactive) + (prog1 + (when (gnus-summary-first-subject t) + (gnus-summary-show-thread) + (gnus-summary-first-subject t)) + (gnus-summary-position-point))) + (defun gnus-summary-first-article () "Select the first article. Return nil if there are no articles." (interactive) (prog1 (when (gnus-summary-first-subject) - (gnus-summary-show-thread) - (gnus-summary-first-subject) - (gnus-summary-display-article (gnus-summary-article-number))) + (gnus-summary-show-thread) + (gnus-summary-first-subject) + (gnus-summary-display-article (gnus-summary-article-number))) (gnus-summary-position-point))) (defun gnus-summary-best-unread-article () @@ -5951,16 +6306,32 @@ If given a prefix, remove all limits." "Limit the summary buffer to articles that are older than (or equal) AGE days. If YOUNGER-P (the prefix) is non-nil, limit the summary buffer to articles that are younger than AGE days." - (interactive "nTime in days: \nP") + (interactive + (let ((younger current-prefix-arg) + (days-got nil) + days) + (while (not days-got) + (setq days (if younger + (read-string "Limit to articles within (in days): ") + (read-string "Limit to articles old than (in days): "))) + (when (> (length days) 0) + (setq days (read days))) + (if (numberp days) + (setq days-got t) + (message "Please enter a number.") + (sleep-for 1))) + (list days younger))) (prog1 (let ((data gnus-newsgroup-data) - (cutoff (nnmail-days-to-time age)) + (cutoff (days-to-time age)) articles d date is-younger) (while (setq d (pop data)) (when (and (vectorp (gnus-data-header d)) (setq date (mail-header-date (gnus-data-header d)))) - (setq is-younger (nnmail-time-less - (nnmail-time-since (nnmail-date-to-time date)) + (setq is-younger (time-less-p + (time-since (condition-case () + (date-to-time date) + (error '(0 0)))) cutoff)) (when (if younger-p is-younger @@ -5969,6 +6340,30 @@ articles that are younger than AGE days." (gnus-summary-limit (nreverse articles))) (gnus-summary-position-point))) +(defun gnus-summary-limit-to-extra (header regexp) + "Limit the summary buffer to articles that match an 'extra' header." + (interactive + (let ((header + (intern + (gnus-completing-read + (symbol-name (car gnus-extra-headers)) + "Limit extra header:" + (mapcar (lambda (x) + (cons (symbol-name x) x)) + gnus-extra-headers) + nil + t)))) + (list header + (read-string (format "Limit to header %s (regexp): " header))))) + (when (not (equal "" regexp)) + (prog1 + (let ((articles (gnus-summary-find-matching + (cons 'extra header) regexp 'all))) + (unless articles + (error "Found no matches for \"%s\"" regexp)) + (gnus-summary-limit articles)) + (gnus-summary-position-point)))) + (defalias 'gnus-summary-delete-marked-as-read 'gnus-summary-limit-to-unread) (make-obsolete 'gnus-summary-delete-marked-as-read 'gnus-summary-limit-to-unread) @@ -6166,6 +6561,7 @@ If ALL, mark even excluded ticked and dormants as read." "Go forwards in the thread until we find an article that we want to display." (when (or (eq gnus-fetch-old-headers 'some) (eq gnus-fetch-old-headers 'invisible) + (numberp gnus-fetch-old-headers) (eq gnus-build-sparse-threads 'some) (eq gnus-build-sparse-threads 'more)) ;; Deal with old-fetched headers and sparse threads. @@ -6195,6 +6591,7 @@ If ALL, mark even excluded ticked and dormants as read." "Cut off all uninteresting articles from the beginning of threads." (when (or (eq gnus-fetch-old-headers 'some) (eq gnus-fetch-old-headers 'invisible) + (numberp gnus-fetch-old-headers) (eq gnus-build-sparse-threads 'some) (eq gnus-build-sparse-threads 'more)) (let ((th threads)) @@ -6212,6 +6609,7 @@ fetch-old-headers verbiage, and so on." (if (or gnus-inhibit-limiting (and (null gnus-newsgroup-dormant) (not (eq gnus-fetch-old-headers 'some)) + (not (numberp gnus-fetch-old-headers)) (not (eq gnus-fetch-old-headers 'invisible)) (null gnus-summary-expunge-below) (not (eq gnus-build-sparse-threads 'some)) @@ -6265,7 +6663,8 @@ fetch-old-headers verbiage, and so on." (zerop children)) ;; If this is "fetch-old-headered" and there is no ;; visible children, then we don't want this article. - (and (eq gnus-fetch-old-headers 'some) + (and (or (eq gnus-fetch-old-headers 'some) + (numberp gnus-fetch-old-headers)) (gnus-summary-article-ancient-p number) (zerop children)) ;; If this is "fetch-old-headered" and `invisible', then @@ -6416,11 +6815,9 @@ of what's specified by the `gnus-refer-thread-limit' variable." (gnus-message 5 "Fetching headers for %s...done" gnus-newsgroup-name)) (gnus-summary-limit-include-thread id))) -(defun gnus-summary-refer-article (message-id &optional arg) - "Fetch an article specified by MESSAGE-ID. -If ARG (the prefix), fetch the article using `gnus-refer-article-method' -or `gnus-select-method', no matter what backend the article comes from." - (interactive "sMessage-ID: \nP") +(defun gnus-summary-refer-article (message-id) + "Fetch an article specified by MESSAGE-ID." + (interactive "sMessage-ID: ") (when (and (stringp message-id) (not (zerop (length message-id)))) ;; Construct the correct Message-ID if necessary. @@ -6434,7 +6831,8 @@ or `gnus-select-method', no matter what backend the article comes from." (gnus-summary-article-sparse-p (mail-header-number header)) (memq (mail-header-number header) - gnus-newsgroup-limit)))) + gnus-newsgroup-limit))) + number) (cond ;; If the article is present in the buffer we just go to it. ((and header @@ -6447,28 +6845,48 @@ or `gnus-select-method', no matter what backend the article comes from." (when sparse (gnus-summary-update-article (mail-header-number header))))) (t - ;; We fetch the article - (let ((gnus-override-method - (cond ((gnus-news-group-p gnus-newsgroup-name) - gnus-refer-article-method) - (arg - (or gnus-refer-article-method gnus-select-method)) - (t nil))) - number) - ;; Start the special refer-article method, if necessary. - (when (and gnus-refer-article-method - (gnus-news-group-p gnus-newsgroup-name)) - (gnus-check-server gnus-refer-article-method)) - ;; Fetch the header, and display the article. - (if (setq number (gnus-summary-insert-subject message-id)) + ;; We fetch the article. + (catch 'found + (dolist (gnus-override-method (gnus-refer-article-methods)) + (gnus-check-server gnus-override-method) + ;; Fetch the header, and display the article. + (when (setq number (gnus-summary-insert-subject message-id)) (gnus-summary-select-article nil nil nil number) - (gnus-message 3 "Couldn't fetch article %s" message-id)))))))) + (throw 'found t))) + (gnus-message 3 "Couldn't fetch article %s" message-id))))))) + +(defun gnus-refer-article-methods () + "Return a list of referrable methods." + (cond + ;; No method, so we default to current and native. + ((null gnus-refer-article-method) + (list gnus-current-select-method gnus-select-method)) + ;; Current. + ((eq 'current gnus-refer-article-method) + (list gnus-current-select-method)) + ;; List of select methods. + ((not (stringp (cadr gnus-refer-article-method))) + (let (out) + (dolist (method gnus-refer-article-method) + (push (if (eq 'current method) + gnus-current-select-method + method) + out)) + (nreverse out))) + ;; One single select method. + (t + (list gnus-refer-article-method)))) (defun gnus-summary-edit-parameters () "Edit the group parameters of the current group." (interactive) (gnus-group-edit-group gnus-newsgroup-name 'params)) +(defun gnus-summary-customize-parameters () + "Customize the group parameters of the current group." + (interactive) + (gnus-group-customize gnus-newsgroup-name)) + (defun gnus-summary-enter-digest-group (&optional force) "Enter an nndoc group based on the current article. If FORCE, force a digest interpretation. If not, try @@ -6490,8 +6908,14 @@ to guess what the document format is." (list (cons 'save-article-group ogroup)))) (case-fold-search t) (buf (current-buffer)) - dig) + dig to-address) (save-excursion + (set-buffer gnus-original-article-buffer) + ;; Have the digest group inherit the main mail address of + ;; the parent article. + (when (setq to-address (or (message-fetch-field "reply-to") + (message-fetch-field "from"))) + (setq params (append (list (cons 'to-address to-address))))) (setq dig (nnheader-set-temp-buffer " *gnus digest buffer*")) (insert-buffer-substring gnus-original-article-buffer) ;; Remove lines that may lead nndoc to misinterpret the @@ -6500,14 +6924,17 @@ to guess what the document format is." (goto-char (point-min)) (or (search-forward "\n\n" nil t) (point))) (goto-char (point-min)) - (delete-matching-lines "^\\(Path\\):\\|^From ") + (delete-matching-lines "^Path:\\|^From ") (widen)) (unwind-protect - (if (gnus-group-read-ephemeral-group - name `(nndoc ,name (nndoc-address ,(get-buffer dig)) - (nndoc-article-type - ,(if force 'digest 'guess))) t) - ;; Make all postings to this group go to the parent group. + (if (let ((gnus-newsgroup-ephemeral-charset gnus-newsgroup-charset) + (gnus-newsgroup-ephemeral-ignored-charsets + gnus-newsgroup-ignored-charsets)) + (gnus-group-read-ephemeral-group + name `(nndoc ,name (nndoc-address ,(get-buffer dig)) + (nndoc-article-type + ,(if force 'mbox 'guess))) t)) + ;; Make all postings to this group go to the parent group. (nconc (gnus-info-params (gnus-get-info name)) params) ;; Couldn't select this doc group. @@ -6533,7 +6960,7 @@ Obeys the standard process/prefix convention." (gnus-summary-remove-process-mark article) (when (gnus-summary-display-article article) (save-excursion - (nnheader-temp-write nil + (with-temp-buffer (insert-buffer-substring gnus-original-article-buffer) ;; Remove some headers that may lead nndoc to make ;; the wrong guess. @@ -6613,18 +7040,21 @@ Optional argument BACKWARD means do search for backward. ;; We have to require this here to make sure that the following ;; dynamic binding isn't shadowed by autoloading. (require 'gnus-async) + (require 'gnus-art) (let ((gnus-select-article-hook nil) ;Disable hook. - (gnus-article-display-hook nil) + (gnus-article-prepare-hook nil) (gnus-mark-article-hook nil) ;Inhibit marking as read. (gnus-use-article-prefetch nil) (gnus-xmas-force-redisplay nil) ;Inhibit XEmacs redisplay. (gnus-use-trees nil) ;Inhibit updating tree buffer. (sum (current-buffer)) + (gnus-display-mime-function nil) (found nil) point) (gnus-save-hidden-threads (gnus-summary-select-article) (set-buffer gnus-article-buffer) + (goto-char (window-point (get-buffer-window (current-buffer)))) (when backward (forward-line -1)) (while (not found) @@ -6640,6 +7070,9 @@ Optional argument BACKWARD means do search for backward. (get-buffer-window (current-buffer)) (point)) (forward-line 1) + (set-window-point + (get-buffer-window (current-buffer)) + (point)) (set-buffer sum) (setq point (point))) ;; We didn't find it, so we go to the next article. @@ -6678,11 +7111,18 @@ in the comparisons." (let ((data (if (eq backward 'all) gnus-newsgroup-data (gnus-data-find-list (gnus-summary-article-number) (gnus-data-list backward)))) - (func `(lambda (h) (,(intern (concat "mail-header-" header)) h))) (case-fold-search (not not-case-fold)) - articles d) - (unless (fboundp (intern (concat "mail-header-" header))) - (error "%s is not a valid header" header)) + articles d func) + (if (consp header) + (if (eq (car header) 'extra) + (setq func + `(lambda (h) + (or (cdr (assq ',(cdr header) (mail-header-extra h))) + ""))) + (error "%s is an invalid header" header)) + (unless (fboundp (intern (concat "mail-header-" header))) + (error "%s is not a valid header" header)) + (setq func `(lambda (h) (,(intern (concat "mail-header-" header)) h)))) (while data (setq d (car data)) (and (or (not unread) ; We want all articles... @@ -6751,7 +7191,7 @@ article. If BACKWARD (the prefix) is non-nil, search backward instead." If N is negative, print the N previous articles. If N is nil and articles have been marked with the process mark, print these instead. -If the optional second argument FILENAME is nil, send the image to the +If the optional first argument FILENAME is nil, send the image to the printer. If FILENAME is a string, save the PostScript image in a file with that name. If FILENAME is a number, prompt the user for the name of the file to save in." @@ -6784,20 +7224,42 @@ to save in." (defun gnus-summary-show-article (&optional arg) "Force re-fetching of the current article. -If ARG (the prefix) is non-nil, show the raw article without any -article massaging functions being run." +If ARG (the prefix) is a number, show the article with the charset +defined in `gnus-summary-show-article-charset-alist', or the charset +inputed. +If ARG (the prefix) is non-nil and not a number, show the raw article +without any article massaging functions being run." (interactive "P") - (if (not arg) - ;; Select the article the normal way. - (gnus-summary-select-article nil 'force) + (cond + ((numberp arg) + (let ((gnus-newsgroup-charset + (or (cdr (assq arg gnus-summary-show-article-charset-alist)) + (read-coding-system "Charset: "))) + (gnus-newsgroup-ignored-charsets 'gnus-all)) + (gnus-summary-select-article nil 'force))) + ((not arg) + ;; Select the article the normal way. + (gnus-summary-select-article nil 'force)) + (t + ;; We have to require this here to make sure that the following + ;; dynamic binding isn't shadowed by autoloading. + (require 'gnus-async) + (require 'gnus-art) ;; Bind the article treatment functions to nil. (let ((gnus-have-all-headers t) - gnus-article-display-hook gnus-article-prepare-hook - gnus-break-pages - gnus-show-mime - gnus-visual) - (gnus-summary-select-article nil 'force))) + gnus-article-decode-hook + gnus-display-mime-function + gnus-break-pages) + ;; Destroy any MIME parts. + (when (gnus-buffer-live-p gnus-article-buffer) + (save-excursion + (set-buffer gnus-article-buffer) + (mm-destroy-parts gnus-article-mime-handles) + ;; Set it to nil for safety reason. + (setq gnus-article-mime-handle-alist nil) + (setq gnus-article-mime-handles nil))) + (gnus-summary-select-article nil 'force)))) (gnus-summary-goto-subject gnus-current-article) (gnus-summary-position-point)) @@ -6821,40 +7283,42 @@ If ARG is a negative number, hide the unwanted header lines." (interactive "P") (save-excursion (set-buffer gnus-article-buffer) - (let* ((buffer-read-only nil) - (inhibit-point-motion-hooks t) - (hidden (text-property-any - (goto-char (point-min)) (search-forward "\n\n") - 'invisible t)) - e) - (goto-char (point-min)) - (when (search-forward "\n\n" nil t) - (delete-region (point-min) (1- (point)))) - (goto-char (point-min)) - (save-excursion - (set-buffer gnus-original-article-buffer) + (save-restriction + (let* ((buffer-read-only nil) + (inhibit-point-motion-hooks t) + hidden e) + (setq hidden + (if (numberp arg) + (>= arg 0) + (save-restriction + (article-narrow-to-head) + (gnus-article-hidden-text-p 'headers)))) (goto-char (point-min)) - (setq e (1- (or (search-forward "\n\n" nil t) (point-max))))) - (insert-buffer-substring gnus-original-article-buffer 1 e) - (let ((article-inhibit-hiding t)) - (gnus-run-hooks 'gnus-article-display-hook)) - (when (or (not hidden) (and (numberp arg) (< arg 0))) - (gnus-article-hide-headers))))) + (when (search-forward "\n\n" nil t) + (delete-region (point-min) (1- (point)))) + (goto-char (point-min)) + (save-excursion + (set-buffer gnus-original-article-buffer) + (goto-char (point-min)) + (setq e (1- (or (search-forward "\n\n" nil t) (point-max))))) + (insert-buffer-substring gnus-original-article-buffer 1 e) + (save-restriction + (narrow-to-region (point-min) (point)) + (article-decode-encoded-words) + (if hidden + (let ((gnus-treat-hide-headers nil) + (gnus-treat-hide-boring-headers nil)) + (setq gnus-article-wash-types + (delq 'headers gnus-article-wash-types)) + (gnus-treat-article 'head)) + (gnus-treat-article 'head))) + (gnus-set-mode-line 'article))))) (defun gnus-summary-show-all-headers () "Make all header lines visible." (interactive) (gnus-article-show-all-headers)) -(defun gnus-summary-toggle-mime (&optional arg) - "Toggle MIME processing. -If ARG is a positive number, turn MIME processing on." - (interactive "P") - (setq gnus-show-mime - (if (null arg) (not gnus-show-mime) - (> (prefix-numeric-value arg) 0))) - (gnus-summary-select-article t 'force)) - (defun gnus-summary-caesar-message (&optional arg) "Caesar rotate the current article by 13. The numerical prefix specifies how many places to rotate each letter @@ -6895,7 +7359,9 @@ re-spool using this method. For this function to work, both the current newsgroup and the newsgroup that you want to move to have to support the `request-move' -and `request-accept' functions." +and `request-accept' functions. + +ACTION can be either `move' (the default), `crosspost' or `copy'." (interactive "P") (unless action (setq action 'move)) @@ -6913,7 +7379,10 @@ and `request-accept' functions." 'request-replace-article gnus-newsgroup-name))) (error "The current group does not support article editing"))) (let ((articles (gnus-summary-work-articles n)) - (prefix (gnus-group-real-prefix gnus-newsgroup-name)) + (prefix (if (gnus-check-backend-function + 'request-move-article gnus-newsgroup-name) + (gnus-group-real-prefix gnus-newsgroup-name) + "")) (names '((move "Move" "Moving") (copy "Copy" "Copying") (crosspost "Crosspost" "Crossposting"))) @@ -6932,7 +7401,8 @@ and `request-accept' functions." articles prefix)) (set (intern (format "gnus-current-%s-group" action)) to-newsgroup)) (setq to-method (or select-method - (gnus-group-name-to-method to-newsgroup))) + (gnus-server-to-method + (gnus-group-method to-newsgroup)))) ;; Check the method we are to move this article to... (unless (gnus-check-backend-function 'request-accept-article (car to-method)) @@ -6958,7 +7428,7 @@ and `request-accept' functions." gnus-newsgroup-name)) ; Server (list 'gnus-request-accept-article to-newsgroup (list 'quote select-method) - (not articles)) ; Accept form + (not articles) t) ; Accept form (not articles))) ; Only save nov last time ;; Copy the article. ((eq action 'copy) @@ -6966,7 +7436,7 @@ and `request-accept' functions." (set-buffer copy-buf) (when (gnus-request-article-this-buffer article gnus-newsgroup-name) (gnus-request-accept-article - to-newsgroup select-method (not articles))))) + to-newsgroup select-method (not articles) t)))) ;; Crosspost the article. ((eq action 'crosspost) (let ((xref (message-tokenize-header @@ -6999,19 +7469,21 @@ and `request-accept' functions." art-group)))))) (cond ((not art-group) - (gnus-message 1 "Couldn't %s article %s" - (cadr (assq action names)) article)) - ((and (eq art-group 'junk) - (eq action 'move)) - (gnus-summary-mark-article article gnus-canceled-mark) - (gnus-message 4 "Deleted article %s" article)) + (gnus-message 1 "Couldn't %s article %s: %s" + (cadr (assq action names)) article + (nnheader-get-report (car to-method)))) + ((eq art-group 'junk) + (when (eq action 'move) + (gnus-summary-mark-article article gnus-canceled-mark) + (gnus-message 4 "Deleted article %s" article))) (t (let* ((pto-group (gnus-group-prefixed-name (car art-group) to-method)) (entry (gnus-gethash pto-group gnus-newsrc-hashtb)) (info (nth 2 entry)) - (to-group (gnus-info-group info))) + (to-group (gnus-info-group info)) + to-marks) ;; Update the group that has been moved to. (when (and info (memq action '(move copy))) @@ -7019,49 +7491,54 @@ and `request-accept' functions." (push to-group to-groups)) (unless (memq article gnus-newsgroup-unreads) + (push 'read to-marks) (gnus-info-set-read info (gnus-add-to-range (gnus-info-read info) (list (cdr art-group))))) - ;; Copy any marks over to the new group. + ;; See whether the article is to be put in the cache. (let ((marks gnus-article-mark-lists) (to-article (cdr art-group))) - ;; See whether the article is to be put in the cache. + ;; Enter the article into the cache in the new group, + ;; if that is required. (when gnus-use-cache (gnus-cache-possibly-enter-article to-group to-article - (let ((header (copy-sequence - (gnus-summary-article-header article)))) - (mail-header-set-number header to-article) - header) (memq article gnus-newsgroup-marked) (memq article gnus-newsgroup-dormant) (memq article gnus-newsgroup-unreads))) - (when (and (equal to-group gnus-newsgroup-name) - (not (memq article gnus-newsgroup-unreads))) - ;; Mark this article as read in this group. - (push (cons to-article gnus-read-mark) gnus-newsgroup-reads) - (setcdr (gnus-active to-group) to-article) - (setcdr gnus-newsgroup-active to-article)) + (when gnus-preserve-marks + ;; Copy any marks over to the new group. + (when (and (equal to-group gnus-newsgroup-name) + (not (memq article gnus-newsgroup-unreads))) + ;; Mark this article as read in this group. + (push (cons to-article gnus-read-mark) gnus-newsgroup-reads) + (setcdr (gnus-active to-group) to-article) + (setcdr gnus-newsgroup-active to-article)) - (while marks - (when (memq article (symbol-value - (intern (format "gnus-newsgroup-%s" - (caar marks))))) - ;; If the other group is the same as this group, - ;; then we have to add the mark to the list. - (when (equal to-group gnus-newsgroup-name) - (set (intern (format "gnus-newsgroup-%s" (caar marks))) - (cons to-article - (symbol-value - (intern (format "gnus-newsgroup-%s" - (caar marks))))))) - ;; Copy the marks to other group. - (gnus-add-marked-articles - to-group (cdar marks) (list to-article) info)) - (setq marks (cdr marks))) + (while marks + (when (memq article (symbol-value + (intern (format "gnus-newsgroup-%s" + (caar marks))))) + (push (cdar marks) to-marks) + ;; If the other group is the same as this group, + ;; then we have to add the mark to the list. + (when (equal to-group gnus-newsgroup-name) + (set (intern (format "gnus-newsgroup-%s" (caar marks))) + (cons to-article + (symbol-value + (intern (format "gnus-newsgroup-%s" + (caar marks))))))) + ;; Copy the marks to other group. + (gnus-add-marked-articles + to-group (cdar marks) (list to-article) info)) + (setq marks (cdr marks))) + + (gnus-request-set-mark to-group (list (list (list to-article) + 'set + to-marks)))) (gnus-dribble-enter (concat "(gnus-group-set-info '" @@ -7174,9 +7651,8 @@ latter case, they will be copied into the relevant groups." (error "Can't read %s" file)) (save-excursion (set-buffer (gnus-get-buffer-create " *import file*")) - (buffer-disable-undo (current-buffer)) (erase-buffer) - (insert-file-contents file) + (nnheader-insert-file-contents file) (goto-char (point-min)) (unless (nnheader-article-p) ;; This doesn't look like an article, so we fudge some headers. @@ -7184,10 +7660,7 @@ latter case, they will be copied into the relevant groups." lines (count-lines (point-min) (point-max))) (insert "From: " (read-string "From: ") "\n" "Subject: " (read-string "Subject: ") "\n" - "Date: " (timezone-make-date-arpa-standard - (current-time-string (nth 5 atts)) - (current-time-zone now) - (current-time-zone now)) + "Date: " (message-make-date (nth 5 atts)) "\n" "Message-ID: " (message-make-message-id) "\n" "Lines: " (int-to-string lines) "\n" @@ -7196,12 +7669,11 @@ latter case, they will be copied into the relevant groups." (kill-buffer (current-buffer))))) (defun gnus-summary-article-posted-p () - "Say whether the current (mail) article is available from `gnus-select-method' as well. + "Say whether the current (mail) article is available from news as well. This will be the case if the article has both been mailed and posted." (interactive) (let ((id (mail-header-references (gnus-summary-article-header))) - (gnus-override-method - (or gnus-refer-article-method gnus-select-method))) + (gnus-override-method (car (gnus-refer-article-methods)))) (if (gnus-request-head id "") (gnus-message 2 "The current message was found on %s" gnus-override-method) @@ -7229,11 +7701,16 @@ This will be the case if the article has both been mailed and posted." (expiry-wait (if now 'immediate (gnus-group-find-parameter gnus-newsgroup-name 'expiry-wait))) + (nnmail-expiry-target + (or (gnus-group-find-parameter gnus-newsgroup-name 'expiry-target) + nnmail-expiry-target)) es) (when expirable ;; There are expirable articles in this group, so we run them ;; through the expiry process. (gnus-message 6 "Expiring articles...") + (unless (gnus-check-group gnus-newsgroup-name) + (error "Can't open server for %s" gnus-newsgroup-name)) ;; The list of articles that weren't expired is returned. (save-excursion (if expiry-wait @@ -7281,6 +7758,8 @@ delete these instead." (unless (gnus-check-backend-function 'request-expire-articles gnus-newsgroup-name) (error "The current newsgroup does not support article deletion")) + (unless (gnus-check-server (gnus-find-method-for-group gnus-newsgroup-name)) + (error "Couldn't open server")) ;; Compute the list of articles to delete. (let ((articles (sort (copy-sequence (gnus-summary-work-articles n)) '<)) not-deleted) @@ -7307,28 +7786,63 @@ delete these instead." (gnus-set-mode-line 'summary) not-deleted)) -(defun gnus-summary-edit-article (&optional force) +(defun gnus-summary-edit-article (&optional arg) "Edit the current article. This will have permanent effect only in mail groups. -If FORCE is non-nil, allow editing of articles even in read-only +If ARG is nil, edit the decoded articles. +If ARG is 1, edit the raw articles. +If ARG is 2, edit the raw articles even in read-only groups. +Otherwise, allow editing of articles even in read-only groups." (interactive "P") - (save-excursion - (set-buffer gnus-summary-buffer) - (gnus-set-global-variables) - (when (and (not force) - (gnus-group-read-only-p)) - (error "The current newsgroup does not support article editing")) - ;; Select article if needed. - (unless (eq (gnus-summary-article-number) - gnus-current-article) - (gnus-summary-select-article t)) - (gnus-article-date-original) - (gnus-article-edit-article - `(lambda (no-highlight) - (gnus-summary-edit-article-done - ,(or (mail-header-references gnus-current-headers) "") - ,(gnus-group-read-only-p) ,gnus-summary-buffer no-highlight))))) + (let (force raw) + (cond + ((null arg)) + ((eq arg 1) (setq raw t)) + ((eq arg 2) (setq raw t + force t)) + (t (setq force t))) + (if (and raw (not force) (equal gnus-newsgroup-name "nndraft:drafts")) + (error "Can't edit the raw article in group nndraft:drafts.")) + (save-excursion + (set-buffer gnus-summary-buffer) + (let ((mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets)) + (gnus-set-global-variables) + (when (and (not force) + (gnus-group-read-only-p)) + (error "The current newsgroup does not support article editing")) + (gnus-summary-show-article t) + (when (and (not raw) (gnus-buffer-live-p gnus-article-buffer)) + (with-current-buffer gnus-article-buffer + (mm-enable-multibyte))) + (if (equal gnus-newsgroup-name "nndraft:drafts") + (setq raw t)) + (gnus-article-edit-article + (if raw 'ignore + #'(lambda () + (let ((mbl mml-buffer-list)) + (setq mml-buffer-list nil) + (mime-to-mml) + (make-local-hook 'kill-buffer-hook) + (let ((mml-buffer-list mml-buffer-list)) + (setq mml-buffer-list mbl) + (make-local-variable 'mml-buffer-list)) + (add-hook 'kill-buffer-hook 'mml-destroy-buffers t t)))) + `(lambda (no-highlight) + (let ((mail-parse-charset ',gnus-newsgroup-charset) + (mail-parse-ignored-charsets + ',gnus-newsgroup-ignored-charsets)) + ,(if (not raw) '(progn + (mml-to-mime) + (mml-destroy-buffers) + (remove-hook 'kill-buffer-hook + 'mml-destroy-buffers t) + (kill-local-variable 'mml-buffer-list))) + (gnus-summary-edit-article-done + ,(or (mail-header-references gnus-current-headers) "") + ,(gnus-group-read-only-p) + ,gnus-summary-buffer no-highlight)))))))) (defalias 'gnus-summary-edit-article-postpone 'gnus-article-edit-exit) @@ -7338,12 +7852,12 @@ groups." (interactive) ;; Replace the article. (let ((buf (current-buffer))) - (nnheader-temp-write nil - (insert-buffer buf) + (with-temp-buffer + (insert-buffer-substring buf) (if (and (not read-only) (not (gnus-request-replace-article (cdr gnus-article-current) (car gnus-article-current) - (current-buffer)))) + (current-buffer) t))) (error "Couldn't replace article") ;; Update the summary buffer. (if (and references @@ -7356,7 +7870,7 @@ groups." (message-narrow-to-head) (let ((head (buffer-string)) header) - (nnheader-temp-write nil + (with-temp-buffer (insert (format "211 %d Article retrieved.\n" (cdr gnus-article-current))) (insert head) @@ -7381,7 +7895,8 @@ groups." (unless no-highlight (save-excursion (set-buffer gnus-article-buffer) - (gnus-run-hooks 'gnus-article-display-hook) + ;;;!!! Fix this -- article should be rehighlighted. + ;;;(gnus-run-hooks 'gnus-article-display-hook) (set-buffer gnus-original-article-buffer) (gnus-request-article (cdr gnus-article-current) @@ -7544,7 +8059,7 @@ the actual number of articles marked is returned." "Mark ARTICLE replied and update the summary line." (push article gnus-newsgroup-replied) (let ((buffer-read-only nil)) - (when (gnus-summary-goto-subject article) + (when (gnus-summary-goto-subject article nil t) (gnus-summary-update-secondary-mark article)))) (defun gnus-summary-set-bookmark (article) @@ -7624,8 +8139,10 @@ the actual number of articles marked is returned." "Mark N articles as read forwards. If N is negative, mark backwards instead. Mark with MARK, ?r by default. The difference between N and the actual number of articles marked is -returned." +returned. +Iff NO-EXPIRE, auto-expiry will be inhibited." (interactive "p") + (gnus-summary-show-thread) (let ((backward (< n 0)) (gnus-summary-goto-unread (and gnus-summary-goto-unread @@ -7663,11 +8180,7 @@ returned." (setq mark (gnus-request-update-mark gnus-newsgroup-name article mark)) ;; Check for auto-expiry. (when (and gnus-newsgroup-auto-expire - (or (= mark gnus-killed-mark) (= mark gnus-del-mark) - (= mark gnus-catchup-mark) (= mark gnus-low-score-mark) - (= mark gnus-ancient-mark) - (= mark gnus-read-mark) (= mark gnus-souped-mark) - (= mark gnus-duplicate-mark))) + (memq mark gnus-auto-expirable-marks)) (setq mark gnus-expirable-mark) ;; Let the backend know about the mark change. (setq mark (gnus-request-update-mark gnus-newsgroup-name article mark)) @@ -7706,7 +8219,6 @@ returned." (save-excursion (gnus-cache-possibly-enter-article gnus-newsgroup-name article - (gnus-summary-article-header article) (= mark gnus-ticked-mark) (= mark gnus-dormant-mark) (= mark gnus-unread-mark)))) @@ -7718,25 +8230,22 @@ returned." "Mark ARTICLE with MARK. MARK can be any character. Four MARK strings are reserved: `? ' (unread), `?!' (ticked), `??' (dormant) and `?E' (expirable). -If MARK is nil, then the default character `?D' is used. +If MARK is nil, then the default character `?r' is used. If ARTICLE is nil, then the article on the current line will be -marked." +marked. +Iff NO-EXPIRE, auto-expiry will be inhibited." ;; The mark might be a string. (when (stringp mark) (setq mark (aref mark 0))) ;; If no mark is given, then we check auto-expiring. - (and (not no-expire) - gnus-newsgroup-auto-expire - (or (not mark) - (and (gnus-characterp mark) - (or (= mark gnus-killed-mark) (= mark gnus-del-mark) - (= mark gnus-catchup-mark) (= mark gnus-low-score-mark) - (= mark gnus-read-mark) (= mark gnus-souped-mark) - (= mark gnus-duplicate-mark)))) - (setq mark gnus-expirable-mark)) - (let* ((mark (or mark gnus-del-mark)) - (article (or article (gnus-summary-article-number))) - (old-mark (gnus-summary-article-mark article))) + (when (null mark) + (setq mark gnus-del-mark)) + (when (and (not no-expire) + gnus-newsgroup-auto-expire + (memq mark gnus-auto-expirable-marks)) + (setq mark gnus-expirable-mark)) + (let ((article (or article (gnus-summary-article-number))) + (old-mark (gnus-summary-article-mark article))) ;; Allow the backend to change the mark. (setq mark (gnus-request-update-mark gnus-newsgroup-name article mark)) (if (eq mark old-mark) @@ -7756,7 +8265,6 @@ marked." (save-excursion (gnus-cache-possibly-enter-article gnus-newsgroup-name article - (gnus-summary-article-header article) (= mark gnus-ticked-mark) (= mark gnus-dormant-mark) (= mark gnus-unread-mark)))) @@ -7788,19 +8296,19 @@ marked." (let ((forward (cdr (assq type gnus-summary-mark-positions))) (buffer-read-only nil)) (re-search-backward "[\n\r]" (gnus-point-at-bol) 'move-to-limit) - (when (looking-at "\r") - (incf forward)) - (when (and forward - (<= (+ forward (point)) (point-max))) - ;; Go to the right position on the line. - (goto-char (+ forward (point))) - ;; Replace the old mark with the new mark. - (subst-char-in-region (point) (1+ (point)) (following-char) mark) - ;; Optionally update the marks by some user rule. - (when (eq type 'unread) - (gnus-data-set-mark - (gnus-data-find (gnus-summary-article-number)) mark) - (gnus-summary-update-line (eq mark gnus-unread-mark)))))) + (when forward + (when (looking-at "\r") + (incf forward)) + (when (<= (+ forward (point)) (point-max)) + ;; Go to the right position on the line. + (goto-char (+ forward (point))) + ;; Replace the old mark with the new mark. + (subst-char-in-region (point) (1+ (point)) (char-after) mark) + ;; Optionally update the marks by some user rule. + (when (eq type 'unread) + (gnus-data-set-mark + (gnus-data-find (gnus-summary-article-number)) mark) + (gnus-summary-update-line (eq mark gnus-unread-mark))))))) (defun gnus-mark-article-as-read (article &optional mark) "Enter ARTICLE in the pertinent lists and remove it from others." @@ -7881,14 +8389,15 @@ If N is negative, mark backwards instead. The difference between N and the actual number of articles marked is returned." (interactive "p") - (gnus-summary-mark-forward n gnus-del-mark t)) + (gnus-summary-mark-forward n gnus-del-mark gnus-inhibit-user-auto-expire)) (defun gnus-summary-mark-as-read-backward (n) "Mark the N articles as read backwards. The difference between N and the actual number of articles marked is returned." (interactive "p") - (gnus-summary-mark-forward (- n) gnus-del-mark t)) + (gnus-summary-mark-forward + (- n) gnus-del-mark gnus-inhibit-user-auto-expire)) (defun gnus-summary-mark-as-read (&optional article mark) "Mark current article as read. @@ -8069,7 +8578,7 @@ If ALL is non-nil, also mark ticked and dormant articles as read." (gnus-summary-catchup t quietly)) (defun gnus-summary-catchup-and-exit (&optional all quietly) - "Mark all articles not marked as unread in this newsgroup as read, then exit. + "Mark all unread articles in this group as read, then exit. If prefix argument ALL is non-nil, all articles are marked as read." (interactive "P") (when (gnus-summary-catchup all quietly nil 'fast) @@ -8084,7 +8593,6 @@ If prefix argument ALL is non-nil, all articles are marked as read." (interactive "P") (gnus-summary-catchup-and-exit t quietly)) -;; Suggested by "Arne Eofsson" . (defun gnus-summary-catchup-and-goto-next-group (&optional all) "Mark all articles in this group as read and select the next group. If given a prefix, mark all articles, unread as well as ticked, as @@ -8092,7 +8600,38 @@ read." (interactive "P") (save-excursion (gnus-summary-catchup all)) - (gnus-summary-next-article t nil nil t)) + (gnus-summary-next-group)) + +;;; +;;; with article +;;; + +(defmacro gnus-with-article (article &rest forms) + "Select ARTICLE and perform FORMS in the original article buffer. +Then replace the article with the result." + `(progn + ;; We don't want the article to be marked as read. + (let (gnus-mark-article-hook) + (gnus-summary-select-article t t nil ,article)) + (set-buffer gnus-original-article-buffer) + ,@forms + (if (not (gnus-check-backend-function + 'request-replace-article (car gnus-article-current))) + (gnus-message 5 "Read-only group; not replacing") + (unless (gnus-request-replace-article + ,article (car gnus-article-current) + (current-buffer) t) + (error "Couldn't replace article"))) + ;; The cache and backlog have to be flushed somewhat. + (when gnus-keep-backlog + (gnus-backlog-remove-article + (car gnus-article-current) (cdr gnus-article-current))) + (when gnus-use-cache + (gnus-cache-update-article + (car gnus-article-current) (cdr gnus-article-current))))) + +(put 'gnus-with-article 'lisp-indent-function 1) +(put 'gnus-with-article 'edebug-form-spec '(form body)) ;; Thread-based commands. @@ -8171,25 +8710,17 @@ is non-nil or the Subject: of both articles are the same." (gnus-summary-article-header parent-article)))) (unless (and message-id (not (equal message-id ""))) (error "No message-id in desired parent")) - ;; We don't want the article to be marked as read. - (let (gnus-mark-article-hook) - (gnus-summary-select-article t t nil current-article)) - (set-buffer gnus-original-article-buffer) - (let ((buf (format "%s" (buffer-string)))) - (nnheader-temp-write nil - (insert buf) + (gnus-with-article current-article + (save-restriction (goto-char (point-min)) + (message-narrow-to-head) (if (re-search-forward "^References: " nil t) (progn (re-search-forward "^[^ \t]" nil t) (forward-line -1) (end-of-line) (insert " " message-id)) - (insert "References: " message-id "\n")) - (unless (gnus-request-replace-article - current-article (car gnus-article-current) - (current-buffer)) - (error "Couldn't replace article")))) + (insert "References: " message-id "\n")))) (set-buffer gnus-summary-buffer) (gnus-summary-unmark-all-processable) (gnus-summary-update-article current-article) @@ -8264,9 +8795,7 @@ Returns nil if no threads were there to be hidden." (subst-char-in-region start (point) ?\n ?\^M) (gnus-summary-goto-subject article)) (goto-char start) - nil) - ;;(gnus-summary-position-point) - )))) + nil))))) (defun gnus-summary-go-to-next-thread (&optional previous) "Go to the same level (or less) next thread. @@ -8398,14 +8927,14 @@ Argument REVERSE means reverse order." (defun gnus-summary-sort-by-author (&optional reverse) "Sort the summary buffer by author name alphabetically. -If case-fold-search is non-nil, case of letters is ignored. +If `case-fold-search' is non-nil, case of letters is ignored. Argument REVERSE means reverse order." (interactive "P") (gnus-summary-sort 'author reverse)) (defun gnus-summary-sort-by-subject (&optional reverse) "Sort the summary buffer by subject alphabetically. `Re:'s are ignored. -If case-fold-search is non-nil, case of letters is ignored. +If `case-fold-search' is non-nil, case of letters is ignored. Argument REVERSE means reverse order." (interactive "P") (gnus-summary-sort 'subject reverse)) @@ -8423,27 +8952,33 @@ Argument REVERSE means reverse order." (gnus-summary-sort 'score reverse)) (defun gnus-summary-sort-by-lines (&optional reverse) - "Sort the summary buffer by article length. + "Sort the summary buffer by the number of lines. Argument REVERSE means reverse order." (interactive "P") (gnus-summary-sort 'lines reverse)) +(defun gnus-summary-sort-by-chars (&optional reverse) + "Sort the summary buffer by article length. +Argument REVERSE means reverse order." + (interactive "P") + (gnus-summary-sort 'chars reverse)) + (defun gnus-summary-sort (predicate reverse) "Sort summary buffer by PREDICATE. REVERSE means reverse order." (let* ((thread (intern (format "gnus-thread-sort-by-%s" predicate))) (article (intern (format "gnus-article-sort-by-%s" predicate))) (gnus-thread-sort-functions - (list - (if (not reverse) - thread - `(lambda (t1 t2) - (,thread t2 t1))))) + (if (not reverse) + thread + `(lambda (t1 t2) + (,thread t2 t1)))) + (gnus-sort-gathered-threads-function + gnus-thread-sort-functions) (gnus-article-sort-functions - (list - (if (not reverse) - article - `(lambda (t1 t2) - (,article t2 t1))))) + (if (not reverse) + article + `(lambda (t1 t2) + (,article t2 t1)))) (buffer-read-only) (gnus-summary-prepare-hook nil)) ;; We do the sorting by regenerating the threads. @@ -8466,10 +9001,9 @@ The variable `gnus-default-article-saver' specifies the saver function." (save-buffer (save-excursion (nnheader-set-temp-buffer " *Gnus Save*"))) (num (length articles)) - header article file) - (while articles - (setq header (gnus-summary-article-header - (setq article (pop articles)))) + header file) + (dolist (article articles) + (setq header (gnus-summary-article-header article)) (if (not (vectorp header)) ;; This is a pseudo-article. (if (assq 'name header) @@ -8599,16 +9133,14 @@ save those articles instead." split-name)) ((consp result) (setq split-name (append result split-name))))))))) - split-name)) + (nreverse split-name))) (defun gnus-valid-move-group-p (group) (and (boundp group) (symbol-name group) - (memq 'respool - (assoc (symbol-name - (car (gnus-find-method-for-group - (symbol-name group)))) - gnus-valid-select-methods)))) + (symbol-value group) + (gnus-get-function (gnus-find-method-for-group + (symbol-name group)) 'request-accept-article t))) (defun gnus-read-move-group-name (prompt default articles prefix) "Read a group name." @@ -8639,7 +9171,8 @@ save those articles instead." (mapcar (lambda (el) (list el)) (nreverse split-name)) nil nil nil - 'gnus-group-history))))) + 'gnus-group-history)))) + (to-method (gnus-server-to-method (gnus-group-method to-newsgroup)))) (when to-newsgroup (if (or (string= to-newsgroup "") (string= to-newsgroup prefix)) @@ -8647,18 +9180,56 @@ save those articles instead." (unless to-newsgroup (error "No group name entered")) (or (gnus-active to-newsgroup) - (gnus-activate-group to-newsgroup) + (gnus-activate-group to-newsgroup nil nil to-method) (if (gnus-y-or-n-p (format "No such group: %s. Create it? " to-newsgroup)) - (or (and (gnus-request-create-group - to-newsgroup (gnus-group-name-to-method to-newsgroup)) - (gnus-activate-group to-newsgroup nil nil - (gnus-group-name-to-method - to-newsgroup))) + (or (and (gnus-request-create-group to-newsgroup to-method) + (gnus-activate-group + to-newsgroup nil nil to-method) + (gnus-subscribe-group to-newsgroup)) (error "Couldn't create group %s" to-newsgroup))) (error "No such group: %s" to-newsgroup))) to-newsgroup)) +(defun gnus-summary-save-parts (type dir n &optional reverse) + "Save parts matching TYPE to DIR. +If REVERSE, save parts that do not match TYPE." + (interactive + (list (read-string "Save parts of type: " "image/.*") + (read-file-name "Save to directory: " nil nil t) + current-prefix-arg)) + (gnus-summary-iterate n + (let ((gnus-display-mime-function nil) + (gnus-inhibit-treatment t)) + (gnus-summary-select-article)) + (save-excursion + (set-buffer gnus-article-buffer) + (let ((handles (or gnus-article-mime-handles + (mm-dissect-buffer) (mm-uu-dissect)))) + (when handles + (gnus-summary-save-parts-1 type dir handles reverse) + (unless gnus-article-mime-handles ;; Don't destroy this case. + (mm-destroy-parts handles))))))) + +(defun gnus-summary-save-parts-1 (type dir handle reverse) + (if (stringp (car handle)) + (mapcar (lambda (h) (gnus-summary-save-parts-1 type dir h reverse)) + (cdr handle)) + (when (if reverse + (not (string-match type (mm-handle-media-type handle))) + (string-match type (mm-handle-media-type handle))) + (let ((file (expand-file-name + (file-name-nondirectory + (or + (mail-content-type-get + (mm-handle-disposition handle) 'filename) + (concat gnus-newsgroup-name + "." (number-to-string + (cdr gnus-article-current))))) + dir))) + (unless (file-exists-p file) + (mm-save-part-to-file handle file)))))) + ;; Summary extract commands (defun gnus-summary-insert-pseudos (pslist &optional not-view) @@ -8694,7 +9265,7 @@ save those articles instead." (lambda (f) (if (equal f " ") f - (gnus-quote-arg-for-sh-or-csh f))) + (mm-quote-arg f))) files " "))))) (setq ps (cdr ps))))) (if (and gnus-view-pseudos (not not-view)) @@ -8771,8 +9342,10 @@ save those articles instead." "Read the headers of article ID and enter them into the Gnus system." (let ((group gnus-newsgroup-name) (gnus-override-method - (and (gnus-news-group-p gnus-newsgroup-name) - gnus-refer-article-method)) + (or + gnus-override-method + (and (gnus-news-group-p gnus-newsgroup-name) + (car (gnus-refer-article-methods))))) where) ;; First we check to see whether the header in question is already ;; fetched. @@ -8846,8 +9419,8 @@ save those articles instead." ;;; (defun gnus-highlight-selected-summary () + "Highlight selected article in summary buffer." ;; Added by Per Abrahamsen . - ;; Highlight selected article in summary buffer (when gnus-summary-selected-face (save-excursion (let* ((beg (progn (beginning-of-line) (point))) @@ -8938,19 +9511,38 @@ save those articles instead." (setq unread (cdr unread))) (when (<= prev (cdr active)) (push (cons prev (cdr active)) read)) + (setq read (if (> (length read) 1) (nreverse read) read)) (if compute - (if (> (length read) 1) (nreverse read) read) + read (save-excursion - (set-buffer gnus-group-buffer) - (gnus-undo-register - `(progn - (gnus-info-set-marks ',info ',(gnus-info-marks info) t) - (gnus-info-set-read ',info ',(gnus-info-read info)) - (gnus-get-unread-articles-in-group ',info (gnus-active ,group)) - (gnus-group-update-group ,group t)))) + (let (setmarkundo) + ;; Propagate the read marks to the backend. + (when (gnus-check-backend-function 'request-set-mark group) + (let ((del (gnus-remove-from-range (gnus-info-read info) read)) + (add (gnus-remove-from-range read (gnus-info-read info)))) + (when (or add del) + (unless (gnus-check-group group) + (error "Can't open server for %s" group)) + (gnus-request-set-mark + group (delq nil (list (if add (list add 'add '(read))) + (if del (list del 'del '(read)))))) + (setq setmarkundo + `(gnus-request-set-mark + ,group + ',(delq nil (list + (if del (list del 'add '(read))) + (if add (list add 'del '(read)))))))))) + (set-buffer gnus-group-buffer) + (gnus-undo-register + `(progn + (gnus-info-set-marks ',info ',(gnus-info-marks info) t) + (gnus-info-set-read ',info ',(gnus-info-read info)) + (gnus-get-unread-articles-in-group ',info + (gnus-active ,group)) + (gnus-group-update-group ,group t) + ,setmarkundo)))) ;; Enter this list into the group info. - (gnus-info-set-read - info (if (> (length read) 1) (nreverse read) read)) + (gnus-info-set-read info read) ;; Set the number of unread articles in gnus-newsrc-hashtb. (gnus-get-unread-articles-in-group info (gnus-active group)) t)))) @@ -8983,6 +9575,165 @@ save those articles instead." (gnus-summary-exit)) buffers))))) +(defun gnus-summary-setup-default-charset () + "Setup newsgroup default charset." + (if (equal gnus-newsgroup-name "nndraft:drafts") + (setq gnus-newsgroup-charset nil) + (let* ((name (and gnus-newsgroup-name + (gnus-group-real-name gnus-newsgroup-name))) + (ignored-charsets + (or gnus-newsgroup-ephemeral-ignored-charsets + (append + (and gnus-newsgroup-name + (or (gnus-group-find-parameter gnus-newsgroup-name + 'ignored-charsets t) + (let ((alist gnus-group-ignored-charsets-alist) + elem (charsets nil)) + (while (setq elem (pop alist)) + (when (and name + (string-match (car elem) name)) + (setq alist nil + charsets (cdr elem)))) + charsets))) + gnus-newsgroup-ignored-charsets)))) + (setq gnus-newsgroup-charset + (or gnus-newsgroup-ephemeral-charset + (and gnus-newsgroup-name + (or (gnus-group-find-parameter gnus-newsgroup-name 'charset) + (let ((alist gnus-group-charset-alist) + elem charset) + (while (setq elem (pop alist)) + (when (and name + (string-match (car elem) name)) + (setq alist nil + charset (cadr elem)))) + charset))) + gnus-default-charset)) + (set (make-local-variable 'gnus-newsgroup-ignored-charsets) + ignored-charsets)))) + +;;; +;;; Mime Commands +;;; + +(defun gnus-summary-display-buttonized (&optional show-all-parts) + "Display the current article buffer fully MIME-buttonized. +If SHOW-ALL-PARTS (the prefix) is non-nil, all multipart/* parts are +treated as multipart/mixed." + (interactive "P") + (require 'gnus-art) + (let ((gnus-unbuttonized-mime-types nil) + (gnus-mime-display-multipart-as-mixed show-all-parts)) + (gnus-summary-show-article))) + +(defun gnus-summary-repair-multipart (article) + "Add a Content-Type header to a multipart article without one." + (interactive (list (gnus-summary-article-number))) + (gnus-with-article article + (message-narrow-to-head) + (goto-char (point-max)) + (widen) + (when (search-forward "\n--" nil t) + (let ((separator (buffer-substring (point) (gnus-point-at-eol)))) + (message-narrow-to-head) + (message-remove-header "Mime-Version") + (message-remove-header "Content-Type") + (goto-char (point-max)) + (insert (format "Content-Type: multipart/mixed; boundary=\"%s\"\n" + separator)) + (insert "Mime-Version: 1.0\n") + (widen)))) + (let (gnus-mark-article-hook) + (gnus-summary-select-article t t nil article))) + +(defun gnus-summary-toggle-display-buttonized () + "Toggle the buttonizing of the article buffer." + (interactive) + (require 'gnus-art) + (if (setq gnus-inhibit-mime-unbuttonizing + (not gnus-inhibit-mime-unbuttonizing)) + (let ((gnus-unbuttonized-mime-types nil)) + (gnus-summary-show-article)) + (gnus-summary-show-article))) + +;;; +;;; Generic summary marking commands +;;; + +(defvar gnus-summary-marking-alist + '((read gnus-del-mark "d") + (unread gnus-unread-mark "u") + (ticked gnus-ticked-mark "!") + (dormant gnus-dormant-mark "?") + (expirable gnus-expirable-mark "e")) + "An alist of names/marks/keystrokes.") + +(defvar gnus-summary-generic-mark-map (make-sparse-keymap)) +(defvar gnus-summary-mark-map) + +(defun gnus-summary-make-all-marking-commands () + (define-key gnus-summary-mark-map "M" gnus-summary-generic-mark-map) + (dolist (elem gnus-summary-marking-alist) + (apply 'gnus-summary-make-marking-command elem))) + +(defun gnus-summary-make-marking-command (name mark keystroke) + (let ((map (make-sparse-keymap))) + (define-key gnus-summary-generic-mark-map keystroke map) + (dolist (lway `((next "next" next nil "n") + (next-unread "next unread" next t "N") + (prev "previous" prev nil "p") + (prev-unread "previous unread" prev t "P") + (nomove "" nil nil ,keystroke))) + (let ((func (gnus-summary-make-marking-command-1 + mark (car lway) lway name))) + (setq func (eval func)) + (define-key map (nth 4 lway) func))))) + +(defun gnus-summary-make-marking-command-1 (mark way lway name) + `(defun ,(intern + (format "gnus-summary-put-mark-as-%s%s" + name (if (eq way 'nomove) + "" + (concat "-" (symbol-name way))))) + (n) + ,(format + "Mark the current article as %s%s. +If N, the prefix, then repeat N times. +If N is negative, move in reverse order. +The difference between N and the actual number of articles marked is +returned." + name (cadr lway)) + (interactive "p") + (gnus-summary-generic-mark n ,mark ',(nth 2 lway) ,(nth 3 lway)))) + +(defun gnus-summary-generic-mark (n mark move unread) + "Mark N articles with MARK." + (unless (eq major-mode 'gnus-summary-mode) + (error "This command can only be used in the summary buffer")) + (gnus-summary-show-thread) + (let ((nummove + (cond + ((eq move 'next) 1) + ((eq move 'prev) -1) + (t 0)))) + (if (zerop nummove) + (setq n 1) + (when (< n 0) + (setq n (abs n) + nummove (* -1 nummove)))) + (while (and (> n 0) + (gnus-summary-mark-article nil mark) + (zerop (gnus-summary-next-subject nummove unread t))) + (setq n (1- n))) + (when (/= 0 n) + (gnus-message 7 "No more %sarticles" (if mark "" "unread "))) + (gnus-summary-recenter) + (gnus-summary-position-point) + (gnus-set-mode-line 'summary) + n)) + +(gnus-summary-make-all-marking-commands) + (gnus-ems-redefine) (provide 'gnus-sum) diff --git a/lisp/gnus/gnus-topic.el b/lisp/gnus/gnus-topic.el index 26b91f8072f..2a320d40866 100644 --- a/lisp/gnus/gnus-topic.el +++ b/lisp/gnus/gnus-topic.el @@ -1,5 +1,6 @@ ;;; gnus-topic.el --- a folding minor mode for Gnus group buffers -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Ilja Weis ;; Lars Magne Ingebrigtsen @@ -28,8 +29,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-group) (require 'gnus-start) @@ -151,11 +150,20 @@ with some simple extensions. (gnus-group-topic group)))) (defun gnus-topic-goto-topic (topic) - "Go to TOPIC." (when topic (gnus-goto-char (text-property-any (point-min) (point-max) 'gnus-topic (intern topic))))) +(defun gnus-topic-jump-to-topic (topic) + "Go to TOPIC." + (interactive + (list (completing-read "Go to topic: " + (mapcar 'list (gnus-topic-list)) + nil t))) + (dolist (topic (gnus-current-topics topic)) + (gnus-topic-fold t)) + (gnus-topic-goto-topic topic)) + (defun gnus-current-topic () "Return the name of the current topic." (let ((result @@ -205,16 +213,17 @@ If TOPIC, start with that topic." (if (member group gnus-zombie-list) gnus-level-zombie gnus-level-killed)))) (and - unread ; nil means that the group is dead. + info ; nil means that the group is dead. (<= clevel level) (>= clevel lowest) ; Is inside the level we want. (or all - (if (eq unread t) + (if (or (eq unread t) + (eq unread nil)) gnus-group-list-inactive-groups (> unread 0)) (and gnus-list-groups-with-ticked-articles (cdr (assq 'tick (gnus-info-marks info)))) - ; Has right readedness. + ;; Has right readedness. ;; Check for permanent visibility. (and gnus-permanently-visible-groups (string-match gnus-permanently-visible-groups group)) @@ -363,7 +372,8 @@ If TOPIC, start with that topic." ;;; Generating group buffers -(defun gnus-group-prepare-topics (level &optional all lowest regexp list-topic topic-level) +(defun gnus-group-prepare-topics (level &optional all lowest + regexp list-topic topic-level) "List all newsgroups with unread articles of level LEVEL or lower. Use the `gnus-group-topics' to sort the groups. If ALL is non-nil, list groups that have no unread articles. @@ -418,7 +428,7 @@ articles in the topic and its subtopics." (entries (gnus-topic-find-groups (car type) list-level (or all - (cdr (assq 'visible + (cdr (assq 'visible (gnus-topic-hierarchical-parameters (car type))))) lowest)) @@ -446,7 +456,8 @@ articles in the topic and its subtopics." (if (stringp entry) ;; Dead groups. (gnus-group-insert-group-line - entry (if (member entry gnus-zombie-list) gnus-level-zombie gnus-level-killed) + entry (if (member entry gnus-zombie-list) + gnus-level-zombie gnus-level-killed) nil (- (1+ (cdr (setq active (gnus-active entry)))) (car active)) nil) @@ -494,7 +505,7 @@ articles in the topic and its subtopics." (let ((data (cadr (gnus-topic-find-topology topic)))) (setcdr data (list (if insert 'visible 'invisible) - (if hide 'hide nil) + (caddr data) (cadddr data)))) (if total-remove (setq gnus-topic-alist @@ -507,9 +518,9 @@ articles in the topic and its subtopics." (car gnus-group-list-mode) (cdr gnus-group-list-mode) nil nil topic level)) -(defun gnus-topic-fold (&optional insert) +(defun gnus-topic-fold (&optional insert topic) "Remove/insert the current topic." - (let ((topic (gnus-group-topic-name))) + (let ((topic (or topic (gnus-group-topic-name)))) (when topic (save-excursion (if (not (gnus-group-active-topic-p)) @@ -533,15 +544,16 @@ articles in the topic and its subtopics." (gnus-topic-update-unreads name unread) (beginning-of-line) ;; Insert the text. - (gnus-add-text-properties - (point) - (prog1 (1+ (point)) - (eval gnus-topic-line-format-spec)) - (list 'gnus-topic (intern name) - 'gnus-topic-level level - 'gnus-topic-unread unread - 'gnus-active active-topic - 'gnus-topic-visible visiblep)))) + (if shownp + (gnus-add-text-properties + (point) + (prog1 (1+ (point)) + (eval gnus-topic-line-format-spec)) + (list 'gnus-topic (intern name) + 'gnus-topic-level level + 'gnus-topic-unread unread + 'gnus-active active-topic + 'gnus-topic-visible visiblep))))) (defun gnus-topic-update-unreads (topic unreads) (setq gnus-topic-unreads (delq (assoc topic gnus-topic-unreads) @@ -584,7 +596,8 @@ articles in the topic and its subtopics." (let* ((topic (gnus-group-topic group)) (groups (cdr (assoc topic gnus-topic-alist))) (g (cdr (member group groups))) - (unfound t)) + (unfound t) + entry) ;; Try to jump to a visible group. (while (and g (not (gnus-group-goto-group (car g) t))) (pop g)) @@ -598,8 +611,20 @@ articles in the topic and its subtopics." (when (and unfound topic (not (gnus-topic-goto-missing-topic topic))) - (gnus-topic-insert-topic-line - topic t t (car (gnus-topic-find-topology topic)) nil 0))))) + (let* ((top (gnus-topic-find-topology topic)) + (children (cddr top)) + (type (cadr top)) + (unread 0) + (entries (gnus-topic-find-groups + (car type) (car gnus-group-list-mode) + (cdr gnus-group-list-mode)))) + (while children + (incf unread (gnus-topic-unread (caar (pop children))))) + (while (setq entry (pop entries)) + (when (numberp (car entry)) + (incf unread (car entry)))) + (gnus-topic-insert-topic-line + topic t t (car (gnus-topic-find-topology topic)) nil unread)))))) (defun gnus-topic-goto-missing-topic (topic) (if (gnus-topic-goto-topic topic) @@ -608,15 +633,18 @@ articles in the topic and its subtopics." (let* ((top (gnus-topic-find-topology (gnus-topic-parent-topic topic))) (tp (reverse (cddr top)))) - (while (not (equal (caaar tp) topic)) - (setq tp (cdr tp))) - (pop tp) - (while (and tp - (not (gnus-topic-goto-topic (caaar tp)))) - (pop tp)) - (if tp - (gnus-topic-forward-topic 1) - (gnus-topic-goto-missing-topic (caadr top)))) + (if (not top) + (gnus-topic-insert-topic-line + topic t t (car (gnus-topic-find-topology topic)) nil 0) + (while (not (equal (caaar tp) topic)) + (setq tp (cdr tp))) + (pop tp) + (while (and tp + (not (gnus-topic-goto-topic (caaar tp)))) + (pop tp)) + (if tp + (gnus-topic-forward-topic 1) + (gnus-topic-goto-missing-topic (caadr top))))) nil)) (defun gnus-topic-update-topic-line (topic-name &optional reads) @@ -908,6 +936,7 @@ articles in the topic and its subtopics." "=" gnus-topic-select-group "\r" gnus-topic-select-group " " gnus-topic-read-group + "\C-c\C-x" gnus-topic-expire-articles "\C-k" gnus-topic-kill-group "\C-y" gnus-topic-yank-group "\M-g" gnus-topic-get-new-news-this-topic @@ -931,6 +960,7 @@ articles in the topic and its subtopics." "c" gnus-topic-copy-group "h" gnus-topic-hide-topic "s" gnus-topic-show-topic + "j" gnus-topic-jump-to-topic "M" gnus-topic-move-matching "C" gnus-topic-copy-matching "\C-i" gnus-topic-indent @@ -962,6 +992,7 @@ articles in the topic and its subtopics." ["Copy matching" gnus-topic-copy-matching t] ["Move matching" gnus-topic-move-matching t]) ("Topics" + ["Goto" gnus-topic-jump-to-topic t] ["Show" gnus-topic-show-topic t] ["Hide" gnus-topic-hide-topic t] ["Delete" gnus-topic-delete t] @@ -969,6 +1000,7 @@ articles in the topic and its subtopics." ["Create" gnus-topic-create-topic t] ["Mark" gnus-topic-mark-topic t] ["Indent" gnus-topic-indent t] + ["Sort" gnus-topic-sort-topics t] ["Toggle hide empty" gnus-topic-toggle-display-empty-topics t] ["Edit parameters" gnus-topic-edit-parameters t]) ["List active" gnus-topic-list-active t])))) @@ -982,12 +1014,15 @@ articles in the topic and its subtopics." (if (null arg) (not gnus-topic-mode) (> (prefix-numeric-value arg) 0))) ;; Infest Gnus with topics. - (if (not gnus-topic-mode) + (if (not gnus-topic-mode) (setq gnus-goto-missing-group-function nil) (when (gnus-visual-p 'topic-menu 'menu) (gnus-topic-make-menu-bar)) (gnus-set-format 'topic t) - (gnus-add-minor-mode 'gnus-topic-mode " Topic" gnus-topic-mode-map) + (gnus-add-minor-mode 'gnus-topic-mode " Topic" + gnus-topic-mode-map nil (lambda (&rest junk) + (interactive) + (gnus-topic-mode nil t))) (add-hook 'gnus-group-catchup-group-hook 'gnus-topic-update-topic) (set (make-local-variable 'gnus-group-prepare-function) 'gnus-group-prepare-topics) @@ -1032,7 +1067,8 @@ If performed over a topic line, toggle folding the topic." (if (gnus-group-topic-p) (let ((gnus-group-list-mode (if all (cons (if (numberp all) all 7) t) gnus-group-list-mode))) - (gnus-topic-fold all)) + (gnus-topic-fold all) + (gnus-dribble-touch)) (gnus-group-select-group all))) (defun gnus-mouse-pick-topic (e) @@ -1041,6 +1077,19 @@ If performed over a topic line, toggle folding the topic." (mouse-set-point e) (gnus-topic-read-group nil)) +(defun gnus-topic-expire-articles (topic) + "Expire articles in this topic or group." + (interactive (list (gnus-group-topic-name))) + (if (not topic) + (call-interactively 'gnus-group-expire-articles) + (save-excursion + (gnus-message 5 "Expiring groups in %s..." topic) + (let ((gnus-group-marked + (mapcar (lambda (entry) (car (nth 2 entry))) + (gnus-topic-find-groups topic gnus-level-killed t)))) + (gnus-group-expire-articles nil)) + (gnus-message 5 "Expiring groups in %s...done" topic)))) + (defun gnus-topic-read-group (&optional all no-article group) "Read news in this newsgroup. If the prefix argument ALL is non-nil, already read articles become @@ -1086,44 +1135,60 @@ When used interactively, PARENT will be the topic under point." (gnus-group-list-groups) (gnus-topic-goto-topic topic)) +;; FIXME: +;; 1. When the marked groups are overlapped with the process +;; region, the behavior of move or remove is not right. +;; 2. Can't process on several marked groups with a same name, +;; because gnus-group-marked only keeps one copy. + (defun gnus-topic-move-group (n topic &optional copyp) "Move the next N groups to TOPIC. If COPYP, copy the groups instead." (interactive (list current-prefix-arg (completing-read "Move to topic: " gnus-topic-alist nil t))) - (let ((groups (gnus-group-process-prefix n)) + (let ((use-marked (and (not n) (not (gnus-region-active-p)) + gnus-group-marked t)) + (groups (gnus-group-process-prefix n)) (topicl (assoc topic gnus-topic-alist)) - (start-group (progn (forward-line 1) (gnus-group-group-name))) (start-topic (gnus-group-topic-name)) + (start-group (progn (forward-line 1) (gnus-group-group-name))) entry) - (mapcar - (lambda (g) - (gnus-group-remove-mark g) - (when (and - (setq entry (assoc (gnus-current-topic) gnus-topic-alist)) - (not copyp)) - (setcdr entry (gnus-delete-first g (cdr entry)))) - (nconc topicl (list g))) - groups) - (gnus-topic-enter-dribble) - (if start-group - (gnus-group-goto-group start-group) - (gnus-topic-goto-topic start-topic)) - (gnus-group-list-groups))) + (if (and (not groups) (not copyp) start-topic) + (gnus-topic-move start-topic topic) + (mapcar + (lambda (g) + (gnus-group-remove-mark g use-marked) + (when (and + (setq entry (assoc (gnus-current-topic) gnus-topic-alist)) + (not copyp)) + (setcdr entry (gnus-delete-first g (cdr entry)))) + (nconc topicl (list g))) + groups) + (gnus-topic-enter-dribble) + (if start-group + (gnus-group-goto-group start-group) + (gnus-topic-goto-topic start-topic)) + (gnus-group-list-groups)))) -(defun gnus-topic-remove-group (&optional arg) +(defun gnus-topic-remove-group (&optional n) "Remove the current group from the topic." (interactive "P") - (gnus-group-iterate arg - (lambda (group) - (let ((topicl (assoc (gnus-current-topic) gnus-topic-alist)) - (buffer-read-only nil)) - (when (and topicl group) - (gnus-delete-line) - (gnus-delete-first group topicl)) - (gnus-topic-update-topic) - (gnus-group-position-point))))) + (let ((use-marked (and (not n) (not (gnus-region-active-p)) + gnus-group-marked t)) + (groups (gnus-group-process-prefix n))) + (mapcar + (lambda (group) + (gnus-group-remove-mark group use-marked) + (let ((topicl (assoc (gnus-current-topic) gnus-topic-alist)) + (buffer-read-only nil)) + (when (and topicl group) + (gnus-delete-line) + (gnus-delete-first group topicl)) + (gnus-topic-update-topic))) + groups) + (gnus-topic-enter-dribble) + (gnus-group-position-point))) (defun gnus-topic-copy-group (n topic) "Copy the current group to a topic." @@ -1145,7 +1210,12 @@ If COPYP, copy the groups instead." (gnus-topic-find-topology topic nil nil gnus-topic-topology) (gnus-topic-enter-dribble)) (gnus-group-kill-group n discard) - (gnus-topic-update-topic))) + (if (not (gnus-group-topic-p)) + (gnus-topic-update-topic) + ;; Move up one line so that we update the right topic. + (forward-line -1) + (gnus-topic-update-topic) + (forward-line 1)))) (defun gnus-topic-yank-group (&optional arg) "Yank the last topic." @@ -1195,18 +1265,32 @@ If COPYP, copy the groups instead." (setq alist (cdr alist)))))) (gnus-topic-update-topic))) -(defun gnus-topic-hide-topic () - "Hide the current topic." - (interactive) +(defun gnus-topic-hide-topic (&optional permanent) + "Hide the current topic. +If PERMANENT, make it stay hidden in subsequent sessions as well." + (interactive "P") (when (gnus-current-topic) (gnus-topic-goto-topic (gnus-current-topic)) - (gnus-topic-remove-topic nil nil 'hidden))) + (if permanent + (setcar (cddr + (cadr + (gnus-topic-find-topology (gnus-current-topic)))) + 'hidden)) + (gnus-topic-remove-topic nil nil))) -(defun gnus-topic-show-topic () - "Show the hidden topic." - (interactive) +(defun gnus-topic-show-topic (&optional permanent) + "Show the hidden topic. +If PERMANENT, make it stay shown in subsequent sessions as well." + (interactive "P") (when (gnus-group-topic-p) - (gnus-topic-remove-topic t nil 'shown))) + (if (not permanent) + (gnus-topic-remove-topic t nil) + (let ((topic + (gnus-topic-find-topology + (completing-read "Show topic: " gnus-topic-alist nil t)))) + (setcar (cddr (cadr topic)) nil) + (setcar (cdr (cadr topic)) 'visible) + (gnus-group-list-groups))))) (defun gnus-topic-mark-topic (topic &optional unmark) "Mark all groups in the topic with the process mark." @@ -1450,6 +1534,68 @@ If REVERSE, sort in reverse order." (interactive "P") (gnus-topic-sort-groups 'gnus-group-sort-by-method reverse)) +(defun gnus-topic-sort-topics-1 (top reverse) + (if (cdr top) + (let ((subtop + (mapcar `(lambda (top) + (gnus-topic-sort-topics-1 top ,reverse)) + (sort (cdr top) + '(lambda (t1 t2) + (string-lessp (caar t1) (caar t2))))))) + (setcdr top (if reverse (reverse subtop) subtop)))) + top) + +(defun gnus-topic-sort-topics (&optional topic reverse) + "Sort topics in TOPIC alphabeticaly by topic name. +If REVERSE, reverse the sorting order." + (interactive + (list (completing-read "Sort topics in : " gnus-topic-alist nil t + (gnus-current-topic)) + current-prefix-arg)) + (let ((topic-topology (or (and topic (cdr (gnus-topic-find-topology topic))) + gnus-topic-topology))) + (gnus-topic-sort-topics-1 topic-topology reverse) + (gnus-topic-enter-dribble) + (gnus-group-list-groups) + (gnus-topic-goto-topic topic))) + +(defun gnus-topic-move (current to) + "Move the CURRENT topic to TO." + (interactive + (list + (gnus-group-topic-name) + (completing-read "Move to topic: " gnus-topic-alist nil t))) + (unless (and current to) + (error "Can't find topic")) + (let ((current-top (cdr (gnus-topic-find-topology current))) + (to-top (cdr (gnus-topic-find-topology to)))) + (unless current-top + (error "Can't find topic `%s'" current)) + (unless to-top + (error "Can't find topic `%s'" to)) + (if (gnus-topic-find-topology to current-top 0);; Don't care the level + (error "Can't move `%s' to its sub-level" current)) + (gnus-topic-find-topology current nil nil 'delete) + (while (cdr to-top) + (setq to-top (cdr to-top))) + (setcdr to-top (list current-top)) + (gnus-topic-enter-dribble) + (gnus-group-list-groups) + (gnus-topic-goto-topic current))) + +(defun gnus-subscribe-topics (newsgroup) + (catch 'end + (let (match gnus-group-change-level-function) + (dolist (topic (gnus-topic-list)) + (when (and (setq match (cdr (assq 'subscribe + (gnus-topic-parameters topic)))) + (string-match match newsgroup)) + ;; Just subscribe the group. + (gnus-subscribe-alphabetically newsgroup) + ;; Add the group to the topic. + (nconc (assoc topic gnus-topic-alist) (list newsgroup)) + (throw 'end t)))))) + (provide 'gnus-topic) ;;; gnus-topic.el ends here diff --git a/lisp/gnus/gnus-undo.el b/lisp/gnus/gnus-undo.el index 624b34a9916..7dd333f1c93 100644 --- a/lisp/gnus/gnus-undo.el +++ b/lisp/gnus/gnus-undo.el @@ -1,5 +1,7 @@ ;;; gnus-undo.el --- minor mode for undoing in Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -46,8 +48,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus-util) (require 'gnus) (require 'custom) @@ -86,11 +86,11 @@ (setq gnus-undo-mode-map (make-sparse-keymap)) (gnus-define-keys gnus-undo-mode-map - "\M-\C-_" gnus-undo - "\C-_" gnus-undo - "\C-xu" gnus-undo - ;; many people are used to type `C-/' on X terminals and get `C-_'. - [(control /)] gnus-undo)) + "\M-\C-_" gnus-undo + "\C-_" gnus-undo + "\C-xu" gnus-undo + ;; many people are used to type `C-/' on X terminals and get `C-_'. + [(control /)] gnus-undo)) (defun gnus-undo-make-menu-bar () ;; This is disabled for the time being. diff --git a/lisp/gnus/gnus-util.el b/lisp/gnus/gnus-util.el index 8885fbd8719..10f5076815e 100644 --- a/lisp/gnus/gnus-util.el +++ b/lisp/gnus/gnus-util.el @@ -1,5 +1,6 @@ ;;; gnus-util.el --- utility functions for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -33,12 +34,10 @@ (require 'custom) (eval-when-compile (require 'cl)) (require 'nnheader) -(require 'timezone) (require 'message) -(eval-when-compile (require 'rmail)) +(require 'time-date) (eval-and-compile - (autoload 'nnmail-date-to-time "nnmail") (autoload 'rmail-insert-rmail-file-header "rmail") (autoload 'rmail-count-new-messages "rmail") (autoload 'rmail-show-message "rmail")) @@ -76,9 +75,6 @@ (set symbol nil)) symbol)) -(defun gnus-truncate-string (str width) - (substring str 0 width)) - ;; Added by Geoffrey T. Dairiki . A safe way ;; to limit the length of a string. This function is necessary since ;; `(substr "abc" 0 30)' pukes with "Args out of range". @@ -107,25 +103,15 @@ (when (gnus-buffer-exists-p buf) (kill-buffer buf)))) -(if (fboundp 'point-at-bol) - (fset 'gnus-point-at-bol 'point-at-bol) - (defun gnus-point-at-bol () - "Return point at the beginning of the line." - (let ((p (point))) - (beginning-of-line) - (prog1 - (point) - (goto-char p))))) +(defalias 'gnus-point-at-bol + (if (fboundp 'point-at-bol) + 'point-at-bol + 'line-beginning-position)) -(if (fboundp 'point-at-eol) - (fset 'gnus-point-at-eol 'point-at-eol) - (defun gnus-point-at-eol () - "Return point at the end of the line." - (let ((p (point))) - (end-of-line) - (prog1 - (point) - (goto-char p))))) +(defalias 'gnus-point-at-eol + (if (fboundp 'point-at-eol) + 'point-at-eol + 'line-end-position)) (defun gnus-delete-first (elt list) "Delete by side effect the first occurrence of ELT as a member of LIST." @@ -179,8 +165,8 @@ (and (string-match "(.*" from) (setq name (substring from (1+ (match-beginning 0)) (match-end 0))))) - ;; Fix by Hallvard B Furuseth . - (list (or name from) (or address from)))) + (list (if (string= name "") nil name) (or address from)))) + (defun gnus-fetch-field (field) "Return the value of the header FIELD of current article." @@ -232,43 +218,6 @@ ;;; Time functions. -(defun gnus-days-between (date1 date2) - ;; Return the number of days between date1 and date2. - (- (gnus-day-number date1) (gnus-day-number date2))) - -(defun gnus-day-number (date) - (let ((dat (mapcar (lambda (s) (and s (string-to-int s)) ) - (timezone-parse-date date)))) - (timezone-absolute-from-gregorian - (nth 1 dat) (nth 2 dat) (car dat)))) - -(defun gnus-time-to-day (time) - "Convert TIME to day number." - (let ((tim (decode-time time))) - (timezone-absolute-from-gregorian - (nth 4 tim) (nth 3 tim) (nth 5 tim)))) - -(defun gnus-encode-date (date) - "Convert DATE to internal time." - (let* ((parse (timezone-parse-date date)) - (date (mapcar (lambda (d) (and d (string-to-int d))) parse)) - (time (mapcar 'string-to-int (timezone-parse-time (aref parse 3))))) - (encode-time (caddr time) (cadr time) (car time) - (caddr date) (cadr date) (car date) - (* 60 (timezone-zone-to-minute (nth 4 date)))))) - -(defun gnus-time-minus (t1 t2) - "Subtract two internal times." - (let ((borrow (< (cadr t1) (cadr t2)))) - (list (- (car t1) (car t2) (if borrow 1 0)) - (- (+ (if borrow 65536 0) (cadr t1)) (cadr t2))))) - -(defun gnus-time-less (t1 t2) - "Say whether time T1 is less than time T2." - (or (< (car t1) (car t2)) - (and (= (car t1) (car t2)) - (< (nth 1 t1) (nth 1 t2))))) - (defun gnus-file-newer-than (file date) (let ((fdate (nth 5 (file-attributes file)))) (or (> (car fdate) (car date)) @@ -343,20 +292,9 @@ (defun gnus-dd-mmm (messy-date) "Return a string like DD-MMM from a big messy string." - (let ((datevec (ignore-errors (timezone-parse-date messy-date)))) - (if (or (not datevec) - (string-equal "0" (aref datevec 1))) - "??-???" - (format "%2s-%s" - (condition-case () - ;; Make sure leading zeroes are stripped. - (number-to-string (string-to-number (aref datevec 2))) - (error "??")) - (capitalize - (or (car - (nth (1- (string-to-number (aref datevec 1))) - timezone-months-assoc)) - "???")))))) + (condition-case () + (format-time-string "%d-%b" (safe-date-to-time messy-date)) + (error " - "))) (defmacro gnus-date-get-time (date) "Convert DATE string to Emacs time. @@ -367,7 +305,7 @@ Cache the result as a text property stored in DATE." '(0 0) (or (get-text-property 0 'gnus-time d) ;; or compute the value... - (let ((time (nnmail-date-to-time d))) + (let ((time (safe-date-to-time d))) ;; and store it back in the string. (put-text-property 0 1 'gnus-time time d) time))))) @@ -451,12 +389,14 @@ jabbering all the time." ids)) (nreverse ids))) -(defun gnus-parent-id (references &optional n) +(defsubst gnus-parent-id (references &optional n) "Return the last Message-ID in REFERENCES. If N, return the Nth ancestor instead." (when references (let ((ids (inline (gnus-split-references references)))) - (car (last ids (or n 1)))))) + (while (nthcdr (or n 1) ids) + (setq ids (cdr ids))) + (car ids)))) (defsubst gnus-buffer-live-p (buffer) "Say whether BUFFER is alive or not." @@ -496,20 +436,8 @@ If N, return the Nth ancestor instead." (cons (and (numberp event) event) event))) (defun gnus-sortable-date (date) - "Make sortable string by string-lessp from DATE. -Timezone package is used." - (condition-case () - (progn - (setq date (inline (timezone-fix-time - date nil - (aref (inline (timezone-parse-date date)) 4)))) - (inline - (timezone-make-sortable-date - (aref date 0) (aref date 1) (aref date 2) - (inline - (timezone-make-time-string - (aref date 3) (aref date 4) (aref date 5)))))) - (error ""))) + "Make string suitable for sorting from DATE." + (gnus-time-iso8601 (date-to-time date))) (defun gnus-copy-file (file &optional to) "Copy FILE to TO." @@ -541,7 +469,7 @@ Timezone package is used." (erase-buffer)) (set-buffer (gnus-get-buffer-create gnus-work-buffer)) (kill-all-local-variables) - (buffer-disable-undo (current-buffer)))) + (mm-enable-multibyte))) (defmacro gnus-group-real-name (group) "Find the real name of a foreign newsgroup." @@ -553,21 +481,41 @@ Timezone package is used." (defun gnus-make-sort-function (funs) "Return a composite sort condition based on the functions in FUNC." (cond - ((not (listp funs)) funs) + ;; Just a simple function. + ((gnus-functionp funs) funs) + ;; No functions at all. ((null funs) funs) - ((cdr funs) + ;; A list of functions. + ((or (cdr funs) + (listp (car funs))) `(lambda (t1 t2) ,(gnus-make-sort-function-1 (reverse funs)))) + ;; A list containing just one function. (t (car funs)))) (defun gnus-make-sort-function-1 (funs) "Return a composite sort condition based on the functions in FUNC." - (if (cdr funs) - `(or (,(car funs) t1 t2) - (and (not (,(car funs) t2 t1)) - ,(gnus-make-sort-function-1 (cdr funs)))) - `(,(car funs) t1 t2))) + (let ((function (car funs)) + (first 't1) + (last 't2)) + (when (consp function) + (cond + ;; Reversed spec. + ((eq (car function) 'not) + (setq function (cadr function) + first 't2 + last 't1)) + ((gnus-functionp function) + ;; Do nothing. + ) + (t + (error "Invalid sort spec: %s" function)))) + (if (cdr funs) + `(or (,function ,first ,last) + (and (not (,function ,last ,first)) + ,(gnus-make-sort-function-1 (cdr funs)))) + `(,function ,first ,last)))) (defun gnus-turn-off-edit-menu (type) "Turn off edit menu in `gnus-TYPE-mode-map'." @@ -591,17 +539,19 @@ Bind `print-quoted' and `print-readably' to t while printing." (defun gnus-make-directory (directory) "Make DIRECTORY (and all its parents) if it doesn't exist." - (when (and directory - (not (file-exists-p directory))) - (make-directory directory t)) + (let ((file-name-coding-system nnmail-pathname-coding-system)) + (when (and directory + (not (file-exists-p directory))) + (make-directory directory t))) t) (defun gnus-write-buffer (file) "Write the current buffer's contents to FILE." ;; Make sure the directory exists. (gnus-make-directory (file-name-directory file)) - ;; Write the buffer. - (write-region (point-min) (point-max) file nil 'quietly)) + (let ((file-name-coding-system nnmail-pathname-coding-system)) + ;; Write the buffer. + (write-region (point-min) (point-max) file nil 'quietly))) (defun gnus-delete-file (file) "Delete FILE if it exists." @@ -614,13 +564,13 @@ Bind `print-quoted' and `print-readably' to t while printing." (setq string (replace-match "" t t string))) string) -(defun gnus-put-text-property-excluding-newlines (beg end prop val) +(defsubst gnus-put-text-property-excluding-newlines (beg end prop val) "The same as `put-text-property', but don't put this prop on any newlines in the region." (save-match-data (save-excursion (save-restriction (goto-char beg) - (while (re-search-forward "[ \t]*\n" end 'move) + (while (re-search-forward gnus-emphasize-whitespace-regexp end 'move) (gnus-put-text-property beg (match-beginning 0) prop val) (setq beg (point))) (gnus-put-text-property beg (point) prop val))))) @@ -733,7 +683,8 @@ with potentially long computations." (save-excursion (set-buffer file-buffer) (rmail-insert-rmail-file-header) - (let ((require-final-newline nil)) + (let ((require-final-newline nil) + (coding-system-for-write mm-text-coding-system)) (gnus-write-buffer filename))) (kill-buffer file-buffer)) (error "Output file does not exist"))) @@ -744,7 +695,7 @@ with potentially long computations." ;; Decide whether to append to a file or to an Emacs buffer. (let ((outbuf (get-file-buffer filename))) (if (not outbuf) - (append-to-file (point-min) (point-max) filename) + (mm-append-to-file (point-min) (point-max) filename) ;; File has been visited, in buffer OUTBUF. (set-buffer outbuf) (let ((buffer-read-only nil) @@ -784,7 +735,8 @@ with potentially long computations." (let ((file-buffer (create-file-buffer filename))) (save-excursion (set-buffer file-buffer) - (let ((require-final-newline nil)) + (let ((require-final-newline nil) + (coding-system-for-write mm-text-coding-system)) (gnus-write-buffer filename))) (kill-buffer file-buffer)) (error "Output file does not exist"))) @@ -812,7 +764,7 @@ with potentially long computations." (insert "\n")) (insert "\n")) (goto-char (point-max)) - (append-to-file (point-min) (point-max) filename))) + (mm-append-to-file (point-min) (point-max) filename))) ;; File has been visited, in buffer OUTBUF. (set-buffer outbuf) (let ((buffer-read-only nil)) @@ -853,84 +805,84 @@ ARG is passed to the first function." ;;; .netrc and .authinforc parsing ;;; -(defvar gnus-netrc-syntax-table - (let ((table (copy-syntax-table text-mode-syntax-table))) - (modify-syntax-entry ?@ "w" table) - (modify-syntax-entry ?- "w" table) - (modify-syntax-entry ?_ "w" table) - (modify-syntax-entry ?! "w" table) - (modify-syntax-entry ?. "w" table) - (modify-syntax-entry ?, "w" table) - (modify-syntax-entry ?: "w" table) - (modify-syntax-entry ?\; "w" table) - (modify-syntax-entry ?% "w" table) - (modify-syntax-entry ?) "w" table) - (modify-syntax-entry ?( "w" table) - table) - "Syntax table when parsing .netrc files.") - (defun gnus-parse-netrc (file) "Parse FILE and return an list of all entries in the file." - (if (not (file-exists-p file)) - () - (save-excursion + (when (file-exists-p file) + (with-temp-buffer (let ((tokens '("machine" "default" "login" - "password" "account" "macdef" "force")) + "password" "account" "macdef" "force" + "port")) alist elem result pair) - (nnheader-set-temp-buffer " *netrc*") - (unwind-protect - (progn - (set-syntax-table gnus-netrc-syntax-table) - (insert-file-contents file) - (goto-char (point-min)) - ;; Go through the file, line by line. - (while (not (eobp)) - (narrow-to-region (point) (gnus-point-at-eol)) - ;; For each line, get the tokens and values. - (while (not (eobp)) - (skip-chars-forward "\t ") - (unless (eobp) - (setq elem (buffer-substring - (point) (progn (forward-sexp 1) (point)))) - (cond - ((equal elem "macdef") - ;; We skip past the macro definition. - (widen) - (while (and (zerop (forward-line 1)) - (looking-at "$"))) - (narrow-to-region (point) (point))) - ((member elem tokens) - ;; Tokens that don't have a following value are ignored, - ;; except "default". - (when (and pair (or (cdr pair) - (equal (car pair) "default"))) - (push pair alist)) - (setq pair (list elem))) - (t - ;; Values that haven't got a preceding token are ignored. - (when pair - (setcdr pair elem) - (push pair alist) - (setq pair nil)))))) - (if alist - (push (nreverse alist) result)) - (setq alist nil - pair nil) - (widen) - (forward-line 1)) - (nreverse result)) - (kill-buffer " *netrc*")))))) + (insert-file-contents file) + (goto-char (point-min)) + ;; Go through the file, line by line. + (while (not (eobp)) + (narrow-to-region (point) (gnus-point-at-eol)) + ;; For each line, get the tokens and values. + (while (not (eobp)) + (skip-chars-forward "\t ") + ;; Skip lines that begin with a "#". + (if (eq (char-after) ?#) + (goto-char (point-max)) + (unless (eobp) + (setq elem + (if (= (following-char) ?\") + (read (current-buffer)) + (buffer-substring + (point) (progn (skip-chars-forward "^\t ") + (point))))) + (cond + ((equal elem "macdef") + ;; We skip past the macro definition. + (widen) + (while (and (zerop (forward-line 1)) + (looking-at "$"))) + (narrow-to-region (point) (point))) + ((member elem tokens) + ;; Tokens that don't have a following value are ignored, + ;; except "default". + (when (and pair (or (cdr pair) + (equal (car pair) "default"))) + (push pair alist)) + (setq pair (list elem))) + (t + ;; Values that haven't got a preceding token are ignored. + (when pair + (setcdr pair elem) + (push pair alist) + (setq pair nil))))))) + (when alist + (push (nreverse alist) result)) + (setq alist nil + pair nil) + (widen) + (forward-line 1)) + (nreverse result))))) -(defun gnus-netrc-machine (list machine) - "Return the netrc values from LIST for MACHINE or for the default entry." - (let ((rest list)) - (while (and list - (not (equal (cdr (assoc "machine" (car list))) machine))) +(defun gnus-netrc-machine (list machine &optional port defaultport) + "Return the netrc values from LIST for MACHINE or for the default entry. +If PORT specified, only return entries with matching port tokens. +Entries without port tokens default to DEFAULTPORT." + (let ((rest list) + result) + (while list + (when (equal (cdr (assoc "machine" (car list))) machine) + (push (car list) result)) (pop list)) - (car (or list - (progn (while (and rest (not (assoc "default" (car rest)))) - (pop rest)) - rest))))) + (unless result + ;; No machine name matches, so we look for default entries. + (while rest + (when (assoc "default" (car rest)) + (push (car rest) result)) + (pop rest))) + (when result + (setq result (nreverse result)) + (while (and result + (not (equal (or port defaultport "nntp") + (or (gnus-netrc-get (car result) "port") + defaultport "nntp")))) + (pop result)) + (car result)))) (defun gnus-netrc-get (alist type) "Return the value of token TYPE from ALIST." @@ -938,7 +890,7 @@ ARG is passed to the first function." ;;; Various -(defvar gnus-group-buffer) ; Compiler directive +(defvar gnus-group-buffer) ; Compiler directive (defun gnus-alive-p () "Say whether Gnus is running or not." (and (boundp 'gnus-group-buffer) @@ -971,11 +923,12 @@ ARG is passed to the first function." (setq alist (delq entry alist))) alist)) -(defmacro gnus-pull (key alist) +(defmacro gnus-pull (key alist &optional assoc-p) "Modify ALIST to be without KEY." (unless (symbolp alist) (error "Not a symbol: %s" alist)) - `(setq ,alist (delq (assq ,key ,alist) ,alist))) + (let ((fun (if assoc-p 'assoc 'assq))) + `(setq ,alist (delq (,fun ,key ,alist) ,alist)))) (defun gnus-globalify-regexp (re) "Returns a regexp that matches a whole line, iff RE matches a part of it." @@ -983,6 +936,52 @@ ARG is passed to the first function." re (unless (string-match "\\$$" re) ".*$"))) +(defun gnus-set-window-start (&optional point) + "Set the window start to POINT, or (point) if nil." + (let ((win (get-buffer-window (current-buffer) t))) + (when win + (set-window-start win (or point (point)))))) + +(defun gnus-annotation-in-region-p (b e) + (if (= b e) + (eq (cadr (memq 'gnus-undeletable (text-properties-at b))) t) + (text-property-any b e 'gnus-undeletable t))) + +(defun gnus-or (&rest elems) + "Return non-nil if any of the elements are non-nil." + (catch 'found + (while elems + (when (pop elems) + (throw 'found t))))) + +(defun gnus-and (&rest elems) + "Return non-nil if all of the elements are non-nil." + (catch 'found + (while elems + (unless (pop elems) + (throw 'found nil))) + t)) + +(defun gnus-write-active-file (file hashtb &optional full-names) + (let ((coding-system-for-write nnmail-active-file-coding-system)) + (with-temp-file file + (mapatoms + (lambda (sym) + (when (and sym + (boundp sym) + (symbol-value sym)) + (insert (format "%S %d %d y\n" + (if full-names + sym + (intern (gnus-group-real-name (symbol-name sym)))) + (or (cdr (symbol-value sym)) + (car (symbol-value sym))) + (car (symbol-value sym)))))) + hashtb) + (goto-char (point-max)) + (while (search-backward "\\." nil t) + (delete-char 1))))) + (provide 'gnus-util) ;;; gnus-util.el ends here diff --git a/lisp/gnus/gnus-uu.el b/lisp/gnus/gnus-uu.el index f99f05b515d..c1960f6057f 100644 --- a/lisp/gnus/gnus-uu.el +++ b/lisp/gnus/gnus-uu.el @@ -1,5 +1,6 @@ ;;; gnus-uu.el --- extract (uu)encoded files in Gnus -;; Copyright (C) 1985,86,87,93,94,95,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1985, 1986, 1987, 1993, 1994, 1995, 1996, 1997, 1998, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Created: 2 Oct 1993 @@ -28,12 +29,11 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (require 'gnus-art) (require 'message) (require 'gnus-msg) +(require 'mm-decode) (defgroup gnus-extract nil "Extracting encoded files." @@ -217,9 +217,12 @@ Note that this variable can be used in conjunction with the ;; Various variables users may set -(defcustom gnus-uu-tmp-dir temporary-file-directory +(defcustom gnus-uu-tmp-dir + (cond ((fboundp 'temp-directory) (temp-directory)) + ((boundp 'temporary-file-directory) temporary-file-directory) + ("/tmp/")) "*Variable saying where gnus-uu is to do its work. -Defaults to `temporary-file-directory'." +Default is \"/tmp/\"." :group 'gnus-extract :type 'directory) @@ -292,7 +295,9 @@ so I simply dropped them." (defcustom gnus-uu-digest-headers '("^Date:" "^From:" "^To:" "^Cc:" "^Subject:" "^Message-ID:" "^Keywords:" - "^Summary:" "^References:" "^Content-Type:" "^Content-Transfer-Encoding:") + "^Summary:" "^References:" "^Content-Type:" "^Content-Transfer-Encoding:" + "^MIME-Version:" "^Content-Disposition:" "^Content-Description:" + "^Content-ID:") "*List of regexps to match headers included in digested messages. The headers will be included in the sequence they are matched." :group 'gnus-extract @@ -330,7 +335,8 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (defvar gnus-uu-shar-begin-string "^#! */bin/sh") (defvar gnus-uu-shar-file-name nil) -(defvar gnus-uu-shar-name-marker "begin [0-7][0-7][0-7][ \t]+\\(\\(\\w\\|\\.\\)*\\b\\)") +(defvar gnus-uu-shar-name-marker + "begin [0-7][0-7][0-7][ \t]+\\(\\(\\w\\|\\.\\)*\\b\\)") (defvar gnus-uu-postscript-begin-string "^%!PS-") (defvar gnus-uu-postscript-end-string "^%%EOF$") @@ -345,6 +351,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (defvar gnus-uu-default-dir gnus-article-save-directory) (defvar gnus-uu-digest-from-subject nil) +(defvar gnus-uu-digest-buffer nil) ;; Keymaps @@ -370,7 +377,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (gnus-define-keys (gnus-uu-extract-map "X" gnus-summary-mode-map) ;;"x" gnus-uu-extract-any - ;;"m" gnus-uu-extract-mime + "m" gnus-summary-save-parts "u" gnus-uu-decode-uu "U" gnus-uu-decode-uu-and-save "s" gnus-uu-decode-unshar @@ -383,17 +390,17 @@ didn't work, and overwrite existing files. Otherwise, ask each time." "P" gnus-uu-decode-postscript-and-save) (gnus-define-keys - (gnus-uu-extract-view-map "v" gnus-uu-extract-map) - "u" gnus-uu-decode-uu-view - "U" gnus-uu-decode-uu-and-save-view - "s" gnus-uu-decode-unshar-view - "S" gnus-uu-decode-unshar-and-save-view - "o" gnus-uu-decode-save-view - "O" gnus-uu-decode-save-view - "b" gnus-uu-decode-binhex-view - "B" gnus-uu-decode-binhex-view - "p" gnus-uu-decode-postscript-view - "P" gnus-uu-decode-postscript-and-save-view) + (gnus-uu-extract-view-map "v" gnus-uu-extract-map) + "u" gnus-uu-decode-uu-view + "U" gnus-uu-decode-uu-and-save-view + "s" gnus-uu-decode-unshar-view + "S" gnus-uu-decode-unshar-and-save-view + "o" gnus-uu-decode-save-view + "O" gnus-uu-decode-save-view + "b" gnus-uu-decode-binhex-view + "B" gnus-uu-decode-binhex-view + "p" gnus-uu-decode-postscript-view + "P" gnus-uu-decode-postscript-and-save-view) ;; Commands. @@ -450,7 +457,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." gnus-uu-default-dir gnus-uu-default-dir)))) (setq gnus-uu-binhex-article-name - (make-temp-file (concat gnus-uu-work-dir "binhex"))) + (make-temp-name (concat gnus-uu-work-dir "binhex"))) (gnus-uu-decode-with-method 'gnus-uu-binhex-article n dir)) (defun gnus-uu-decode-uu-view (&optional n) @@ -490,7 +497,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (interactive (list current-prefix-arg (read-file-name (if gnus-uu-save-separate-articles - "Save articles in dir: " + "Save articles is dir: " "Save articles in file: ") gnus-uu-default-dir gnus-uu-default-dir))) (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic))) @@ -503,7 +510,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (read-file-name "Unbinhex, view and save in dir: " gnus-uu-default-dir gnus-uu-default-dir))) (setq gnus-uu-binhex-article-name - (make-temp-file (concat gnus-uu-work-dir "binhex"))) + (make-temp-name (concat gnus-uu-work-dir "binhex"))) (let ((gnus-view-pseudos (or gnus-view-pseudos 'automatic))) (gnus-uu-decode-binhex n file))) @@ -514,15 +521,20 @@ didn't work, and overwrite existing files. Otherwise, ask each time." "Digests and forwards all articles in this series." (interactive "P") (let ((gnus-uu-save-in-digest t) - (file (make-temp-file (nnheader-concat gnus-uu-tmp-dir "forward"))) - buf subject from) + (file (make-temp-name (nnheader-concat gnus-uu-tmp-dir "forward"))) + (message-forward-as-mime message-forward-as-mime) + (mail-parse-charset gnus-newsgroup-charset) + (mail-parse-ignored-charsets gnus-newsgroup-ignored-charsets) + gnus-uu-digest-buffer subject from) + (if (and n (not (numberp n))) + (setq message-forward-as-mime (not message-forward-as-mime) + n nil)) (gnus-setup-message 'forward (setq gnus-uu-digest-from-subject nil) + (setq gnus-uu-digest-buffer + (gnus-get-buffer-create " *gnus-uu-forward*")) (gnus-uu-decode-save n file) - (setq buf (switch-to-buffer - (gnus-get-buffer-create " *gnus-uu-forward*"))) - (erase-buffer) - (insert-file file) + (switch-to-buffer gnus-uu-digest-buffer) (let ((fs gnus-uu-digest-from-subject)) (when fs (setq from (caar fs) @@ -552,9 +564,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (when (re-search-forward "^From: ") (delete-region (point) (gnus-point-at-eol)) (insert from)) - (message-forward post)) - (delete-file file) - (kill-buffer buf) + (message-forward post t)) (setq gnus-uu-digest-from-subject nil))) (defun gnus-uu-digest-post-forward (&optional n) @@ -565,8 +575,10 @@ didn't work, and overwrite existing files. Otherwise, ask each time." ;; Process marking. (defun gnus-uu-mark-by-regexp (regexp &optional unmark) - "Ask for a regular expression and set the process mark on all articles that match." - (interactive (list (read-from-minibuffer "Mark (regexp): "))) + "Set the process mark on articles whose subjects match REGEXP. +When called interactively, prompt for REGEXP. +Optional UNMARK non-nil means unmark instead of mark." + (interactive "sMark (regexp): \nP") (let ((articles (gnus-uu-find-articles-matching regexp))) (while articles (if unmark @@ -575,9 +587,10 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (message "")) (gnus-summary-position-point)) -(defun gnus-uu-unmark-by-regexp (regexp &optional unmark) - "Ask for a regular expression and remove the process mark on all articles that match." - (interactive (list (read-from-minibuffer "Mark (regexp): "))) +(defun gnus-uu-unmark-by-regexp (regexp) + "Remove the process mark from articles whose subjects match REGEXP. +When called interactively, prompt for REGEXP." + (interactive "sUnmark (regexp): ") (gnus-uu-mark-by-regexp regexp t)) (defun gnus-uu-mark-series () @@ -620,10 +633,12 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (defun gnus-uu-mark-thread () "Marks all articles downwards in this thread." (interactive) - (let ((level (gnus-summary-thread-level))) - (while (and (gnus-summary-set-process-mark (gnus-summary-article-number)) - (zerop (gnus-summary-next-subject 1)) - (> (gnus-summary-thread-level) level)))) + (gnus-save-hidden-threads + (let ((level (gnus-summary-thread-level))) + (while (and (gnus-summary-set-process-mark + (gnus-summary-article-number)) + (zerop (gnus-summary-next-subject 1 nil t)) + (> (gnus-summary-thread-level) level))))) (gnus-summary-position-point)) (defun gnus-uu-unmark-thread () @@ -652,7 +667,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (defun gnus-uu-mark-over (&optional score) "Mark all articles with a score over SCORE (the prefix)." (interactive "P") - (let ((score (gnus-score-default score)) + (let ((score (or score gnus-summary-default-score 0)) (data gnus-newsgroup-data)) (save-excursion (while data @@ -808,8 +823,9 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (gnus-uu-save-separate-articles (save-excursion (set-buffer buffer) - (gnus-write-buffer - (concat gnus-uu-saved-article-name gnus-current-article)) + (let ((coding-system-for-write mm-text-coding-system)) + (gnus-write-buffer + (concat gnus-uu-saved-article-name gnus-current-article))) (cond ((eq in-state 'first) (list gnus-uu-saved-article-name 'begin)) ((eq in-state 'first-and-last) (list gnus-uu-saved-article-name 'begin 'end)) @@ -835,14 +851,20 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (eq in-state 'first-and-last)) (progn (setq state (list 'begin)) - (save-excursion (set-buffer (gnus-get-buffer-create "*gnus-uu-body*")) - (erase-buffer)) + (save-excursion + (set-buffer (gnus-get-buffer-create "*gnus-uu-body*")) + (erase-buffer)) (save-excursion (set-buffer (gnus-get-buffer-create "*gnus-uu-pre*")) (erase-buffer) (insert (format - "Date: %s\nFrom: %s\nSubject: %s Digest\n\nTopics:\n" - (current-time-string) name name)))) + "Date: %s\nFrom: %s\nSubject: %s Digest\n\n" + (current-time-string) name name)) + (when (and message-forward-as-mime gnus-uu-digest-buffer) + ;; The default part in multipart/digest is message/rfc822. + ;; Subject is a fake head. + (insert "<#part type=text/plain>\nSubject: Topics\n\n")) + (insert "Topics:\n"))) (when (not (eq in-state 'end)) (setq state (list 'middle)))) (save-excursion @@ -856,14 +878,20 @@ didn't work, and overwrite existing files. Otherwise, ask each time." ;; These two are necessary for XEmacs 19.12 fascism. (put-text-property (point-min) (point-max) 'invisible nil) (put-text-property (point-min) (point-max) 'intangible nil)) + (when (and message-forward-as-mime + message-forward-show-mml + gnus-uu-digest-buffer) + (mm-enable-multibyte) + (mime-to-mml)) (goto-char (point-min)) (re-search-forward "\n\n") - ;; Quote all 30-dash lines. - (save-excursion - (while (re-search-forward "^-" nil t) - (beginning-of-line) - (delete-char 1) - (insert "- "))) + (unless (and message-forward-as-mime gnus-uu-digest-buffer) + ;; Quote all 30-dash lines. + (save-excursion + (while (re-search-forward "^-" nil t) + (beginning-of-line) + (delete-char 1) + (insert "- ")))) (setq body (buffer-substring (1- (point)) (point-max))) (narrow-to-region (point-min) (point)) (if (not (setq headers gnus-uu-digest-headers)) @@ -881,30 +909,66 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (1- (point))) (progn (forward-line 1) (point))))))))) (widen))) - (insert sorthead) (goto-char (point-max)) - (insert body) (goto-char (point-max)) - (insert (concat "\n" (make-string 30 ?-) "\n\n")) + (if (and message-forward-as-mime gnus-uu-digest-buffer) + (if message-forward-show-mml + (progn + (insert "\n<#mml type=message/rfc822>\n") + (insert sorthead) (goto-char (point-max)) + (insert body) (goto-char (point-max)) + (insert "\n<#/mml>\n")) + (let ((buf (mml-generate-new-buffer " *mml*"))) + (with-current-buffer buf + (insert sorthead) + (goto-char (point-min)) + (when (re-search-forward "^Subject: \\(.*\\)$" nil t) + (setq subj (buffer-substring (match-beginning 1) + (match-end 1)))) + (goto-char (point-max)) + (insert body)) + (insert "\n<#part type=message/rfc822" + " buffer=\"" (buffer-name buf) "\">\n"))) + (insert sorthead) (goto-char (point-max)) + (insert body) (goto-char (point-max)) + (insert (concat "\n" (make-string 30 ?-) "\n\n"))) (goto-char beg) (when (re-search-forward "^Subject: \\(.*\\)$" nil t) - (setq subj (buffer-substring (match-beginning 1) (match-end 1))) + (setq subj (buffer-substring (match-beginning 1) (match-end 1)))) + (when subj (save-excursion (set-buffer "*gnus-uu-pre*") (insert (format " %s\n" subj))))) (when (or (eq in-state 'last) (eq in-state 'first-and-last)) - (save-excursion - (set-buffer "*gnus-uu-pre*") - (insert (format "\n\n%s\n\n" (make-string 70 ?-))) - (gnus-write-buffer gnus-uu-saved-article-name)) - (save-excursion - (set-buffer "*gnus-uu-body*") - (goto-char (point-max)) - (insert - (concat (setq end-string (format "End of %s Digest" name)) - "\n")) - (insert (concat (make-string (length end-string) ?*) "\n")) - (write-region - (point-min) (point-max) gnus-uu-saved-article-name t)) + (if (and message-forward-as-mime gnus-uu-digest-buffer) + (with-current-buffer gnus-uu-digest-buffer + (erase-buffer) + (insert-buffer "*gnus-uu-pre*") + (goto-char (point-max)) + (insert-buffer "*gnus-uu-body*")) + (save-excursion + (set-buffer "*gnus-uu-pre*") + (insert (format "\n\n%s\n\n" (make-string 70 ?-))) + (if gnus-uu-digest-buffer + (with-current-buffer gnus-uu-digest-buffer + (erase-buffer) + (insert-buffer "*gnus-uu-pre*")) + (let ((coding-system-for-write mm-text-coding-system)) + (gnus-write-buffer gnus-uu-saved-article-name)))) + (save-excursion + (set-buffer "*gnus-uu-body*") + (goto-char (point-max)) + (insert + (concat (setq end-string (format "End of %s Digest" name)) + "\n")) + (insert (concat (make-string (length end-string) ?*) "\n")) + (if gnus-uu-digest-buffer + (with-current-buffer gnus-uu-digest-buffer + (goto-char (point-max)) + (insert-buffer "*gnus-uu-body*")) + (let ((coding-system-for-write mm-text-coding-system) + (file-name-coding-system nnmail-pathname-coding-system)) + (write-region + (point-min) (point-max) gnus-uu-saved-article-name t))))) (gnus-kill-buffer "*gnus-uu-pre*") (gnus-kill-buffer "*gnus-uu-body*") (push 'end state)) @@ -951,7 +1015,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (beginning-of-line) (forward-line 1) (when (file-exists-p gnus-uu-binhex-article-name) - (append-to-file start-char (point) gnus-uu-binhex-article-name)))) + (mm-append-to-file start-char (point) gnus-uu-binhex-article-name)))) (if (memq 'begin state) (cons gnus-uu-binhex-article-name state) state))) @@ -1026,7 +1090,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." ;; finally just replaces the next to last number with "[0-9]+". (save-excursion (set-buffer (gnus-get-buffer-create gnus-uu-output-buffer-name)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) (insert (regexp-quote string)) @@ -1126,7 +1190,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." string) (save-excursion (set-buffer (gnus-get-buffer-create gnus-uu-output-buffer-name)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (while string-list (erase-buffer) (insert (caar string-list)) @@ -1201,9 +1265,10 @@ didn't work, and overwrite existing files. Otherwise, ask each time." &optional sloppy limit no-errors) (let ((state 'first) (gnus-asynchronous nil) + (gnus-inhibit-treatment t) has-been-begin article result-file result-files process-state gnus-summary-display-article-function - gnus-article-display-hook gnus-article-prepare-hook + gnus-article-prepare-hook gnus-display-mime-function article-series files) (while (and articles @@ -1394,7 +1459,7 @@ didn't work, and overwrite existing files. Otherwise, ask each time." ;; We replace certain characters that could make things messy. (setq gnus-uu-file-name (let ((nnheader-file-name-translation-alist - '((?/ . ?,) (? . ?_) (?* . ?_) (?$ . ?_)))) + '((?/ . ?,) (? . ?_) (?* . ?_) (?$ . ?_)))) (nnheader-translate-file-chars (match-string 1)))) (replace-match (concat "begin 644 " gnus-uu-file-name) t t) @@ -1471,6 +1536,21 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (cons (if (= (length files) 1) (car files) files) state) state)))) +(defvar gnus-uu-unshar-warning + "*** WARNING *** + +Shell archives are an archaic method of bundling files for distribution +across computer networks. During the unpacking process, arbitrary commands +are executed on your system, and all kinds of nasty things can happen. +Please examine the archive very carefully before you instruct Emacs to +unpack it. You can browse the archive buffer using \\[scroll-other-window]. + +If you are unsure what to do, please answer \"no\"." + "Text of warning message displayed by `gnus-uu-unshar-article'. +Make sure that this text consists only of few text lines. Otherwise, +Gnus might fail to display all of it.") + + ;; This function is used by `gnus-uu-grab-articles' to treat ;; a shared article. (defun gnus-uu-unshar-article (process-buffer in-state) @@ -1481,14 +1561,31 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (goto-char (point-min)) (if (not (re-search-forward gnus-uu-shar-begin-string nil t)) (setq state (list 'wrong-type)) - (beginning-of-line) - (setq start-char (point)) - (call-process-region - start-char (point-max) shell-file-name nil - (gnus-get-buffer-create gnus-uu-output-buffer-name) nil - shell-command-switch - (concat "cd " gnus-uu-work-dir " " - gnus-shell-command-separator " sh")))) + (save-window-excursion + (save-excursion + (switch-to-buffer (current-buffer)) + (delete-other-windows) + (let ((buffer (get-buffer-create (generate-new-buffer-name + "*Warning*")))) + (unless + (unwind-protect + (with-current-buffer buffer + (insert (substitute-command-keys + gnus-uu-unshar-warning)) + (goto-char (point-min)) + (display-buffer buffer) + (yes-or-no-p "This is a shell archive, unshar it? ")) + (kill-buffer buffer)) + (setq state (list 'error)))))) + (unless (memq 'error state) + (beginning-of-line) + (setq start-char (point)) + (call-process-region + start-char (point-max) shell-file-name nil + (gnus-get-buffer-create gnus-uu-output-buffer-name) nil + shell-command-switch + (concat "cd " gnus-uu-work-dir " " + gnus-shell-command-separator " sh"))))) state)) ;; Returns the name of what the shar file is going to unpack. @@ -1678,7 +1775,8 @@ didn't work, and overwrite existing files. Otherwise, ask each time." gnus-uu-tmp-dir))) (setq gnus-uu-work-dir - (make-temp-file (concat gnus-uu-tmp-dir "gnus") t)) + (make-temp-name (concat gnus-uu-tmp-dir "gnus"))) + (gnus-make-directory gnus-uu-work-dir) (set-file-modes gnus-uu-work-dir 448) (setq gnus-uu-work-dir (file-name-as-directory gnus-uu-work-dir)) (push (cons gnus-newsgroup-name gnus-uu-work-dir) @@ -1695,23 +1793,11 @@ didn't work, and overwrite existing files. Otherwise, ask each time." (when (setq buf (get-buffer gnus-uu-output-buffer-name)) (kill-buffer buf)))) -(defun gnus-quote-arg-for-sh-or-csh (arg) - (let ((pos 0) new-pos accum) - ;; *** bug: we don't handle newline characters properly - (while (setq new-pos (string-match "[;!`\"$\\& \t{}]" arg pos)) - (push (substring arg pos new-pos) accum) - (push "\\" accum) - (push (list (aref arg new-pos)) accum) - (setq pos (1+ new-pos))) - (if (= pos 0) - arg - (apply 'concat (nconc (nreverse accum) (list (substring arg pos))))))) - ;; Inputs an action and a filename and returns a full command, making sure ;; that the filename will be treated as a single argument when the shell ;; executes the command. (defun gnus-uu-command (action file) - (let ((quoted-file (gnus-quote-arg-for-sh-or-csh file))) + (let ((quoted-file (mm-quote-arg file))) (if (string-match "%s" action) (format action quoted-file) (concat action " " quoted-file)))) @@ -1807,7 +1893,9 @@ is t." (gnus-summary-post-news) - (use-local-map (copy-keymap (current-local-map))) + (let ((map (make-sparse-keymap))) + (set-keymap-parent map (current-local-map)) + (use-local-map map)) (local-set-key "\C-c\C-c" 'gnus-summary-edit-article-done) (local-set-key "\C-c\C-c" 'gnus-uu-post-news-inews) (local-set-key "\C-c\C-s" 'gnus-uu-post-news-inews) diff --git a/lisp/gnus/gnus-vm.el b/lisp/gnus/gnus-vm.el index 2420c0b3596..d9abe3e5867 100644 --- a/lisp/gnus/gnus-vm.el +++ b/lisp/gnus/gnus-vm.el @@ -1,7 +1,9 @@ ;;; gnus-vm.el --- vm interface for Gnus -;; Copyright (C) 1994,95,96,97,98 Free Software Foundation, Inc. -;; Author: Per Persson +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. + +;; Author: Per Persson ;; Keywords: news, mail ;; This file is part of GNU Emacs. @@ -36,6 +38,7 @@ (require 'gnus-msg) (eval-when-compile + (require 'cl) (autoload 'vm-mode "vm") (autoload 'vm-save-message "vm") (autoload 'vm-forward-message "vm") @@ -46,11 +49,10 @@ "Inhibit loading `win-vm' if using a window-system. Has to be set before gnus-vm is loaded.") -(or gnus-vm-inhibit-window-system - (condition-case nil - (when window-system - (require 'win-vm)) - (error nil))) +(unless gnus-vm-inhibit-window-system + (ignore-errors + (when window-system + (require 'win-vm)))) (when (not (featurep 'vm)) (load "vm")) diff --git a/lisp/gnus/gnus-win.el b/lisp/gnus/gnus-win.el index ea0d65ddd11..6a9ce292c17 100644 --- a/lisp/gnus/gnus-win.el +++ b/lisp/gnus/gnus-win.el @@ -1,5 +1,6 @@ ;;; gnus-win.el --- window configuration functions for Gnus -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -27,8 +28,6 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'gnus) (defgroup gnus-windows nil @@ -87,9 +86,9 @@ (article 1.0))) (t '(vertical 1.0 - (summary 0.25 point) - (if gnus-carpal '(summary-carpal 4)) - (article 1.0))))) + (summary 0.25 point) + (if gnus-carpal '(summary-carpal 4)) + (article 1.0))))) (server (vertical 1.0 (server 1.0 point) @@ -288,7 +287,7 @@ See the Gnus manual for an explanation of the syntax used.") (defun gnus-configure-frame (split &optional window) "Split WINDOW according to SPLIT." (unless window - (setq window (get-buffer-window (current-buffer)))) + (setq window (or (get-buffer-window (current-buffer)) (selected-window)))) (select-window window) ;; This might be an old-stylee buffer config. (when (vectorp split) @@ -320,9 +319,11 @@ See the Gnus manual for an explanation of the syntax used.") (let ((buffer (cond ((stringp type) type) (t (cdr (assq type gnus-window-to-buffer)))))) (unless buffer - (error "Illegal buffer type: %s" type)) - (switch-to-buffer (gnus-get-buffer-create - (gnus-window-to-buffer-helper buffer))) + (error "Invalid buffer type: %s" type)) + (let ((buf (gnus-get-buffer-create + (gnus-window-to-buffer-helper buffer)))) + (if (eq buf (window-buffer (selected-window))) (set-buffer buf) + (switch-to-buffer buf))) (when (memq 'frame-focus split) (setq gnus-window-frame-focus window)) ;; We return the window if it has the `point' spec. @@ -375,7 +376,7 @@ See the Gnus manual for an explanation of the syntax used.") ((integerp size) (setq s size)) (t - (error "Illegal size: %s" size))) + (error "Invalid size: %s" size))) ;; Try to make sure that we are inside the safe limits. (cond ((zerop s)) ((eq type 'horizontal) @@ -410,48 +411,50 @@ See the Gnus manual for an explanation of the syntax used.") (defvar gnus-frame-split-p nil) (defun gnus-configure-windows (setting &optional force) - (setq gnus-current-window-configuration setting) - (setq force (or force gnus-always-force-window-configuration)) - (setq setting (gnus-windows-old-to-new setting)) - (let ((split (if (symbolp setting) - (cadr (assq setting gnus-buffer-configuration)) - setting)) - all-visible) + (if (window-configuration-p setting) + (set-window-configuration setting) + (setq gnus-current-window-configuration setting) + (setq force (or force gnus-always-force-window-configuration)) + (setq setting (gnus-windows-old-to-new setting)) + (let ((split (if (symbolp setting) + (cadr (assq setting gnus-buffer-configuration)) + setting)) + all-visible) - (setq gnus-frame-split-p nil) + (setq gnus-frame-split-p nil) - (unless split - (error "No such setting: %s" setting)) + (unless split + (error "No such setting in `gnus-buffer-configuration': %s" setting)) - (if (and (setq all-visible (gnus-all-windows-visible-p split)) - (not force)) - ;; All the windows mentioned are already visible, so we just - ;; put point in the assigned buffer, and do not touch the - ;; winconf. - (select-window all-visible) + (if (and (setq all-visible (gnus-all-windows-visible-p split)) + (not force)) + ;; All the windows mentioned are already visible, so we just + ;; put point in the assigned buffer, and do not touch the + ;; winconf. + (select-window all-visible) - ;; Either remove all windows or just remove all Gnus windows. - (let ((frame (selected-frame))) - (unwind-protect - (if gnus-use-full-window - ;; We want to remove all other windows. - (if (not gnus-frame-split-p) - ;; This is not a `frame' split, so we ignore the - ;; other frames. - (delete-other-windows) - ;; This is a `frame' split, so we delete all windows - ;; on all frames. - (gnus-delete-windows-in-gnusey-frames)) - ;; Just remove some windows. - (gnus-remove-some-windows) - (switch-to-buffer nntp-server-buffer)) - (select-frame frame))) + ;; Either remove all windows or just remove all Gnus windows. + (let ((frame (selected-frame))) + (unwind-protect + (if gnus-use-full-window + ;; We want to remove all other windows. + (if (not gnus-frame-split-p) + ;; This is not a `frame' split, so we ignore the + ;; other frames. + (delete-other-windows) + ;; This is a `frame' split, so we delete all windows + ;; on all frames. + (gnus-delete-windows-in-gnusey-frames)) + ;; Just remove some windows. + (gnus-remove-some-windows) + (switch-to-buffer nntp-server-buffer)) + (select-frame frame))) - (switch-to-buffer nntp-server-buffer) - (let (gnus-window-frame-focus) - (gnus-configure-frame split (get-buffer-window (current-buffer))) - (when gnus-window-frame-focus - (select-frame (window-frame gnus-window-frame-focus))))))) + (let (gnus-window-frame-focus) + (switch-to-buffer nntp-server-buffer) + (gnus-configure-frame split) + (when gnus-window-frame-focus + (select-frame (window-frame gnus-window-frame-focus)))))))) (defun gnus-delete-windows-in-gnusey-frames () "Do a `delete-other-windows' in all frames that have Gnus windows." @@ -502,11 +505,11 @@ should have point." (setq buffer (cond ((stringp type) type) (t (cdr (assq type gnus-window-to-buffer))))) (unless buffer - (error "Illegal buffer type: %s" type)) + (error "Invalid buffer type: %s" type)) (if (and (setq buf (get-buffer (gnus-window-to-buffer-helper buffer))) (setq win (get-buffer-window buf t))) (if (memq 'point split) - (setq all-visible win)) + (setq all-visible win)) (setq all-visible nil))) (t (when (eq type 'frame) diff --git a/lisp/gnus/gnus.el b/lisp/gnus/gnus.el index a59c3873890..dbc76daff43 100644 --- a/lisp/gnus/gnus.el +++ b/lisp/gnus/gnus.el @@ -1,5 +1,6 @@ ;;; gnus.el --- a newsreader for GNU Emacs -;; Copyright (C) 1987,88,89,90,93,94,95,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1987, 1988, 1989, 1990, 1993, 1994, 1995, 1996, +;; 1997, 1998, 2000 Free Software Foundation, Inc. ;; Author: Masanobu UMEDA ;; Lars Magne Ingebrigtsen @@ -29,13 +30,8 @@ (eval '(run-hooks 'gnus-load-hook)) (eval-when-compile (require 'cl)) +(require 'mm-util) -(eval-when-compile (require 'cl)) - -(require 'custom) -(eval-and-compile - (if (< emacs-major-version 20) - (require 'gnus-load))) (require 'message) (defgroup gnus nil @@ -43,6 +39,12 @@ :group 'news :group 'mail) +(defgroup gnus-charset nil + "Group character set issues." + :link '(custom-manual "(gnus)Charsets") + :version "21.1" + :group 'gnus) + (defgroup gnus-cache nil "Cache interface." :group 'gnus) @@ -247,12 +249,16 @@ is restarted, and sometimes reloaded." :link '(custom-manual "(gnus)Various Various") :group 'gnus) +(defgroup gnus-mime nil + "Variables for controlling the Gnus MIME interface." + :group 'gnus) + (defgroup gnus-exit nil "Exiting gnus." :link '(custom-manual "(gnus)Exiting Gnus") :group 'gnus) -(defconst gnus-version-number "5.7" +(defconst gnus-version-number "5.8.8" "Version number for this version of Gnus.") (defconst gnus-version (format "Gnus v%s" gnus-version-number) @@ -270,8 +276,6 @@ be set in `.emacs' instead." :group 'gnus-start :type 'boolean) -;;; Kludges to help the transition from the old `custom.el'. - (unless (featurep 'gnus-xmas) (defalias 'gnus-make-overlay 'make-overlay) (defalias 'gnus-delete-overlay 'delete-overlay) @@ -287,11 +291,33 @@ be set in `.emacs' instead." (defalias 'gnus-character-to-event 'identity) (defalias 'gnus-add-text-properties 'add-text-properties) (defalias 'gnus-put-text-property 'put-text-property) - (defalias 'gnus-mode-line-buffer-identification 'identity) + (defvar gnus-mode-line-image-cache t) + (if (fboundp 'find-image) + (defun gnus-mode-line-buffer-identification (line) + (let ((str (car-safe line))) + (if (and (stringp str) + (string-match "^Gnus:" str)) + (progn (add-text-properties + 0 5 + (list 'display + (if (eq t gnus-mode-line-image-cache) + (setq gnus-mode-line-image-cache + (find-image + '((:type xpm :file "gnus-pointer.xpm" + :ascent 100) + (:type xbm :file "gnus-pointer.xbm" + :ascent 100)))) + gnus-mode-line-image-cache) + 'help-echo "This is Gnus") + str) + (list str)) + line))) + (defalias 'gnus-mode-line-buffer-identification 'identity)) (defalias 'gnus-characterp 'numberp) (defalias 'gnus-deactivate-mark 'deactivate-mark) (defalias 'gnus-window-edges 'window-edges) - (defalias 'gnus-key-press-event-p 'numberp)) + (defalias 'gnus-key-press-event-p 'numberp) + (defalias 'gnus-decode-rfc1522 'ignore)) ;; We define these group faces here to avoid the display ;; update forced when creating new faces. @@ -362,6 +388,72 @@ be set in `.emacs' instead." ())) "Level 3 empty newsgroup face.") +(defface gnus-group-news-4-face + '((((class color) + (background dark)) + (:bold t)) + (((class color) + (background light)) + (:bold t)) + (t + ())) + "Level 4 newsgroup face.") + +(defface gnus-group-news-4-empty-face + '((((class color) + (background dark)) + ()) + (((class color) + (background light)) + ()) + (t + ())) + "Level 4 empty newsgroup face.") + +(defface gnus-group-news-5-face + '((((class color) + (background dark)) + (:bold t)) + (((class color) + (background light)) + (:bold t)) + (t + ())) + "Level 5 newsgroup face.") + +(defface gnus-group-news-5-empty-face + '((((class color) + (background dark)) + ()) + (((class color) + (background light)) + ()) + (t + ())) + "Level 5 empty newsgroup face.") + +(defface gnus-group-news-6-face + '((((class color) + (background dark)) + (:bold t)) + (((class color) + (background light)) + (:bold t)) + (t + ())) + "Level 6 newsgroup face.") + +(defface gnus-group-news-6-empty-face + '((((class color) + (background dark)) + ()) + (((class color) + (background light)) + ()) + (t + ())) + "Level 6 empty newsgroup face.") + (defface gnus-group-news-low-face '((((class color) (background dark)) @@ -639,13 +731,13 @@ be set in `.emacs' instead." (defface gnus-splash-face '((((class color) (background dark)) - (:foreground "ForestGreen")) + (:foreground "Brown")) (((class color) (background light)) - (:foreground "ForestGreen")) + (:foreground "Brown")) (t ())) - "Level 1 newsgroup face.") + "Face of the splash screen.") (defun gnus-splash () (save-excursion @@ -677,8 +769,28 @@ be set in `.emacs' instead." "Insert startup message in current buffer." ;; Insert the message. (erase-buffer) - (insert - (format " %s + (cond + ((and + (fboundp 'find-image) + (display-graphic-p) + (let ((image (find-image + `((:type xpm :file "gnus.xpm") + (:type xbm :file "gnus.xbm" + ;; Account for the xbm's blackground. + :background ,(face-foreground 'gnus-splash-face) + :foreground ,(face-background 'default)))))) + (when image + (let ((size (image-size image))) + (insert-char ?\n (max 0 (round (- (window-height) + (or y (cdr size)) 1) 2))) + (insert-char ?\ (max 0 (round (- (window-width) + (or x (car size))) 2))) + (insert-image image)) + (setq gnus-simple-splash nil) + t)))) + (t + (insert + (format " %s _ ___ _ _ _ ___ __ ___ __ _ ___ __ _ ___ __ ___ @@ -698,21 +810,21 @@ be set in `.emacs' instead." __ " - "")) - ;; And then hack it. - (gnus-indent-rigidly (point-min) (point-max) - (/ (max (- (window-width) (or x 46)) 0) 2)) - (goto-char (point-min)) - (forward-line 1) - (let* ((pheight (count-lines (point-min) (point-max))) - (wheight (window-height)) - (rest (- wheight pheight))) - (insert (make-string (max 0 (* 2 (/ rest 3))) ?\n))) - ;; Fontify some. - (put-text-property (point-min) (point-max) 'face 'gnus-splash-face) + "")) + ;; And then hack it. + (gnus-indent-rigidly (point-min) (point-max) + (/ (max (- (window-width) (or x 46)) 0) 2)) + (goto-char (point-min)) + (forward-line 1) + (let* ((pheight (count-lines (point-min) (point-max))) + (wheight (window-height)) + (rest (- wheight pheight))) + (insert (make-string (max 0 (* 2 (/ rest 3))) ?\n))) + ;; Fontify some. + (put-text-property (point-min) (point-max) 'face 'gnus-splash-face) + (setq gnus-simple-splash t))) (goto-char (point-min)) (setq mode-line-buffer-identification (concat " " gnus-version)) - (setq gnus-simple-splash t) (set-buffer-modified-p t)) (eval-when (load) @@ -784,31 +896,30 @@ used to 899, you would say something along these lines: (and (file-readable-p gnus-nntpserver-file) (save-excursion (set-buffer (gnus-get-buffer-create " *gnus nntp*")) - (buffer-disable-undo (current-buffer)) (insert-file-contents gnus-nntpserver-file) (let ((name (buffer-string))) (prog1 - (if (string-match "^[ \t\n]*$" name) + (if (string-match "\\'[ \t\n]*$" name) nil name) (kill-buffer (current-buffer)))))))) (defcustom gnus-select-method (condition-case nil - (nconc - (list 'nntp (or (condition-case nil - (gnus-getenv-nntpserver) - (error nil)) - (when (and gnus-default-nntp-server - (not (string= gnus-default-nntp-server ""))) - gnus-default-nntp-server) - "news")) - (if (or (null gnus-nntp-service) - (equal gnus-nntp-service "nntp")) - nil - (list gnus-nntp-service))) + (nconc + (list 'nntp (or (condition-case nil + (gnus-getenv-nntpserver) + (error nil)) + (when (and gnus-default-nntp-server + (not (string= gnus-default-nntp-server ""))) + gnus-default-nntp-server) + "news")) + (if (or (null gnus-nntp-service) + (equal gnus-nntp-service "nntp")) + nil + (list gnus-nntp-service))) (error nil)) - "*Default method for selecting a newsgroup. + "Default method for selecting a newsgroup. This variable should be a list, where the first element is how the news is to be fetched, the second is the address. @@ -839,7 +950,7 @@ see the manual for details." "*Method used for archiving messages you've sent. This should be a mail method. -It's probably not a very effective to change this variable once you've +It's probably not very effective to change this variable once you've run Gnus once. After doing that, you must edit this server from the server buffer." :group 'gnus-server @@ -868,6 +979,7 @@ that case, just return a fully prefixed name of the group -- \"nnml+private:mail.misc\", for instance." :group 'gnus-message :type '(choice (const :tag "none" nil) + function sexp string)) @@ -895,8 +1007,8 @@ If, for instance, you want to read your mail with the nnml backend, you could set this variable: \(setq gnus-secondary-select-methods '((nnml \"\")))" -:group 'gnus-server -:type '(repeat gnus-select-method)) + :group 'gnus-server + :type '(repeat gnus-select-method)) (defvar gnus-backup-default-subscribed-newsgroups '("news.announce.newusers" "news.groups.questions" "gnu.emacs.gnus") @@ -925,10 +1037,23 @@ articles by Message-ID is painfully slow. By setting this method to an nntp method, you might get acceptable results. The value of this variable must be a valid select method as discussed -in the documentation of `gnus-select-method'." +in the documentation of `gnus-select-method'. + +It can also be a list of select methods, as well as the special symbol +`current', which means to use the current select method. If it is a +list, Gnus will try all the methods in the list until it finds a match." :group 'gnus-server :type '(choice (const :tag "default" nil) - gnus-select-method)) + (const :tag "DejaNews" (nnweb "refer" (nnweb-type dejanews))) + gnus-select-method + (repeat :menu-tag "Try multiple" + :tag "Multiple" + :value (current (nnweb "refer" (nnweb-type dejanews))) + (choice :tag "Method" + (const current) + (const :tag "DejaNews" + (nnweb "refer" (nnweb-type dejanews))) + gnus-select-method)))) (defcustom gnus-group-faq-directory '("/ftp@mirrors.aol.com:/pub/rtfm/usenet/" @@ -987,11 +1112,6 @@ newsgroups." :group 'gnus-summary-marks :type 'character) -(defcustom gnus-asynchronous nil - "*If non-nil, Gnus will supply backends with data needed for async article fetching." - :group 'gnus-asynchronous - :type 'boolean) - (defcustom gnus-large-newsgroup 200 "*The number of articles which indicates a large newsgroup. If the number of articles in a newsgroup is greater than this value, @@ -1083,18 +1203,13 @@ articles. This is not a good idea." :group 'gnus-meta :type 'boolean) -(defcustom gnus-use-demon nil - "If non-nil, Gnus might use some demons." - :group 'gnus-meta - :type 'boolean) - (defcustom gnus-use-scoring t "*If non-nil, enable scoring." :group 'gnus-meta :type 'boolean) (defcustom gnus-use-picons nil - "*If non-nil, display picons." + "*If non-nil, display picons in a frame of their own." :group 'gnus-meta :type 'boolean) @@ -1167,8 +1282,12 @@ slower." ("nnfolder" mail respool address) ("nngateway" post-mail address prompt-address physical-address) ("nnweb" none) + ("nnslashdot" post) + ("nnultimate" none) + ("nnwarchive" none) ("nnlistserv" none) - ("nnagent" post-mail)) + ("nnagent" post-mail) + ("nnimap" post-mail address prompt-address physical-address)) "*An alist of valid select methods. The first element of each list lists should be a string with the name of the select method. The other elements may be the category of @@ -1189,18 +1308,28 @@ this variable. I think." (const :format "%v " virtual) (const respool))))) -(define-widget 'gnus-select-method 'list - "Widget for entering a select method." - :args `((choice :tag "Method" - ,@(mapcar (lambda (entry) - (list 'const :format "%v\n" - (intern (car entry)))) - gnus-valid-select-methods)) - (string :tag "Address") - (editable-list :inline t - (list :format "%v" - variable - (sexp :tag "Value"))))) +(defun gnus-redefine-select-method-widget () + "Recomputes the select-method widget based on the value of +`gnus-valid-select-methods'." + (define-widget 'gnus-select-method 'list + "Widget for entering a select method." + :value '(nntp "") + :tag "Select Method" + :args `((choice :tag "Method" + ,@(mapcar (lambda (entry) + (list 'const :format "%v\n" + (intern (car entry)))) + gnus-valid-select-methods) + (symbol :tag "other")) + (string :tag "Address") + (repeat :tag "Options" + :inline t + (list :format "%v" + variable + (sexp :tag "Value")))) + )) + +(gnus-redefine-select-method-widget) (defcustom gnus-updated-mode-lines '(group article summary tree) "List of buffers that should update their mode lines. @@ -1283,7 +1412,7 @@ following hook: (defcustom gnus-group-change-level-function nil "Function run when a group level is changed. It is called with three parameters -- GROUP, LEVEL and OLDLEVEL." - :group 'gnus-group-level + :group 'gnus-group-levels :type 'function) ;;; Face thingies. @@ -1345,60 +1474,6 @@ face." :group 'gnus-visual :type 'face) -(defcustom gnus-article-display-hook - (if (and (string-match "XEmacs" emacs-version) - (featurep 'xface)) - '(gnus-article-hide-headers-if-wanted - gnus-article-hide-boring-headers - gnus-article-treat-overstrike - gnus-article-maybe-highlight - gnus-article-display-x-face) - '(gnus-article-hide-headers-if-wanted - gnus-article-hide-boring-headers - gnus-article-treat-overstrike - gnus-article-maybe-highlight)) - "*Controls how the article buffer will look. - -If you leave the list empty, the article will appear exactly as it is -stored on the disk. The list entries will hide or highlight various -parts of the article, making it easier to find the information you -want." - :group 'gnus-article-highlight - :group 'gnus-visual - :type 'hook - :options '(gnus-article-add-buttons - gnus-article-add-buttons-to-head - gnus-article-emphasize - gnus-article-fill-cited-article - gnus-article-remove-cr - gnus-article-de-quoted-unreadable - gnus-summary-stop-page-breaking - ;; gnus-summary-caesar-message - ;; gnus-summary-verbose-headers - gnus-summary-toggle-mime - gnus-article-hide - gnus-article-hide-headers - gnus-article-hide-boring-headers - gnus-article-hide-signature - gnus-article-hide-citation - gnus-article-hide-pgp - gnus-article-hide-pem - gnus-article-highlight - gnus-article-highlight-headers - gnus-article-highlight-citation - gnus-article-highlight-signature - gnus-article-date-ut - gnus-article-date-local - gnus-article-date-lapsed - gnus-article-date-original - gnus-article-remove-trailing-blank-lines - gnus-article-strip-leading-blank-lines - gnus-article-strip-multiple-blank-lines - gnus-article-strip-blank-lines - gnus-article-treat-overstrike - gnus-article-display-x-face - gnus-smiley-display)) - (defcustom gnus-article-save-directory gnus-directory "*Name of the directory articles will be saved in (default \"~/News\")." :group 'gnus-article-saving @@ -1407,9 +1482,27 @@ want." (defvar gnus-plugged t "Whether Gnus is plugged or not.") +(defcustom gnus-default-charset 'iso-8859-1 + "Default charset assumed to be used when viewing non-ASCII characters. +This variable is overridden on a group-to-group basis by the +gnus-group-charset-alist variable and is only used on groups not +covered by that variable." + :type 'symbol + :group 'gnus-charset) + +(defcustom gnus-default-posting-charset nil + "Default charset assumed to be used when posting non-ASCII characters. +This variable is overridden on a group-to-group basis by the +gnus-group-posting-charset-alist variable and is only used on groups not +covered by that variable. +If nil, no default charset is assumed when posting." + :type 'symbol + :group 'gnus-charset) + ;;; Internal variables +(defvar gnus-agent-meta-information-header "X-Gnus-Agent-Meta-Information") (defvar gnus-group-get-parameter-function 'gnus-group-get-parameter) (defvar gnus-original-article-buffer " *Original Article*") (defvar gnus-newsgroup-name nil) @@ -1457,7 +1550,7 @@ want." ,(nnheader-concat gnus-cache-directory "active")))) "List of predefined (convenience) servers.") -(defvar gnus-topic-indentation "") ;; Obsolete variable. +(defvar gnus-topic-indentation "");; Obsolete variable. (defconst gnus-article-mark-lists '((marked . tick) (replied . reply) @@ -1485,7 +1578,6 @@ want." '((gnus-group-mode "(gnus)The Group Buffer") (gnus-summary-mode "(gnus)The Summary Buffer") (gnus-article-mode "(gnus)The Article Buffer") - (mime/viewer-mode "(gnus)The Article Buffer") (gnus-server-mode "(gnus)The Server Buffer") (gnus-browse-mode "(gnus)Browse Foreign Server") (gnus-tree-mode "(gnus)Tree Display")) @@ -1504,11 +1596,11 @@ want." (defvar gnus-variable-list '(gnus-newsrc-options gnus-newsrc-options-n - gnus-newsrc-last-checked-date - gnus-newsrc-alist gnus-server-alist - gnus-killed-list gnus-zombie-list - gnus-topic-topology gnus-topic-alist - gnus-format-specs) + gnus-newsrc-last-checked-date + gnus-newsrc-alist gnus-server-alist + gnus-killed-list gnus-zombie-list + gnus-topic-topology gnus-topic-alist + gnus-format-specs) "Gnus variables saved in the quick startup file.") (defvar gnus-newsrc-alist nil @@ -1549,6 +1641,9 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") (defvar gnus-dead-summary nil) +(defvar gnus-invalid-group-regexp "[: `'\"/]\\|^$" + "Regexp matching invalid groups.") + ;;; End of variables. ;; Define some autoload functions Gnus might use. @@ -1565,24 +1660,22 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") (when (consp function) (setq keymap (car (memq 'keymap function))) (setq function (car function))) - (autoload function (car package) nil interactive keymap))) + (unless (fboundp function) + (autoload function (car package) nil interactive keymap)))) (if (eq (nth 1 package) ':interactive) - (cdddr package) + (nthcdr 3 package) (cdr package))))) - '(("metamail" metamail-buffer) - ("info" Info-goto-node) - ("hexl" hexl-hex-string-to-integer) + '(("info" :interactive t Info-goto-node) ("pp" pp pp-to-string pp-eval-expression) + ("qp" quoted-printable-decode-region quoted-printable-decode-string) ("ps-print" ps-print-preprint) - ("mail-extr" mail-extract-address-components) - ("browse-url" browse-url) + ("browse-url" :interactive t browse-url) ("message" :interactive t message-send-and-exit message-yank-original) - ("nnmail" nnmail-split-fancy nnmail-article-group nnmail-date-to-time) + ("babel" babel-as-string) + ("nnmail" nnmail-split-fancy nnmail-article-group) ("nnvirtual" nnvirtual-catchup-group nnvirtual-convert-headers) - ("timezone" timezone-make-date-arpa-standard timezone-fix-time - timezone-make-sortable-date timezone-make-time-string) - ("rmailout" rmail-output) + ("rmailout" rmail-output rmail-output-to-rmail-file) ("rmail" rmail-insert-rmail-file-header rmail-count-new-messages rmail-show-message rmail-summary-exists rmail-select-summary rmail-update-summary) @@ -1615,35 +1708,36 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") gnus-article-hide-citation-in-followups) ("gnus-kill" gnus-kill gnus-apply-kill-file-internal gnus-kill-file-edit-file gnus-kill-file-raise-followups-to-author - gnus-execute gnus-expunge) + gnus-execute gnus-expunge gnus-batch-kill gnus-batch-score) ("gnus-cache" gnus-cache-possibly-enter-article gnus-cache-save-buffers gnus-cache-possibly-remove-articles gnus-cache-request-article gnus-cache-retrieve-headers gnus-cache-possibly-alter-active gnus-cache-enter-remove-article gnus-cached-article-p - gnus-cache-open gnus-cache-close gnus-cache-update-article) - ("gnus-cache" :interactive t gnus-jog-cache gnus-cache-enter-article - gnus-cache-remove-article gnus-summary-insert-cached-articles) - ("gnus-score" :interactive t - gnus-summary-increase-score gnus-summary-set-score - gnus-summary-raise-thread gnus-summary-raise-same-subject - gnus-summary-raise-score gnus-summary-raise-same-subject-and-select - gnus-summary-lower-thread gnus-summary-lower-same-subject - gnus-summary-lower-score gnus-summary-lower-same-subject-and-select - gnus-summary-current-score gnus-score-default - gnus-score-flush-cache gnus-score-close - gnus-possibly-score-headers gnus-score-followup-article - gnus-score-followup-thread) - ("gnus-score" - (gnus-summary-score-map keymap) gnus-score-save gnus-score-headers + gnus-cache-open gnus-cache-close gnus-cache-update-article + gnus-cache-articles-in-group) + ("gnus-cache" :interactive t gnus-jog-cache gnus-cache-enter-article + gnus-cache-remove-article gnus-summary-insert-cached-articles) + ("gnus-score" :interactive t + gnus-summary-increase-score gnus-summary-set-score + gnus-summary-raise-thread gnus-summary-raise-same-subject + gnus-summary-raise-score gnus-summary-raise-same-subject-and-select + gnus-summary-lower-thread gnus-summary-lower-same-subject + gnus-summary-lower-score gnus-summary-lower-same-subject-and-select + gnus-summary-current-score gnus-score-delta-default + gnus-score-flush-cache gnus-score-close + gnus-possibly-score-headers gnus-score-followup-article + gnus-score-followup-thread) + ("gnus-score" + (gnus-summary-score-map keymap) gnus-score-save gnus-score-headers gnus-current-score-file-nondirectory gnus-score-adaptive gnus-score-find-trace gnus-score-file-name) ("gnus-cus" :interactive t gnus-group-customize gnus-score-customize) ("gnus-topic" :interactive t gnus-topic-mode) - ("gnus-topic" gnus-topic-remove-group gnus-topic-set-parameters) + ("gnus-topic" gnus-topic-remove-group gnus-topic-set-parameters + gnus-subscribe-topics) ("gnus-salt" :interactive t gnus-pick-mode gnus-binary-mode) ("gnus-uu" (gnus-uu-extract-map keymap) (gnus-uu-mark-map keymap)) ("gnus-uu" :interactive t - gnus-uu-post-news gnus-uu-digest-mail-forward gnus-uu-digest-post-forward gnus-uu-mark-series gnus-uu-mark-region gnus-uu-mark-buffer gnus-uu-mark-by-regexp gnus-uu-mark-all @@ -1654,17 +1748,11 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") gnus-uu-decode-uu-and-save-view gnus-uu-decode-unshar-view gnus-uu-decode-unshar-and-save-view gnus-uu-decode-save-view gnus-uu-decode-binhex-view gnus-uu-unmark-thread - gnus-uu-mark-over gnus-uu-post-news gnus-uu-post-news) - ("gnus-uu" gnus-uu-delete-work-dir gnus-quote-arg-for-sh-or-csh - gnus-uu-unmark-thread) + gnus-uu-mark-over gnus-uu-post-news) + ("gnus-uu" gnus-uu-delete-work-dir gnus-uu-unmark-thread) ("gnus-msg" (gnus-summary-send-map keymap) gnus-article-mail gnus-copy-article-buffer gnus-extended-version) ("gnus-msg" :interactive t - gnus-summary-wide-reply - gnus-summary-wide-reply-with-original - gnus-summary-followup-to-mail - gnus-summary-followup-to-mail-with-original - gnus-summary-post-forward gnus-group-post-news gnus-group-mail gnus-summary-post-news gnus-summary-followup gnus-summary-followup-with-original gnus-summary-cancel-article gnus-summary-supersede-article @@ -1679,6 +1767,7 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") ("gnus-picon" :interactive t gnus-article-display-picons gnus-group-display-picons gnus-picons-article-display-x-face gnus-picons-display-x-face) + ("gnus-picon" gnus-picons-buffer-name) ("gnus-gl" bbb-login bbb-logout bbb-grouplens-group-p gnus-grouplens-mode) ("smiley" :interactive t gnus-smiley-display) @@ -1694,8 +1783,7 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") gnus-group-set-mode-line gnus-group-set-info gnus-group-save-newsrc gnus-group-setup-buffer gnus-group-get-new-news gnus-group-make-help-group gnus-group-update-group - gnus-clear-inboxes-moved gnus-group-iterate - gnus-group-group-name) + gnus-group-iterate gnus-group-group-name) ("gnus-bcklg" gnus-backlog-request-article gnus-backlog-enter-article gnus-backlog-remove-article) ("gnus-art" gnus-article-read-summary-keys gnus-article-save @@ -1703,20 +1791,24 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") gnus-article-next-page gnus-article-prev-page gnus-request-article-this-buffer gnus-article-mode gnus-article-setup-buffer gnus-narrow-to-page - gnus-article-delete-invisible-text gnus-hack-decode-rfc1522) + gnus-article-delete-invisible-text gnus-treat-article) ("gnus-art" :interactive t gnus-article-hide-headers gnus-article-hide-boring-headers - gnus-article-treat-overstrike gnus-article-word-wrap + gnus-article-treat-overstrike gnus-article-remove-cr gnus-article-remove-trailing-blank-lines gnus-article-display-x-face gnus-article-de-quoted-unreadable - gnus-article-mime-decode-quoted-printable gnus-article-hide-pgp + gnus-article-de-base64-unreadable + gnus-article-decode-HZ + gnus-article-wash-html + gnus-article-hide-pgp gnus-article-hide-pem gnus-article-hide-signature gnus-article-strip-leading-blank-lines gnus-article-date-local gnus-article-date-original gnus-article-date-lapsed gnus-article-show-all-headers gnus-article-edit-mode gnus-article-edit-article - gnus-article-edit-done gnus-decode-rfc1522 article-decode-rfc1522 - gnus-start-date-timer gnus-stop-date-timer) + gnus-article-edit-done gnus-article-decode-encoded-words + gnus-start-date-timer gnus-stop-date-timer + gnus-mime-view-all-parts) ("gnus-int" gnus-request-type) ("gnus-start" gnus-newsrc-parse-options gnus-1 gnus-no-server-1 gnus-dribble-enter gnus-read-init-file gnus-dribble-touch) @@ -1739,7 +1831,10 @@ gnus-newsrc-hashtb should be kept so that both hold the same information.") gnus-unplugged gnus-agentize gnus-agent-batch) ("gnus-vm" :interactive t gnus-summary-save-in-vm gnus-summary-save-article-vm) - ("gnus-draft" :interactive t gnus-draft-mode gnus-group-send-drafts)))) + ("gnus-draft" :interactive t gnus-draft-mode gnus-group-send-drafts) + ("gnus-mlspl" gnus-group-split gnus-group-split-fancy) + ("gnus-mlspl" :interactive t gnus-group-split-setup + gnus-group-split-update)))) ;;; gnus-sum.el thingies @@ -1757,6 +1852,7 @@ with some simple extensions. %a Extracted name of the poster (string) %A Extracted address of the poster (string) %F Contents of the From: header (string) +%f Contents of the From: or To: headers (string) %x Contents of the Xref: header (string) %D Date of the article (string) %d Date of the article (string) in DD-MMM format @@ -1795,7 +1891,7 @@ such area. The %U (status), %R (replied) and %z (zcore) specs have to be handled with care. For reasons of efficiency, Gnus will compute what column these characters will end up in, and \"hard-code\" that. This means that -it is illegal to have these specs after a variable-length spec. Well, +it is invalid to have these specs after a variable-length spec. Well, you might not be arrested, but your summary buffer will look strange, which is bad enough. @@ -1817,7 +1913,7 @@ This restriction may disappear in later versions of Gnus." (define-key keymap (pop keys) 'undefined)))) (defvar gnus-article-mode-map - (let ((keymap (make-keymap))) + (let ((keymap (make-sparse-keymap))) (gnus-suppress-keymap keymap) keymap)) (defvar gnus-summary-mode-map @@ -2012,14 +2108,13 @@ If ARG, insert string at point." (string-to-number (if (zerop major) (format "%s00%02d%02d" - (cond - ((member alpha '("(ding)" "d")) "4.99") - ((member alpha '("September" "s")) "5.01") - ((member alpha '("Red" "r")) "5.03") - ((member alpha '("Quassia" "q")) "5.05") - ((member alpha '("p")) "5.07") - ((member alpha '("o")) "5.09") - ((member alpha '("n")) "5.11")) + (if (member alpha '("(ding)" "d")) + "4.99" + (+ 5 (* 0.02 + (abs + (- (mm-char-int (aref (downcase alpha) 0)) + (mm-char-int ?t)))) + -0.01)) minor least) (format "%d.%02d%02d" major minor least)))))) @@ -2307,7 +2402,14 @@ that that variable is buffer-local to the summary buffers." (not (equal server (format "%s:%s" (caaar opened) (cadaar opened))))) (pop opened)) - (caar opened)))) + (caar opened)) + ;; It could be a named method, search all servers + (let ((servers gnus-secondary-select-methods)) + (while (and servers + (not (equal server (format "%s:%s" (caar servers) + (cadar servers))))) + (pop servers)) + (car servers)))) (defmacro gnus-method-equal (ss1 ss2) "Say whether two servers are equal." @@ -2320,6 +2422,15 @@ that that variable is buffer-local to the summary buffers." (setq s1 (cdr s1))) (null s1)))))) +(defun gnus-methods-equal-p (m1 m2) + (let ((m1 (or m1 gnus-select-method)) + (m2 (or m2 gnus-select-method))) + (or (equal m1 m2) + (and (eq (car m1) (car m2)) + (or (not (memq 'address (assoc (symbol-name (car m1)) + gnus-valid-select-methods))) + (equal (nth 1 m1) (nth 1 m2))))))) + (defun gnus-server-equal (m1 m2) "Say whether two methods are equal." (let ((m1 (cond ((null m1) gnus-select-method) @@ -2401,16 +2512,32 @@ You should probably use `gnus-find-method-for-group' instead." possible (list backend server)))))) +(defsubst gnus-native-method-p (method) + "Return whether METHOD is the native select method." + (gnus-method-equal method gnus-select-method)) + (defsubst gnus-secondary-method-p (method) "Return whether METHOD is a secondary select method." (let ((methods gnus-secondary-select-methods) (gmethod (gnus-server-get-method nil method))) (while (and methods - (not (equal (gnus-server-get-method nil (car methods)) - gmethod))) + (not (gnus-method-equal + (gnus-server-get-method nil (car methods)) + gmethod))) (setq methods (cdr methods))) methods)) +(defun gnus-method-simplify (method) + "Return the shortest uniquely identifying string or method for METHOD." + (cond ((stringp method) + method) + ((gnus-native-method-p method) + nil) + ((gnus-secondary-method-p method) + (format "%s:%s" (nth 0 method) (nth 1 method))) + (t + method))) + (defun gnus-groups-from-server (server) "Return a list of all groups that are fetched from SERVER." (let ((alist (cdr gnus-newsrc-alist)) @@ -2510,7 +2637,6 @@ If SCORE is nil, add 1 to the score of GROUP." (when info (gnus-info-set-score info (+ (gnus-info-score info) (or score 1)))))) -;; Function written by Stainless Steel Rat (defun gnus-short-group-name (group &optional levels) "Collapse GROUP name LEVELS. Select methods are stripped and any remote host name is stripped down to @@ -2520,40 +2646,51 @@ just the host name." (depth 0) (skip 1) (levels (or levels + gnus-group-uncollapsed-levels (progn (while (string-match "\\." group skip) (setq skip (match-end 0) depth (+ depth 1))) depth)))) - ;; separate foreign select method from group name and collapse. - ;; if method contains a server, collapse to non-domain server name, - ;; otherwise collapse to select method - (when (string-match ":" group) - (cond ((string-match "+" group) - (let* ((plus (string-match "+" group)) - (colon (string-match ":" group (or plus 0))) - (dot (string-match "\\." group))) - (setq foreign (concat - (substring group (+ 1 plus) - (cond ((null dot) colon) - ((< colon dot) colon) - ((< dot colon) dot))) - ":") - group (substring group (+ 1 colon))))) - (t - (let* ((colon (string-match ":" group))) - (setq foreign (concat (substring group 0 (+ 1 colon))) - group (substring group (+ 1 colon))))))) - ;; collapse group name leaving LEVELS uncollapsed elements - (while group - (if (and (string-match "\\." group) (> levels 0)) - (setq name (concat name (substring group 0 1)) - group (substring group (match-end 0)) - levels (- levels 1) - name (concat name ".")) - (setq name (concat foreign name group) - group nil))) - name)) + ;; Separate foreign select method from group name and collapse. + ;; If method contains a server, collapse to non-domain server name, + ;; otherwise collapse to select method. + (let* ((colon (string-match ":" group)) + (server (and colon (substring group 0 colon))) + (plus (and server (string-match "+" server)))) + (when server + (if plus + (setq foreign (substring server (+ 1 plus) + (string-match "\\." server)) + group (substring group (+ 1 colon))) + (setq foreign server + group (substring group (+ 1 colon)))) + (setq foreign (concat foreign ":"))) + ;; Collapse group name leaving LEVELS uncollapsed elements + (let* ((slist (split-string group "/")) + (slen (length slist)) + (dlist (split-string group "\\.")) + (dlen (length dlist)) + glist + glen + gsep + res) + (if (> slen dlen) + (setq glist slist + glen slen + gsep "/") + (setq glist dlist + glen dlen + gsep ".")) + (setq levels (- glen levels)) + (dolist (g glist) + (push (if (>= (decf levels) 0) + (if (zerop (length g)) + "" + (substring g 0 1)) + g) + res)) + (concat foreign (mapconcat 'identity (nreverse res) gsep)))))) (defun gnus-narrow-to-body () "Narrow to the body of an article." @@ -2631,6 +2768,7 @@ If NEWSGROUP is nil, return the global kill file name instead." (let ((opened gnus-opened-servers)) (while (and method opened) (when (and (equal (cadr method) (cadaar opened)) + (equal (car method) (caaar opened)) (not (equal method (caar opened)))) (setq method nil)) (pop opened)) @@ -2667,6 +2805,8 @@ If NEWSGROUP is nil, return the global kill file name instead." (or gnus-override-method (and (not group) gnus-select-method) + (and (not (gnus-group-entry group));; a new group + (gnus-group-name-to-method group)) (let ((info (or info (gnus-get-info group))) method) (if (or (not info) @@ -2699,16 +2839,16 @@ If NEWSGROUP is nil, return the global kill file name instead." (defun gnus-read-group (prompt &optional default) "Prompt the user for a group name. -Disallow illegal group names." +Disallow invalid group names." (let ((prefix "") group) (while (not group) - (when (string-match - "[: `'\"/]\\|^$" + (when (string-match + gnus-invalid-group-regexp (setq group (read-string (concat prefix prompt) (cons (or default "") 0) 'gnus-group-history))) - (setq prefix (format "Illegal group name: \"%s\". " group) + (setq prefix (format "Invalid group name: \"%s\". " group) group nil))) group)) @@ -2717,6 +2857,9 @@ Disallow illegal group names." Allow completion over sensible values." (let* ((servers (append gnus-valid-select-methods + (mapcar (lambda (i) (list (format "%s:%s" (caar i) + (cadar i)))) + gnus-opened-servers) gnus-predefined-server-alist gnus-server-alist)) (method @@ -2727,11 +2870,18 @@ Allow completion over sensible values." ((equal method "") (setq method gnus-select-method)) ((assoc method gnus-valid-select-methods) - (list (intern method) - (if (memq 'prompt-address - (assoc method gnus-valid-select-methods)) - (read-string "Address: ") - ""))) + (let ((address (if (memq 'prompt-address + (assoc method gnus-valid-select-methods)) + (read-string "Address: ") + ""))) + (or (let ((opened gnus-opened-servers)) + (while (and opened + (not (equal (format "%s:%s" method address) + (format "%s:%s" (caaar opened) + (cadaar opened))))) + (pop opened)) + (caar opened)) + (list (intern method) address)))) ((assoc method servers) method) (t @@ -2769,12 +2919,13 @@ As opposed to `gnus', this command will not connect to the local server." (let ((window (get-buffer-window gnus-group-buffer))) (cond (window (select-frame (window-frame window))) - ((= (length (frame-list)) 1) - (select-frame (make-frame))) - (t - (other-frame 1)))) + (t + (select-frame (make-frame))))) (gnus arg)) +;;(setq thing ? ; this is a comment +;; more 'yes) + ;;;###autoload (defun gnus (&optional arg dont-connect slave) "Read network news. diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index 7204669fb86..a919ddf749a 100644 --- a/lisp/gnus/message.el +++ b/lisp/gnus/message.el @@ -1,5 +1,6 @@ ;;; message.el --- composing mail and news messages -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: mail, news @@ -29,16 +30,16 @@ ;;; Code: -(eval-when-compile (require 'cl)) - +(eval-when-compile + (require 'cl) + (defvar gnus-list-identifiers)) ; gnus-sum is required where necessary (require 'mailheader) (require 'nnheader) -(require 'timezone) -(require 'easymenu) -(require 'custom) -(if (string-match "XEmacs\\|Lucid" emacs-version) - (require 'mail-abbrevs) - (require 'mailabbrev)) +;; This is apparently necessary even though things are autoloaded: +(if (featurep 'xemacs) + (require 'mail-abbrevs)) +(require 'mail-parse) +(require 'mml) (defgroup message '((user-mail-address custom-variable) (user-full-name custom-variable)) @@ -156,7 +157,7 @@ Otherwise, most addresses look like `angles', but they look like :group 'message-headers) (defcustom message-syntax-checks nil - ; Guess this one shouldn't be easy to customize... + ;; Guess this one shouldn't be easy to customize... "*Controls what syntax checks should not be performed on outgoing posts. To disable checking of long signatures, for instance, add `(signature . disabled)' to this list. @@ -164,19 +165,21 @@ To disable checking of long signatures, for instance, add Don't touch this variable unless you really know what you're doing. Checks include subject-cmsg multiple-headers sendsys message-id from -long-lines control-chars size new-text redirected-followup signature -approved sender empty empty-headers message-id from subject -shorten-followup-to existing-newsgroups buffer-file-name unchanged." - :group 'message-news) +long-lines control-chars size new-text quoting-style +redirected-followup signature approved sender empty empty-headers +message-id from subject shorten-followup-to existing-newsgroups +buffer-file-name unchanged newsgroups." + :group 'message-news + :type '(repeat sexp)) (defcustom message-required-news-headers '(From Newsgroups Subject Date Message-ID (optional . Organization) Lines - (optional . X-Newsreader)) + (optional . User-Agent)) "*Headers to be generated or prompted for when posting an article. RFC977 and RFC1036 require From, Date, Newsgroups, Subject, Message-ID. Organization, Lines, In-Reply-To, Expires, and -X-Newsreader are optional. If don't you want message to insert some +User-Agent are optional. If don't you want message to insert some header, remove it from this list." :group 'message-news :group 'message-headers @@ -184,10 +187,10 @@ header, remove it from this list." (defcustom message-required-mail-headers '(From Subject Date (optional . In-Reply-To) Message-ID Lines - (optional . X-Mailer)) + (optional . User-Agent)) "*Headers to be generated or prompted for when mailing a message. RFC822 required that From, Date, To, Subject and Message-ID be -included. Organization, Lines and X-Mailer are optional." +included. Organization, Lines and User-Agent are optional." :group 'message-mail :group 'message-headers :type '(repeat sexp)) @@ -210,7 +213,7 @@ included. Organization, Lines and X-Mailer are optional." :group 'message-headers :type 'regexp) -(defcustom message-ignored-supersedes-headers "^Path:\\|^Date\\|^NNTP-Posting-Host:\\|^Xref:\\|^Lines:\\|^Received:\\|^X-From-Line:\\|^X-Trace:\\|^X-Complaints-To:\\|Return-Path:\\|^Supersedes:\\|^X-Trace:\\|^X-Complaints-To:\\|^NNTP-Posting-Date:" +(defcustom message-ignored-supersedes-headers "^Path:\\|^Date\\|^NNTP-Posting-Host:\\|^Xref:\\|^Lines:\\|^Received:\\|^X-From-Line:\\|^X-Trace:\\|^X-Complaints-To:\\|Return-Path:\\|^Supersedes:\\|^NNTP-Posting-Date:\\|^X-Trace:\\|^X-Complaints-To:" "*Header lines matching this regexp will be deleted before posting. It's best to delete old Path and Date headers before posting to avoid any confusion." @@ -228,7 +231,7 @@ any confusion." :type 'regexp :group 'message-various) -(defcustom message-elide-elipsis "\n[...]\n\n" +(defcustom message-elide-ellipsis "\n[...]\n\n" "*The string which is inserted for elided text." :type 'string :group 'message-various) @@ -240,14 +243,15 @@ nil means let mailer mail back a message to report errors." :group 'message-mail :type 'boolean) -(defcustom message-generate-new-buffers t +(defcustom message-generate-new-buffers 'unique "*Non-nil means that a new message buffer will be created whenever `message-setup' is called. If this is a function, call that function with three parameters: The type, the to address and the group name. (Any of these may be nil.) The function should return the new buffer name." :group 'message-buffers :type '(choice (const :tag "off" nil) - (const :tag "on" t) + (const :tag "unique" unique) + (const :tag "unsent" unsent) (function fun))) (defcustom message-kill-buffer-on-exit nil @@ -274,32 +278,9 @@ If t, use `message-user-organization-file'." :type 'file :group 'message-headers) -(defcustom message-forward-start-separator - "------- Start of forwarded message -------\n" - "*Delimiter inserted before forwarded messages." - :group 'message-forwarding - :type 'string) - -(defcustom message-forward-end-separator - "------- End of forwarded message -------\n" - "*Delimiter inserted after forwarded messages." - :group 'message-forwarding - :type 'string) - -(defcustom message-signature-before-forwarded-message t - "*If non-nil, put the signature before any included forwarded message." - :group 'message-forwarding - :type 'boolean) - -(defcustom message-included-forward-headers - "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-\\|^Message-ID:\\|^References:\\|^Content-Transfer-Encoding:\\|^Content-Type:\\|^Mime-Version:" - "*Regexp matching headers to be included in forwarded messages." - :group 'message-forwarding - :type 'regexp) - (defcustom message-make-forward-subject-function 'message-forward-subject-author-subject - "*A list of functions that are called to generate a subject header for forwarded messages. + "*A list of functions that are called to generate a subject header for forwarded messages. The subject generated by the previous function is passed into each successive function. @@ -309,26 +290,47 @@ The provided functions are: newsgroup)), in brackets followed by the subject * message-forward-subject-fwd (Subject of article with 'Fwd:' prepended to it." - :group 'message-forwarding - :type '(radio (function-item message-forward-subject-author-subject) - (function-item message-forward-subject-fwd))) + :group 'message-forwarding + :type '(radio (function-item message-forward-subject-author-subject) + (function-item message-forward-subject-fwd))) + +(defcustom message-forward-as-mime t + "*If non-nil, forward messages as an inline/rfc822 MIME section. Otherwise, directly inline the old message in the forwarded message." + :group 'message-forwarding + :type 'boolean) + +(defcustom message-forward-show-mml t + "*If non-nil, forward messages are shown as mml. Otherwise, forward messages are unchanged." + :group 'message-forwarding + :type 'boolean) + +(defcustom message-forward-before-signature t + "*If non-nil, put forwarded message before signature, else after." + :group 'message-forwarding + :type 'boolean) (defcustom message-wash-forwarded-subjects nil "*If non-nil, try to remove as much old cruft as possible from the subject of messages before generating the new subject of a forward." :group 'message-forwarding :type 'boolean) -(defcustom message-ignored-resent-headers "^Return-receipt" +(defcustom message-ignored-resent-headers "^Return-receipt\\|^X-Gnus\\|^Gnus-Warning:" "*All headers that match this regexp will be deleted when resending a message." :group 'message-interface :type 'regexp) +(defcustom message-forward-ignored-headers "^Content-Transfer-Encoding:\\|^X-Gnus" + "*All headers that match this regexp will be deleted when forwarding a message." + :group 'message-forwarding + :type '(choice (const :tag "None" nil) + regexp)) + (defcustom message-ignored-cited-headers "." "*Delete these headers from the messages you yank." :group 'message-insertion :type 'regexp) -(defcustom message-cancel-message "I am canceling my own article." +(defcustom message-cancel-message "I am canceling my own article.\n" "Message to be inserted in the cancel message." :group 'message-interface :type 'string) @@ -340,7 +342,7 @@ The provided functions are: The headers should be delimited by a line whose contents match the variable `mail-header-separator'. -Legal values include `message-send-mail-with-sendmail' (the default), +Valid values include `message-send-mail-with-sendmail' (the default), `message-send-mail-with-mh', `message-send-mail-with-qmail' and `smtpmail-send-it'." :type '(radio (function-item message-send-mail-with-sendmail) @@ -391,10 +393,9 @@ always query the user whether to use the value. If it is the symbol (const use) (const ask))) -;; stuff relating to broken sendmail in MMDF (defcustom message-sendmail-f-is-evil nil - "*Non-nil means that \"-f username\" should not be added to the sendmail -command line, because it is even more evil than leaving it out." + "*Non-nil means that \"-f username\" should not be added to the sendmail command line. +Doing so would be even more evil than leaving it out." :group 'message-sending :type 'boolean) @@ -414,6 +415,11 @@ might set this variable to '(\"-f\" \"you@some.where\")." :group 'message-sending :type '(repeat string)) +(defvar message-cater-to-broken-inn t + "Non-nil means Gnus should not fold the `References' header. +Folding `References' makes ancient versions of INN create incorrect +NOV lines.") + (defvar gnus-post-method) (defvar gnus-select-method) (defcustom message-post-method @@ -444,6 +450,11 @@ The function `message-setup' runs this hook." :group 'message-various :type 'hook) +(defcustom message-cancel-hook nil + "Hook run when cancelling articles." + :group 'message-various + :type 'hook) + (defcustom message-signature-setup-hook nil "Normal hook, run each time a new outgoing message is initialized. It is run after the headers have been inserted and before @@ -474,8 +485,7 @@ the signature is inserted." ;;;###autoload (defcustom message-yank-prefix "> " - "*Prefix inserted on the lines of yanked messages. -nil means use indentation." + "*Prefix inserted on the lines of yanked messages." :type 'string :group 'message-insertion) @@ -492,6 +502,7 @@ Predefined functions include `message-cite-original' and `message-cite-original-without-signature'. Note that `message-cite-original' uses `mail-citation-hook' if that is non-nil." :type '(radio (function-item message-cite-original) + (function-item message-cite-original-without-signature) (function-item sc-cite-original) (function :tag "Other")) :group 'message-insertion) @@ -580,8 +591,7 @@ these lines." :type 'message-header-lines) (defcustom message-default-news-headers "" - "*A string of header lines to be inserted in outgoing news -articles." + "*A string of header lines to be inserted in outgoing news articles." :group 'message-headers :group 'message-news :type 'message-header-lines) @@ -613,14 +623,10 @@ actually occur." :group 'message-sending :type 'sexp) -;; Ignore errors in case this is used in Emacs 19. -;; Don't use ignore-errors because this is copied into loaddefs.el. ;;;###autoload -(condition-case nil - (define-mail-user-agent 'message-user-agent - 'message-mail 'message-send-and-exit - 'message-kill-buffer 'message-send-hook) - (error nil)) +(define-mail-user-agent 'message-user-agent + 'message-mail 'message-send-and-exit + 'message-kill-buffer 'message-send-hook) (defvar message-mh-deletable-headers '(Message-ID Date Lines Sender) "If non-nil, delete the deletable headers before feeding to mh.") @@ -651,12 +657,34 @@ If nil, Message won't auto-save." :group 'message-buffers :type 'directory) +(defcustom message-buffer-naming-style 'unique + "*The way new message buffers are named. +Valid valued are `unique' and `unsent'." + :group 'message-buffers + :type '(choice (const :tag "unique" unique) + (const :tag "unsent" unsent))) + +(defcustom message-default-charset nil + "Default charset used in non-MULE XEmacsen." + :group 'message + :type 'symbol) + +(defcustom message-dont-reply-to-names + (and (boundp 'rmail-dont-reply-to-names) rmail-dont-reply-to-names) + "*A regexp specifying names to prune when doing wide replies. +A value of nil means exclude your own name only." + :group 'message + :type '(choice (const :tag "Yourself" nil) + regexp)) + ;;; Internal variables. ;;; Well, not really internal. (defvar message-mode-syntax-table (let ((table (copy-syntax-table text-mode-syntax-table))) (modify-syntax-entry ?% ". " table) + (modify-syntax-entry ?> ". " table) + (modify-syntax-entry ?< ". " table) table) "Syntax table used while in Message mode.") @@ -776,6 +804,18 @@ Defaults to `text-mode-abbrev-table'.") "Face used for displaying cited text names." :group 'message-faces) +(defface message-mml-face + '((((class color) + (background dark)) + (:foreground "ForestGreen")) + (((class color) + (background light)) + (:foreground "ForestGreen")) + (t + (:bold t))) + "Face used for displaying MML." + :group 'message-faces) + (defvar message-font-lock-keywords (let* ((cite-prefix "A-Za-z") (cite-suffix (concat cite-prefix "0-9_.@-")) @@ -806,7 +846,9 @@ Defaults to `text-mode-abbrev-table'.") (,(concat "^[ \t]*" "\\([" cite-prefix "]+[" cite-suffix "]*\\)?" "[:>|}].*") - (0 'message-cited-text-face)))) + (0 'message-cited-text-face)) + ("<#/?\\(multipart\\|part\\|external\\|mml\\).*>" + (0 'message-mml-face)))) "Additional expressions to highlight in Message mode.") ;; XEmacs does it like this. For Emacs, we have to set the @@ -846,12 +888,26 @@ The cdr of ech entry is a function for applying the face to a region.") (defvar message-send-coding-system 'binary "Coding system to encode outgoing mail.") +(defvar message-draft-coding-system + mm-auto-save-coding-system + "Coding system to compose mail.") + +(defcustom message-send-mail-partially-limit 1000000 + "The limitation of messages sent as message/partial. +The lower bound of message size in characters, beyond which the message +should be sent in several parts. If it is nil, the size is unlimited." + :group 'message-buffers + :type '(choice (const :tag "unlimited" nil) + (integer 1000000))) + ;;; Internal variables. (defvar message-buffer-list nil) (defvar message-this-is-news nil) (defvar message-this-is-mail nil) (defvar message-draft-article nil) +(defvar message-mime-part nil) +(defvar message-posting-charset nil) ;; Byte-compiler warning (defvar gnus-active-hashtb) @@ -891,10 +947,10 @@ The cdr of ech entry is a function for applying the face to a region.") "\\([^\0-\b\n-\r\^?].*\\)? " ;; The time the message was sent. - "\\([^\0-\r \^?]+\\) +" ; day of the week - "\\([^\0-\r \^?]+\\) +" ; month - "\\([0-3]?[0-9]\\) +" ; day of month - "\\([0-2][0-9]:[0-5][0-9]\\(:[0-6][0-9]\\)?\\) *" ; time of day + "\\([^\0-\r \^?]+\\) +" ; day of the week + "\\([^\0-\r \^?]+\\) +" ; month + "\\([0-3]?[0-9]\\) +" ; day of month + "\\([0-2][0-9]:[0-5][0-9]\\(:[0-6][0-9]\\)?\\) *" ; time of day ;; Perhaps a time zone, specified by an abbreviation, or by a ;; numeric offset. @@ -919,6 +975,7 @@ The cdr of ech entry is a function for applying the face to a region.") "^ *---+ +Original message +---+ *$\\|" "^ *--+ +begin message +--+ *$\\|" "^ *---+ +Original message follows +---+ *$\\|" + "^ *---+ +Undelivered message follows +---+ *$\\|" "^|? *---+ +Message text follows: +---+ *|?$") "A regexp that matches the separator before the text of a failed message.") @@ -937,8 +994,7 @@ The cdr of ech entry is a function for applying the face to a region.") (Expires) (Message-ID) (References . message-shorten-references) - (X-Mailer) - (X-Newsreader)) + (User-Agent)) "Alist used for formatting headers.") (eval-and-compile @@ -947,14 +1003,15 @@ The cdr of ech entry is a function for applying the face to a region.") (autoload 'mh-send-letter "mh-comp") (autoload 'gnus-point-at-eol "gnus-util") (autoload 'gnus-point-at-bol "gnus-util") - (autoload 'gnus-output-to-mail "gnus-util") (autoload 'gnus-output-to-rmail "gnus-util") + (autoload 'gnus-output-to-mail "gnus-util") (autoload 'mail-abbrev-in-expansion-header-p "mailabbrev") (autoload 'nndraft-request-associate-buffer "nndraft") (autoload 'nndraft-request-expire-articles "nndraft") (autoload 'gnus-open-server "gnus-int") (autoload 'gnus-request-post "gnus-int") (autoload 'gnus-alive-p "gnus-util") + (autoload 'gnus-group-name-charset "gnus-group") (autoload 'rmail-output "rmail")) @@ -972,9 +1029,19 @@ The cdr of ech entry is a function for applying the face to a region.") `(delete-region (progn (beginning-of-line) (point)) (progn (forward-line ,(or n 1)) (point)))) +(defun message-unquote-tokens (elems) + "Remove double quotes (\") from strings in list." + (mapcar (lambda (item) + (while (string-match "^\\(.*\\)\"\\(.*\\)$" item) + (setq item (concat (match-string 1 item) + (match-string 2 item)))) + item) + elems)) + (defun message-tokenize-header (header &optional separator) "Split HEADER into a list of header elements. -\",\" is used as the separator." +SEPARATOR is a string of characters to be used as separators. \",\" +is used by default." (if (not header) nil (let ((regexp (format "[%s]+" (or separator ","))) @@ -996,22 +1063,22 @@ The cdr of ech entry is a function for applying the face to a region.") (not paren)))) (push (buffer-substring beg (point)) elems) (setq beg (match-end 0))) - ((= (following-char) ?\") + ((eq (char-after) ?\") (setq quoted (not quoted))) - ((and (= (following-char) ?\() + ((and (eq (char-after) ?\() (not quoted)) (setq paren t)) - ((and (= (following-char) ?\)) + ((and (eq (char-after) ?\)) (not quoted)) (setq paren nil)))) - (nreverse elems))))) + (nreverse elems))))) (defun message-mail-file-mbox-p (file) "Say whether FILE looks like a Unix mbox file." (when (and (file-exists-p file) (file-readable-p file) (file-regular-p file)) - (nnheader-temp-write nil + (with-temp-buffer (nnheader-insert-file-contents file) (goto-char (point-min)) (looking-at message-unix-mail-delimiter)))) @@ -1019,9 +1086,27 @@ The cdr of ech entry is a function for applying the face to a region.") (defun message-fetch-field (header &optional not-all) "The same as `mail-fetch-field', only remove all newlines." (let* ((inhibit-point-motion-hooks t) + (case-fold-search t) (value (mail-fetch-field header nil (not not-all)))) (when value - (nnheader-replace-chars-in-string value ?\n ? )))) + (while (string-match "\n[\t ]+" value) + (setq value (replace-match " " t t value))) + (set-text-properties 0 (length value) nil value) + value))) + +(defun message-narrow-to-field () + "Narrow the buffer to the header on the current line." + (beginning-of-line) + (narrow-to-region + (point) + (progn + (forward-line 1) + (if (re-search-forward "^[^ \n\t]" nil t) + (progn + (beginning-of-line) + (point)) + (point-max)))) + (goto-char (point-min))) (defun message-add-header (&rest headers) "Add the HEADERS to the message header, skipping those already present." @@ -1030,12 +1115,13 @@ The cdr of ech entry is a function for applying the face to a region.") (unless (string-match "^\\([^:]+\\):[ \t]*[^ \t]" (car headers)) (error "Invalid header `%s'" (car headers))) (setq hclean (match-string 1 (car headers))) - (save-restriction - (message-narrow-to-headers) - (unless (re-search-forward (concat "^" (regexp-quote hclean) ":") nil t) - (insert (car headers) ?\n)))) + (save-restriction + (message-narrow-to-headers) + (unless (re-search-forward (concat "^" (regexp-quote hclean) ":") nil t) + (insert (car headers) ?\n)))) (setq headers (cdr headers)))) + (defun message-fetch-reply-field (header) "Fetch FIELD from the message we're replying to." (when (and message-reply-buffer @@ -1051,7 +1137,7 @@ The cdr of ech entry is a function for applying the face to a region.") (erase-buffer)) (set-buffer (get-buffer-create " *message work*")) (kill-all-local-variables) - (buffer-disable-undo (current-buffer)))) + (mm-enable-multibyte))) (defun message-functionp (form) "Return non-nil if FORM is funcallable." @@ -1059,6 +1145,21 @@ The cdr of ech entry is a function for applying the face to a region.") (and (listp form) (eq (car form) 'lambda)) (byte-code-function-p form))) +(defun message-strip-list-identifiers (subject) + "Remove list identifiers in `gnus-list-identifiers'." + (require 'gnus-sum) ; for gnus-list-identifiers + (let ((regexp (if (stringp gnus-list-identifiers) + gnus-list-identifiers + (mapconcat 'identity gnus-list-identifiers " *\\|")))) + (if (string-match (concat "\\(\\(\\(Re: +\\)?\\(" regexp + " *\\)\\)+\\(Re: +\\)?\\)") subject) + (concat (substring subject 0 (match-beginning 1)) + (or (match-string 3 subject) + (match-string 5 subject)) + (substring subject + (match-end 1))) + subject))) + (defun message-strip-subject-re (subject) "Remove \"Re:\" from subject lines." (if (string-match message-subject-re-regexp subject) @@ -1096,9 +1197,21 @@ Return the number of headers removed." (forward-line 1) (if (re-search-forward "^[^ \t]" nil t) (goto-char (match-beginning 0)) - (point-max)))) + (goto-char (point-max))))) number)) +(defun message-remove-first-header (header) + "Remove the first instance of HEADER if there is more than one." + (let ((count 0) + (regexp (concat "^" (regexp-quote header) ":"))) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (incf count))) + (while (> count 1) + (message-remove-header header nil t) + (decf count)))) + (defun message-narrow-to-headers () "Narrow the buffer to the head of the message." (widen) @@ -1111,7 +1224,8 @@ Return the number of headers removed." (goto-char (point-min))) (defun message-narrow-to-head () - "Narrow the buffer to the head of the message." + "Narrow the buffer to the head of the message. +Point is left at the beginning of the narrowed-to region." (widen) (narrow-to-region (goto-char (point-min)) @@ -1120,6 +1234,21 @@ Return the number of headers removed." (point-max))) (goto-char (point-min))) +(defun message-narrow-to-headers-or-head () + "Narrow the buffer to the head of the message." + (widen) + (narrow-to-region + (goto-char (point-min)) + (cond + ((re-search-forward + (concat "^" (regexp-quote mail-header-separator) "\n") nil t) + (match-beginning 0)) + ((search-forward "\n\n" nil t) + (1- (point))) + (t + (point-max)))) + (goto-char (point-min))) + (defun message-news-p () "Say whether the current buffer contains a news message." (and (not message-this-is-mail) @@ -1152,6 +1281,7 @@ Return the number of headers removed." (defun message-sort-headers-1 () "Sort the buffer as headers using `message-rank' text props." (goto-char (point-min)) + (require 'sort) (sort-subr nil 'message-next-header (lambda () @@ -1194,7 +1324,8 @@ Return the number of headers removed." (defvar message-mode-map nil) (unless message-mode-map - (setq message-mode-map (copy-keymap text-mode-map)) + (setq message-mode-map (make-keymap)) + (set-keymap-parent message-mode-map text-mode-map) (define-key message-mode-map "\C-c?" 'describe-mode) (define-key message-mode-map "\C-c\C-f\C-t" 'message-goto-to) @@ -1215,8 +1346,10 @@ Return the number of headers removed." (define-key message-mode-map "\C-c\C-n" 'message-insert-newsgroups) (define-key message-mode-map "\C-c\C-y" 'message-yank-original) + (define-key message-mode-map "\C-c\M-\C-y" 'message-yank-buffer) (define-key message-mode-map "\C-c\C-q" 'message-fill-yanked-message) (define-key message-mode-map "\C-c\C-w" 'message-insert-signature) + (define-key message-mode-map "\C-c\M-h" 'message-insert-headers) (define-key message-mode-map "\C-c\C-r" 'message-caesar-buffer-body) (define-key message-mode-map "\C-c\C-o" 'message-sort-headers) (define-key message-mode-map "\C-c\M-r" 'message-rename-buffer) @@ -1231,6 +1364,8 @@ Return the number of headers removed." (define-key message-mode-map "\C-c\C-z" 'message-kill-to-signature) (define-key message-mode-map "\M-\r" 'message-newline-and-reformat) + (define-key message-mode-map "\C-c\C-a" 'mml-attach-file) + (define-key message-mode-map "\t" 'message-tab)) (easy-menu-define @@ -1248,6 +1383,7 @@ Return the number of headers removed." ["Newline and Reformat" message-newline-and-reformat t] ["Rename buffer" message-rename-buffer t] ["Spellcheck" ispell-message t] + ["Attach file as MIME" mml-attach-file t] "----" ["Send Message" message-send-and-exit t] ["Abort Message" message-dont-send t] @@ -1279,6 +1415,7 @@ Return the number of headers removed." "Major mode for editing mail and news to be sent. Like Text Mode but with these additional commands: C-c C-s message-send (send the message) C-c C-c message-send-and-exit +C-c C-d Pospone sending the message C-c C-k Kill the message C-c C-f move to a header field (and create it if there isn't): C-c C-f C-t move to To C-c C-f C-s move to Subject C-c C-f C-c move to Cc C-c C-f C-b move to Bcc @@ -1294,12 +1431,16 @@ C-c C-w message-insert-signature (insert `message-signature-file' file). C-c C-y message-yank-original (insert current message, if any). C-c C-q message-fill-yanked-message (fill what was yanked). C-c C-e message-elide-region (elide the text between point and mark). +C-c C-v message-delete-not-region (remove the text outside the region). C-c C-z message-kill-to-signature (kill the text up to the signature). -C-c C-r message-caesar-buffer-body (rot13 the message body)." +C-c C-r message-caesar-buffer-body (rot13 the message body). +C-c C-a mml-attach-file (attach a file as MIME). +M-RET message-newline-and-reformat (break the line and reformat)." (interactive) + (if (local-variable-p 'mml-buffer-list (current-buffer)) + (mml-destroy-buffers)) (kill-all-local-variables) - (make-local-variable 'message-reply-buffer) - (setq message-reply-buffer nil) + (set (make-local-variable 'message-reply-buffer) nil) (make-local-variable 'message-send-actions) (make-local-variable 'message-exit-actions) (make-local-variable 'message-kill-actions) @@ -1328,51 +1469,51 @@ C-c C-r message-caesar-buffer-body (rot13 the message body)." ;; lines that delimit forwarded messages. ;; Lines containing just >= 3 dashes, perhaps after whitespace, ;; are also sometimes used and should be separators. - (setq paragraph-start (concat (regexp-quote mail-header-separator) - "$\\|[ \t]*[a-z0-9A-Z]*>+[ \t]*$\\|[ \t]*$\\|" - "-- $\\|---+$\\|" - page-delimiter)) + (setq paragraph-start + (concat (regexp-quote mail-header-separator) + "$\\|[ \t]*[a-z0-9A-Z]*>+[ \t]*$\\|[ \t]*$\\|" + "-- $\\|---+$\\|" + page-delimiter + ;;!!! Uhm... shurely this can't be right? + "[> " (regexp-quote message-yank-prefix) "]+$")) (setq paragraph-separate paragraph-start) (make-local-variable 'message-reply-headers) (setq message-reply-headers nil) (make-local-variable 'message-newsreader) (make-local-variable 'message-mailer) (make-local-variable 'message-post-method) - (make-local-variable 'message-sent-message-via) - (setq message-sent-message-via nil) - (make-local-variable 'message-checksum) - (setq message-checksum nil) + (set (make-local-variable 'message-sent-message-via) nil) + (set (make-local-variable 'message-checksum) nil) + (set (make-local-variable 'message-mime-part) 0) ;;(when (fboundp 'mail-hist-define-keys) ;; (mail-hist-define-keys)) - (when (string-match "XEmacs\\|Lucid" emacs-version) - (message-setup-toolbar)) + (if (featurep 'xemacs) + (message-setup-toolbar) + (set (make-local-variable 'font-lock-defaults) + '(message-font-lock-keywords t))) (easy-menu-add message-mode-menu message-mode-map) (easy-menu-add message-mode-field-menu message-mode-map) - (make-local-variable 'adaptive-fill-regexp) - (setq adaptive-fill-regexp - (concat "[ \t]*[-a-z0-9A-Z]*>+[ \t]*\\|" adaptive-fill-regexp)) - (make-local-variable 'adaptive-fill-first-line-regexp) - (setq adaptive-fill-first-line-regexp - (concat "[ \t]*[-a-z0-9A-Z]*>+[ \t]*\\|" - adaptive-fill-first-line-regexp)) ;; Allow mail alias things. (when (eq message-mail-alias-type 'abbrev) (if (fboundp 'mail-abbrevs-setup) (mail-abbrevs-setup) (mail-aliases-setup))) (message-set-auto-save-file-name) - (unless (string-match "XEmacs" emacs-version) - (set (make-local-variable 'font-lock-defaults) - '(message-font-lock-keywords t))) (make-local-variable 'adaptive-fill-regexp) (setq adaptive-fill-regexp - (concat "[ \t]*[-a-z0-9A-Z]*>+[ \t]*\\|" adaptive-fill-regexp)) + (concat "[ \t]*[-a-z0-9A-Z]*\\(>[ \t]*\\)+[ \t]*\\|" adaptive-fill-regexp)) (unless (boundp 'adaptive-fill-first-line-regexp) (setq adaptive-fill-first-line-regexp nil)) (make-local-variable 'adaptive-fill-first-line-regexp) (setq adaptive-fill-first-line-regexp - (concat "[ \t]*[-a-z0-9A-Z]*>+[ \t]*\\|" + (concat "[ \t]*[-a-z0-9A-Z]*\\(>[ \t]*\\)+[ \t]*\\|" adaptive-fill-first-line-regexp)) + (make-local-variable 'auto-fill-inhibit-regexp) + (setq auto-fill-inhibit-regexp "^[A-Z][^: \n\t]+:") + (mm-enable-multibyte) + (make-local-variable 'indent-tabs-mode) ;Turn off tabs for indentation. + (setq indent-tabs-mode nil) + (mml-mode) (run-hooks 'text-mode-hook 'message-mode-hook)) @@ -1443,13 +1584,14 @@ C-c C-r message-caesar-buffer-body (rot13 the message body)." (interactive) (if (looking-at "[ \t]*\n") (expand-abbrev)) (goto-char (point-min)) - (search-forward (concat "\n" mail-header-separator "\n") nil t)) + (or (search-forward (concat "\n" mail-header-separator "\n") nil t) + (search-forward "\n\n" nil t))) (defun message-goto-eoh () "Move point to the end of the headers." (interactive) (message-goto-body) - (forward-line -2)) + (forward-line -1)) (defun message-goto-signature () "Move point to the beginning of the message signature. @@ -1473,7 +1615,8 @@ With the prefix argument FORCE, insert the header anyway." (let ((co (message-fetch-reply-field "mail-copies-to"))) (when (and (null force) co - (equal (downcase co) "never")) + (or (equal (downcase co) "never") + (equal (downcase co) "nobody"))) (error "The user has requested not to have copies sent via mail"))) (when (and (message-position-on-field "To") (mail-fetch-field "to") @@ -1482,6 +1625,24 @@ With the prefix argument FORCE, insert the header anyway." (insert (or (message-fetch-reply-field "reply-to") (message-fetch-reply-field "from") ""))) +(defun message-widen-reply () + "Widen the reply to include maximum recipients." + (interactive) + (let ((follow-to + (and message-reply-buffer + (buffer-name message-reply-buffer) + (save-excursion + (set-buffer message-reply-buffer) + (message-get-reply-headers t))))) + (save-excursion + (save-restriction + (message-narrow-to-headers) + (dolist (elem follow-to) + (message-remove-header (symbol-name (car elem))) + (goto-char (point-min)) + (insert (symbol-name (car elem)) ": " + (cdr elem) "\n")))))) + (defun message-insert-newsgroups () "Insert the Newsgroups header from the article being replied to." (interactive) @@ -1526,17 +1687,24 @@ With the prefix argument FORCE, insert the header anyway." (defun message-newline-and-reformat () "Insert four newlines, and then reformat if inside quoted text." (interactive) - (let ((point (point)) - quoted) - (save-excursion - (beginning-of-line) - (setq quoted (looking-at (regexp-quote message-yank-prefix)))) - (insert "\n\n\n\n") + (let ((prefix "[]>»|:}+ \t]*") + (supercite-thing "[-._a-zA-Z0-9]*[>]+[ \t]*") + quoted point) + (unless (bolp) + (save-excursion + (beginning-of-line) + (when (looking-at (concat prefix + supercite-thing)) + (setq quoted (match-string 0)))) + (insert "\n")) + (setq point (point)) + (insert "\n\n\n") + (delete-region (point) (re-search-forward "[ \t]*")) (when quoted - (insert message-yank-prefix)) + (insert quoted)) (fill-paragraph nil) (goto-char point) - (forward-line 2))) + (forward-line 1))) (defun message-insert-signature (&optional force) "Insert a signature. See documentation for the `message-signature' variable." @@ -1547,8 +1715,7 @@ With the prefix argument FORCE, insert the header anyway." (eq force 0)) (save-excursion (goto-char (point-max)) - (not (re-search-backward - message-signature-separator nil t)))) + (not (re-search-backward message-signature-separator nil t)))) ((and (null message-signature) force) t) @@ -1578,13 +1745,11 @@ With the prefix argument FORCE, insert the header anyway." (defun message-elide-region (b e) "Elide the text between point and mark. -An ellipsis (from `message-elide-elipsis') will be inserted where the +An ellipsis (from `message-elide-ellipsis') will be inserted where the text was killed." (interactive "r") (kill-region b e) - (unless (bolp) - (insert "\n")) - (insert message-elide-elipsis)) + (insert message-elide-ellipsis)) (defvar message-caesar-translation-table nil) @@ -1603,15 +1768,9 @@ text was killed." ;; We build the table, if necessary. (when (or (not message-caesar-translation-table) (/= (aref message-caesar-translation-table ?a) (+ ?a n))) - (setq message-caesar-translation-table - (message-make-caesar-translation-table n))) - ;; Then we translate the region. Do it this way to retain - ;; text properties. - (while (< b e) - (subst-char-in-region - b (1+ b) (char-after b) - (aref message-caesar-translation-table (char-after b))) - (incf b)))) + (setq message-caesar-translation-table + (message-make-caesar-translation-table n))) + (translate-region b e message-caesar-translation-table))) (defun message-make-caesar-translation-table (n) "Create a rot table with offset N." @@ -1648,11 +1807,8 @@ Mail and USENET news headers are not rotated." (save-restriction (when (message-goto-body) (narrow-to-region (point) (point-max))) - (let ((body (buffer-substring (point-min) (point-max)))) - (unless (equal 0 (call-process-region - (point-min) (point-max) program t t)) - (insert body) - (message "%s failed." program)))))) + (shell-command-on-region + (point-min) (point-max) program nil t)))) (defun message-rename-buffer (&optional enter-string) "Rename the *message* buffer to \"*message* RECIPIENT\". @@ -1686,8 +1842,7 @@ Numeric argument means justify as well." (goto-char (point-min)) (search-forward (concat "\n" mail-header-separator "\n") nil t) (let ((fill-prefix message-yank-prefix)) - (fill-individual-paragraphs (point) (point-max) justifyp - mail-citation-prefix-regexp)))) + (fill-individual-paragraphs (point) (point-max) justifyp)))) (defun message-indent-citation () "Modify text just inserted from a message to be cited. @@ -1758,6 +1913,24 @@ prefix, and don't delete any headers." (unless modified (setq message-checksum (message-checksum)))))) +(defun message-yank-buffer (buffer) + "Insert BUFFER into the current buffer and quote it." + (interactive "bYank buffer: ") + (let ((message-reply-buffer buffer)) + (save-window-excursion + (message-yank-original)))) + +(defun message-buffers () + "Return a list of active message buffers." + (let (buffers) + (save-excursion + (dolist (buffer (buffer-list t)) + (set-buffer buffer) + (when (and (eq major-mode 'message-mode) + (null message-sent-message-via)) + (push (buffer-name buffer) buffers)))) + (nreverse buffers))) + (defun message-cite-original-without-signature () "Cite function in the standard Message manner." (let ((start (point)) @@ -1767,8 +1940,11 @@ prefix, and don't delete any headers." (if (listp message-indent-citation-function) message-indent-citation-function (list message-indent-citation-function))))) + (mml-quote-region start end) + ;; Allow undoing. + (undo-boundary) (goto-char end) - (when (re-search-backward "^-- $" start t) + (when (re-search-backward message-signature-separator start t) ;; Also peel off any blank lines before the signature. (forward-line -1) (while (looking-at "^[ \t]*$") @@ -1783,25 +1959,27 @@ prefix, and don't delete any headers." (insert "\n")) (funcall message-citation-line-function)))) -(defvar mail-citation-hook) ;Compiler directive +(defvar mail-citation-hook) ;Compiler directive (defun message-cite-original () "Cite function in the standard Message manner." (if (and (boundp 'mail-citation-hook) - mail-citation-hook) + mail-citation-hook) (run-hooks 'mail-citation-hook) (let ((start (point)) - (functions - (when message-indent-citation-function - (if (listp message-indent-citation-function) - message-indent-citation-function - (list message-indent-citation-function))))) + (end (mark t)) + (functions + (when message-indent-citation-function + (if (listp message-indent-citation-function) + message-indent-citation-function + (list message-indent-citation-function))))) + (mml-quote-region start end) (goto-char start) (while functions - (funcall (pop functions))) + (funcall (pop functions))) (when message-citation-line-function - (unless (bolp) - (insert "\n")) - (funcall message-citation-line-function))))) + (unless (bolp) + (insert "\n")) + (funcall message-citation-line-function))))) (defun message-insert-citation-line () "Function that inserts a simple citation line." @@ -1910,51 +2088,50 @@ The text will also be indented the normal way." (defun message-send (&optional arg) "Send the message in the current buffer. -If `message-interactive' is non-nil, wait for success indication -or error messages, and inform user. -Otherwise any failure is reported in a message back to -the user from the mailer." +If `message-interactive' is non-nil, wait for success indication or +error messages, and inform user. +Otherwise any failure is reported in a message back to the user from +the mailer. +The usage of ARG is defined by the instance that called Message. +It should typically alter the sending method in some way or other." (interactive "P") - ;; Disabled test. - (when (or (buffer-modified-p) - (message-check-element 'unchanged) - (y-or-n-p "No changes in the buffer; really send? ")) - ;; Make it possible to undo the coming changes. - (undo-boundary) - (let ((inhibit-read-only t)) - (put-text-property (point-min) (point-max) 'read-only nil)) - (message-fix-before-sending) - (run-hooks 'message-send-hook) - (message "Sending...") - (let ((alist message-send-method-alist) - (success t) - elem sent) - (while (and success - (setq elem (pop alist))) - (when (and (or (not (funcall (cadr elem))) - (and (or (not (memq (car elem) - message-sent-message-via)) - (y-or-n-p - (format - "Already sent message via %s; resend? " - (car elem)))) - (setq success (funcall (caddr elem) arg))))) - (setq sent t))) - (when (and success sent) - (message-do-fcc) - ;;(when (fboundp 'mail-hist-put-headers-into-history) - ;; (mail-hist-put-headers-into-history)) - (run-hooks 'message-sent-hook) - (message "Sending...done") - ;; Mark the buffer as unmodified and delete auto-save. - (set-buffer-modified-p nil) - (delete-auto-save-file-if-necessary t) - (message-disassociate-draft) - ;; Delete other mail buffers and stuff. - (message-do-send-housekeeping) - (message-do-actions message-send-actions) - ;; Return success. - t)))) + ;; Make it possible to undo the coming changes. + (undo-boundary) + (let ((inhibit-read-only t)) + (put-text-property (point-min) (point-max) 'read-only nil)) + (message-fix-before-sending) + (run-hooks 'message-send-hook) + (message "Sending...") + (let ((alist message-send-method-alist) + (success t) + elem sent) + (while (and success + (setq elem (pop alist))) + (when (or (not (funcall (cadr elem))) + (and (or (not (memq (car elem) + message-sent-message-via)) + (y-or-n-p + (format + "Already sent message via %s; resend? " + (car elem)))) + (setq success (funcall (caddr elem) arg)))) + (setq sent t))) + (unless (or sent (not success)) + (error "No methods specified to send by")) + (when (and success sent) + (message-do-fcc) + (save-excursion + (run-hooks 'message-sent-hook)) + (message "Sending...done") + ;; Mark the buffer as unmodified and delete auto-save. + (set-buffer-modified-p nil) + (delete-auto-save-file-if-necessary t) + (message-disassociate-draft) + ;; Delete other mail buffers and stuff. + (message-do-send-housekeeping) + (message-do-actions message-send-actions) + ;; Return success. + t))) (defun message-send-via-mail (arg) "Send the current message via mail." @@ -1964,18 +2141,28 @@ the user from the mailer." "Send the current message via news." (funcall message-send-news-function arg)) +(defmacro message-check (type &rest forms) + "Eval FORMS if TYPE is to be checked." + `(or (message-check-element ,type) + (save-excursion + ,@forms))) + +(put 'message-check 'lisp-indent-function 1) +(put 'message-check 'edebug-form-spec '(form body)) + (defun message-fix-before-sending () "Do various things to make the message nice before sending it." ;; Make sure there's a newline at the end of the message. (goto-char (point-max)) (unless (bolp) (insert "\n")) - ;; Make all invisible text visible. - ;;(when (text-property-any (point-min) (point-max) 'invisible t) - ;; (put-text-property (point-min) (point-max) 'invisible nil) - ;; (unless (yes-or-no-p "Invisible text found and made visible; continue posting?") - ;; (error "Invisible text found and made visible"))) - ) + ;; Delete all invisible text. + (message-check 'invisible-text + (when (text-property-any (point-min) (point-max) 'invisible t) + (put-text-property (point-min) (point-max) 'invisible nil) + (unless (yes-or-no-p + "Invisible text found and made visible; continue posting? ") + (error "Invisible text found and made visible"))))) (defun message-add-action (action &rest types) "Add ACTION to be performed when doing an exit of type TYPES." @@ -1998,12 +2185,83 @@ the user from the mailer." (eval (car actions))))) (pop actions))) +(defun message-send-mail-partially () + "Sendmail as message/partial." + (let ((p (goto-char (point-min))) + (tembuf (message-generate-new-buffer-clone-locals " message temp")) + (curbuf (current-buffer)) + (id (message-make-message-id)) (n 1) + plist total header required-mail-headers) + (while (not (eobp)) + (if (< (point-max) (+ p message-send-mail-partially-limit)) + (goto-char (point-max)) + (goto-char (+ p message-send-mail-partially-limit)) + (beginning-of-line) + (if (<= (point) p) (forward-line 1))) ;; In case of bad message. + (push p plist) + (setq p (point))) + (setq total (length plist)) + (push (point-max) plist) + (setq plist (nreverse plist)) + (unwind-protect + (save-excursion + (setq p (pop plist)) + (while plist + (set-buffer curbuf) + (copy-to-buffer tembuf p (car plist)) + (set-buffer tembuf) + (goto-char (point-min)) + (if header + (progn + (goto-char (point-min)) + (narrow-to-region (point) (point)) + (insert header)) + (message-goto-eoh) + (setq header (buffer-substring (point-min) (point))) + (goto-char (point-min)) + (narrow-to-region (point) (point)) + (insert header) + (message-remove-header "Mime-Version") + (message-remove-header "Content-Type") + (message-remove-header "Content-Transfer-Encoding") + (message-remove-header "Message-ID") + (message-remove-header "Lines") + (goto-char (point-max)) + (insert "Mime-Version: 1.0\n") + (setq header (buffer-substring (point-min) (point-max)))) + (goto-char (point-max)) + (insert (format "Content-Type: message/partial; id=\"%s\"; number=%d; total=%d\n" + id n total)) + (let ((mail-header-separator "")) + (when (memq 'Message-ID message-required-mail-headers) + (insert "Message-ID: " (message-make-message-id) "\n")) + (when (memq 'Lines message-required-mail-headers) + (let ((mail-header-separator "")) + (insert "Lines: " (message-make-lines) "\n"))) + (message-goto-subject) + (end-of-line) + (insert (format " (%d/%d)" n total)) + (goto-char (point-max)) + (insert "\n") + (widen) + (mm-with-unibyte-current-buffer + (funcall message-send-mail-function))) + (setq n (+ n 1)) + (setq p (pop plist)) + (erase-buffer))) + (kill-buffer tembuf)))) + (defun message-send-mail (&optional arg) (require 'mail-utils) - (let ((tembuf (message-generate-new-buffer-clone-locals " message temp")) - (case-fold-search nil) - (news (message-news-p)) - (mailbuf (current-buffer))) + (let* ((tembuf (message-generate-new-buffer-clone-locals " message temp")) + (case-fold-search nil) + (news (message-news-p)) + (mailbuf (current-buffer)) + (message-this-is-mail t) + (message-posting-charset + (if (fboundp 'gnus-setup-posting-charset) + (gnus-setup-posting-charset nil) + message-posting-charset))) (save-restriction (message-narrow-to-headers) ;; Insert some headers. @@ -2022,19 +2280,37 @@ the user from the mailer." (set-buffer mailbuf) (buffer-string)))) ;; Remove some headers. + (message-encode-message-body) (save-restriction (message-narrow-to-headers) + ;; We (re)generate the Lines header. + (when (memq 'Lines message-required-mail-headers) + (message-generate-headers '(Lines))) ;; Remove some headers. - (message-remove-header message-ignored-mail-headers t)) + (message-remove-header message-ignored-mail-headers t) + (let ((mail-parse-charset message-default-charset)) + (mail-encode-encoded-word-buffer))) (goto-char (point-max)) ;; require one newline at the end. (or (= (preceding-char) ?\n) (insert ?\n)) - (when (and news + (when + (save-restriction + (message-narrow-to-headers) + (and news (or (message-fetch-field "cc") - (message-fetch-field "to"))) + (message-fetch-field "to")) + (string= "text/plain" + (car + (mail-header-parse-content-type + (message-fetch-field "content-type")))))) (message-insert-courtesy-copy)) - (funcall message-send-mail-function)) + (if (or (not message-send-mail-partially-limit) + (< (point-max) message-send-mail-partially-limit) + (not (y-or-n-p "The message size is too large, should it be sent partially?"))) + (mm-with-unibyte-current-buffer + (funcall message-send-mail-function)) + (message-send-mail-partially))) (kill-buffer tembuf)) (set-buffer mailbuf) (push 'mail message-sent-message-via))) @@ -2042,7 +2318,8 @@ the user from the mailer." (defun message-send-mail-with-sendmail () "Send off the prepared buffer with sendmail." (let ((errbuf (if message-interactive - (generate-new-buffer " sendmail errors") + (message-generate-new-buffer-clone-locals + " sendmail errors") 0)) resend-to-addresses delimline) (let ((case-fold-search t)) @@ -2067,7 +2344,7 @@ the user from the mailer." (set-buffer errbuf) (erase-buffer)))) (let ((default-directory "/") - (coding-system-for-write message-send-coding-system)) + (coding-system-for-write message-send-coding-system)) (apply 'call-process-region (append (list (point-min) (point-max) (if (boundp 'sendmail-program) @@ -2079,7 +2356,7 @@ the user from the mailer." ;; But some systems are more broken with -f, so ;; we'll let users override this. (if (null message-sendmail-f-is-evil) - (list "-f" (user-login-name))) + (list "-f" (message-make-address))) ;; These mean "report errors by mail" ;; and "deliver in background". (if (null message-interactive) '("-oem" "-odb")) @@ -2164,85 +2441,92 @@ to find out how to use this." (mh-send-letter))) (defun message-send-news (&optional arg) - (let ((tembuf (message-generate-new-buffer-clone-locals " *message temp*")) - (case-fold-search nil) - (method (if (message-functionp message-post-method) - (funcall message-post-method arg) - message-post-method)) - (messbuf (current-buffer)) - (message-syntax-checks - (if arg - (cons '(existing-newsgroups . disabled) - message-syntax-checks) - message-syntax-checks)) - result) - (save-restriction - (message-narrow-to-headers) - ;; Insert some headers. - (message-generate-headers message-required-news-headers) - ;; Let the user do all of the above. - (run-hooks 'message-header-hook)) - (message-cleanup-headers) - (if (not (message-check-news-syntax)) - (progn - ;;(message "Posting not performed") - nil) - (unwind-protect - (save-excursion - (set-buffer tembuf) - (buffer-disable-undo (current-buffer)) - (erase-buffer) - ;; Avoid copying text props. - (insert (format - "%s" (save-excursion - (set-buffer messbuf) - (buffer-string)))) - ;; Remove some headers. - (save-restriction - (message-narrow-to-headers) + (let* ((tembuf (message-generate-new-buffer-clone-locals " *message temp*")) + (case-fold-search nil) + (method (if (message-functionp message-post-method) + (funcall message-post-method arg) + message-post-method)) + (group-name-charset (gnus-group-name-charset method "")) + (rfc2047-header-encoding-alist + (if group-name-charset + (cons (cons "Newsgroups" group-name-charset) + rfc2047-header-encoding-alist) + rfc2047-header-encoding-alist)) + (messbuf (current-buffer)) + (message-syntax-checks + (if arg + (cons '(existing-newsgroups . disabled) + message-syntax-checks) + message-syntax-checks)) + (message-this-is-news t) + (message-posting-charset (gnus-setup-posting-charset + (save-restriction + (message-narrow-to-headers-or-head) + (message-fetch-field "Newsgroups")))) + result) + (if (not (message-check-news-body-syntax)) + nil + (save-restriction + (message-narrow-to-headers) + ;; Insert some headers. + (message-generate-headers message-required-news-headers) + ;; Let the user do all of the above. + (run-hooks 'message-header-hook)) + (if group-name-charset + (setq message-syntax-checks + (cons '(valid-newsgroups . disabled) + message-syntax-checks))) + (message-cleanup-headers) + (if (not (message-check-news-syntax)) + nil + (unwind-protect + (save-excursion + (set-buffer tembuf) + (buffer-disable-undo) + (erase-buffer) + ;; Avoid copying text props. + (insert (format + "%s" (save-excursion + (set-buffer messbuf) + (buffer-string)))) + (message-encode-message-body) ;; Remove some headers. - (message-remove-header message-ignored-news-headers t)) - (goto-char (point-max)) - ;; require one newline at the end. - (or (= (preceding-char) ?\n) - (insert ?\n)) - (let ((case-fold-search t)) - ;; Remove the delimiter. - (goto-char (point-min)) - (re-search-forward - (concat "^" (regexp-quote mail-header-separator) "\n")) - (replace-match "\n") - (backward-char 1)) - (run-hooks 'message-send-news-hook) - ;;(require (car method)) - ;;(funcall (intern (format "%s-open-server" (car method))) - ;;(cadr method) (cddr method)) - ;;(setq result - ;; (funcall (intern (format "%s-request-post" (car method))) - ;; (cadr method))) - (gnus-open-server method) - (setq result (gnus-request-post method))) - (kill-buffer tembuf)) - (set-buffer messbuf) - (if result - (push 'news message-sent-message-via) - (message "Couldn't send message via news: %s" - (nnheader-get-report (car method))) - nil)))) + (save-restriction + (message-narrow-to-headers) + ;; We (re)generate the Lines header. + (when (memq 'Lines message-required-mail-headers) + (message-generate-headers '(Lines))) + ;; Remove some headers. + (message-remove-header message-ignored-news-headers t) + (let ((mail-parse-charset message-default-charset)) + (mail-encode-encoded-word-buffer))) + (goto-char (point-max)) + ;; require one newline at the end. + (or (= (preceding-char) ?\n) + (insert ?\n)) + (let ((case-fold-search t)) + ;; Remove the delimiter. + (goto-char (point-min)) + (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "\n")) + (replace-match "\n") + (backward-char 1)) + (run-hooks 'message-send-news-hook) + (gnus-open-server method) + (setq result (let ((mail-header-separator "")) + (gnus-request-post method)))) + (kill-buffer tembuf)) + (set-buffer messbuf) + (if result + (push 'news message-sent-message-via) + (message "Couldn't send message via news: %s" + (nnheader-get-report (car method))) + nil))))) ;;; ;;; Header generation & syntax checking. ;;; -(defmacro message-check (type &rest forms) - "Eval FORMS if TYPE is to be checked." - `(or (message-check-element ,type) - (save-excursion - ,@forms))) - -(put 'message-check 'lisp-indent-function 1) -(put 'message-check 'edebug-form-spec '(form body)) - (defun message-check-element (type) "Returns non-nil if this type is not to be checked." (if (eq message-syntax-checks 'dont-check-for-anything-just-trust-me) @@ -2256,17 +2540,23 @@ to find out how to use this." (save-excursion (save-restriction (widen) - (and - ;; We narrow to the headers and check them first. - (save-excursion - (save-restriction - (message-narrow-to-headers) - (message-check-news-header-syntax))) - ;; Check the body. - (message-check-news-body-syntax))))) + ;; We narrow to the headers and check them first. + (save-excursion + (save-restriction + (message-narrow-to-headers) + (message-check-news-header-syntax)))))) (defun message-check-news-header-syntax () (and + ;; Check Newsgroups header. + (message-check 'newsgroups + (let ((group (message-fetch-field "newsgroups"))) + (or + (and group + (not (string-match "\\`[ \t]*\\'" group))) + (ignore + (message + "The newsgroups field is empty or missing. Posting is denied."))))) ;; Check the Subject header. (message-check 'subject (let* ((case-fold-search t) @@ -2429,12 +2719,15 @@ to find out how to use this." (message-check 'from (let* ((case-fold-search t) (from (message-fetch-field "from")) - (ad (nth 1 (mail-extract-address-components from)))) + ad) (cond ((not from) (message "There is no From line. Posting is denied.") nil) - ((or (not (string-match "@[^\\.]*\\." ad)) ;larsi@ifi + ((or (not (string-match + "@[^\\.]*\\." + (setq ad (nth 1 (mail-extract-address-components + from))))) ;larsi@ifi (string-match "\\.\\." ad) ;larsi@ifi..uio (string-match "@\\." ad) ;larsi@.ifi.uio (string-match "\\.$" ad) ;larsi@ifi.uio. @@ -2475,7 +2768,7 @@ to find out how to use this." (y-or-n-p "Empty article. Really post? ")))) ;; Check for control characters. (message-check 'control-chars - (if (re-search-forward "[\000-\007\013\015-\037\200-\237]" nil t) + (if (re-search-forward "[\000-\007\013\015-\032\034-\037\200-\237]" nil t) (y-or-n-p "The article contains control characters. Really post? ") t)) @@ -2496,15 +2789,25 @@ to find out how to use this." ;; Check the length of the signature. (message-check 'signature (goto-char (point-max)) - (if (or (not (re-search-backward message-signature-separator nil t)) - (search-forward message-forward-end-separator nil t)) - t - (if (> (count-lines (point) (point-max)) 5) - (y-or-n-p - (format - "Your .sig is %d lines; it should be max 4. Really post? " - (1- (count-lines (point) (point-max))))) - t))))) + (if (> (count-lines (point) (point-max)) 5) + (y-or-n-p + (format + "Your .sig is %d lines; it should be max 4. Really post? " + (1- (count-lines (point) (point-max))))) + t)) + ;; Ensure that text follows last quoted portion. + (message-check 'quoting-style + (goto-char (point-max)) + (let ((no-problem t)) + (when (search-backward-regexp "^>[^\n]*\n>" nil t) + (setq no-problem nil) + (while (not (eobp)) + (when (and (not (eolp)) (looking-at "[^> \t]")) + (setq no-problem t)) + (forward-line))) + (if no-problem + t + (y-or-n-p "Your text should follow quoted text. Really post? ")))))) (defun message-checksum () "Return a \"checksum\" for the current buffer." @@ -2516,7 +2819,7 @@ to find out how to use this." (while (not (eobp)) (when (not (looking-at "[ \t\n]")) (setq sum (logxor (ash sum 1) (if (natnump sum) 0 1) - (following-char)))) + (char-after)))) (forward-char 1))) sum)) @@ -2527,7 +2830,6 @@ to find out how to use this." list file) (save-excursion (set-buffer (get-buffer-create " *message temp*")) - (buffer-disable-undo (current-buffer)) (erase-buffer) (insert-buffer-substring buf) (save-restriction @@ -2535,9 +2837,19 @@ to find out how to use this." (while (setq file (message-fetch-field "fcc")) (push file list) (message-remove-header "fcc" nil t))) + (message-encode-message-body) + (save-restriction + (message-narrow-to-headers) + (let ((mail-parse-charset message-default-charset) + (rfc2047-header-encoding-alist + (cons '("Newsgroups" . default) + rfc2047-header-encoding-alist))) + (mail-encode-encoded-word-buffer))) (goto-char (point-min)) - (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$")) - (replace-match "" t t) + (when (re-search-forward + (concat "^" (regexp-quote mail-header-separator) "$") + nil t) + (replace-match "" t t )) ;; Process FCC operations. (while list (setq file (pop list)) @@ -2557,7 +2869,6 @@ to find out how to use this." (rmail-output file 1 nil t) (let ((mail-use-rfc822 t)) (rmail-output file 1 t t)))))) - (kill-buffer (current-buffer))))) (defun message-output (filename) @@ -2599,11 +2910,24 @@ to find out how to use this." (when (re-search-forward ",+$" nil t) (replace-match "" t t)))))) -(defun message-make-date () - "Make a valid data header." - (let ((now (current-time))) - (timezone-make-date-arpa-standard - (current-time-string now) (current-time-zone now)))) +(defun message-make-date (&optional now) + "Make a valid data header. +If NOW, use that time instead." + (let* ((now (or now (current-time))) + (zone (nth 8 (decode-time now))) + (sign "+")) + (when (< zone 0) + (setq sign "-") + (setq zone (- zone))) + (concat + (format-time-string "%d" now) + ;; The month name of the %b spec is locale-specific. Pfff. + (format " %s " + (capitalize (car (rassoc (nth 4 (decode-time now)) + parse-time-months)))) + (format-time-string "%Y %H:%M:%S " now) + ;; We do all of this because XEmacs doesn't have the %z spec. + (format "%s%02d%02d" sign (/ zone 3600) (/ (% zone 3600) 60))))) (defun message-make-message-id () "Make a unique Message-ID." @@ -2670,9 +2994,9 @@ to find out how to use this." "Make an Organization header." (let* ((organization (when message-user-organization - (if (message-functionp message-user-organization) - (funcall message-user-organization) - message-user-organization)))) + (if (message-functionp message-user-organization) + (funcall message-user-organization) + message-user-organization)))) (save-excursion (message-set-work-buffer) (cond ((stringp organization) @@ -2728,9 +3052,7 @@ to find out how to use this." ;; Add the future to current. (setcar current (+ (car current) (round (/ future (expt 2 16))))) (setcar (cdr current) (+ (nth 1 current) (% (round future) (expt 2 16)))) - ;; Return the date in the future in UT. - (timezone-make-date-arpa-standard - (current-time-string current) (current-time-zone current) '(0 "UT")))) + (message-make-date current))) (defun message-make-path () "Return uucp path." @@ -2868,9 +3190,7 @@ Headers already prepared in the buffer are not modified." (To nil) (Distribution (message-make-distribution)) (Lines (message-make-lines)) - (X-Newsreader message-newsreader) - (X-Mailer (and (not (message-fetch-field "X-Newsreader")) - message-mailer)) + (User-Agent message-newsreader) (Expires (message-make-expires)) (case-fold-search t) header value elem) @@ -2909,9 +3229,9 @@ Headers already prepared in the buffer are not modified." (progn ;; The header was found. We insert a space after the ;; colon, if there is none. - (if (/= (following-char) ? ) (insert " ") (forward-char 1)) + (if (/= (char-after) ? ) (insert " ") (forward-char 1)) ;; Find out whether the header is empty... - (looking-at "[ \t]*$"))) + (looking-at "[ \t]*\n[^ \t]"))) ;; So we find out what value we should insert. (setq value (cond @@ -2933,7 +3253,7 @@ Headers already prepared in the buffer are not modified." ;; The element is a symbol. We insert the value ;; of this symbol, if any. (symbol-value header)) - (t + ((not (message-check-element header)) ;; We couldn't generate a value for this header, ;; so we just ask the user. (read-from-minibuffer @@ -3018,7 +3338,7 @@ Headers already prepared in the buffer are not modified." (goto-char (point-min)) (while (not (eobp)) (skip-chars-forward "^,\"" (point-max)) - (if (or (= (following-char) ?,) + (if (or (eq (char-after) ?,) (eobp)) (when (not quoted) (if (and (> (current-column) 78) @@ -3038,7 +3358,7 @@ Headers already prepared in the buffer are not modified." (defun message-fill-header (header value) (let ((begin (point)) - (fill-column 990) + (fill-column 78) (fill-prefix "\t")) (insert (capitalize (symbol-name header)) ": " @@ -3057,23 +3377,63 @@ Headers already prepared in the buffer are not modified." (replace-match " " t t)) (goto-char (point-max))))) +(defun message-shorten-1 (list cut surplus) + ;; Cut SURPLUS elements out of LIST, beginning with CUTth one. + (setcdr (nthcdr (- cut 2) list) + (nthcdr (+ (- cut 2) surplus 1) list))) + (defun message-shorten-references (header references) - "Limit REFERENCES to be shorter than 988 characters." - (let ((max 988) - (cut 4) + "Trim REFERENCES to be less than 31 Message-ID long, and fold them. +If folding is disallowed, also check that the REFERENCES are less +than 988 characters long, and if they are not, trim them until they are." + (let ((maxcount 31) + (count 0) + (cut 6) refs) - (nnheader-temp-write nil + (with-temp-buffer (insert references) (goto-char (point-min)) + ;; Cons a list of valid references. (while (re-search-forward "<[^>]+>" nil t) (push (match-string 0) refs)) - (setq refs (nreverse refs)) - (while (> (length (mapconcat 'identity refs " ")) max) - (when (< (length refs) (1+ cut)) - (decf cut)) - (setcdr (nthcdr cut refs) (cddr (nthcdr cut refs))))) - (insert (capitalize (symbol-name header)) ": " - (mapconcat 'identity refs " ") "\n"))) + (setq refs (nreverse refs) + count (length refs))) + + ;; If the list has more than MAXCOUNT elements, trim it by + ;; removing the CUTth element and the required number of + ;; elements that follow. + (when (> count maxcount) + (let ((surplus (- count maxcount))) + (message-shorten-1 refs cut surplus) + (decf count surplus))) + + ;; If folding is disallowed, make sure the total length (including + ;; the spaces between) will be less than MAXSIZE characters. + ;; + ;; Only disallow folding for News messages. At this point the headers + ;; have not been generated, thus we use message-this-is-news directly. + (when (and message-this-is-news message-cater-to-broken-inn) + (let ((maxsize 988) + (totalsize (+ (apply #'+ (mapcar #'length refs)) + (1- count))) + (surplus 0) + (ptr (nthcdr (1- cut) refs))) + ;; Decide how many elements to cut off... + (while (> totalsize maxsize) + (decf totalsize (1+ (length (car ptr)))) + (incf surplus) + (setq ptr (cdr ptr))) + ;; ...and do it. + (when (> surplus 0) + (message-shorten-1 refs cut surplus)))) + + ;; Finally, collect the references back into a string and insert + ;; it into the buffer. + (let ((refstring (mapconcat #'identity refs " "))) + (if (and message-this-is-news message-cater-to-broken-inn) + (insert (capitalize (symbol-name header)) ": " + refstring "\n") + (message-fill-header header refstring))))) (defun message-position-point () "Move point to where the user probably wants to find it." @@ -3083,7 +3443,7 @@ Headers already prepared in the buffer are not modified." (search-backward ":" ) (widen) (forward-char 1) - (if (= (following-char) ? ) + (if (eq (char-after) ? ) (forward-char 1) (insert " "))) (t @@ -3097,14 +3457,24 @@ Headers already prepared in the buffer are not modified." (defun message-buffer-name (type &optional to group) "Return a new (unique) buffer name based on TYPE and TO." (cond + ;; Generate a new buffer name The Message Way. + ((eq message-generate-new-buffers 'unique) + (generate-new-buffer-name + (concat "*" type + (if to + (concat " to " + (or (car (mail-extract-address-components to)) + to) "") + "") + (if (and group (not (string= group ""))) (concat " on " group) "") + "*"))) ;; Check whether `message-generate-new-buffers' is a function, ;; and if so, call it. ((message-functionp message-generate-new-buffers) (funcall message-generate-new-buffers type to group)) - ;; Generate a new buffer name The Message Way. - (message-generate-new-buffers + ((eq message-generate-new-buffers 'unsent) (generate-new-buffer-name - (concat "*" type + (concat "*unsent " type (if to (concat " to " (or (car (mail-extract-address-components to)) @@ -3147,7 +3517,7 @@ Headers already prepared in the buffer are not modified." ;; Rename the buffer. (if message-send-rename-function (funcall message-send-rename-function) - (when (string-match "\\`\\*" (buffer-name)) + (when (string-match "\\`\\*\\(unsent \\)?" (buffer-name)) (rename-buffer (concat "*sent " (substring (buffer-name) (match-end 0))) t))) ;; Push the current buffer onto the list. @@ -3225,7 +3595,8 @@ Headers already prepared in the buffer are not modified." (setq buffer-file-name (expand-file-name "*message*" message-auto-save-directory)) (setq buffer-auto-save-file-name (make-auto-save-file-name))) - (clear-visited-file-modtime))) + (clear-visited-file-modtime) + (setq buffer-file-coding-system message-draft-coding-system))) (defun message-disassociate-draft () "Disassociate the message buffer from the drafts directory." @@ -3233,6 +3604,23 @@ Headers already prepared in the buffer are not modified." (nndraft-request-expire-articles (list message-draft-article) "drafts" nil t))) +(defun message-insert-headers () + "Generate the headers for the article." + (interactive) + (save-excursion + (save-restriction + (message-narrow-to-headers) + (when (message-news-p) + (message-generate-headers + (delq 'Lines + (delq 'Subject + (copy-sequence message-required-news-headers))))) + (when (message-mail-p) + (message-generate-headers + (delq 'Lines + (delq 'Subject + (copy-sequence message-required-mail-headers)))))))) + ;;; @@ -3262,15 +3650,79 @@ OTHER-HEADERS is an alist of header/value pairs." (message-setup `((Newsgroups . ,(or newsgroups "")) (Subject . ,(or subject "")))))) +(defun message-get-reply-headers (wide &optional to-address) + (let (follow-to mct never-mct from to cc reply-to ccalist) + ;; Find all relevant headers we need. + (setq from (message-fetch-field "from") + to (message-fetch-field "to") + cc (message-fetch-field "cc") + mct (message-fetch-field "mail-copies-to") + reply-to (message-fetch-field "reply-to")) + + ;; Handle special values of Mail-Copies-To. + (when mct + (cond ((or (equal (downcase mct) "never") + (equal (downcase mct) "nobody")) + (setq never-mct t) + (setq mct nil)) + ((or (equal (downcase mct) "always") + (equal (downcase mct) "poster")) + (setq mct (or reply-to from))))) + + (if (or (not wide) + to-address) + (progn + (setq follow-to (list (cons 'To (or to-address reply-to from)))) + (when (and wide mct) + (push (cons 'Cc mct) follow-to))) + (let (ccalist) + (save-excursion + (message-set-work-buffer) + (unless never-mct + (insert (or reply-to from ""))) + (insert (if to (concat (if (bolp) "" ", ") to "") "")) + (insert (if mct (concat (if (bolp) "" ", ") mct) "")) + (insert (if cc (concat (if (bolp) "" ", ") cc) "")) + (goto-char (point-min)) + (while (re-search-forward "[ \t]+" nil t) + (replace-match " " t t)) + ;; Remove addresses that match `rmail-dont-reply-to-names'. + (let ((rmail-dont-reply-to-names message-dont-reply-to-names)) + (insert (prog1 (rmail-dont-reply-to (buffer-string)) + (erase-buffer)))) + (goto-char (point-min)) + ;; Perhaps "Mail-Copies-To: never" removed the only address? + (when (eobp) + (insert (or reply-to from ""))) + (setq ccalist + (mapcar + (lambda (addr) + (cons (mail-strip-quoted-names addr) addr)) + (message-tokenize-header (buffer-string)))) + (let ((s ccalist)) + (while s + (setq ccalist (delq (assoc (car (pop s)) s) ccalist))))) + (setq follow-to (list (cons 'To (cdr (pop ccalist))))) + (when ccalist + (let ((ccs (cons 'Cc (mapconcat + (lambda (addr) (cdr addr)) ccalist ", ")))) + (when (string-match "^ +" (cdr ccs)) + (setcdr ccs (substring (cdr ccs) (match-end 0)))) + (push ccs follow-to))))) + follow-to)) + + ;;;###autoload (defun message-reply (&optional to-address wide) "Start editing a reply to the article in the current buffer." (interactive) + (require 'gnus-sum) ; for gnus-list-identifiers (let ((cur (current-buffer)) from subject date reply-to to cc references message-id follow-to (inhibit-point-motion-hooks t) - mct never-mct gnus-warning) + (message-this-is-mail t) + gnus-warning) (save-restriction (message-narrow-to-head) ;; Allow customizations to have their say. @@ -3283,79 +3735,26 @@ OTHER-HEADERS is an alist of header/value pairs." (save-excursion (setq follow-to (funcall message-wide-reply-to-function))))) - ;; Find all relevant headers we need. - (setq from (message-fetch-field "from") - date (message-fetch-field "date") - subject (or (message-fetch-field "subject") "none") - to (message-fetch-field "to") - cc (message-fetch-field "cc") - mct (message-fetch-field "mail-copies-to") - reply-to (message-fetch-field "reply-to") + (setq message-id (message-fetch-field "message-id" t) references (message-fetch-field "references") - message-id (message-fetch-field "message-id" t)) - ;; Remove any (buggy) Re:'s that are present and make a - ;; proper one. - (when (string-match message-subject-re-regexp subject) - (setq subject (substring subject (match-end 0)))) - (setq subject (concat "Re: " subject)) + date (message-fetch-field "date") + from (message-fetch-field "from") + subject (or (message-fetch-field "subject") "none")) + (if gnus-list-identifiers + (setq subject (message-strip-list-identifiers subject))) + (setq subject (concat "Re: " (message-strip-subject-re subject))) - (when (and (setq gnus-warning (message-fetch-field "gnus-warning")) - (string-match "<[^>]+>" gnus-warning)) - (setq message-id (match-string 0 gnus-warning))) + (when (and (setq gnus-warning (message-fetch-field "gnus-warning")) + (string-match "<[^>]+>" gnus-warning)) + (setq message-id (match-string 0 gnus-warning))) - ;; Handle special values of Mail-Copies-To. - (when mct - (cond ((equal (downcase mct) "never") - (setq never-mct t) - (setq mct nil)) - ((equal (downcase mct) "always") - (setq mct (or reply-to from))))) + (unless follow-to + (setq follow-to (message-get-reply-headers wide to-address)))) - (unless follow-to - (if (or (not wide) - to-address) - (progn - (setq follow-to (list (cons 'To (or to-address reply-to from)))) - (when (and wide mct) - (push (cons 'Cc mct) follow-to))) - (let (ccalist) - (save-excursion - (message-set-work-buffer) - (unless never-mct - (insert (or reply-to from ""))) - (insert (if to (concat (if (bolp) "" ", ") to "") "")) - (insert (if mct (concat (if (bolp) "" ", ") mct) "")) - (insert (if cc (concat (if (bolp) "" ", ") cc) "")) - (goto-char (point-min)) - (while (re-search-forward "[ \t]+" nil t) - (replace-match " " t t)) - ;; Remove addresses that match `rmail-dont-reply-to-names'. - (insert (prog1 (rmail-dont-reply-to (buffer-string)) - (erase-buffer))) - (goto-char (point-min)) - ;; Perhaps Mail-Copies-To: never removed the only address? - (when (eobp) - (insert (or reply-to from ""))) - (setq ccalist - (mapcar - (lambda (addr) - (cons (mail-strip-quoted-names addr) addr)) - (message-tokenize-header (buffer-string)))) - (let ((s ccalist)) - (while s - (setq ccalist (delq (assoc (car (pop s)) s) ccalist))))) - (setq follow-to (list (cons 'To (cdr (pop ccalist))))) - (when ccalist - (let ((ccs (cons 'Cc (mapconcat - (lambda (addr) (cdr addr)) ccalist ", ")))) - (when (string-match "^ +" (cdr ccs)) - (setcdr ccs (substring (cdr ccs) (match-end 0)))) - (push ccs follow-to)))))) - (widen)) - - (message-pop-to-buffer (message-buffer-name - (if wide "wide reply" "reply") from - (if wide to-address nil))) + (message-pop-to-buffer + (message-buffer-name + (if wide "wide reply" "reply") from + (if wide to-address nil))) (setq message-reply-headers (vector 0 subject from date message-id references 0 0 "")) @@ -3380,6 +3779,7 @@ OTHER-HEADERS is an alist of header/value pairs." "Follow up to the message in the current buffer. If TO-NEWSGROUPS, use that as the new Newsgroups line." (interactive) + (require 'gnus-sum) ; for gnus-list-identifiers (let ((cur (current-buffer)) from subject date reply-to mct references message-id follow-to @@ -3414,11 +3814,9 @@ If TO-NEWSGROUPS, use that as the new Newsgroups line." (let ((case-fold-search t)) (string-match "world" distribution))) (setq distribution nil)) - ;; Remove any (buggy) Re:'s that are present and make a - ;; proper one. - (when (string-match message-subject-re-regexp subject) - (setq subject (substring subject (match-end 0)))) - (setq subject (concat "Re: " subject)) + (if gnus-list-identifiers + (setq subject (message-strip-list-identifiers subject))) + (setq subject (concat "Re: " (message-strip-subject-re subject))) (widen)) (message-pop-to-buffer (message-buffer-name "followup" from newsgroups)) @@ -3475,8 +3873,10 @@ responses here are directed to other newsgroups.")) `((References . ,(concat (or references "") (and references " ") (or message-id ""))))) ,@(when (and mct - (not (equal (downcase mct) "never"))) - (list (cons 'Cc (if (equal (downcase mct) "always") + (not (or (equal (downcase mct) "never") + (equal (downcase mct) "nobody")))) + (list (cons 'Cc (if (or (equal (downcase mct) "always") + (equal (downcase mct) "poster")) (or reply-to from "") mct))))) @@ -3487,15 +3887,16 @@ responses here are directed to other newsgroups.")) ;;;###autoload -(defun message-cancel-news () - "Cancel an article you posted." - (interactive) +(defun message-cancel-news (&optional arg) + "Cancel an article you posted. +If ARG, allow editing of the cancellation message." + (interactive "P") (unless (message-news-p) (error "This is not a news article; canceling is impossible")) (when (yes-or-no-p "Do you really want to cancel this article? ") (let (from newsgroups message-id distribution buf sender) (save-excursion - ;; Get header info. from original article. + ;; Get header info from original article. (save-restriction (message-narrow-to-head) (setq from (message-fetch-field "from") @@ -3514,11 +3915,12 @@ responses here are directed to other newsgroups.")) (message-make-from)))))) (error "This article is not yours")) ;; Make control message. - (setq buf (set-buffer (get-buffer-create " *message cancel*"))) - (buffer-disable-undo (current-buffer)) + (if arg + (message-news) + (setq buf (set-buffer (get-buffer-create " *message cancel*")))) (erase-buffer) (insert "Newsgroups: " newsgroups "\n" - "From: " (message-make-from) "\n" + "From: " from "\n" "Subject: cmsg cancel " message-id "\n" "Control: cancel " message-id "\n" (if distribution @@ -3526,12 +3928,14 @@ responses here are directed to other newsgroups.")) "") mail-header-separator "\n" message-cancel-message) - (message "Canceling your article...") - (if (let ((message-syntax-checks - 'dont-check-for-anything-just-trust-me)) - (funcall message-send-news-function)) - (message "Canceling your article...done")) - (kill-buffer buf))))) + (run-hooks 'message-cancel-hook) + (unless arg + (message "Canceling your article...") + (if (let ((message-syntax-checks + 'dont-check-for-anything-just-trust-me)) + (funcall message-send-news-function)) + (message "Canceling your article...done")) + (kill-buffer buf)))))) ;;;###autoload (defun message-supersede () @@ -3555,6 +3959,7 @@ header line with the old Message-ID." ;; Get a normal message buffer. (message-pop-to-buffer (message-buffer-name "supersede")) (insert-buffer-substring cur) + (mime-to-mml) (message-narrow-to-head) ;; Remove unwanted headers. (when message-ignored-supersedes-headers @@ -3576,6 +3981,8 @@ header line with the old Message-ID." (cond ((save-window-excursion (if (not (eq system-type 'vax-vms)) (with-output-to-temp-buffer "*Directory*" + (with-current-buffer standard-output + (fundamental-mode)) ; for Emacs 20.4+ (buffer-disable-undo standard-output) (let ((default-directory "/")) (call-process @@ -3590,7 +3997,7 @@ header line with the old Message-ID." (defun message-wash-subject (subject) "Remove junk like \"Re:\", \"(fwd)\", etc. that was added to the subject by previous forwarders, replyers, etc." - (nnheader-temp-write nil + (with-temp-buffer (insert-string subject) (goto-char (point-min)) ;; strip Re/Fwd stuff off the beginning @@ -3661,52 +4068,77 @@ the message." subject)))) ;;;###autoload -(defun message-forward (&optional news) +(defun message-forward (&optional news digest) "Forward the current message via mail. -Optional NEWS will use news to forward instead of mail." +Optional NEWS will use news to forward instead of mail. +Optional DIGEST will use digest to forward." (interactive "P") - (let ((cur (current-buffer)) - (subject (message-make-forward-subject)) - art-beg) - (if news (message-news nil subject) (message-mail nil subject)) + (let* ((cur (current-buffer)) + (subject (if message-forward-show-mml + (message-make-forward-subject) + (mail-decode-encoded-word-string + (message-make-forward-subject)))) + art-beg) + (if news + (message-news nil subject) + (message-mail nil subject)) ;; Put point where we want it before inserting the forwarded ;; message. - (if message-signature-before-forwarded-message - (goto-char (point-max)) - (message-goto-body)) - ;; Make sure we're at the start of the line. - (unless (eolp) - (insert "\n")) - ;; Narrow to the area we are to insert. - (narrow-to-region (point) (point)) - ;; Insert the separators and the forwarded buffer. - (insert message-forward-start-separator) - (setq art-beg (point)) - (insert-buffer-substring cur) - (goto-char (point-max)) - (insert message-forward-end-separator) - (set-text-properties (point-min) (point-max) nil) - ;; Remove all unwanted headers. - (goto-char art-beg) - (narrow-to-region (point) (if (search-forward "\n\n" nil t) - (1- (point)) - (point))) - (goto-char (point-min)) - (message-remove-header message-included-forward-headers t nil t) - (widen) + (if message-forward-before-signature + (message-goto-body) + (goto-char (point-max))) + (if message-forward-as-mime + (if digest + (insert "\n<#multipart type=digest>\n") + (if message-forward-show-mml + (insert "\n\n<#mml type=message/rfc822 disposition=inline>\n") + (insert "\n\n<#part type=message/rfc822 disposition=inline raw=t>\n"))) + (insert "\n-------------------- Start of forwarded message --------------------\n")) + (let ((b (point)) e) + (if digest + (if message-forward-as-mime + (insert-buffer-substring cur) + (mml-insert-buffer cur)) + (if message-forward-show-mml + (insert-buffer-substring cur) + (mm-with-unibyte-current-buffer + (mml-insert-buffer cur)))) + (setq e (point)) + (if message-forward-as-mime + (if digest + (insert "<#/multipart>\n") + (if message-forward-show-mml + (insert "<#/mml>\n") + (insert "<#/part>\n"))) + (insert "\n-------------------- End of forwarded message --------------------\n")) + (if (and digest message-forward-as-mime) + (save-restriction + (narrow-to-region b e) + (goto-char b) + (narrow-to-region (point) + (or (search-forward "\n\n" nil t) (point))) + (delete-region (point-min) (point-max))) + (when (and (not current-prefix-arg) + message-forward-ignored-headers) + (save-restriction + (narrow-to-region b e) + (goto-char b) + (narrow-to-region (point) + (or (search-forward "\n\n" nil t) (point))) + (message-remove-header message-forward-ignored-headers t))))) (message-position-point))) ;;;###autoload (defun message-resend (address) "Resend the current article to ADDRESS." - (interactive "sResend message to: ") + (interactive + (list (message-read-from-minibuffer "Resend message to: "))) (message "Resending message to %s..." address) (save-excursion (let ((cur (current-buffer)) beg) ;; We first set up a normal mail buffer. (set-buffer (get-buffer-create " *message resend*")) - (buffer-disable-undo (current-buffer)) (erase-buffer) (message-setup `((To . ,address))) ;; Insert our usual headers. @@ -3739,44 +4171,42 @@ Optional NEWS will use news to forward instead of mail." (when (looking-at "From ") (replace-match "X-From-Line: ")) ;; Send it. - (message-send-mail) + (let ((message-inhibit-body-encoding t) + message-required-mail-headers) + (message-send-mail)) (kill-buffer (current-buffer))) (message "Resending message to %s...done" address))) ;;;###autoload (defun message-bounce () "Re-mail the current message. -This only makes sense if the current message is a bounce message than +This only makes sense if the current message is a bounce message that contains some mail you have written which has been bounced back to you." (interactive) - (let ((cur (current-buffer)) + (let ((handles (mm-dissect-buffer t)) boundary) (message-pop-to-buffer (message-buffer-name "bounce")) - (insert-buffer-substring cur) - (undo-boundary) - (message-narrow-to-head) - (if (and (message-fetch-field "Mime-Version") - (setq boundary (message-fetch-field "Content-Type"))) - (if (string-match "boundary=\"\\([^\"]+\\)\"" boundary) - (setq boundary (concat (match-string 1 boundary) " *\n" - "Content-Type: message/rfc822")) - (setq boundary nil))) - (widen) - (goto-char (point-min)) - (search-forward "\n\n" nil t) - (or (and boundary - (re-search-forward boundary nil t) - (forward-line 2)) - (and (re-search-forward message-unsent-separator nil t) - (forward-line 1)) - (re-search-forward "^Return-Path:.*\n" nil t)) - ;; We remove everything before the bounced mail. - (delete-region - (point-min) - (if (re-search-forward "^[^ \n\t]+:" nil t) - (match-beginning 0) - (point))) + (if (stringp (car handles)) + ;; This is a MIME bounce. + (mm-insert-part (car (last handles))) + ;; This is a non-MIME bounce, so we try to remove things + ;; manually. + (mm-insert-part handles) + (undo-boundary) + (goto-char (point-min)) + (search-forward "\n\n" nil t) + (or (and (re-search-forward message-unsent-separator nil t) + (forward-line 1)) + (re-search-forward "^Return-Path:.*\n" nil t)) + ;; We remove everything before the bounced mail. + (delete-region + (point-min) + (if (re-search-forward "^[^ \n\t]+:" nil t) + (match-beginning 0) + (point)))) + (mm-enable-multibyte) + (mime-to-mml) (save-restriction (message-narrow-to-head) (message-remove-header message-ignored-bounced-headers t) @@ -3859,7 +4289,7 @@ which specify the range to operate on." (goto-char (min start end)) (while (< (point) end1) (or (looking-at "[_\^@- ]") - (insert (following-char) "\b")) + (insert (char-after) "\b")) (forward-char 1))))) ;;;###autoload @@ -3873,7 +4303,7 @@ which specify the range to operate on." (move-marker end1 (max start end)) (goto-char (min start end)) (while (re-search-forward "\b" end1 t) - (if (eq (following-char) (char-after (- (point) 2))) + (if (eq (char-after) (char-after (- (point) 2))) (delete-char -2)))))) (defalias 'message-exchange-point-and-mark 'exchange-point-and-mark) @@ -3932,7 +4362,7 @@ Do a `tab-to-tab-stop' if not in those headers." (message "No matching groups") (save-selected-window (pop-to-buffer "*Completions*") - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (let ((buffer-read-only nil)) (erase-buffer) (let ((standard-output (current-buffer))) @@ -3952,6 +4382,7 @@ The following arguments may contain lists of values." (save-excursion (with-output-to-temp-buffer " *MESSAGE information message*" (set-buffer " *MESSAGE information message*") + (fundamental-mode) ; for Emacs 20.4+ (mapcar 'princ text) (goto-char (point-min)))) (funcall ask question)) @@ -3975,20 +4406,22 @@ regexp varstr." (let ((oldbuf (current-buffer))) (save-excursion (set-buffer (generate-new-buffer name)) - (message-clone-locals oldbuf) + (message-clone-locals oldbuf varstr) (current-buffer)))) -(defun message-clone-locals (buffer) +(defun message-clone-locals (buffer &optional varstr) "Clone the local variables from BUFFER to the current buffer." (let ((locals (save-excursion (set-buffer buffer) (buffer-local-variables))) - (regexp "^gnus\\|^nn\\|^message")) + (regexp "^gnus\\|^nn\\|^message\\|^user-mail-address")) (mapcar (lambda (local) (when (and (consp local) (car local) - (string-match regexp (symbol-name (car local)))) + (string-match regexp (symbol-name (car local))) + (or (null varstr) + (string-match varstr (symbol-name (car local))))) (ignore-errors (set (make-local-variable (car local)) (cdr local))))) @@ -3997,20 +4430,85 @@ regexp varstr." ;;; Miscellaneous functions ;; stolen (and renamed) from nnheader.el -(defun message-replace-chars-in-string (string from to) - "Replace characters in STRING from FROM to TO." - (let ((string (substring string 0)) ;Copy string. - (len (length string)) - (idx 0)) - ;; Replace all occurrences of FROM with TO. - (while (< idx len) - (when (= (aref string idx) from) - (aset string idx to)) - (setq idx (1+ idx))) - string)) +(if (fboundp 'subst-char-in-string) + (defsubst message-replace-chars-in-string (string from to) + (subst-char-in-string from to string)) + (defun message-replace-chars-in-string (string from to) + "Replace characters in STRING from FROM to TO." + (let ((string (substring string 0)) ;Copy string. + (len (length string)) + (idx 0)) + ;; Replace all occurrences of FROM with TO. + (while (< idx len) + (when (= (aref string idx) from) + (aset string idx to)) + (setq idx (1+ idx))) + string))) -(run-hooks 'message-load-hook) +;;; +;;; MIME functions +;;; + +(defvar message-inhibit-body-encoding nil) + +(defun message-encode-message-body () + (unless message-inhibit-body-encoding + (let ((mail-parse-charset (or mail-parse-charset + message-default-charset)) + (case-fold-search t) + lines content-type-p) + (message-goto-body) + (save-restriction + (narrow-to-region (point) (point-max)) + (let ((new (mml-generate-mime))) + (when new + (delete-region (point-min) (point-max)) + (insert new) + (goto-char (point-min)) + (if (eq (aref new 0) ?\n) + (delete-char 1) + (search-forward "\n\n") + (setq lines (buffer-substring (point-min) (1- (point)))) + (delete-region (point-min) (point)))))) + (save-restriction + (message-narrow-to-headers-or-head) + (message-remove-header "Mime-Version") + (goto-char (point-max)) + (insert "MIME-Version: 1.0\n") + (when lines + (insert lines)) + (setq content-type-p + (re-search-backward "^Content-Type:" nil t))) + (save-restriction + (message-narrow-to-headers-or-head) + (message-remove-first-header "Content-Type") + (message-remove-first-header "Content-Transfer-Encoding")) + ;; We always make sure that the message has a Content-Type header. + ;; This is because some broken MTAs and MUAs get awfully confused + ;; when confronted with a message with a MIME-Version header and + ;; without a Content-Type header. For instance, Solaris' + ;; /usr/bin/mail. + (unless content-type-p + (goto-char (point-min)) + (re-search-forward "^MIME-Version:") + (forward-line 1) + (insert "Content-Type: text/plain; charset=us-ascii\n"))))) + +(defun message-read-from-minibuffer (prompt) + "Read from the minibuffer while providing abbrev expansion." + (if (fboundp 'mail-abbrevs-setup) + (let ((mail-abbrev-mode-regexp "") + (minibuffer-setup-hook 'mail-abbrevs-setup)) + (read-from-minibuffer prompt)) + (let ((minibuffer-setup-hook 'mail-abbrev-minibuffer-setup-hook)) + (read-string prompt)))) (provide 'message) +(run-hooks 'message-load-hook) + +;; Local Variables: +;; coding: iso-8859-1 +;; End: + ;;; message.el ends here diff --git a/lisp/gnus/messcompat.el b/lisp/gnus/messcompat.el index 870992476e7..e3021ce4b0a 100644 --- a/lisp/gnus/messcompat.el +++ b/lisp/gnus/messcompat.el @@ -1,5 +1,7 @@ ;;; messcompat.el --- making message mode compatible with mail mode -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: mail, news @@ -70,6 +72,7 @@ If t, the `message-signature-file' file will be inserted instead. If a function, the result from the function will be used instead. If a form, the result from the form will be used instead.") +;; Deleted the autoload cookie because this crashes in loaddefs.el. (defvar message-signature-file mail-signature-file "*File containing the text inserted at end of the message buffer.") diff --git a/lisp/gnus/nnagent.el b/lisp/gnus/nnagent.el index 8e89182aca5..b445395e287 100644 --- a/lisp/gnus/nnagent.el +++ b/lisp/gnus/nnagent.el @@ -1,5 +1,5 @@ ;;; nnagent.el --- offline backend for Gnus -;; Copyright (C) 1997,98 Free Software Foundation, Inc. +;; Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news, mail @@ -58,13 +58,18 @@ (nnoo-define-basics nnagent) +(defun nnagent-server (server) + (and server (format "%s+%s" (car gnus-command-method) server))) + (deffoo nnagent-open-server (server &optional defs) (setq defs `((nnagent-directory ,(gnus-agent-directory)) (nnagent-active-file ,(gnus-agent-lib-file "active")) (nnagent-newsgroups-file ,(gnus-agent-lib-file "newsgroups")) (nnagent-get-new-mail nil))) - (nnoo-change-server 'nnagent server defs) + (nnoo-change-server 'nnagent + (nnagent-server server) + defs) (let ((dir (gnus-agent-directory)) err) (cond @@ -111,7 +116,81 @@ (deffoo nnagent-request-post (&optional server) (gnus-agent-insert-meta-information 'news gnus-command-method) - (gnus-request-accept-article "nndraft:queue")) + (gnus-request-accept-article "nndraft:queue" nil t t)) + +(deffoo nnagent-request-set-mark (group action server) + (with-temp-buffer + (insert (format "(%s-request-set-mark \"%s\" '%s \"%s\")\n" + (nth 0 gnus-command-method) group action + (or server (nth 1 gnus-command-method)))) + (append-to-file (point-min) (point-max) (gnus-agent-lib-file "flags"))) + nil) + +(deffoo nnagent-request-group (group &optional server dont-check) + (nnoo-parent-function 'nnagent 'nnml-request-group + (list group (nnagent-server server) dont-check))) + +(deffoo nnagent-close-group (group &optional server) + (nnoo-parent-function 'nnagent 'nnml-close-group + (list group (nnagent-server server)))) + +(deffoo nnagent-request-accept-article (group &optional server last) + (nnoo-parent-function 'nnagent 'nnml-request-accept-article + (list group (nnagent-server server) last))) + +(deffoo nnagent-request-article (id &optional group server buffer) + (nnoo-parent-function 'nnagent 'nnml-request-article + (list id group (nnagent-server server) buffer))) + +(deffoo nnagent-request-create-group (group &optional server args) + (nnoo-parent-function 'nnagent 'nnml-request-create-group + (list group (nnagent-server server) args))) + +(deffoo nnagent-request-delete-group (group &optional force server) + (nnoo-parent-function 'nnagent 'nnml-request-delete-group + (list group force (nnagent-server server)))) + +(deffoo nnagent-request-expire-articles (articles group &optional server force) + (nnoo-parent-function 'nnagent 'nnml-request-expire-articles + (list articles group (nnagent-server server) force))) + +(deffoo nnagent-request-list (&optional server) + (nnoo-parent-function 'nnagent 'nnml-request-list + (list (nnagent-server server)))) + +(deffoo nnagent-request-list-newsgroups (&optional server) + (nnoo-parent-function 'nnagent 'nnml-request-list-newsgroups + (list (nnagent-server server)))) + +(deffoo nnagent-request-move-article + (article group server accept-form &optional last) + (nnoo-parent-function 'nnagent 'nnml-request-move-article + (list article group (nnagent-server server) + accept-form last))) + +(deffoo nnagent-request-rename-group (group new-name &optional server) + (nnoo-parent-function 'nnagent 'nnml-request-rename-group + (list group new-name (nnagent-server server)))) + +(deffoo nnagent-request-scan (&optional group server) + (nnoo-parent-function 'nnagent 'nnml-request-scan + (list group (nnagent-server server)))) + +(deffoo nnagent-retrieve-headers (sequence &optional group server fetch-old) + (nnoo-parent-function 'nnagent 'nnml-retrieve-headers + (list sequence group (nnagent-server server) fetch-old))) + +(deffoo nnagent-set-status (article name value &optional group server) + (nnoo-parent-function 'nnagent 'nnml-set-status + (list article name value group (nnagent-server server)))) + +(deffoo nnagent-server-opened (&optional server) + (nnoo-parent-function 'nnagent 'nnml-server-opened + (list (nnagent-server server)))) + +(deffoo nnagent-status-message (&optional server) + (nnoo-parent-function 'nnagent 'nnml-status-message + (list (nnagent-server server)))) ;; Use nnml functions for just about everything. (nnoo-import nnagent diff --git a/lisp/gnus/nnbabyl.el b/lisp/gnus/nnbabyl.el index def1e0c9403..fa5e25aafa8 100644 --- a/lisp/gnus/nnbabyl.el +++ b/lisp/gnus/nnbabyl.el @@ -1,5 +1,7 @@ ;;; nnbabyl.el --- rmail mbox access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1099, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Masanobu UMEDA @@ -32,7 +34,8 @@ (require 'nnheader) (condition-case nil (require 'rmail) - (t (nnheader-message 5 "Ignore rmail errors from this file, you don't have rmail"))) + (t (nnheader-message + 5 "Ignore rmail errors from this file, you don't have rmail"))) (require 'nnmail) (require 'nnoo) (eval-when-compile (require 'cl)) @@ -259,7 +262,7 @@ (nnheader-report 'nnbabyl "nnbabyl: LIST NEWSGROUPS is not implemented.")) (deffoo nnbabyl-request-expire-articles - (articles newsgroup &optional server force) + (articles newsgroup &optional server force) (nnbabyl-possibly-change-newsgroup newsgroup server) (let* ((is-old t) rest) @@ -295,7 +298,7 @@ (nconc rest articles)))) (deffoo nnbabyl-request-move-article - (article group server accept-form &optional last) + (article group server accept-form &optional last) (let ((buf (get-buffer-create " *nnbabyl move*")) result) (and @@ -431,9 +434,9 @@ (widen) (narrow-to-region (save-excursion - (unless (re-search-backward (concat "^" nnbabyl-mail-delimiter) nil t) - (goto-char (point-min)) - (end-of-line)) + (unless (re-search-backward (concat "^" nnbabyl-mail-delimiter) nil t) + (goto-char (point-min)) + (end-of-line)) (if leave-delim (progn (forward-line 1) (point)) (match-beginning 0))) (progn @@ -557,10 +560,10 @@ (nnbabyl-create-mbox) (unless (and nnbabyl-mbox-buffer - (buffer-name nnbabyl-mbox-buffer) - (save-excursion - (set-buffer nnbabyl-mbox-buffer) - (= (buffer-size) (nnheader-file-size nnbabyl-mbox-file)))) + (buffer-name nnbabyl-mbox-buffer) + (save-excursion + (set-buffer nnbabyl-mbox-buffer) + (= (buffer-size) (nnheader-file-size nnbabyl-mbox-file)))) ;; This buffer has changed since we read it last. Possibly. (save-excursion (let ((delim (concat "^" nnbabyl-mail-delimiter)) @@ -568,13 +571,13 @@ start end number) (set-buffer (setq nnbabyl-mbox-buffer (nnheader-find-file-noselect - nnbabyl-mbox-file nil 'raw))) + nnbabyl-mbox-file nil t))) ;; Save previous buffer mode. (setq nnbabyl-previous-buffer-mode (cons (cons (point-min) (point-max)) major-mode)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (widen) (setq buffer-read-only nil) (fundamental-mode) diff --git a/lisp/gnus/nndir.el b/lisp/gnus/nndir.el index a3b5eaef20d..f1a6635c69e 100644 --- a/lisp/gnus/nndir.el +++ b/lisp/gnus/nndir.el @@ -1,5 +1,7 @@ ;;; nndir.el --- single directory newsgroup access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news diff --git a/lisp/gnus/nndoc.el b/lisp/gnus/nndoc.el index 0da245a7cab..09d38aad7ed 100644 --- a/lisp/gnus/nndoc.el +++ b/lisp/gnus/nndoc.el @@ -1,5 +1,6 @@ ;;; nndoc.el --- single file access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Masanobu UMEDA @@ -31,6 +32,7 @@ (require 'nnmail) (require 'nnoo) (require 'gnus-util) +(require 'mm-util) (eval-when-compile (require 'cl)) (nnoo-declare nndoc) @@ -38,8 +40,8 @@ (defvoo nndoc-article-type 'guess "*Type of the file. One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward', -`rfc934', `rfc822-forward', `mime-digest', `mime-parts', `standard-digest', -`slack-digest', `clari-briefs' or `guess'.") +`rfc934', `rfc822-forward', `mime-parts', `standard-digest', +`slack-digest', `clari-briefs', `nsmail' or `guess'.") (defvoo nndoc-post-type 'mail "*Whether the nndoc group is `mail' or `post'.") @@ -47,12 +49,14 @@ One of `mbox', `babyl', `digest', `news', `rnews', `mmdf', `forward', (defvoo nndoc-open-document-hook 'nnheader-ms-strip-cr "Hook run after opening a document. The default function removes all trailing carriage returns -from the document.") +from the document.") (defvar nndoc-type-alist `((mmdf (article-begin . "^\^A\^A\^A\^A\n") (body-end . "^\^A\^A\^A\^A\n")) + (nsmail + (article-begin . "^From - ")) (news (article-begin . "^Path:")) (rnews @@ -67,8 +71,8 @@ from the document.") (body-begin-function . nndoc-babyl-body-begin) (head-begin-function . nndoc-babyl-head-begin)) (forward - (article-begin . "^-+ Start of forwarded message -+\n+") - (body-end . "^-+ End of forwarded message -+$") + (article-begin . "^-+ \\(Start of \\)?forwarded message.*\n+") + (body-end . "^-+ End \\(of \\)?forwarded message.*$") (prepare-body-function . nndoc-unquote-dashes)) (rfc934 (article-begin . "^--.*\n+") @@ -83,6 +87,7 @@ from the document.") (article-transform-function . nndoc-transform-clari-briefs)) (mime-digest (article-begin . "") + (head-begin . "^ ?\n") (head-end . "^ ?$") (body-end . "") (file-end . "") @@ -120,6 +125,9 @@ from the document.") (rfc822-forward (article-begin . "^\n") (body-end-function . nndoc-rfc822-forward-body-end-function)) + (outlook + (article-begin-function . nndoc-outlook-article-begin) + (body-end . "\0")) (guess (guess . t) (subtype nil)) @@ -143,10 +151,13 @@ from the document.") (defvoo nndoc-head-begin-function nil) (defvoo nndoc-body-end nil) ;; nndoc-dissection-alist is a list of sublists. Each sublist holds the -;; following items. ARTICLE is an ordinal starting at 1. HEAD-BEGIN, -;; HEAD-END, BODY-BEGIN and BODY-END are positions in the `nndoc' buffer. -;; LINE-COUNT is a count of lines in the body. SUBJECT, MESSAGE-ID and -;; REFERENCES, only present for MIME dissections, are field values. +;; following items. ARTICLE acts as the association key and is an ordinal +;; starting at 1. HEAD-BEGIN [0], HEAD-END [1], BODY-BEGIN [2] and BODY-END +;; [3] are positions in the `nndoc' buffer. LINE-COUNT [4] is a count of +;; lines in the body. For MIME dissections only, ARTICLE-INSERT [5] and +;; SUMMARY-INSERT [6] give headers to insert for full article or summary line +;; generation, respectively. Other headers usually follow directly from the +;; buffer. Value `nil' means no insert. (defvoo nndoc-dissection-alist nil) (defvoo nndoc-prepare-body-function nil) (defvoo nndoc-generate-head-function nil) @@ -158,8 +169,6 @@ from the document.") (defvoo nndoc-current-buffer nil "Current nndoc news buffer.") (defvoo nndoc-address nil) -(defvoo nndoc-mime-header nil) -(defvoo nndoc-mime-subject nil) (defconst nndoc-version "nndoc 1.0" "nndoc version.") @@ -187,7 +196,7 @@ from the document.") (insert-buffer-substring nndoc-current-buffer (car entry) (nth 1 entry))) (goto-char (point-max)) - (unless (= (char-after (1- (point))) ?\n) + (unless (eq (char-after (1- (point))) ?\n) (insert "\n")) (insert (format "Lines: %d\n" (nth 4 entry))) (insert ".\n"))) @@ -289,7 +298,6 @@ from the document.") (setq nndoc-dissection-alist nil) (save-excursion (set-buffer nndoc-current-buffer) - (buffer-disable-undo (current-buffer)) (erase-buffer) (if (stringp nndoc-address) (nnheader-insert-file-contents nndoc-address) @@ -343,6 +351,9 @@ from the document.") (setq entry (pop alist))) (when (memq subtype (or (cdr (assq 'subtype entry)) '(guess))) (goto-char (point-min)) + ;; Remove blank lines. + (while (eq (following-char) ?\n) + (delete-char 1)) (when (numberp (setq result (funcall (intern (format "nndoc-%s-type-p" (car entry)))))) @@ -425,7 +436,8 @@ from the document.") t)) (defun nndoc-forward-type-p () - (when (and (re-search-forward "^-+ Start of forwarded message -+\n+" nil t) + (when (and (re-search-forward "^-+ \\(Start of \\)?forwarded message.*\n+" + nil t) (not (re-search-forward "^Subject:.*digest" nil t)) (not (re-search-backward "^From:" nil t 2)) (not (re-search-forward "^From:" nil t 2))) @@ -452,38 +464,30 @@ from the document.") (limit (search-forward "\n\n" nil t))) (goto-char (point-min)) (when (and limit - (re-search-forward - (concat "\ -^Content-Type:[ \t]*multipart/[a-z]+;\\(.*;\\)*" - "[ \t\n]*[ \t]boundary=\"?[^\"\n]*[^\" \t\n]") - limit t)) + (re-search-forward + (concat "\ +^Content-Type:[ \t]*multipart/[a-z]+ *; *\\(\\(\n[ \t]\\)?.*;\\)*" + "\\(\n[ \t]\\)?[ \t]*boundary=\"?[^\"\n]*[^\" \t\n]") + limit t)) t))) (defun nndoc-transform-mime-parts (article) - (unless (= article 1) - ;; Ensure some MIME-Version. - (goto-char (point-min)) - (search-forward "\n\n") - (let ((case-fold-search nil) - (limit (point))) + (let* ((entry (cdr (assq article nndoc-dissection-alist))) + (headers (nth 5 entry))) + (when headers (goto-char (point-min)) - (or (save-excursion (re-search-forward "^MIME-Version:" limit t)) - (insert "Mime-Version: 1.0\n"))) - ;; Generate default header before entity fields. - (goto-char (point-min)) - (nndoc-generate-mime-parts-head article t))) + (insert headers)))) -(defun nndoc-generate-mime-parts-head (article &optional body-present) - (let ((entry (cdr (assq (if body-present 1 article) nndoc-dissection-alist)))) - (let ((subject (if body-present - nndoc-mime-subject - (concat "<" (nth 5 entry) ">"))) - (message-id (nth 6 entry)) - (references (nth 7 entry))) - (insert nndoc-mime-header) - (and subject (insert "Subject: " subject "\n")) - (and message-id (insert "Message-ID: " message-id "\n")) - (and references (insert "References: " references "\n"))))) +(defun nndoc-generate-mime-parts-head (article) + (let* ((entry (cdr (assq article nndoc-dissection-alist))) + (headers (nth 6 entry))) + (save-restriction + (narrow-to-region (point) (point)) + (insert-buffer-substring + nndoc-current-buffer (car entry) (nth 1 entry)) + (goto-char (point-max))) + (when headers + (insert headers)))) (defun nndoc-clari-briefs-type-p () (when (let ((case-fold-search nil)) @@ -516,6 +520,7 @@ from the document.") (insert "From: " "clari@clari.net (" (or from "unknown") ")" "\nSubject: " (or subject "(no subject)") "\n"))) + (defun nndoc-mime-digest-type-p () (let ((case-fold-search t) boundary-id b-delimiter entry) @@ -526,10 +531,11 @@ from the document.") nil t) (match-beginning 1)) (setq boundary-id (match-string 1) - b-delimiter (concat "\n--" boundary-id "[\n \t]+")) + b-delimiter (concat "\n--" boundary-id "[ \t]*$")) (setq entry (assq 'mime-digest nndoc-type-alist)) (setcdr entry (list + (cons 'head-begin "^ ?\n") (cons 'head-end "^ ?$") (cons 'body-begin "^ ?\n") (cons 'article-begin b-delimiter) @@ -558,10 +564,7 @@ from the document.") (defun nndoc-transform-lanl-gov-announce (article) (goto-char (point-max)) (when (re-search-backward "^\\\\\\\\ +(\\([^ ]*\\) , *\\([^ ]*\\))" nil t) - (replace-match "\n\nGet it at \\1 (\\2)" t nil)) - ;; (when (re-search-backward "^\\\\\\\\$" nil t) - ;; (replace-match "" t t)) - ) + (replace-match "\n\nGet it at \\1 (\\2)" t nil))) (defun nndoc-generate-lanl-gov-head (article) (let ((entry (cdr (assq article nndoc-dissection-alist))) @@ -579,18 +582,28 @@ from the document.") (when (re-search-forward "^Title: \\([^\f]*\\)\nAuthors?: \\(.*\\)" nil t) (setq subject (concat (match-string 1) subject)) - (setq from (concat (match-string 2) " <" e-mail ">")))) - )) + (setq from (concat (match-string 2) " <" e-mail ">")))))) (while (and from (string-match "(\[^)\]*)" from)) (setq from (replace-match "" t t from))) (insert "From: " (or from "unknown") "\nSubject: " (or subject "(no subject)") "\n"))) +(defun nndoc-nsmail-type-p () + (when (looking-at "From - ") + t)) + +(defun nndoc-outlook-article-begin () + (prog1 (re-search-forward "From:\\|Received:" nil t) + (goto-char (match-beginning 0)))) + +(defun nndoc-outlook-type-p () + ;; FIXME: Is JMF the magic of outlook mailbox? -- ShengHuo. + (looking-at "JMF")) + (deffoo nndoc-request-accept-article (group &optional server last) nil) - ;;; ;;; Functions for dissecting the documents ;;; @@ -609,6 +622,9 @@ from the document.") (save-excursion (set-buffer nndoc-current-buffer) (goto-char (point-min)) + ;; Remove blank lines. + (while (eq (following-char) ?\n) + (delete-char 1)) ;; Find the beginning of the file. (when nndoc-file-begin (nndoc-search nndoc-file-begin)) @@ -669,92 +685,128 @@ the header of this entity, and one article per sub-entity." nndoc-mime-split-ordinal 0) (save-excursion (set-buffer nndoc-current-buffer) - (message-narrow-to-head) - (let ((case-fold-search t) - (message-id (message-fetch-field "Message-ID")) - (references (message-fetch-field "References"))) - (setq nndoc-mime-header (buffer-substring (point-min) (point-max)) - nndoc-mime-subject (message-fetch-field "Subject")) - (while (string-match "\ -^\\(Subject\\|Message-ID\\|References\\|Lines\\|\ -MIME-Version\\|Content-Type\\|Content-Transfer-Encoding\\|\ -\\):.*\n\\([ \t].*\n\\)*" - nndoc-mime-header) - (setq nndoc-mime-header (replace-match "" t t nndoc-mime-header))) - (widen) - (nndoc-dissect-mime-parts-sub (point-min) (point-max) - nil message-id references)))) + (nndoc-dissect-mime-parts-sub (point-min) (point-max) nil nil nil))) -(defun nndoc-dissect-mime-parts-sub (begin end position message-id references) - "Dissect an entity within a composite MIME message. -The article, which corresponds to a MIME entity, extends from BEGIN to END. +(defun nndoc-dissect-mime-parts-sub (head-begin body-end article-insert + position parent) + "Dissect an entity, within a composite MIME message. +The complete message or MIME entity extends from HEAD-BEGIN to BODY-END. +ARTICLE-INSERT should be added at beginning for generating a full article. The string POSITION holds a dotted decimal representation of the article position in the hierarchical structure, it is nil for the outer entity. -The generated article should use MESSAGE-ID and REFERENCES field values." - ;; Note: `case-fold-search' is already `t' from the calling function. - (let ((head-begin begin) - (body-end end) - head-end body-begin type subtype composite comment) - (save-excursion - ;; Gracefully handle a missing body. - (goto-char head-begin) - (if (search-forward "\n\n" body-end t) - (setq head-end (1- (point)) - body-begin (point)) - (setq head-end end - body-begin end)) - ;; Save MIME attributes. - (goto-char head-begin) - (if (re-search-forward "\ -^Content-Type: *\\([^ \t\n/;]+\\)/\\([^ \t\n/;]+\\)" - head-end t) - (setq type (downcase (match-string 1)) - subtype (downcase (match-string 2))) - (setq type "text" - subtype "plain")) - (setq composite (string= type "multipart") - comment (concat position - (when (and position composite) ".") - (when composite "*") - (when (or position composite) " ") - (cond ((string= subtype "plain") type) - ((string= subtype "basic") type) - (t subtype)))) - ;; Generate dissection information for this entity. - (push (list (incf nndoc-mime-split-ordinal) - head-begin head-end body-begin body-end - (count-lines body-begin body-end) - comment message-id references) - nndoc-dissection-alist) - ;; Recurse for all sub-entities, if any. - (goto-char head-begin) - (when (re-search-forward - (concat "\ -^Content-Type: *multipart/\\([a-z]+\\);\\(.*;\\)*" - "[ \t\n]*[ \t]boundary=\"?\\([^\"\n]*[^\" \t\n]\\)") - head-end t) - (let ((boundary (concat "\n--" (match-string 3) "\\(--\\)?[ \t]*\n")) - (part-counter 0) - begin end eof-flag) - (goto-char head-end) - (setq eof-flag (not (re-search-forward boundary body-end t))) +PARENT is the message-ID of the parent summary line, or nil for none." + (let ((case-fold-search t) + (message-id (nnmail-message-id)) + head-end body-begin summary-insert message-rfc822 multipart-any + subject content-type type subtype boundary-regexp) + ;; Gracefully handle a missing body. + (goto-char head-begin) + (if (search-forward "\n\n" body-end t) + (setq head-end (1- (point)) + body-begin (point)) + (setq head-end body-end + body-begin body-end)) + (narrow-to-region head-begin head-end) + ;; Save MIME attributes. + (goto-char head-begin) + (setq content-type (message-fetch-field "Content-Type")) + (when content-type + (when (string-match + "^ *\\([^ \t\n/;]+\\)/\\([^ \t\n/;]+\\)" content-type) + (setq type (downcase (match-string 1 content-type)) + subtype (downcase (match-string 2 content-type)) + message-rfc822 (and (string= type "message") + (string= subtype "rfc822")) + multipart-any (string= type "multipart"))) + (when (string-match ";[ \t\n]*name=\\([^ \t\n;]+\\)" content-type) + (setq subject (match-string 1 content-type))) + (when (string-match "boundary=\"?\\([^\"\n]*[^\" \t\n]\\)" content-type) + (setq boundary-regexp (concat "^--" + (regexp-quote + (match-string 1 content-type)) + "\\(--\\)?[ \t]*\n")))) + (unless subject + (when (or multipart-any (not article-insert)) + (setq subject (message-fetch-field "Subject")))) + (unless type + (setq type "text" + subtype "plain")) + ;; Prepare the article and summary inserts. + (unless article-insert + (setq article-insert (buffer-substring (point-min) (point-max)) + head-end head-begin)) + (setq summary-insert article-insert) + ;; - summary Subject. + (setq summary-insert + (let ((line (concat "Subject: <" position + (and position multipart-any ".") + (and multipart-any "*") + (and (or position multipart-any) " ") + (cond ((string= subtype "plain") type) + ((string= subtype "basic") type) + (t subtype)) + ">" + (and subject " ") + subject + "\n"))) + (if (string-match "Subject:.*\n\\([ \t].*\n\\)*" summary-insert) + (replace-match line t t summary-insert) + (concat summary-insert line)))) + ;; - summary Message-ID. + (setq summary-insert + (let ((line (concat "Message-ID: " message-id "\n"))) + (if (string-match "Message-ID:.*\n\\([ \t].*\n\\)*" summary-insert) + (replace-match line t t summary-insert) + (concat summary-insert line)))) + ;; - summary References. + (when parent + (setq summary-insert + (let ((line (concat "References: " parent "\n"))) + (if (string-match "References:.*\n\\([ \t].*\n\\)*" + summary-insert) + (replace-match line t t summary-insert) + (concat summary-insert line))))) + ;; Generate dissection information for this entity. + (push (list (incf nndoc-mime-split-ordinal) + head-begin head-end body-begin body-end + (count-lines body-begin body-end) + article-insert summary-insert) + nndoc-dissection-alist) + ;; Recurse for all sub-entities, if any. + (widen) + (cond + (message-rfc822 + (save-excursion + (nndoc-dissect-mime-parts-sub body-begin body-end nil + position message-id))) + ((and multipart-any boundary-regexp) + (let ((part-counter 0) + part-begin part-end eof-flag) + (while (string-match "\ +^\\(Lines\\|Content-\\(Type\\|Transfer-Encoding\\|Disposition\\)\\):.*\n\\([ \t].*\n\\)*" + article-insert) + (setq article-insert (replace-match "" t t article-insert))) + (let ((case-fold-search nil)) + (goto-char body-begin) + (setq eof-flag (not (re-search-forward boundary-regexp body-end t))) (while (not eof-flag) - (setq begin (point)) - (cond ((re-search-forward boundary body-end t) + (setq part-begin (point)) + (cond ((re-search-forward boundary-regexp body-end t) (or (not (match-string 1)) (string= (match-string 1) "") (setq eof-flag t)) (forward-line -1) - (setq end (point)) + (setq part-end (point)) (forward-line 1)) - (t (setq end body-end + (t (setq part-end body-end eof-flag t))) - (nndoc-dissect-mime-parts-sub begin end - (concat position (when position ".") - (format "%d" - (incf part-counter))) - (nnmail-message-id) - message-id))))))) + (save-excursion + (nndoc-dissect-mime-parts-sub + part-begin part-end article-insert + (concat position + (and position ".") + (format "%d" (incf part-counter))) + message-id))))))))) ;;;###autoload (defun nndoc-add-type (definition &optional position) diff --git a/lisp/gnus/nndraft.el b/lisp/gnus/nndraft.el index c6f23c41026..1d320a55292 100644 --- a/lisp/gnus/nndraft.el +++ b/lisp/gnus/nndraft.el @@ -1,5 +1,6 @@ ;;; nndraft.el --- draft article access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -30,6 +31,7 @@ (require 'gnus-start) (require 'nnmh) (require 'nnoo) +(require 'mm-util) (eval-when-compile (require 'cl) ;; This is just to shut up the byte-compiler. @@ -77,16 +79,12 @@ (save-excursion (set-buffer nntp-server-buffer) (erase-buffer) - (let* ((buf (get-buffer-create " *draft headers*")) - article) - (set-buffer buf) - (buffer-disable-undo (current-buffer)) - (erase-buffer) + (let* (article) ;; We don't support fetching by Message-ID. (if (stringp (car articles)) 'headers (while articles - (set-buffer buf) + (narrow-to-region (point) (point)) (when (nndraft-request-article (setq article (pop articles)) group server (current-buffer)) (goto-char (point-min)) @@ -94,10 +92,10 @@ (forward-line -1) (goto-char (point-max))) (delete-region (point) (point-max)) - (set-buffer nntp-server-buffer) - (goto-char (point-max)) + (goto-char (point-min)) (insert (format "221 %d Article retrieved.\n" article)) - (insert-buffer-substring buf) + (widen) + (goto-char (point-max)) (insert ".\n"))) (nnheader-fold-continuation-lines) @@ -113,7 +111,13 @@ (newest (if (file-newer-than-file-p file auto) file auto)) (nntp-server-buffer (or buffer nntp-server-buffer))) (when (and (file-exists-p newest) - (nnmail-find-file newest)) + (let ((nnmail-file-coding-system + (if (file-newer-than-file-p file auto) + (if (equal group "drafts") + message-draft-coding-system + mm-text-coding-system) + mm-auto-save-coding-system))) + (nnmail-find-file newest))) (save-excursion (set-buffer nntp-server-buffer) (goto-char (point-min)) @@ -138,8 +142,9 @@ info (gnus-update-read-articles (gnus-group-prefixed-name group '(nndraft "")) (nndraft-articles) t)) - (let (marks) - (when (setq marks (nth 3 info)) + (let ((marks (nth 3 info))) + (when marks + ;; Nix out all marks except the `unsend'-able article marks. (setcar (nthcdr 3 info) (if (assq 'unsend marks) (list (assq 'unsend marks)) @@ -153,14 +158,14 @@ (nndraft-possibly-change-group group) (let ((gnus-verbose-backends nil) (buf (current-buffer)) - article file) - (nnheader-temp-write nil - (insert-buffer buf) + article file) + (with-temp-buffer + (insert-buffer-substring buf) (setq article (nndraft-request-accept-article - group (nnoo-current-server 'nndraft) t 'noinsert)) - (setq file (nndraft-article-filename article))) - (setq buffer-file-name (expand-file-name file)) - (setq buffer-auto-save-file-name (make-auto-save-file-name)) + group (nnoo-current-server 'nndraft) t 'noinsert) + file (nndraft-article-filename article))) + (setq buffer-file-name (expand-file-name file) + buffer-auto-save-file-name (make-auto-save-file-name)) (clear-visited-file-modtime) article)) @@ -177,7 +182,14 @@ (let ((auto (nndraft-auto-save-file-name (nndraft-article-filename article)))) (when (file-exists-p auto) - (funcall nnmail-delete-file-function auto))))) + (funcall nnmail-delete-file-function auto))) + (dolist (backup + (let ((kept-new-versions 1) + (kept-old-versions 0)) + (find-backup-file-name + (nndraft-article-filename article)))) + (when (file-exists-p backup) + (funcall nnmail-delete-file-function backup))))) res)) (deffoo nndraft-request-accept-article (group &optional server last noinsert) @@ -186,6 +198,15 @@ (nnoo-parent-function 'nndraft 'nnmh-request-accept-article (list group server last noinsert)))) +(deffoo nndraft-request-replace-article (article group buffer) + (nndraft-possibly-change-group group) + (let ((nnmail-file-coding-system + (if (equal group "drafts") + mm-auto-save-coding-system + mm-text-coding-system))) + (nnoo-parent-function 'nndraft 'nnmh-request-replace-article + (list article group buffer)))) + (deffoo nndraft-request-create-group (group &optional server args) (nndraft-possibly-change-group group) (if (file-exists-p nndraft-current-directory) @@ -237,10 +258,9 @@ nnmh-retrieve-headers nnmh-request-group nnmh-close-group - nnmh-request-list + nnmh-request-list nnmh-request-newsgroups - nnmh-request-move-article - nnmh-request-replace-article)) + nnmh-request-move-article)) (provide 'nndraft) diff --git a/lisp/gnus/nneething.el b/lisp/gnus/nneething.el index 7da54665884..a9c3bb62e4a 100644 --- a/lisp/gnus/nneething.el +++ b/lisp/gnus/nneething.el @@ -1,5 +1,7 @@ ;;; nneething.el --- arbitrary file access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Masanobu UMEDA @@ -45,6 +47,11 @@ "Regexp saying what files to exclude from the group. If this variable is nil, no files will be excluded.") +(defvoo nneething-include-files nil + "Regexp saying what files to include in the group. +If this variable is non-nil, only files matching this regexp will be +included.") + ;;; Internal variables. @@ -68,8 +75,6 @@ If this variable is nil, no files will be excluded.") -(autoload 'gnus-encode-coding-string "gnus-ems") - ;;; Interface functions. (nnoo-define-basics nneething) @@ -104,7 +109,7 @@ If this variable is nil, no files will be excluded.") (and large (zerop (% count 20)) (nnheader-message 5 "nneething: Receiving headers... %d%%" - (/ (* count 100) number)))) + (/ (* count 100) number)))) (when large (nnheader-message 5 "nneething: Receiving headers...done")) @@ -124,7 +129,8 @@ If this variable is nil, no files will be excluded.") (nnmail-find-file file) ; Insert the file in the nntp buf. (unless (nnheader-article-p) ; Either it's a real article... (goto-char (point-min)) - (nneething-make-head file (current-buffer)) ; ... or we fake some headers. + (nneething-make-head + file (current-buffer)) ; ... or we fake some headers. (insert "\n")) t)))) @@ -213,17 +219,27 @@ If this variable is nil, no files will be excluded.") (setq files (cdr files))) (setq prev f)) (setq f (cdr f))))) + ;; Remove files not matching the inclusion regexp. + (when nneething-include-files + (let ((f files) + prev) + (while f + (if (not (string-match nneething-include-files (car f))) + (if prev (setcdr prev (cdr f)) + (setq files (cdr files))) + (setq prev f)) + (setq f (cdr f))))) ;; Remove deleted files from the map. (let ((map nneething-map) prev) (while map - (if (and (member (cadar map) files) + (if (and (member (cadr (car map)) files) ;; We also remove files that have changed mod times. (equal (nth 5 (file-attributes - (nneething-file-name (cadar map)))) - (caddar map))) + (nneething-file-name (cadr (car map))))) + (cadr (cdar map)))) (progn - (push (cadar map) map-files) + (push (cadr (car map)) map-files) (setq prev map)) (setq touched t) (if prev @@ -243,7 +259,7 @@ If this variable is nil, no files will be excluded.") (setq files (cdr files))) (when (and touched (not nneething-read-only)) - (nnheader-temp-write map-file + (with-temp-file map-file (insert "(setq nneething-map '") (gnus-prin1 nneething-map) (insert ")\n(setq nneething-active '") @@ -281,8 +297,7 @@ If this variable is nil, no files will be excluded.") (concat "Lines: " (int-to-string (count-lines (point-min) (point-max))) "\n")) - "") - ))) + "")))) (defun nneething-from-line (uid &optional file) "Return a From header based of UID." @@ -302,7 +317,8 @@ If this variable is nil, no files will be excluded.") (substring file (match-beginning 1) (match-end 1)) - (when (string-match "/\\(users\\|home\\)/\\([^/]+\\)/" file) + (when (string-match + "/\\(users\\|home\\)/\\([^/]+\\)/" file) (setq login (substring file (match-beginning 2) (match-end 2)) @@ -316,7 +332,7 @@ If this variable is nil, no files will be excluded.") (save-excursion (set-buffer (get-buffer-create nneething-work-buffer)) (setq case-fold-search nil) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) (cond ((not (file-exists-p file)) @@ -344,10 +360,13 @@ If this variable is nil, no files will be excluded.") (defun nneething-file-name (article) "Return the file name of ARTICLE." - (concat (file-name-as-directory nneething-address) - (if (numberp article) - (cadr (assq article nneething-map)) - article))) + (let ((dir (file-name-as-directory nneething-address)) + fname) + (if (numberp article) + (if (setq fname (cadr (assq article nneething-map))) + (expand-file-name fname dir) + (make-temp-name (expand-file-name "nneething" dir))) + (expand-file-name article dir)))) (provide 'nneething) diff --git a/lisp/gnus/nnfolder.el b/lisp/gnus/nnfolder.el index fb14056af93..d782835aafc 100644 --- a/lisp/gnus/nnfolder.el +++ b/lisp/gnus/nnfolder.el @@ -1,5 +1,6 @@ ;;; nnfolder.el --- mail folder access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Scott Byer ;; Lars Magne Ingebrigtsen @@ -40,27 +41,29 @@ "The name of the nnfolder directory.") (defvoo nnfolder-active-file - (nnheader-concat nnfolder-directory "active") + (nnheader-concat nnfolder-directory "active") "The name of the active file.") ;; I renamed this variable to something more in keeping with the general GNU ;; style. -SLB (defvoo nnfolder-ignore-active-file nil - "If non-nil, causes nnfolder to do some extra work in order to determine -the true active ranges of an mbox file. Note that the active file is still -saved, but it's values are not used. This costs some extra time when -scanning an mbox when opening it.") + "If non-nil, the active file is ignores. +This causes nnfolder to do some extra work in order to determine the +true active ranges of an mbox file. Note that the active file is +still saved, but it's values are not used. This costs some extra time +when scanning an mbox when opening it.") (defvoo nnfolder-distrust-mbox nil - "If non-nil, causes nnfolder to not trust the user with respect to -inserting unaccounted for mail in the middle of an mbox file. This can greatly -slow down scans, which now must scan the entire file for unmarked messages. -When nil, scans occur forward from the last marked message, a huge -time saver for large mailboxes.") + "If non-nil, the folder will be distrusted. +This means that nnfolder will not trust the user with respect to +inserting unaccounted for mail in the middle of an mbox file. This +can greatly slow down scans, which now must scan the entire file for +unmarked messages. When nil, scans occur forward from the last marked +message, a huge time saver for large mailboxes.") (defvoo nnfolder-newsgroups-file - (concat (file-name-as-directory nnfolder-directory) "newsgroups") + (concat (file-name-as-directory nnfolder-directory) "newsgroups") "Mail newsgroups description file.") (defvoo nnfolder-get-new-mail t @@ -90,6 +93,13 @@ time saver for large mailboxes.") (defvoo nnfolder-buffer-alist nil) (defvoo nnfolder-scantime-alist nil) (defvoo nnfolder-active-timestamp nil) +(defvoo nnfolder-active-file-coding-system mm-text-coding-system) +(defvoo nnfolder-active-file-coding-system-for-write + nnmail-active-file-coding-system) +(defvoo nnfolder-file-coding-system mm-text-coding-system) +(defvoo nnfolder-file-coding-system-for-write nnheader-file-coding-system + "Coding system for save nnfolder file. +If NIL, NNFOLDER-FILE-CODING-SYSTEM is used.") @@ -112,8 +122,9 @@ time saver for large mailboxes.") (set-buffer nnfolder-current-buffer) (when (nnfolder-goto-article article) (setq start (point)) - (search-forward "\n\n" nil t) - (setq stop (1- (point))) + (setq stop (if (search-forward "\n\n" nil t) + (1- (point)) + (point-max))) (set-buffer nntp-server-buffer) (insert (format "221 %d Article retrieved.\n" article)) (insert-buffer-substring nnfolder-current-buffer start stop) @@ -176,11 +187,13 @@ time saver for large mailboxes.") (if (numberp article) (cons nnfolder-current-group article) (goto-char (point-min)) - (search-forward (concat "\n" nnfolder-article-marker)) (cons nnfolder-current-group - (string-to-int - (buffer-substring - (point) (progn (end-of-line) (point))))))))))) + (if (search-forward (concat "\n" nnfolder-article-marker) + nil t) + (string-to-int + (buffer-substring + (point) (progn (end-of-line) (point)))) + -1)))))))) (deffoo nnfolder-request-group (group &optional server dont-check) (nnfolder-possibly-change-group group server t) @@ -266,15 +279,14 @@ time saver for large mailboxes.") (when group (unless (assoc group nnfolder-group-alist) (push (list group (cons 1 0)) nnfolder-group-alist) - (nnmail-save-active nnfolder-group-alist nnfolder-active-file) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file) (nnfolder-read-folder group))) t) (deffoo nnfolder-request-list (&optional server) (nnfolder-possibly-change-group nil server) (save-excursion - (let ((nnmail-file-coding-system nnmail-active-file-coding-system) - (pathname-coding-system 'binary)) + (let ((nnmail-file-coding-system nnfolder-active-file-coding-system)) (nnmail-find-file nnfolder-active-file) (setq nnfolder-group-alist (nnmail-get-active))) t)) @@ -286,38 +298,69 @@ time saver for large mailboxes.") (deffoo nnfolder-request-list-newsgroups (&optional server) (nnfolder-possibly-change-group nil server) (save-excursion - (nnmail-find-file nnfolder-newsgroups-file))) + (let ((nnmail-file-coding-system nnfolder-file-coding-system)) + (nnmail-find-file nnfolder-newsgroups-file)))) + +;; Return a list consisting of all article numbers existing in the +;; current folder. + +(defun nnfolder-existing-articles () + (save-excursion + (when nnfolder-current-buffer + (set-buffer nnfolder-current-buffer) + (goto-char (point-min)) + (let ((marker (concat "\n" nnfolder-article-marker)) + (number "[0-9]+") + numbers) + + (while (and (search-forward marker nil t) + (re-search-forward number nil t)) + (let ((newnum (string-to-number (match-string 0)))) + (if (nnmail-within-headers-p) + (push newnum numbers)))) + numbers)))) (deffoo nnfolder-request-expire-articles - (articles newsgroup &optional server force) + (articles newsgroup &optional server force) (nnfolder-possibly-change-group newsgroup server) (let* ((is-old t) - rest) + ;; The articles we have deleted so far. + (deleted-articles nil) + ;; The articles that really exist and will + ;; be expired if they are old enough. + (maybe-expirable + (gnus-intersection articles (nnfolder-existing-articles)))) (nnmail-activate 'nnfolder) (save-excursion (set-buffer nnfolder-current-buffer) - (while (and articles is-old) + ;; Since messages are sorted in arrival order and expired in the + ;; same order, we can stop as soon as we find a message that is + ;; too old. + (while (and maybe-expirable is-old) (goto-char (point-min)) - (when (nnfolder-goto-article (car articles)) - (if (setq is-old - (nnmail-expired-article-p - newsgroup - (buffer-substring - (point) (progn (end-of-line) (point))) - force nnfolder-inhibit-expiry)) - (progn - (nnheader-message 5 "Deleting article %d..." - (car articles) newsgroup) - (nnfolder-delete-mail)) - (push (car articles) rest))) - (setq articles (cdr articles))) + (when (and (nnfolder-goto-article (car maybe-expirable)) + (search-forward (concat "\n" nnfolder-article-marker) + nil t)) + (forward-sexp) + (when (setq is-old + (nnmail-expired-article-p + newsgroup + (buffer-substring + (point) (progn (end-of-line) (point))) + force nnfolder-inhibit-expiry)) + (nnheader-message 5 "Deleting article %d..." + (car maybe-expirable) newsgroup) + (nnfolder-delete-mail) + ;; Must remember which articles were actually deleted + (push (car maybe-expirable) deleted-articles))) + (setq maybe-expirable (cdr maybe-expirable))) (unless nnfolder-inhibit-expiry (nnheader-message 5 "Deleting articles...done")) (nnfolder-save-buffer) (nnfolder-adjust-min-active newsgroup) - (nnmail-save-active nnfolder-group-alist nnfolder-active-file) - (nconc rest articles)))) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file) + (gnus-sorted-complement articles (nreverse deleted-articles))))) (deffoo nnfolder-request-move-article (article group server accept-form &optional last) @@ -328,13 +371,13 @@ time saver for large mailboxes.") (nnfolder-request-article article group server) (save-excursion (set-buffer buf) - (buffer-disable-undo (current-buffer)) (erase-buffer) (insert-buffer-substring nntp-server-buffer) (goto-char (point-min)) (while (re-search-forward (concat "^" nnfolder-article-marker) - (save-excursion (search-forward "\n\n" nil t) (point)) t) + (save-excursion (and (search-forward "\n\n" nil t) (point))) + t) (delete-region (progn (beginning-of-line) (point)) (progn (forward-line 1) (point)))) (setq result (eval accept-form)) @@ -349,7 +392,7 @@ time saver for large mailboxes.") (when last (nnfolder-save-buffer) (nnfolder-adjust-min-active group) - (nnmail-save-active nnfolder-group-alist nnfolder-active-file)))) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)))) result))) (deffoo nnfolder-request-accept-article (group &optional server last) @@ -366,8 +409,9 @@ time saver for large mailboxes.") (save-excursion (set-buffer buf) (goto-char (point-min)) - (search-forward "\n\n" nil t) - (forward-line -1) + (if (search-forward "\n\n" nil t) + (forward-line -1) + (goto-char (point-max))) (while (re-search-backward (concat "^" nnfolder-article-marker) nil t) (delete-region (point) (progn (forward-line 1) (point)))) (when nnmail-cache-accepted-message-ids @@ -387,7 +431,7 @@ time saver for large mailboxes.") (nnfolder-save-buffer) (when nnmail-cache-accepted-message-ids (nnmail-cache-close))))) - (nnmail-save-active nnfolder-group-alist nnfolder-active-file) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file) (unless result (nnheader-report 'nnfolder "Couldn't store article")) result))) @@ -404,7 +448,7 @@ time saver for large mailboxes.") (goto-char (point-min)) (if xfrom (insert "From " xfrom "\n") - (unless (looking-at message-unix-mail-delimiter) + (unless (looking-at "From ") (insert "From nobody " (current-time-string) "\n")))) (nnfolder-normalize-buffer) (set-buffer nnfolder-current-buffer) @@ -430,7 +474,7 @@ time saver for large mailboxes.") nnfolder-current-group nil nnfolder-current-buffer nil) ;; Save the active file. - (nnmail-save-active nnfolder-group-alist nnfolder-active-file) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file) t) (deffoo nnfolder-request-rename-group (group new-name &optional server) @@ -441,7 +485,9 @@ time saver for large mailboxes.") (ignore-errors (rename-file buffer-file-name - (nnfolder-group-pathname new-name)) + (let ((new-file (nnfolder-group-pathname new-name))) + (gnus-make-directory (file-name-directory new-file)) + new-file)) t) ;; That went ok, so we change the internal structures. (let ((entry (assoc group nnfolder-group-alist))) @@ -449,7 +495,7 @@ time saver for large mailboxes.") (setq nnfolder-current-buffer nil nnfolder-current-group nil) ;; Save the new group alist. - (nnmail-save-active nnfolder-group-alist nnfolder-active-file) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file) ;; We kill the buffer instead of renaming it and stuff. (kill-buffer (current-buffer)) t)))) @@ -511,17 +557,21 @@ Returns t if successful, nil otherwise." "Delete the message that point is in. If optional argument LEAVE-DELIM is t, then mailbox delimiter is not deleted. Point is left where the deleted region was." - (delete-region - (save-excursion - (forward-line 1) ; in case point is at beginning of message already - (nnmail-search-unix-mail-delim-backward) - (if leave-delim (progn (forward-line 1) (point)) - (point))) - (progn - (forward-line 1) - (if (nnmail-search-unix-mail-delim) - (point) - (point-max))))) + (save-restriction + (narrow-to-region + (save-excursion + ;; In case point is at the beginning of the message already. + (forward-line 1) + (nnmail-search-unix-mail-delim-backward) + (if leave-delim (progn (forward-line 1) (point)) + (point))) + (progn + (forward-line 1) + (if (nnmail-search-unix-mail-delim) + (point) + (point-max)))) + (run-hooks 'nnfolder-delete-mail-hook) + (delete-region (point-min) (point-max)))) (defun nnfolder-possibly-change-group (group &optional server dont-check) ;; Change servers. @@ -534,15 +584,14 @@ deleted. Point is left where the deleted region was." ;; Change group. (when (and group (not (equal group nnfolder-current-group))) - (let ((file-name-coding-system 'binary) - (pathname-coding-system 'binary)) + (let ((file-name-coding-system nnmail-pathname-coding-system)) (nnmail-activate 'nnfolder) (when (and (not (assoc group nnfolder-group-alist)) (not (file-exists-p (nnfolder-group-pathname group)))) ;; The group doesn't exist, so we create a new entry for it. (push (list group (cons 1 0)) nnfolder-group-alist) - (nnmail-save-active nnfolder-group-alist nnfolder-active-file)) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)) (if dont-check (setq nnfolder-current-group group @@ -572,7 +621,10 @@ deleted. Point is left where the deleted region was." ;; See whether we need to create the new file. (unless (file-exists-p file) (gnus-make-directory (file-name-directory file)) - (nnmail-write-region 1 1 file t 'nomesg)) + (let ((nnmail-file-coding-system + (or nnfolder-file-coding-system-for-write + nnfolder-file-coding-system-for-write))) + (nnmail-write-region 1 1 file t 'nomesg))) (when (setq nnfolder-current-buffer (nnfolder-read-folder group)) (set-buffer nnfolder-current-buffer) (push (list group nnfolder-current-buffer) @@ -583,19 +635,14 @@ deleted. Point is left where the deleted region was." (let* (save-list group-art) (goto-char (point-min)) ;; The From line may have been quoted by movemail. - (when (looking-at (concat ">" message-unix-mail-delimiter)) + (when (looking-at ">From") (delete-char 1)) ;; This might come from somewhere else. - (unless (looking-at message-unix-mail-delimiter) + (unless (looking-at "From ") (insert "From nobody " (current-time-string) "\n") (goto-char (point-min))) - (forward-line 1) - ;; Quote subsequent "From " lines in the header. - (while (looking-at message-unix-mail-delimiter) - (delete-region (point) (+ (point) 4)) - (insert "X-From-Line:") - (forward-line 1)) ;; Quote all "From " lines in the article. + (forward-line 1) (let (case-fold-search) (while (re-search-forward "^From " nil t) (beginning-of-line) @@ -610,8 +657,9 @@ deleted. Point is left where the deleted region was." (while (setq group-art (pop group-art-list)) ;; Kill any previous newsgroup markers. (goto-char (point-min)) - (search-forward "\n\n" nil t) - (forward-line -1) + (if (search-forward "\n\n" nil t) + (forward-line -1) + (goto-char (point-max))) (while (search-backward (concat "\n" nnfolder-article-marker) nil t) (delete-region (1+ (point)) (progn (forward-line 2) (point)))) @@ -640,10 +688,12 @@ deleted. Point is left where the deleted region was." (defun nnfolder-insert-newsgroup-line (group-art) (save-excursion (goto-char (point-min)) - (when (search-forward "\n\n" nil t) - (forward-char -1) - (insert (format (concat nnfolder-article-marker "%d %s\n") - (cdr group-art) (current-time-string)))))) + (unless (search-forward "\n\n" nil t) + (goto-char (point-max)) + (insert "\n")) + (forward-char -1) + (insert (format (concat nnfolder-article-marker "%d %s\n") + (cdr group-art) (current-time-string))))) (defun nnfolder-active-number (group) ;; Find the next article number in GROUP. @@ -665,7 +715,7 @@ deleted. Point is left where the deleted region was." (when inf (setq nnfolder-buffer-alist (delq inf nnfolder-buffer-alist))) (when nnfolder-group-alist - (nnmail-save-active nnfolder-group-alist nnfolder-active-file)) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file)) (push (list group (nnfolder-read-folder group)) nnfolder-buffer-alist)))) @@ -686,7 +736,10 @@ deleted. Point is left where the deleted region was." (defun nnfolder-read-folder (group) (let* ((file (nnfolder-group-pathname group)) - (buffer (set-buffer (nnheader-find-file-noselect file)))) + (buffer (set-buffer + (let ((nnheader-file-coding-system + nnfolder-file-coding-system)) + (nnheader-find-file-noselect file))))) (if (equal (cadr (assoc group nnfolder-scantime-alist)) (nth 5 (file-attributes file))) ;; This looks up-to-date, so we don't do any scanning. @@ -694,12 +747,16 @@ deleted. Point is left where the deleted region was." buffer (push (list group buffer) nnfolder-buffer-alist) (set-buffer-modified-p t) - (save-buffer)) + (nnfolder-save-buffer)) ;; Parse the damn thing. (save-excursion + (goto-char (point-min)) + ;; Remove any blank lines at the start. + (while (eq (following-char) ?\n) + (delete-char 1)) (nnmail-activate 'nnfolder) ;; Read in the file. - (let ((delim (concat "^" message-unix-mail-delimiter)) + (let ((delim "^From ") (marker (concat "\n" nnfolder-article-marker)) (number "[0-9]+") (active (or (cadr (assoc group nnfolder-group-alist)) @@ -708,7 +765,7 @@ deleted. Point is left where the deleted region was." (minid (lsh -1 -1)) maxid start end newscantime buffer-read-only) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (setq maxid (cdr active)) (goto-char (point-min)) @@ -768,7 +825,7 @@ deleted. Point is left where the deleted region was." (set-marker end nil) ;; Make absolutely sure that the active list reflects reality! - (nnmail-save-active nnfolder-group-alist nnfolder-active-file) + (nnfolder-save-active nnfolder-group-alist nnfolder-active-file) ;; Set the scantime for this group. (setq newscantime (visited-file-modtime)) (if scantime @@ -779,7 +836,8 @@ deleted. Point is left where the deleted region was." ;;;###autoload (defun nnfolder-generate-active-file () - "Look for mbox folders in the nnfolder directory and make them into groups." + "Look for mbox folders in the nnfolder directory and make them into groups. +This command does not work if you use short group names." (interactive) (nnmail-activate 'nnfolder) (let ((files (directory-files nnfolder-directory)) @@ -803,7 +861,8 @@ deleted. Point is left where the deleted region was." (defun nnfolder-group-pathname (group) "Make pathname for GROUP." - (setq group (gnus-encode-coding-string group nnmail-pathname-coding-system)) + (setq group + (mm-encode-coding-string group nnmail-pathname-coding-system)) (let ((dir (file-name-as-directory (expand-file-name nnfolder-directory)))) ;; If this file exists, we use it directly. (if (or nnmail-use-long-file-names @@ -817,7 +876,16 @@ deleted. Point is left where the deleted region was." (when (buffer-modified-p) (run-hooks 'nnfolder-save-buffer-hook) (gnus-make-directory (file-name-directory (buffer-file-name))) - (save-buffer))) + (let ((coding-system-for-write + (or nnfolder-file-coding-system-for-write + nnfolder-file-coding-system))) + (save-buffer)))) + +(defun nnfolder-save-active (group-alist active-file) + (let ((nnmail-active-file-coding-system + (or nnfolder-active-file-coding-system-for-write + nnfolder-active-file-coding-system))) + (nnmail-save-active group-alist active-file))) (provide 'nnfolder) diff --git a/lisp/gnus/nngateway.el b/lisp/gnus/nngateway.el index c580ac55309..65bd2cc81c2 100644 --- a/lisp/gnus/nngateway.el +++ b/lisp/gnus/nngateway.el @@ -1,5 +1,7 @@ ;;; nngateway.el --- posting news via mail gateways -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news, mail @@ -55,7 +57,7 @@ parameter -- the gateway address.") (nngateway-open-server server)) ;; Rewrite the header. (let ((buf (current-buffer))) - (nnheader-temp-write nil + (with-temp-buffer (insert-buffer-substring buf) (message-narrow-to-head) (funcall nngateway-header-transformation nngateway-address) diff --git a/lisp/gnus/nnheader.el b/lisp/gnus/nnheader.el index 395a2085e00..a8c16c4777e 100644 --- a/lisp/gnus/nnheader.el +++ b/lisp/gnus/nnheader.el @@ -1,5 +1,8 @@ ;;; nnheader.el --- header access macros for Gnus and its backends -;; Copyright (C) 1987,88,89,90,93,94,95,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1987, 1988, 1989, 1990, 1993, 1994, 1995, 1996, +;; 1997, 1998, 2000 +;; Free Software Foundation, Inc. ;; Author: Masanobu UMEDA ;; Lars Magne Ingebrigtsen @@ -24,24 +27,12 @@ ;;; Commentary: -;; These macros may look very much like the ones in GNUS 4.1. They -;; are, in a way, but you should note that the indices they use have -;; been changed from the internal GNUS format to the NOV format. The -;; makes it possible to read headers from XOVER much faster. -;; -;; The format of a header is now: -;; [number subject from date id references chars lines xref] -;; -;; (That last entry is defined as "misc" in the NOV format, but Gnus -;; uses it for xrefs.) - ;;; Code: (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'mail-utils) +(require 'mm-util) (defvar nnheader-max-head-length 4096 "*Max length of the head of articles.") @@ -51,23 +42,32 @@ (defvar nnheader-file-name-translation-alist nil "*Alist that says how to translate characters in file names. -For instance, if \":\" is illegal as a file character in file names +For instance, if \":\" is invalid as a file character in file names on your system, you could say something like: \(setq nnheader-file-name-translation-alist '((?: . ?_)))") (eval-and-compile - (autoload 'nnmail-message-id "nnmail") - (autoload 'mail-position-on-field "sendmail") - (autoload 'message-remove-header "message") - (autoload 'cancel-function-timers "timers") - (autoload 'gnus-point-at-eol "gnus-util") - (autoload 'gnus-delete-line "gnus-util") - (autoload 'gnus-buffer-live-p "gnus-util") - (autoload 'gnus-encode-coding-string "gnus-ems")) + (autoload 'nnmail-message-id "nnmail") + (autoload 'mail-position-on-field "sendmail") + (autoload 'message-remove-header "message") + (autoload 'gnus-point-at-eol "gnus-util") + (autoload 'gnus-delete-line "gnus-util") + (autoload 'gnus-buffer-live-p "gnus-util")) ;;; Header access macros. +;; These macros may look very much like the ones in GNUS 4.1. They +;; are, in a way, but you should note that the indices they use have +;; been changed from the internal GNUS format to the NOV format. The +;; makes it possible to read headers from XOVER much faster. +;; +;; The format of a header is now: +;; [number subject from date id references chars lines xref extra] +;; +;; (That next-to-last entry is defined as "misc" in the NOV format, +;; but Gnus uses it for xrefs.) + (defmacro mail-header-number (header) "Return article number in HEADER." `(aref ,header 0)) @@ -139,17 +139,26 @@ on your system, you could say something like: `(aref ,header 8)) (defmacro mail-header-set-xref (header xref) - "Set article xref of HEADER to xref." + "Set article XREF of HEADER to xref." `(aset ,header 8 ,xref)) -(defun make-mail-header (&optional init) - "Create a new mail header structure initialized with INIT." - (make-vector 9 init)) +(defmacro mail-header-extra (header) + "Return the extra headers in HEADER." + `(aref ,header 9)) -(defun make-full-mail-header (&optional number subject from date id - references chars lines xref) +(defmacro mail-header-set-extra (header extra) + "Set the extra headers in HEADER to EXTRA." + `(aset ,header 9 ',extra)) + +(defsubst make-mail-header (&optional init) + "Create a new mail header structure initialized with INIT." + (make-vector 10 init)) + +(defsubst make-full-mail-header (&optional number subject from date id + references chars lines xref + extra) "Create a new mail header structure initialized with the parameters given." - (vector number subject from date id references chars lines xref)) + (vector number subject from date id references chars lines xref extra)) ;; fake message-ids: generation and detection @@ -235,11 +244,12 @@ on your system, you could say something like: ;; promising. (if (and (search-forward "\nin-reply-to: " nil t) (setq in-reply-to (nnheader-header-value)) - (string-match "<[^>]+>" in-reply-to)) + (string-match "<[^\n>]+>" in-reply-to)) (let (ref2) (setq ref (substring in-reply-to (match-beginning 0) (match-end 0))) - (while (string-match "<[^>]+>" in-reply-to (match-end 0)) + (while (string-match "<[^\n>]+>" + in-reply-to (match-end 0)) (setq ref2 (substring in-reply-to (match-beginning 0) (match-end 0))) (when (> (length ref2) (length ref)) @@ -259,7 +269,20 @@ on your system, you could say something like: (progn (goto-char p) (and (search-forward "\nxref: " nil t) - (nnheader-header-value))))) + (nnheader-header-value))) + + ;; Extra. + (when nnmail-extra-headers + (let ((extra nnmail-extra-headers) + out) + (while extra + (goto-char p) + (when (search-forward + (concat "\n" (symbol-name (car extra)) ": ") nil t) + (push (cons (car extra) (nnheader-header-value)) + out)) + (pop extra)) + out)))) (when naked (goto-char (point-min)) (delete-char 1))))) @@ -272,13 +295,29 @@ on your system, you could say something like: (defmacro nnheader-nov-read-integer () '(prog1 - (if (= (following-char) ?\t) + (if (eq (char-after) ?\t) 0 - (let ((num (ignore-errors (read (current-buffer))))) + (let ((num (condition-case nil + (read (current-buffer)) + (error nil)))) (if (numberp num) num 0))) (or (eobp) (forward-char 1)))) -;; (defvar nnheader-none-counter 0) +(defmacro nnheader-nov-parse-extra () + '(let (out string) + (while (not (memq (char-after) '(?\n nil))) + (setq string (nnheader-nov-field)) + (when (string-match "^\\([^ :]+\\): " string) + (push (cons (intern (match-string 1 string)) + (substring string (match-end 0))) + out))) + out)) + +(defmacro nnheader-nov-read-message-id () + '(let ((id (nnheader-nov-field))) + (if (string-match "^<[^>]+>$" id) + id + (nnheader-generate-fake-message-id)))) (defun nnheader-parse-nov () (let ((eol (gnus-point-at-eol))) @@ -287,34 +326,58 @@ on your system, you could say something like: (nnheader-nov-field) ; subject (nnheader-nov-field) ; from (nnheader-nov-field) ; date - (or (nnheader-nov-field) - (nnheader-generate-fake-message-id)) ; id + (nnheader-nov-read-message-id) ; id (nnheader-nov-field) ; refs (nnheader-nov-read-integer) ; chars (nnheader-nov-read-integer) ; lines - (if (= (following-char) ?\n) + (if (eq (char-after) ?\n) nil (nnheader-nov-field)) ; misc - ))) + (nnheader-nov-parse-extra)))) ; extra (defun nnheader-insert-nov (header) (princ (mail-header-number header) (current-buffer)) + (let ((p (point))) + (insert + "\t" + (or (mail-header-subject header) "(none)") "\t" + (or (mail-header-from header) "(nobody)") "\t" + (or (mail-header-date header) "") "\t" + (or (mail-header-id header) + (nnmail-message-id)) + "\t" + (or (mail-header-references header) "") "\t") + (princ (or (mail-header-chars header) 0) (current-buffer)) + (insert "\t") + (princ (or (mail-header-lines header) 0) (current-buffer)) + (insert "\t") + (when (mail-header-xref header) + (insert "Xref: " (mail-header-xref header))) + (when (or (mail-header-xref header) + (mail-header-extra header)) + (insert "\t")) + (when (mail-header-extra header) + (let ((extra (mail-header-extra header))) + (while extra + (insert (symbol-name (caar extra)) + ": " (cdar extra) "\t") + (pop extra)))) + (insert "\n") + (backward-char 1) + (while (search-backward "\n" p t) + (delete-char 1)) + (forward-line 1))) + +(defun nnheader-insert-header (header) (insert - "\t" - (or (mail-header-subject header) "(none)") "\t" - (or (mail-header-from header) "(nobody)") "\t" - (or (mail-header-date header) "") "\t" - (or (mail-header-id header) - (nnmail-message-id)) - "\t" - (or (mail-header-references header) "") "\t") - (princ (or (mail-header-chars header) 0) (current-buffer)) - (insert "\t") + "Subject: " (or (mail-header-subject header) "(none)") "\n" + "From: " (or (mail-header-from header) "(nobody)") "\n" + "Date: " (or (mail-header-date header) "") "\n" + "Message-ID: " (or (mail-header-id header) (nnmail-message-id)) "\n" + "References: " (or (mail-header-references header) "") "\n" + "Lines: ") (princ (or (mail-header-lines header) 0) (current-buffer)) - (insert "\t") - (when (mail-header-xref header) - (insert "Xref: " (mail-header-xref header) "\t")) - (insert "\n")) + (insert "\n\n")) (defun nnheader-insert-article-line (article) (goto-char (point-min)) @@ -401,6 +464,7 @@ the line could be found." (save-excursion (unless (gnus-buffer-live-p nntp-server-buffer) (setq nntp-server-buffer (get-buffer-create " *nntpd*"))) + (mm-enable-multibyte) (set-buffer nntp-server-buffer) (erase-buffer) (kill-all-local-variables) @@ -447,7 +511,7 @@ the line could be found." nil (narrow-to-region (point-min) (1- (point))) (goto-char (point-min)) - (while (looking-at "[A-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n") + (while (looking-at "[a-zA-Z][^ \t]+:.*\n\\([ \t].*\n\\)*\\|From .*\n") (goto-char (match-end 0))) (prog1 (eobp) @@ -456,7 +520,8 @@ the line could be found." (defun nnheader-insert-references (references message-id) "Insert a References header based on REFERENCES and MESSAGE-ID." (if (and (not references) (not message-id)) - () ; This is illegal, but not all articles have Message-IDs. + ;; This is invalid, but not all articles have Message-IDs. + () (mail-position-on-field "References") (let ((begin (save-excursion (beginning-of-line) (point))) (fill-column 78) @@ -495,58 +560,12 @@ the line could be found." (defun nnheader-set-temp-buffer (name &optional noerase) "Set-buffer to an empty (possibly new) buffer called NAME with undo disabled." (set-buffer (get-buffer-create name)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (unless noerase (erase-buffer)) (current-buffer)) -(defmacro nnheader-temp-write (file &rest forms) - "Create a new buffer, evaluate FORMS there, and write the buffer to FILE. -Return the value of FORMS. -If FILE is nil, just evaluate FORMS and don't save anything. -If FILE is t, return the buffer contents as a string." - (let ((temp-file (make-symbol "temp-file")) - (temp-buffer (make-symbol "temp-buffer")) - (temp-results (make-symbol "temp-results"))) - `(save-excursion - (let* ((,temp-file ,file) - (default-major-mode 'fundamental-mode) - (,temp-buffer - (set-buffer - (get-buffer-create - (generate-new-buffer-name " *nnheader temp*")))) - ,temp-results) - (unwind-protect - (progn - (setq ,temp-results (progn ,@forms)) - (cond - ;; Don't save anything. - ((null ,temp-file) - ,temp-results) - ;; Return the buffer contents. - ((eq ,temp-file t) - (set-buffer ,temp-buffer) - (buffer-string)) - ;; Save a file. - (t - (set-buffer ,temp-buffer) - ;; Make sure the directory where this file is - ;; to be saved exists. - (when (not (file-directory-p - (file-name-directory ,temp-file))) - (make-directory (file-name-directory ,temp-file) t)) - ;; Save the file. - (write-region (point-min) (point-max) - ,temp-file nil 'nomesg) - ,temp-results))) - ;; Kill the buffer. - (when (buffer-name ,temp-buffer) - (kill-buffer ,temp-buffer))))))) - -(put 'nnheader-temp-write 'lisp-indent-function 1) -(put 'nnheader-temp-write 'edebug-form-spec '(form body)) - -(defvar jka-compr-compression-info-list) +(eval-when-compile (defvar jka-compr-compression-info-list)) (defvar nnheader-numerical-files (if (boundp 'jka-compr-compression-info-list) (concat "\\([0-9]+\\)\\(" @@ -563,7 +582,7 @@ If FILE is t, return the buffer contents as a string." "Regexp that matches numerical full file paths.") (defsubst nnheader-file-to-number (file) - "Take a file name and return the article number." + "Take a FILE name and return the article number." (if (string= nnheader-numerical-short-files "^[0-9]+$") (string-to-int file) (string-match nnheader-numerical-short-files file) @@ -581,7 +600,7 @@ If FILE is t, return the buffer contents as a string." second))) (defun nnheader-directory-articles (dir) - "Return a list of all article files in a directory." + "Return a list of all article files in directory DIR." (mapcar 'nnheader-file-to-number (nnheader-directory-files-safe dir nil nnheader-numerical-short-files t))) @@ -607,7 +626,9 @@ If FULL, translate everything." (if full ;; Do complete translation. (setq leaf (copy-sequence file) - path "") + path "" + i (if (and (< 1 (length leaf)) (eq ?: (aref leaf 1))) + 2 0)) ;; We translate -- but only the file name. We leave the directory ;; alone. (if (string-match "/[^/]+\\'" file) @@ -638,7 +659,7 @@ The first string in ARGS can be a format string." "Get the most recent report from BACKEND." (condition-case () (nnheader-message 5 "%s" (symbol-value (intern (format "%s-status-string" - backend)))) + backend)))) (error (nnheader-message 5 "")))) (defun nnheader-insert (format &rest args) @@ -653,15 +674,33 @@ without formatting." (apply 'insert format args)) t)) -(defun nnheader-replace-chars-in-string (string from to) +(if (fboundp 'subst-char-in-string) + (defsubst nnheader-replace-chars-in-string (string from to) + (subst-char-in-string from to string)) + (defun nnheader-replace-chars-in-string (string from to) + "Replace characters in STRING from FROM to TO." + (let ((string (substring string 0)) ;Copy string. + (len (length string)) + (idx 0)) + ;; Replace all occurrences of FROM with TO. + (while (< idx len) + (when (= (aref string idx) from) + (aset string idx to)) + (setq idx (1+ idx))) + string))) + +(defun nnheader-replace-duplicate-chars-in-string (string from to) "Replace characters in STRING from FROM to TO." (let ((string (substring string 0)) ;Copy string. (len (length string)) - (idx 0)) + (idx 0) prev i) ;; Replace all occurrences of FROM with TO. (while (< idx len) - (when (= (aref string idx) from) + (setq i (aref string idx)) + (when (and (eq prev from) (= i from)) + (aset string (1- idx) to) (aset string idx to)) + (setq prev i) (setq idx (1+ idx))) string)) @@ -690,12 +729,7 @@ without formatting." (or (not (numberp gnus-verbose-backends)) (<= level gnus-verbose-backends))) -(defvar nnheader-pathname-coding-system 'iso-8859-1 - "*Coding system for pathname.") - -;; 1997/8/10 by MORIOKA Tomohiko -(defvar nnheader-pathname-coding-system - 'iso-8859-1 +(defvar nnheader-pathname-coding-system 'binary "*Coding system for pathname.") (defun nnheader-group-pathname (group dir &optional file) @@ -703,14 +737,14 @@ without formatting." (concat (let ((dir (file-name-as-directory (expand-file-name dir)))) ;; If this directory exists, we use it directly. - (if (file-directory-p (concat dir group)) - (concat dir group "/") - ;; If not, we translate dots into slashes. - (concat dir - (gnus-encode-coding-string - (nnheader-replace-chars-in-string group ?. ?/) - nnheader-pathname-coding-system) - "/"))) + (file-name-as-directory + (if (file-directory-p (concat dir group)) + (expand-file-name group dir) + ;; If not, we translate dots into slashes. + (expand-file-name (mm-encode-coding-string + (nnheader-replace-chars-in-string group ?. ?/) + nnheader-pathname-coding-system) + dir)))) (cond ((null file) "") ((numberp file) (int-to-string file)) (t file)))) @@ -721,7 +755,7 @@ without formatting." (and (listp form) (eq (car form) 'lambda)))) (defun nnheader-concat (dir &rest files) - "Concat DIR as directory to FILE." + "Concat DIR as directory to FILES." (apply 'concat (file-name-as-directory dir) files)) (defun nnheader-ms-strip-cr () @@ -770,45 +804,26 @@ If FILE, find the \".../etc/PACKAGE\" file instead." (defvar nnheader-file-coding-system 'raw-text "Coding system used in file backends of Gnus.") -;; 1997/5/4 by MORIOKA Tomohiko -(defvar nnheader-file-coding-system nil - "Coding system used in file backends of Gnus.") - (defun nnheader-insert-file-contents (filename &optional visit beg end replace) "Like `insert-file-contents', q.v., but only reads in the file. A buffer may be modified in several ways after reading into the buffer due to advanced Emacs features, such as file-name-handlers, format decoding, find-file-hooks, etc. This function ensures that none of these modifications will take place." - (let ((format-alist nil) - (auto-mode-alist (nnheader-auto-mode-alist)) - (default-major-mode 'fundamental-mode) - (enable-local-variables nil) - (after-insert-file-functions nil) - (find-file-hooks nil) - (coding-system-for-read nnheader-file-coding-system)) - (insert-file-contents filename visit beg end replace))) + (let ((coding-system-for-read nnheader-file-coding-system)) + (mm-insert-file-contents filename visit beg end replace))) (defun nnheader-find-file-noselect (&rest args) (let ((format-alist nil) - (auto-mode-alist (nnheader-auto-mode-alist)) + (auto-mode-alist (mm-auto-mode-alist)) (default-major-mode 'fundamental-mode) (enable-local-variables nil) (after-insert-file-functions nil) + (enable-local-eval nil) (find-file-hooks nil) (coding-system-for-read nnheader-file-coding-system)) (apply 'find-file-noselect args))) -(defun nnheader-auto-mode-alist () - "Return an `auto-mode-alist' with only the .gz (etc) thingies." - (let ((alist auto-mode-alist) - out) - (while alist - (when (listp (cdar alist)) - (push (car alist) out)) - (pop alist)) - (nreverse out))) - (defun nnheader-directory-regular-files (dir) "Return a list of all regular files in DIR." (let ((files (directory-files dir t)) @@ -833,8 +848,6 @@ find-file-hooks, etc. `(let ((new (generate-new-buffer " *nnheader replace*")) (cur (current-buffer)) (start (point-min))) - (set-buffer new) - (buffer-disable-undo (current-buffer)) (set-buffer cur) (goto-char (point-min)) (while (,(if regexp 're-search-forward 'search-forward) @@ -852,22 +865,22 @@ find-file-hooks, etc. (set-buffer cur))) (defun nnheader-replace-string (from to) - "Do a fast replacement of FROM to TO from point to point-max." + "Do a fast replacement of FROM to TO from point to `point-max'." (nnheader-skeleton-replace from to)) (defun nnheader-replace-regexp (from to) - "Do a fast regexp replacement of FROM to TO from point to point-max." + "Do a fast regexp replacement of FROM to TO from point to `point-max'." (nnheader-skeleton-replace from to t)) (defun nnheader-strip-cr () "Strip all \r's from the current buffer." (nnheader-skeleton-replace "\r")) -(fset 'nnheader-run-at-time 'run-at-time) -(fset 'nnheader-cancel-timer 'cancel-timer) -(fset 'nnheader-cancel-function-timers 'cancel-function-timers) +(defalias 'nnheader-run-at-time 'run-at-time) +(defalias 'nnheader-cancel-timer 'cancel-timer) +(defalias 'nnheader-cancel-function-timers 'cancel-function-timers) -(when (string-match "XEmacs\\|Lucid" emacs-version) +(when (string-match "XEmacs" emacs-version) (require 'nnheaderxm)) (run-hooks 'nnheader-load-hook) diff --git a/lisp/gnus/nnkiboze.el b/lisp/gnus/nnkiboze.el index c47a10d3911..864a18acb9e 100644 --- a/lisp/gnus/nnkiboze.el +++ b/lisp/gnus/nnkiboze.el @@ -1,5 +1,7 @@ ;;; nnkiboze.el --- select virtual news access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999,.2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -136,7 +138,7 @@ ;; Remove NOV lines of articles that are marked as read. (when (and (file-exists-p (nnkiboze-nov-file-name)) nnkiboze-remove-read-articles) - (nnheader-temp-write (nnkiboze-nov-file-name) + (with-temp-file (nnkiboze-nov-file-name) (let ((cur (current-buffer))) (nnheader-insert-file-contents (nnkiboze-nov-file-name)) (goto-char (point-min)) @@ -155,15 +157,15 @@ (deffoo nnkiboze-request-delete-group (group &optional force server) (nnkiboze-possibly-change-group group) (when force - (let ((files (nconc - (nnkiboze-score-file group) - (list (nnkiboze-nov-file-name) - (nnkiboze-nov-file-name ".newsrc"))))) - (while files - (and (file-exists-p (car files)) - (file-writable-p (car files)) - (delete-file (car files))) - (setq files (cdr files))))) + (let ((files (nconc + (nnkiboze-score-file group) + (list (nnkiboze-nov-file-name) + (nnkiboze-nov-file-name ".newsrc"))))) + (while files + (and (file-exists-p (car files)) + (file-writable-p (car files)) + (delete-file (car files))) + (setq files (cdr files))))) (setq nnkiboze-current-group nil) t) @@ -184,6 +186,7 @@ Finds out what articles are to be part of the nnkiboze groups." (interactive) (let ((nnmail-spool-file nil) + (mail-sources nil) (gnus-use-dribble-file nil) (gnus-read-active-file t) (gnus-expert-user t)) @@ -209,7 +212,7 @@ Finds out what articles are to be part of the nnkiboze groups." (defun nnkiboze-generate-group (group) (let* ((info (nth 2 (gnus-gethash group gnus-newsrc-hashtb))) - (newsrc-file (concat nnkiboze-directory + (newsrc-file (concat nnkiboze-directory (nnheader-translate-file-chars (concat group ".newsrc")))) (nov-file (concat nnkiboze-directory @@ -230,7 +233,7 @@ Finds out what articles are to be part of the nnkiboze groups." ;; Load the kiboze newsrc file for this group. (when (file-exists-p newsrc-file) (load newsrc-file)) - (nnheader-temp-write nov-file + (with-temp-file nov-file (when (file-exists-p nov-file) (insert-file-contents nov-file)) (setq nov-buffer (current-buffer)) @@ -287,7 +290,7 @@ Finds out what articles are to be part of the nnkiboze groups." (car ginfo))) 0)) (progn - (ignore-errors + (ignore-errors (gnus-group-select-group nil)) (eq major-mode 'gnus-summary-mode))) ;; We are now in the group where we want to be. @@ -318,7 +321,7 @@ Finds out what articles are to be part of the nnkiboze groups." (gnus-message 3 "nnkiboze: Checking %s...done" (caar newsrc)) (setq newsrc (cdr newsrc)))) ;; We save the kiboze newsrc for this group. - (nnheader-temp-write newsrc-file + (with-temp-file newsrc-file (insert "(setq nnkiboze-newsrc '") (gnus-prin1 nnkiboze-newsrc) (insert ")\n"))) diff --git a/lisp/gnus/nnlistserv.el b/lisp/gnus/nnlistserv.el index e1d50aa732e..d6db1f3dec3 100644 --- a/lisp/gnus/nnlistserv.el +++ b/lisp/gnus/nnlistserv.el @@ -1,5 +1,6 @@ ;;; nnlistserv.el --- retrieving articles via web mailing list archives -;; Copyright (C) 1997,98 Free Software Foundation, Inc. + +;; Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news, mail @@ -31,8 +32,7 @@ (eval-when-compile (require 'cl)) (require 'nnoo) -(eval-when-compile (ignore-errors (require 'nnweb))) -(eval '(require 'nnweb)) +(require 'nnweb) (nnoo-declare nnlistserv nnweb) @@ -46,15 +46,15 @@ nnweb-type) (defvoo nnlistserv-type-definition - '((kk - (article . nnlistserv-kk-wash-article) - (map . nnlistserv-kk-create-mapping) - (search . nnlistserv-kk-search) - (address . "http://www.itk.ntnu.no/ansatte/Andresen_Trond/kk-f/%s/") - (pages "fra160396" "fra160796" "fra061196" "fra160197" - "fra090997" "fra040797" "fra130397" "nye") - (index . "date.html") - (identifier . nnlistserv-kk-identity))) + '((kk + (article . nnlistserv-kk-wash-article) + (map . nnlistserv-kk-create-mapping) + (search . nnlistserv-kk-search) + (address . "http://www.itk.ntnu.no/ansatte/Andresen_Trond/kk-f/%s/") + (pages "fra160396" "fra160796" "fra061196" "fra160197" + "fra090997" "fra040797" "fra130397" "nye") + (index . "date.html") + (identifier . nnlistserv-kk-identity))) "Type-definition alist." nnweb-type-definition) @@ -112,8 +112,7 @@ nil 0 0 url)) map) (nnweb-set-hashtb (cadar map) (car map)) - (nnheader-message 5 "%s %s %s" (cdr active) (point) pages) - )))) + (nnheader-message 5 "%s %s %s" (cdr active) (point) pages))))) ;; Return the articles in the right order. (setq nnweb-articles (sort (nconc nnweb-articles map) 'car-less-than-car))))) @@ -142,7 +141,7 @@ (defun nnlistserv-kk-search (search) (url-insert-file-contents - (concat (format (nnweb-definition 'address) search) + (concat (format (nnweb-definition 'address) search) (nnweb-definition 'index))) t) diff --git a/lisp/gnus/nnmail.el b/lisp/gnus/nnmail.el index 5b67668cac5..be8984866d7 100644 --- a/lisp/gnus/nnmail.el +++ b/lisp/gnus/nnmail.el @@ -1,5 +1,6 @@ ;;; nnmail.el --- mail support functions for the Gnus mail backends -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news, mail @@ -28,15 +29,15 @@ (eval-when-compile (require 'cl)) (require 'nnheader) -(require 'timezone) (require 'message) (require 'custom) (require 'gnus-util) +(require 'mail-source) +(require 'mm-util) (eval-and-compile (autoload 'gnus-error "gnus-util") - (autoload 'gnus-buffer-live-p "gnus-util") - (autoload 'gnus-encode-coding-string "gnus-ems")) + (autoload 'gnus-buffer-live-p "gnus-util")) (defgroup nnmail nil "Reading mail with Gnus." @@ -166,54 +167,42 @@ Eg.: :type '(choice (const :tag "nnmail-expiry-wait" nil) (function :format "%v" nnmail-))) +(defcustom nnmail-expiry-target 'delete + "*Variable that says where expired messages should end up. +The default value is `delete' (which says to delete the messages), +but it can also be a string or a function. If it is a string, expired +messages end up in that group. If it is a function, the function is +called in a buffer narrowed to the message in question. The function +receives one argument, the name of the group the message comes from. +The return value should be `delete' or a group name (a string)." + :group 'nnmail-expire + :type '(choice (const delete) + (function :format "%v" nnmail-) + string)) + (defcustom nnmail-cache-accepted-message-ids nil "If non-nil, put Message-IDs of Gcc'd articles into the duplicate cache." :group 'nnmail :type 'boolean) -(defcustom nnmail-spool-file - (or (getenv "MAIL") - (concat "/usr/spool/mail/" (user-login-name))) +(defcustom nnmail-spool-file '((file)) "*Where the mail backends will look for incoming mail. -This variable is \"/usr/spool/mail/$user\" by default. -If this variable is nil, no mail backends will read incoming mail. -If this variable is a list, all files mentioned in this list will be -used as incoming mailboxes. -If this variable is a directory (i. e., it's name ends with a \"/\"), -treat all files in that directory as incoming spool files." +This variable is a list of mail source specifiers. +This variable is obsolete; `mail-sources' should be used instead." :group 'nnmail-files - :type '(choice (file :tag "File") - (repeat :tag "Files" file))) - -(defcustom nnmail-crash-box "~/.gnus-crash-box" - "File where Gnus will store mail while processing it." - :group 'nnmail-files - :type 'file) - -(defcustom nnmail-use-procmail nil - "*If non-nil, the mail backends will look in `nnmail-procmail-directory' for spool files. -The file(s) in `nnmail-spool-file' will also be read." - :group 'nnmail-procmail - :type 'boolean) - -(defcustom nnmail-procmail-directory "~/incoming/" - "*When using procmail (and the like), incoming mail is put in this directory. -The Gnus mail backends will read the mail from this directory." - :group 'nnmail-procmail - :type 'directory) - -(defcustom nnmail-procmail-suffix "\\.spool" - "*Suffix of files created by procmail (and the like). -This variable might be a suffix-regexp to match the suffixes of -several files - eg. \".spool[0-9]*\"." - :group 'nnmail-procmail - :type 'regexp) + :type 'sexp) (defcustom nnmail-resplit-incoming nil "*If non-nil, re-split incoming procmail sorted mail." :group 'nnmail-procmail :type 'boolean) +(defcustom nnmail-scan-directory-mail-source-once nil + "*If non-nil, scan all incoming procmail sorted mails once. +It scans low-level sorted spools even when not required." + :group 'nnmail-procmail + :type 'boolean) + (defcustom nnmail-delete-file-function 'delete-file "Function called to delete files in some mail backends." :group 'nnmail-files @@ -232,22 +221,6 @@ links, you could set this variable to `copy-file' instead." (function-item copy-file) (function :tag "Other"))) -(defcustom nnmail-movemail-program "movemail" - "*A command to be executed to move mail from the inbox. -The default is \"movemail\". - -This can also be a function. In that case, the function will be -called with two parameters -- the name of the INBOX file, and the file -to be moved to." - :group 'nnmail-files - :group 'nnmail-retrieve - :type 'string) - -(defcustom nnmail-pop-password-required nil - "*Non-nil if a password is required when reading mail using POP." - :group 'nnmail-retrieve - :type 'boolean) - (defcustom nnmail-read-incoming-hook (if (eq system-type 'windows-nt) '(nnheader-ms-strip-cr) @@ -262,9 +235,9 @@ running (\"xwatch\", etc.) Eg. \(add-hook 'nnmail-read-incoming-hook - (lambda () - (start-process \"mailsend\" nil - \"/local/bin/mailsend\" \"read\" \"mbox\"))) + (lambda () + (call-process \"/local/bin/mailsend\" nil nil nil + \"read\" nnmail-spool-file))) If you have xwatch running, this will alert it that mail has been read. @@ -280,7 +253,6 @@ If you use `display-time', you could use something like this: :group 'nnmail-prepare :type 'hook) -;; Suggested by Erik Selberg . (defcustom nnmail-prepare-incoming-hook nil "Hook called before treating incoming mail. The hook is run in a buffer with all the new, incoming mail." @@ -325,15 +297,6 @@ discarded after running the split process." :group 'nnmail-split :type 'hook) -;; Suggested by Mejia Pablo J . -(defcustom nnmail-tmp-directory nil - "*If non-nil, use this directory for temporary storage. -Used when reading incoming mail." - :group 'nnmail-files - :group 'nnmail-retrieve - :type '(choice (const :tag "default" nil) - (directory :format "%v"))) - (defcustom nnmail-large-newsgroup 50 "*The number of the articles which indicates a large newsgroup. If the number of the articles is greater than the value, verbose @@ -350,8 +313,12 @@ the following: GROUP: Mail will be stored in GROUP (a string). -\(FIELD VALUE SPLIT): If the message field FIELD (a regexp) contains - VALUE (a regexp), store the messages as specified by SPLIT. +\(FIELD VALUE [- RESTRICT [- RESTRICT [...]]] SPLIT): If the message + field FIELD (a regexp) contains VALUE (a regexp), store the messages + as specified by SPLIT. If RESTRICT (a regexp) matches some string + after FIELD and before the end of the matched VALUE, return NIL, + otherwise process SPLIT. Multiple RESTRICTs add up, further + restricting the possibility of processing SPLIT. \(| SPLIT...): Process each SPLIT expression until one of them matches. A SPLIT expression is said to match if it will cause the mail @@ -363,6 +330,10 @@ GROUP: Mail will be stored in GROUP (a string). the buffer containing the message headers. The return value FUNCTION should be a split, which is then recursively processed. +\(! FUNCTION SPLIT): Call FUNCTION with the result of SPLIT. The + return value FUNCTION should be a split, which is then recursively + processed. + FIELD must match a complete field name. VALUE must match a complete word according to the `nnmail-split-fancy-syntax-table' syntax table. You can use \".*\" in the regexps to match partial field names or words. @@ -390,6 +361,13 @@ Example: ;; Other mailing lists... (any \"procmail@informatik\\\\.rwth-aachen\\\\.de\" \"procmail.list\") (any \"SmartList@informatik\\\\.rwth-aachen\\\\.de\" \"SmartList.list\") + ;; Both lists below have the same suffix, so prevent + ;; cross-posting to mkpkg.list of messages posted only to + ;; the bugs- list, but allow cross-posting when the + ;; message was really cross-posted. + (any \"bugs-mypackage@somewhere\" \"mypkg.bugs\") + (any \"mypackage@somewhere\" - \"bugs-mypackage\" \"mypkg.list\") + ;; ;; People... (any \"larsi@ifi\\\\.uio\\\\.no\" \"people.Lars Magne Ingebrigtsen\")) ;; Unmatched mail goes to the catch all group. @@ -409,12 +387,6 @@ Example: :group 'nnmail-split :type '(repeat (cons :format "%v" symbol regexp))) -(defcustom nnmail-delete-incoming t - "*If non-nil, the mail backends will delete incoming files after -splitting." - :group 'nnmail-retrieve - :type 'boolean) - (defcustom nnmail-message-id-cache-length 1000 "*The approximate number of Message-IDs nnmail will keep in its cache. If this variable is nil, no checking on duplicate messages will be @@ -431,7 +403,7 @@ performed." (defcustom nnmail-treat-duplicates 'warn "*If non-nil, nnmail keep a cache of Message-IDs to discover mail duplicates. -Three values are legal: nil, which means that nnmail is not to keep a +Three values are valid: nil, which means that nnmail is not to keep a Message-ID cache; `warn', which means that nnmail should insert extra headers to warn the user about the duplication (this is the default); and `delete', which means that nnmail will delete duplicated mails. @@ -444,16 +416,21 @@ parameter. It should return nil, `warn' or `delete'." (const warn) (const delete))) +(defcustom nnmail-extra-headers nil + "*Extra headers to parse." + :group 'nnmail + :type '(repeat symbol)) + +(defcustom nnmail-split-header-length-limit 512 + "Header lines longer than this limit are excluded from the split function." + :group 'nnmail + :type 'integer) + ;;; Internal variables. (defvar nnmail-split-history nil "List of group/article elements that say where the previous split put messages.") -(defvar nnmail-current-spool nil) - -(defvar nnmail-pop-password nil - "*Password to use when reading mail from a POP server, if required.") - (defvar nnmail-split-fancy-syntax-table nil "Syntax table used by `nnmail-split-fancy'.") (unless (syntax-table-p nnmail-split-fancy-syntax-table) @@ -465,11 +442,6 @@ parameter. It should return nil, `warn' or `delete'." (defvar nnmail-prepare-save-mail-hook nil "Hook called before saving mail.") -(defvar nnmail-moved-inboxes nil - "List of inboxes that have been moved.") - -(defvar nnmail-internal-password nil) - (defvar nnmail-split-tracing nil) (defvar nnmail-split-trace nil) @@ -486,231 +458,90 @@ parameter. It should return nil, `warn' or `delete'." (defvar nnmail-file-coding-system 'raw-text "Coding system used in nnmail.") -(defvar nnmail-file-coding-system nil - "Coding system used in nnmail.") +(defvar nnmail-incoming-coding-system + mm-text-coding-system + "Coding system used in reading inbox") + +(defvar nnmail-pathname-coding-system 'binary + "*Coding system for pathname.") (defun nnmail-find-file (file) "Insert FILE in server buffer safely." (set-buffer nntp-server-buffer) - (erase-buffer) + (delete-region (point-min) (point-max)) (let ((format-alist nil) (after-insert-file-functions nil)) (condition-case () (let ((coding-system-for-read nnmail-file-coding-system) - (file-name-coding-system 'binary) - (pathname-coding-system 'binary)) + (auto-mode-alist (mm-auto-mode-alist)) + (file-name-coding-system nnmail-pathname-coding-system)) (insert-file-contents file) t) (file-error nil)))) -(defvar nnmail-pathname-coding-system - 'iso-8859-1 - "*Coding system for pathname.") - (defun nnmail-group-pathname (group dir &optional file) "Make pathname for GROUP." (concat (let ((dir (file-name-as-directory (expand-file-name dir)))) + (setq group (nnheader-replace-duplicate-chars-in-string + (nnheader-replace-chars-in-string group ?/ ?_) + ?. ?_)) (setq group (nnheader-translate-file-chars group)) ;; If this directory exists, we use it directly. - (if (or nnmail-use-long-file-names - (file-directory-p (concat dir group))) - (concat dir group "/") - ;; If not, we translate dots into slashes. - (concat dir - (gnus-encode-coding-string - (nnheader-replace-chars-in-string group ?. ?/) - nnmail-pathname-coding-system) - "/"))) + (file-name-as-directory + (if (or nnmail-use-long-file-names + (file-directory-p (concat dir group))) + (expand-file-name group dir) + ;; If not, we translate dots into slashes. + (expand-file-name + (mm-encode-coding-string + (nnheader-replace-chars-in-string group ?. ?/) + nnmail-pathname-coding-system) + dir)))) (or file ""))) -(defun nnmail-date-to-time (date) - "Convert DATE into time." - (condition-case () - (let* ((d1 (timezone-parse-date date)) - (t1 (timezone-parse-time (aref d1 3)))) - (apply 'encode-time - (mapcar (lambda (el) - (and el (string-to-number el))) - (list - (aref t1 2) (aref t1 1) (aref t1 0) - (aref d1 2) (aref d1 1) (aref d1 0) - (number-to-string - (* 60 (timezone-zone-to-minute - (or (aref d1 4) (current-time-zone))))))))) - ;; If we get an error, then we just return a 0 time. - (error (list 0 0)))) - -(defun nnmail-time-less (t1 t2) - "Say whether time T1 is less than time T2." - (or (< (car t1) (car t2)) - (and (= (car t1) (car t2)) - (< (nth 1 t1) (nth 1 t2))))) - -(defun nnmail-days-to-time (days) - "Convert DAYS into time." - (let* ((seconds (* 1.0 days 60 60 24)) - (rest (expt 2 16)) - (ms (condition-case nil (floor (/ seconds rest)) - (range-error (expt 2 16))))) - (list ms (condition-case nil (round (- seconds (* ms rest))) - (range-error (expt 2 16)))))) - -(defun nnmail-time-since (time) - "Return the time since TIME, which is either an internal time or a date." - (when (stringp time) - ;; Convert date strings to internal time. - (setq time (nnmail-date-to-time time))) - (let* ((current (current-time)) - (rest (when (< (nth 1 current) (nth 1 time)) - (expt 2 16)))) - (list (- (+ (car current) (if rest -1 0)) (car time)) - (- (+ (or rest 0) (nth 1 current)) (nth 1 time))))) - -;; Function rewritten from rmail.el. -(defun nnmail-move-inbox (inbox) - "Move INBOX to `nnmail-crash-box'." - (if (not (file-writable-p nnmail-crash-box)) - (gnus-error 1 "Can't write to crash box %s. Not moving mail" - nnmail-crash-box) - ;; If the crash box exists and is empty, we delete it. - (when (and (file-exists-p nnmail-crash-box) - (zerop (nnheader-file-size (file-truename nnmail-crash-box)))) - (delete-file nnmail-crash-box)) - (let ((tofile (file-truename (expand-file-name nnmail-crash-box))) - (popmail (string-match "^po:" inbox)) - movemail errors result) - (unless popmail - (setq inbox (file-truename (expand-file-name inbox))) - (setq movemail t) - ;; On some systems, /usr/spool/mail/foo is a directory - ;; and the actual inbox is /usr/spool/mail/foo/foo. - (when (file-directory-p inbox) - (setq inbox (expand-file-name (user-login-name) inbox)))) - (if (member inbox nnmail-moved-inboxes) - ;; We don't try to move an already moved inbox. - nil - (if popmail - (progn - (when (and nnmail-pop-password - (not nnmail-internal-password)) - (setq nnmail-internal-password nnmail-pop-password)) - (when (and nnmail-pop-password-required - (not nnmail-internal-password)) - (setq nnmail-internal-password - (nnmail-read-passwd - (format "Password for %s: " - (substring inbox (+ popmail 3)))))) - (nnheader-message 5 "Getting mail from the post office...")) - (when (or (and (file-exists-p tofile) - (/= 0 (nnheader-file-size tofile))) - (and (file-exists-p inbox) - (/= 0 (nnheader-file-size inbox)))) - (nnheader-message 5 "Getting mail from %s..." inbox))) - ;; Set TOFILE if have not already done so, and - ;; rename or copy the file INBOX to TOFILE if and as appropriate. - (cond - ((file-exists-p tofile) - ;; The crash box exists already. - t) - ((and (not popmail) - (not (file-exists-p inbox))) - ;; There is no inbox. - (setq tofile nil)) - (t - ;; If getting from mail spool directory, use movemail to move - ;; rather than just renaming, so as to interlock with the - ;; mailer. - (unwind-protect - (save-excursion - (setq errors (generate-new-buffer " *nnmail loss*")) - (buffer-disable-undo errors) - (if (nnheader-functionp nnmail-movemail-program) - (condition-case err - (progn - (funcall nnmail-movemail-program inbox tofile) - (setq result 0)) - (error - (save-excursion - (set-buffer errors) - (insert (prin1-to-string err)) - (setq result 255)))) - (let ((default-directory "/")) - (setq result - (apply - 'call-process - (append - (list - (expand-file-name - nnmail-movemail-program exec-directory) - nil errors nil inbox tofile) - (when nnmail-internal-password - (list nnmail-internal-password))))))) - (push inbox nnmail-moved-inboxes) - (if (and (not (buffer-modified-p errors)) - (zerop result)) - ;; No output => movemail won - (progn - (unless popmail - (when (file-exists-p tofile) - (set-file-modes tofile nnmail-default-file-modes)))) - (set-buffer errors) - ;; There may be a warning about older revisions. We - ;; ignore those. - (goto-char (point-min)) - (if (search-forward "older revision" nil t) - (progn - (unless popmail - (when (file-exists-p tofile) - (set-file-modes - tofile nnmail-default-file-modes)))) - ;; Probably a real error. - ;; We nix out the password in case the error - ;; was because of a wrong password being given. - (setq nnmail-internal-password nil) - (subst-char-in-region (point-min) (point-max) ?\n ?\ ) - (goto-char (point-max)) - (skip-chars-backward " \t") - (delete-region (point) (point-max)) - (goto-char (point-min)) - (when (looking-at "movemail: ") - (delete-region (point-min) (match-end 0))) - (unless (yes-or-no-p - (format "movemail: %s (%d return). Continue? " - (buffer-string) result)) - (error "%s" (buffer-string))) - (setq tofile nil))))))) - (nnheader-message 5 "Getting mail from %s...done" inbox) - (and errors - (buffer-name errors) - (kill-buffer errors)) - tofile)))) - (defun nnmail-get-active () "Returns an assoc of group names and active ranges. nn*-request-list should have been called before calling this function." - (let (group-assoc) - ;; Go through all groups from the active list. - (save-excursion - (set-buffer nntp-server-buffer) - (goto-char (point-min)) - (while (re-search-forward - "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)" nil t) - ;; We create an alist with `(GROUP (LOW . HIGH))' elements. - (push (list (match-string 1) - (cons (string-to-int (match-string 3)) - (string-to-int (match-string 2)))) - group-assoc))) + ;; Go through all groups from the active list. + (save-excursion + (set-buffer nntp-server-buffer) + (nnmail-parse-active))) + +(defun nnmail-parse-active () + "Parse the active file in the current buffer and return an alist." + (goto-char (point-min)) + (unless (re-search-forward "[\\\"]" nil t) + (goto-char (point-max)) + (while (re-search-backward "[][';?()#]" nil t) + (insert ?\\))) + (goto-char (point-min)) + (let ((buffer (current-buffer)) + group-assoc group max min) + (while (not (eobp)) + (condition-case err + (progn + (narrow-to-region (point) (gnus-point-at-eol)) + (setq group (read buffer)) + (unless (stringp group) + (setq group (symbol-name group))) + (if (and (numberp (setq max (read nntp-server-buffer))) + (numberp (setq min (read nntp-server-buffer)))) + (push (list group (cons min max)) + group-assoc))) + (error nil)) + (widen) + (forward-line 1)) group-assoc)) -(defvar nnmail-active-file-coding-system 'binary +(defvar nnmail-active-file-coding-system 'raw-text "*Coding system for active file.") (defun nnmail-save-active (group-assoc file-name) "Save GROUP-ASSOC in ACTIVE-FILE." (let ((coding-system-for-write nnmail-active-file-coding-system)) (when file-name - (nnheader-temp-write file-name + (with-temp-file file-name (nnmail-generate-active group-assoc))))) (defun nnmail-generate-active (alist) @@ -718,37 +549,26 @@ nn*-request-list should have been called before calling this function." (erase-buffer) (let (group) (while (setq group (pop alist)) - (insert (format "%s %d %d y\n" (car group) (cdadr group) - (caadr group)))))) + (insert (format "%S %d %d y\n" (intern (car group)) (cdadr group) + (caadr group)))) + (goto-char (point-max)) + (while (search-backward "\\." nil t) + (delete-char 1)))) -(defun nnmail-get-split-group (file group) +(defun nnmail-get-split-group (file source) "Find out whether this FILE is to be split into GROUP only. -If GROUP is non-nil and we are using procmail, return the group name -only when the file is the correct procmail file. When GROUP is nil, -return nil if FILE is a spool file or the procmail group for which it -is a spool. If not using procmail, return GROUP." - (if (or (eq nnmail-spool-file 'procmail) - nnmail-use-procmail) - (if (string-match (concat "^" (regexp-quote - (expand-file-name - (file-name-as-directory - nnmail-procmail-directory))) - "\\([^/]*\\)" - nnmail-procmail-suffix "$") - (expand-file-name file)) - (let ((procmail-group (substring (expand-file-name file) - (match-beginning 1) - (match-end 1)))) - (if group - (if (string-equal group procmail-group) - group - nil) - procmail-group)) - nil) - group)) +If SOURCE is a directory spec, try to return the group name component." + (if (eq (car source) 'directory) + (let ((file (file-name-nondirectory file))) + (mail-source-bind (directory source) + (if (string-match (concat (regexp-quote suffix) "$") file) + (substring file 0 (match-beginning 0)) + nil))) + nil)) (defun nnmail-process-babyl-mail-format (func artnum-func) (let ((case-fold-search t) + (count 0) start message-id content-length do-search end) (while (not (eobp)) (goto-char (point-min)) @@ -820,8 +640,10 @@ is a spool. If not using procmail, return GROUP." (narrow-to-region start (point)) (goto-char (point-min)) (nnmail-check-duplication message-id func artnum-func) + (incf count) (setq end (point-max)))) - (goto-char end)))) + (goto-char end)) + count)) (defsubst nnmail-search-unix-mail-delim () "Put point at the beginning of the next Unix mbox message." @@ -842,7 +664,7 @@ is a spool. If not using procmail, return GROUP." (when (and (or (bobp) (save-excursion (forward-line -1) - (= (following-char) ?\n))) + (eq (char-after) ?\n))) (save-excursion (forward-line 1) (while (looking-at ">From \\|From ") @@ -871,7 +693,7 @@ is a spool. If not using procmail, return GROUP." (when (and (or (bobp) (save-excursion (forward-line -1) - (= (following-char) ?\n))) + (eq (char-after) ?\n))) (save-excursion (forward-line 1) (while (looking-at ">From \\|From ") @@ -883,14 +705,13 @@ is a spool. If not using procmail, return GROUP." (defun nnmail-process-unix-mail-format (func artnum-func) (let ((case-fold-search t) + (count 0) start message-id content-length end skip head-end) (goto-char (point-min)) (if (not (and (re-search-forward "^From " nil t) (goto-char (match-beginning 0)))) ;; Possibly wrong format? - (progn - (pop-to-buffer (nnheader-find-file-noselect nnmail-current-spool)) - (error "Error, unknown mail format! (Possibly corrupted.)")) + (error "Error, unknown mail format! (Possibly corrupted.)") ;; Carry on until the bitter end. (while (not (eobp)) (setq start (point) @@ -963,21 +784,22 @@ is a spool. If not using procmail, return GROUP." (save-restriction (narrow-to-region start (point)) (goto-char (point-min)) + (incf count) (nnmail-check-duplication message-id func artnum-func) (setq end (point-max)))) - (goto-char end))))) + (goto-char end))) + count)) (defun nnmail-process-mmdf-mail-format (func artnum-func) (let ((delim "^\^A\^A\^A\^A$") (case-fold-search t) + (count 0) start message-id end) (goto-char (point-min)) (if (not (and (re-search-forward delim nil t) (forward-line 1))) ;; Possibly wrong format? - (progn - (pop-to-buffer (nnheader-find-file-noselect nnmail-current-spool)) - (error "Error, unknown mail format! (Possibly corrupted.)")) + (error "Error, unknown mail format! (Possibly corrupted.)") ;; Carry on until the bitter end. (while (not (eobp)) (setq start (point)) @@ -1015,10 +837,47 @@ is a spool. If not using procmail, return GROUP." (save-restriction (narrow-to-region start (point)) (goto-char (point-min)) + (incf count) (nnmail-check-duplication message-id func artnum-func) (setq end (point-max)))) (goto-char end) - (forward-line 2))))) + (forward-line 2))) + count)) + +(defun nnmail-process-maildir-mail-format (func artnum-func) + ;; In a maildir, every file contains exactly one mail. + (let ((case-fold-search t) + message-id) + (goto-char (point-min)) + ;; Find the end of the head. + (narrow-to-region + (point-min) + (if (search-forward "\n\n" nil t) + (1- (point)) + ;; This will never happen, but just to be on the safe side -- + ;; if there is no head-body delimiter, we search a bit manually. + (while (and (looking-at "From \\|[^ \t]+:") + (not (eobp))) + (forward-line 1)) + (point))) + ;; Find the Message-ID header. + (goto-char (point-min)) + (if (re-search-forward "^Message-ID:[ \t]*\\(<[^>]+>\\)" nil t) + (setq message-id (match-string 1)) + ;; There is no Message-ID here, so we create one. + (save-excursion + (when (re-search-backward "^Message-ID[ \t]*:" nil t) + (beginning-of-line) + (insert "Original-"))) + (forward-line 1) + (insert "Message-ID: " (setq message-id (nnmail-message-id)) "\n")) + (run-hooks 'nnmail-prepare-incoming-header-hook) + ;; Allow the backend to save the article. + (widen) + (save-excursion + (goto-char (point-min)) + (nnmail-check-duplication message-id func artnum-func)) + 1)) (defun nnmail-split-incoming (incoming func &optional exit-func group artnum-func) @@ -1027,33 +886,35 @@ FUNC will be called with the buffer narrowed to each mail." (let (;; If this is a group-specific split, we bind the split ;; methods to just this group. (nnmail-split-methods (if (and group - (or (eq nnmail-spool-file 'procmail) - nnmail-use-procmail) (not nnmail-resplit-incoming)) (list (list group "")) nnmail-split-methods))) (save-excursion ;; Insert the incoming file. (set-buffer (get-buffer-create " *nnmail incoming*")) - (buffer-disable-undo (current-buffer)) (erase-buffer) - (nnheader-insert-file-contents incoming) - (unless (zerop (buffer-size)) - (goto-char (point-min)) - (save-excursion (run-hooks 'nnmail-prepare-incoming-hook)) - ;; Handle both babyl, MMDF and unix mail formats, since movemail will - ;; use the former when fetching from a mailbox, the latter when - ;; fetching from a file. - (cond ((or (looking-at "\^L") - (looking-at "BABYL OPTIONS:")) - (nnmail-process-babyl-mail-format func artnum-func)) - ((looking-at "\^A\^A\^A\^A") - (nnmail-process-mmdf-mail-format func artnum-func)) - (t - (nnmail-process-unix-mail-format func artnum-func)))) - (when exit-func - (funcall exit-func)) - (kill-buffer (current-buffer))))) + (let ((coding-system-for-read nnmail-incoming-coding-system)) + (mm-insert-file-contents incoming)) + (prog1 + (if (zerop (buffer-size)) + 0 + (goto-char (point-min)) + (save-excursion (run-hooks 'nnmail-prepare-incoming-hook)) + ;; Handle both babyl, MMDF and unix mail formats, since + ;; movemail will use the former when fetching from a + ;; mailbox, the latter when fetching from a file. + (cond ((or (looking-at "\^L") + (looking-at "BABYL OPTIONS:")) + (nnmail-process-babyl-mail-format func artnum-func)) + ((looking-at "\^A\^A\^A\^A") + (nnmail-process-mmdf-mail-format func artnum-func)) + ((looking-at "Return-Path:") + (nnmail-process-maildir-mail-format func artnum-func)) + (t + (nnmail-process-unix-mail-format func artnum-func)))) + (when exit-func + (funcall exit-func)) + (kill-buffer (current-buffer)))))) (defun nnmail-article-group (func &optional trace) "Look at the headers and return an alist of groups that match. @@ -1061,7 +922,7 @@ FUNC will be called with the group name to determine the article number." (let ((methods nnmail-split-methods) (obuf (current-buffer)) (beg (point-min)) - end group-art method regrepp) + end group-art method grp) (if (and (sequencep methods) (= (length methods) 1)) ;; If there is only just one group to put everything in, we @@ -1087,10 +948,10 @@ FUNC will be called with the group name to determine the article number." ;; existence to process. (goto-char (point-min)) (while (not (eobp)) - (end-of-line) - (if (> (current-column) 1024) - (gnus-delete-line) - (forward-line 1))) + (unless (< (move-to-column nnmail-split-header-length-limit) + nnmail-split-header-length-limit) + (delete-region (point) (progn (end-of-line) (point)))) + (forward-line 1)) ;; Allow washing. (goto-char (point-min)) (run-hooks 'nnmail-split-hook) @@ -1107,7 +968,7 @@ FUNC will be called with the group name to determine the article number." '("bogus")) (error (nnheader-message 5 - "Error in `nnmail-split-methods'; using `bogus' mail group") + "Error in `nnmail-split-methods'; using `bogus' mail group") (sit-for 1) '("bogus"))))) (setq split (gnus-remove-duplicates split)) @@ -1128,25 +989,24 @@ FUNC will be called with the group name to determine the article number." (not group-art))) (goto-char (point-max)) (setq method (pop methods) - regrepp nil) + grp (car method)) (if (or methods (not (equal "" (nth 1 method)))) (when (and (ignore-errors (if (stringp (nth 1 method)) - (progn - (setq regrepp - (string-match "\\\\[0-9&]" (car method))) - (re-search-backward (cadr method) nil t)) + (let ((expand (string-match "\\\\[0-9&]" grp)) + (pos (re-search-backward (cadr method) + nil t))) + (and expand + (setq grp (nnmail-expand-newtext grp))) + pos) ;; Function to say whether this is a match. - (funcall (nth 1 method) (car method)))) + (funcall (nth 1 method) grp))) ;; Don't enter the article into the same ;; group twice. - (not (assoc (car method) group-art))) - (push (cons (if regrepp - (nnmail-expand-newtext (car method)) - (car method)) - (funcall func (car method))) + (not (assoc grp group-art))) + (push (cons grp (funcall func grp)) group-art)) ;; This is the final group, which is used as a ;; catch-all. @@ -1183,35 +1043,39 @@ Return the number of characters in the body." (let (lines chars) (save-excursion (goto-char (point-min)) - (when (search-forward "\n\n" nil t) - (setq chars (- (point-max) (point))) - (setq lines (count-lines (point) (point-max))) - (forward-char -1) - (save-excursion - (when (re-search-backward "^Lines: " nil t) - (delete-region (point) (progn (forward-line 1) (point))))) - (beginning-of-line) - (insert (format "Lines: %d\n" (max lines 0))) - chars)))) + (unless (search-forward "\n\n" nil t) + (goto-char (point-max)) + (insert "\n")) + (setq chars (- (point-max) (point))) + (setq lines (count-lines (point) (point-max))) + (forward-char -1) + (save-excursion + (when (re-search-backward "^Lines: " nil t) + (delete-region (point) (progn (forward-line 1) (point))))) + (beginning-of-line) + (insert (format "Lines: %d\n" (max lines 0))) + chars))) (defun nnmail-insert-xref (group-alist) "Insert an Xref line based on the (group . article) alist." (save-excursion (goto-char (point-min)) - (when (search-forward "\n\n" nil t) - (forward-char -1) - (when (re-search-backward "^Xref: " nil t) - (delete-region (match-beginning 0) - (progn (forward-line 1) (point)))) - (insert (format "Xref: %s" (system-name))) - (while group-alist - (insert (format " %s:%d" - (gnus-encode-coding-string - (caar group-alist) - nnmail-pathname-coding-system) - (cdar group-alist))) - (setq group-alist (cdr group-alist))) - (insert "\n")))) + (unless (search-forward "\n\n" nil t) + (goto-char (point-max)) + (insert "\n")) + (forward-char -1) + (when (re-search-backward "^Xref: " nil t) + (delete-region (match-beginning 0) + (progn (forward-line 1) (point)))) + (insert (format "Xref: %s" (system-name))) + (while group-alist + (insert (format " %s:%d" + (mm-encode-coding-string + (caar group-alist) + nnmail-pathname-coding-system) + (cdar group-alist))) + (setq group-alist (cdr group-alist))) + (insert "\n"))) ;;; Message washing functions @@ -1224,11 +1088,11 @@ Return the number of characters in the body." (defun nnmail-remove-list-identifiers () "Remove list identifiers from Subject headers." (let ((regexp (if (stringp nnmail-list-identifiers) nnmail-list-identifiers - (mapconcat 'identity nnmail-list-identifiers "\\|")))) + (mapconcat 'identity nnmail-list-identifiers " *\\|")))) (when regexp (goto-char (point-min)) (when (re-search-forward - (concat "^Subject: +\\(Re: +\\)?\\(" regexp "\\) *") + (concat "^Subject: +\\(Re: +\\)?\\(" regexp " *\\)") nil t) (delete-region (match-beginning 2) (match-end 0)))))) @@ -1236,11 +1100,23 @@ Return the number of characters in the body." "Translate TAB characters into SPACE characters." (subst-char-in-region (point-min) (point-max) ?\t ? t)) +(defun nnmail-fix-eudora-headers () + "Eudora has a broken References line, but an OK In-Reply-To." + (goto-char (point-min)) + (when (re-search-forward "^X-Mailer:.*Eudora" nil t) + (goto-char (point-min)) + (when (re-search-forward "^References:" nil t) + (beginning-of-line) + (insert "X-Gnus-Broken-Eudora-")) + (goto-char (point-min)) + (when (re-search-forward "^In-Reply-To:[^\n]+\\(\n[ \t]+\\)" nil t) + (replace-match "" t t nil 1)))) + +(custom-add-option 'nnmail-prepare-incoming-header-hook + 'nnmail-fix-eudora-headers) + ;;; Utility functions - -;; Written by Per Abrahamsen . - (defun nnmail-split-fancy () "Fancy splitting method. See the documentation for the variable `nnmail-split-fancy' for documentation." @@ -1290,49 +1166,78 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." ((eq (car split) ':) (nnmail-split-it (save-excursion (eval (cdr split))))) + ;; Builtin ! operation. + ((eq (car split) '!) + (funcall (cadr split) (nnmail-split-it (caddr split)))) + ;; Check the cache for the regexp for this split. ((setq cached-pair (assq split nnmail-split-cache)) - (goto-char (point-max)) - ;; FIX FIX FIX problem with re-search-backward is that if you have - ;; a split: (from "foo-\\(bar\\|baz\\)@gnus.org "mail.foo.\\1") - ;; and someone mails a message with 'To: foo-bar@gnus.org' and - ;; 'CC: foo-baz@gnus.org', we'll pick 'mail.foo.baz' as the group - ;; if the cc line is a later header, even though the other choice - ;; is probably better. Also, this routine won't do a crosspost - ;; when there are two different matches. - ;; I guess you could just make this more determined, and it could - ;; look for still more matches prior to this one, and recurse - ;; on each of the multiple matches hit. Of course, then you'd - ;; want to make sure that nnmail-article-group or nnmail-split-fancy - ;; removed duplicates, since there might be more of those. - ;; I guess we could also remove duplicates in the & split case, since - ;; that's the only thing that can introduce them. - (when (re-search-backward (cdr cached-pair) nil t) - (when nnmail-split-tracing - (push (cdr cached-pair) nnmail-split-trace)) - ;; Someone might want to do a \N sub on this match, so get the - ;; correct match positions. - (goto-char (match-end 0)) - (let ((value (nth 1 split))) - (re-search-backward (if (symbolp value) - (cdr (assq value nnmail-split-abbrev-alist)) - value) - (match-end 1))) - (nnmail-split-it (nth 2 split)))) + (let (split-result + (end-point (point-max)) + (value (nth 1 split))) + (if (symbolp value) + (setq value (cdr (assq value nnmail-split-abbrev-alist)))) + (while (and (goto-char end-point) + (re-search-backward (cdr cached-pair) nil t)) + (when nnmail-split-tracing + (push (cdr cached-pair) nnmail-split-trace)) + (let ((split-rest (cddr split)) + (end (match-end 0)) + ;; The searched regexp is \(\(FIELD\).*\)\(VALUE\). So, + ;; start-of-value is the the point just before the + ;; beginning of the value, whereas after-header-name is + ;; the point just after the field name. + (start-of-value (match-end 1)) + (after-header-name (match-end 2))) + ;; Start the next search just before the beginning of the + ;; VALUE match. + (setq end-point (1- start-of-value)) + ;; Handle - RESTRICTs + (while (eq (car split-rest) '-) + ;; RESTRICT must start after-header-name and + ;; end after start-of-value, so that, for + ;; (any "foo" - "x-foo" "foo.list") + ;; we do not exclude foo.list just because + ;; the header is: ``To: x-foo, foo'' + (goto-char end) + (if (and (re-search-backward (cadr split-rest) + after-header-name t) + (> (match-end 0) start-of-value)) + (setq split-rest nil) + (setq split-rest (cddr split-rest)))) + (when split-rest + (goto-char end) + (let ((value (nth 1 split))) + (if (symbolp value) + (setq value (cdr (assq value nnmail-split-abbrev-alist)))) + ;; Someone might want to do a \N sub on this match, so get the + ;; correct match positions. + (re-search-backward value start-of-value)) + (dolist (sp (nnmail-split-it (car split-rest))) + (unless (memq sp split-result) + (push sp split-result)))))) + split-result)) ;; Not in cache, compute a regexp for the field/value pair. (t (let* ((field (nth 0 split)) (value (nth 1 split)) - (regexp (concat "^\\(\\(" + partial regexp) + (if (symbolp value) + (setq value (cdr (assq value nnmail-split-abbrev-alist)))) + (if (and (>= (length value) 2) + (string= ".*" (substring value 0 2))) + (setq value (substring value 2) + partial "")) + (setq regexp (concat "^\\(\\(" (if (symbolp field) (cdr (assq field nnmail-split-abbrev-alist)) field) - "\\):.*\\)\\<\\(" - (if (symbolp value) - (cdr (assq value nnmail-split-abbrev-alist)) - value) - "\\)\\>"))) + "\\):.*\\)" + (or partial "\\<") + "\\(" + value + "\\)\\>")) (push (cons split regexp) nnmail-split-cache) ;; Now that it's in the cache, just call nnmail-split-it again ;; on the same split, which will find it immediately in the cache. @@ -1371,68 +1276,6 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." (apply 'concat (nreverse expanded)) newtext))) -;; Get a list of spool files to read. -(defun nnmail-get-spool-files (&optional group) - (if (null nnmail-spool-file) - ;; No spool file whatsoever. - nil - (let* ((procmails - ;; If procmail is used to get incoming mail, the files - ;; are stored in this directory. - (and (file-exists-p nnmail-procmail-directory) - (or (eq nnmail-spool-file 'procmail) - nnmail-use-procmail) - (directory-files - nnmail-procmail-directory - t (concat (if group (concat "^" (regexp-quote group)) "") - nnmail-procmail-suffix "$")))) - (p procmails) - (crash (when (and (file-exists-p nnmail-crash-box) - (> (nnheader-file-size - (file-truename nnmail-crash-box)) - 0)) - (list nnmail-crash-box)))) - ;; Remove any directories that inadvertently match the procmail - ;; suffix, which might happen if the suffix is "". - (while p - (when (file-directory-p (car p)) - (setq procmails (delete (car p) procmails))) - (setq p (cdr p))) - ;; Return the list of spools. - (append - crash - (cond ((and group - (or (eq nnmail-spool-file 'procmail) - nnmail-use-procmail) - procmails) - procmails) - ((and group - (eq nnmail-spool-file 'procmail)) - nil) - ((listp nnmail-spool-file) - (nconc - (apply - 'nconc - (mapcar - (lambda (file) - (if (and (not (string-match "^po:" file)) - (file-directory-p file)) - (nnheader-directory-regular-files file) - (list file))) - nnmail-spool-file)) - procmails)) - ((stringp nnmail-spool-file) - (if (and (not (string-match "^po:" nnmail-spool-file)) - (file-directory-p nnmail-spool-file)) - (nconc - (nnheader-directory-regular-files nnmail-spool-file) - procmails) - (cons nnmail-spool-file procmails))) - ((eq nnmail-spool-file 'pop) - (cons (format "po:%s" (user-login-name)) procmails)) - (t - procmails)))))) - ;; Activate a backend only if it isn't already activated. ;; If FORCE, re-read the active file even if the backend is ;; already activated. @@ -1482,7 +1325,6 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." (set-buffer (setq nnmail-cache-buffer (get-buffer-create " *nnmail message-id cache*"))) - (buffer-disable-undo (current-buffer)) (when (file-exists-p nnmail-message-id-cache-file) (nnheader-insert-file-contents nnmail-message-id-cache-file)) (set-buffer-modified-p nil) @@ -1511,14 +1353,84 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." (setq nnmail-cache-buffer nil) (kill-buffer (current-buffer))))) +;; Compiler directives. +(defvar group) +(defvar group-art-list) +(defvar group-art) (defun nnmail-cache-insert (id) (when nnmail-treat-duplicates - (unless (gnus-buffer-live-p nnmail-cache-buffer) - (nnmail-cache-open)) + ;; Store some information about the group this message is written + ;; to. This function might have been called from various places. + ;; Sometimes, a function up in the calling sequence has an + ;; argument GROUP which is bound to a string, the group name. At + ;; other times, there is a function up in the calling sequence + ;; which has an argument GROUP-ART which is a list of pairs, and + ;; the car of a pair is a group name. Should we check that the + ;; length of the list is equal to 1? -- kai + (let ((g nil)) + (cond ((and (boundp 'group) group) + (setq g group)) + ((and (boundp 'group-art-list) group-art-list + (listp group-art-list)) + (setq g (caar group-art-list))) + ((and (boundp 'group-art) group-art (listp group-art)) + (setq g (caar group-art))) + (t (setq g ""))) + (unless (gnus-buffer-live-p nnmail-cache-buffer) + (nnmail-cache-open)) + (save-excursion + (set-buffer nnmail-cache-buffer) + (goto-char (point-max)) + (if (and g (not (string= "" g)) + (gnus-methods-equal-p gnus-command-method + (nnmail-cache-primary-mail-backend))) + (insert id "\t" g "\n") + (insert id "\n")))))) + +(defun nnmail-cache-primary-mail-backend () + (let ((be-list (cons gnus-select-method gnus-secondary-select-methods)) + (be nil) + (res nil)) + (while (and (null res) be-list) + (setq be (car be-list)) + (setq be-list (cdr be-list)) + (when (and (gnus-method-option-p be 'respool) + (eval (intern (format "%s-get-new-mail" (car be))))) + (setq res be))) + res)) + +;; Fetch the group name corresponding to the message id stored in the +;; cache. +(defun nnmail-cache-fetch-group (id) + (when (and nnmail-treat-duplicates nnmail-cache-buffer) (save-excursion (set-buffer nnmail-cache-buffer) (goto-char (point-max)) - (insert id "\n")))) + (when (search-backward id nil t) + (beginning-of-line) + (skip-chars-forward "^\n\r\t") + (unless (eolp) + (forward-char 1) + (buffer-substring (point) + (progn (end-of-line) (point)))))))) + +;; Function for nnmail-split-fancy: look up all references in the +;; cache and if a match is found, return that group. +(defun nnmail-split-fancy-with-parent () + (let* ((refstr (or (message-fetch-field "references") + (message-fetch-field "in-reply-to"))) + (references nil) + (res nil)) + (when refstr + (setq references (nreverse (gnus-split-references refstr))) + (unless (gnus-buffer-live-p nnmail-cache-buffer) + (nnmail-cache-open)) + (mapcar (lambda (x) + (setq res (or (nnmail-cache-fetch-group x) res)) + (when (string= "drafts" res) + (setq res nil))) + references) + res))) (defun nnmail-cache-id-exists-p (id) (when nnmail-treat-duplicates @@ -1547,12 +1459,19 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." (t nnmail-treat-duplicates)))) group-art) + ;; We insert a line that says what the mail source is. + (let ((case-fold-search t)) + (goto-char (point-min)) + (re-search-forward "^message-id[ \t]*:" nil t) + (beginning-of-line) + (insert (format "X-Gnus-Mail-Source: %s\n" mail-source-string))) + ;; Let the backend save the article (or not). (cond ((not duplication) - (nnmail-cache-insert message-id) (funcall func (setq group-art - (nreverse (nnmail-article-group artnum-func))))) + (nreverse (nnmail-article-group artnum-func)))) + (nnmail-cache-insert message-id)) ((eq action 'delete) (setq group-art nil)) ((eq action 'warn) @@ -1575,6 +1494,8 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." ;;; Get new mail. +(defvar nnmail-fetched-sources nil) + (defun nnmail-get-value (&rest args) (let ((sym (intern (apply 'format args)))) (when (boundp sym) @@ -1583,72 +1504,89 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." (defun nnmail-get-new-mail (method exit-func temp &optional group spool-func) "Read new incoming mail." - (let* ((spools (nnmail-get-spool-files group)) + (let* ((sources (or mail-sources + (if (listp nnmail-spool-file) nnmail-spool-file + (list nnmail-spool-file)))) + fetching-sources (group-in group) - nnmail-current-spool incoming incomings spool) - (when (and (nnmail-get-value "%s-get-new-mail" method) - nnmail-spool-file) + (i 0) + (new 0) + (total 0) + incoming incomings source) + (when (nnmail-get-value "%s-get-new-mail" method) + (while (setq source (pop sources)) + ;; Be compatible with old values. + (cond + ((stringp source) + (setq source + (cond + ((string-match "^po:" source) + (list 'pop :user (substring source (match-end 0)))) + ((file-directory-p source) + (list 'directory :path source)) + (t + (list 'file :path source))))) + ((eq source 'procmail) + (message "Invalid value for nnmail-spool-file: `procmail'") + nil)) + ;; Hack to only fetch the contents of a single group's spool file. + (when (and (eq (car source) 'directory) + (null nnmail-scan-directory-mail-source-once) + group) + (mail-source-bind (directory source) + (setq source (append source + (list + :predicate + `(lambda (file) + (string-match + ,(concat + (regexp-quote (concat group suffix)) + "$") + file))))))) + (when nnmail-fetched-sources + (if (member source nnmail-fetched-sources) + (setq source nil) + (push source nnmail-fetched-sources) + (push source fetching-sources))))) + (when fetching-sources ;; We first activate all the groups. (nnmail-activate method) ;; Allow the user to hook. (run-hooks 'nnmail-pre-get-new-mail-hook) ;; Open the message-id cache. (nnmail-cache-open) - ;; The we go through all the existing spool files and split the - ;; mail from each. - (while spools - (setq spool (pop spools)) - ;; We read each spool file if either the spool is a POP-mail - ;; spool, or the file exists. We can't check for the - ;; existence of POPped mail. - (when (or (string-match "^po:" spool) - (and (file-exists-p (file-truename spool)) - (> (nnheader-file-size (file-truename spool)) 0))) - (nnheader-message 3 "%s: Reading incoming mail..." method) - (when (and (nnmail-move-inbox spool) - (file-exists-p nnmail-crash-box)) - (setq nnmail-current-spool spool) - ;; There is new mail. We first find out if all this mail - ;; is supposed to go to some specific group. - (setq group (nnmail-get-split-group spool group-in)) - ;; We split the mail - (nnmail-split-incoming - nnmail-crash-box (intern (format "%s-save-mail" method)) - spool-func group (intern (format "%s-active-number" method))) - ;; Check whether the inbox is to be moved to the special tmp dir. - (let ((prefix - (expand-file-name - (if nnmail-tmp-directory - (concat - (file-name-as-directory nnmail-tmp-directory) - (file-name-nondirectory - (concat (file-name-as-directory temp) "Incoming"))) - (concat (file-name-as-directory temp) "Incoming"))))) - (unless (file-exists-p (file-name-directory prefix)) - (make-directory (file-name-directory prefix) t)) - (setq incoming (make-temp-file prefix))) - (rename-file nnmail-crash-box incoming t) - (push incoming incomings)))) + ;; The we go through all the existing mail source specification + ;; and fetch the mail from each. + (while (setq source (pop fetching-sources)) + (nnheader-message 4 "%s: Reading incoming mail from %s..." + method (car source)) + (when (setq new + (mail-source-fetch + source + `(lambda (file orig-file) + (nnmail-split-incoming + file ',(intern (format "%s-save-mail" method)) + ',spool-func + (nnmail-get-split-group orig-file source) + ',(intern (format "%s-active-number" method)))))) + (incf total new) + (incf i))) ;; If we did indeed read any incoming spools, we save all info. - (when incomings + (if (zerop total) + (nnheader-message 4 "%s: Reading incoming mail (no new mail)...done" + method (car source)) (nnmail-save-active (nnmail-get-value "%s-group-alist" method) (nnmail-get-value "%s-active-file" method)) (when exit-func (funcall exit-func)) (run-hooks 'nnmail-read-incoming-hook) - (nnheader-message 3 "%s: Reading incoming mail...done" method)) + (nnheader-message 4 "%s: Reading incoming mail (%d new)...done" method + total)) ;; Close the message-id cache. (nnmail-cache-close) ;; Allow the user to hook. - (run-hooks 'nnmail-post-get-new-mail-hook) - ;; Delete all the temporary files. - (while incomings - (setq incoming (pop incomings)) - (and nnmail-delete-incoming - (file-exists-p incoming) - (file-writable-p incoming) - (delete-file incoming)))))) + (run-hooks 'nnmail-post-get-new-mail-hook)))) (defun nnmail-expired-article-p (group time force &optional inhibit) "Say whether an article that is TIME old in GROUP should be expired." @@ -1669,27 +1607,15 @@ See the documentation for the variable `nnmail-split-fancy' for documentation." ;; This is an ange-ftp group, and we don't have any dates. nil) ((numberp days) - (setq days (nnmail-days-to-time days)) + (setq days (days-to-time days)) ;; Compare the time with the current time. - (nnmail-time-less days (nnmail-time-since time))))))) + (ignore-errors (time-less-p days (time-since time)))))))) -(defvar nnmail-read-passwd nil) -(defun nnmail-read-passwd (prompt &rest args) - "Read a password using PROMPT. -If ARGS, PROMPT is used as an argument to `format'." - (let ((prompt - (if args - (apply 'format prompt args) - prompt))) - (unless nnmail-read-passwd - (if (fboundp 'read-passwd) - (setq nnmail-read-passwd 'read-passwd) - (if (load "passwd" t) - (setq nnmail-read-passwd 'read-passwd) - (unless (fboundp 'ange-ftp-read-passwd) - (autoload 'ange-ftp-read-passwd "ange-ftp")) - (setq nnmail-read-passwd 'ange-ftp-read-passwd)))) - (funcall nnmail-read-passwd prompt))) +(defun nnmail-expiry-target-group (target group) + (when (nnheader-functionp target) + (setq target (funcall target group))) + (unless (eq target 'delete) + (gnus-request-accept-article target nil nil t))) (defun nnmail-check-syntax () "Check (and modify) the syntax of the message in the current buffer." @@ -1702,7 +1628,7 @@ If ARGS, PROMPT is used as an argument to `format'." (defun nnmail-write-region (start end filename &optional append visit lockname) "Do a `write-region', and then set the file modes." (let ((coding-system-for-write nnmail-file-coding-system) - (pathname-coding-system 'binary)) + (file-name-coding-system nnmail-pathname-coding-system)) (write-region start end filename append visit lockname) (set-file-modes filename nnmail-default-file-modes))) @@ -1738,11 +1664,11 @@ If ARGS, PROMPT is used as an argument to `format'." (goto-char (point-min)) (while (re-search-forward "[^ \t=]+" nil t) (setq name (match-string 0)) - (if (not (= (following-char) ?=)) + (if (not (eq (char-after) ?=)) ;; Implied "yes". (setq value "yes") (forward-char 1) - (if (not (= (following-char) ?\")) + (if (not (eq (char-after) ?\")) (if (not (looking-at "[^ \t]")) ;; Implied "no". (setq value "no") @@ -1771,6 +1697,8 @@ If ARGS, PROMPT is used as an argument to `format'." (unless nnmail-split-history (error "No current split history")) (with-output-to-temp-buffer "*nnmail split history*" + (with-current-buffer standard-output + (fundamental-mode)) ; for Emacs 20.4+ (let ((history nnmail-split-history) elem) (while (setq elem (pop history)) @@ -1799,15 +1727,6 @@ If ARGS, PROMPT is used as an argument to `format'." his nil))) found)) -(eval-and-compile - (autoload 'pop3-movemail "pop3")) - -(defun nnmail-pop3-movemail (inbox crashbox) - "Function to move mail from INBOX on a pop3 server to file CRASHBOX." - (let ((pop3-maildrop - (substring inbox (match-end (string-match "^po:" inbox))))) - (pop3-movemail crashbox))) - (defun nnmail-within-headers-p () "Check to see if point is within the headers of a unix mail message. Doesn't change point." diff --git a/lisp/gnus/nnmbox.el b/lisp/gnus/nnmbox.el index 1f05d1d16b5..33a951d1fad 100644 --- a/lisp/gnus/nnmbox.el +++ b/lisp/gnus/nnmbox.el @@ -1,5 +1,7 @@ ;;; nnmbox.el --- mail mbox access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Masanobu UMEDA @@ -59,6 +61,11 @@ (defvoo nnmbox-group-alist nil) (defvoo nnmbox-active-timestamp nil) +(defvoo nnmbox-file-coding-system mm-text-coding-system) +(defvoo nnmbox-file-coding-system-for-write nil) +(defvoo nnmbox-active-file-coding-system mm-text-coding-system) +(defvoo nnmbox-active-file-coding-system-for-write nil) + ;;; Interface functions @@ -166,6 +173,7 @@ (nnmbox-article-group-number))))))) (deffoo nnmbox-request-group (group &optional server dont-check) + (nnmbox-possibly-change-newsgroup nil server) (let ((active (cadr (assoc group nnmbox-group-alist)))) (cond ((or (null active) @@ -180,6 +188,18 @@ (1+ (- (cdr active) (car active))) (car active) (cdr active) group))))) +(defun nnmbox-save-buffer () + (let ((coding-system-for-write + (or nnmbox-file-coding-system-for-write + nnmbox-file-coding-system))) + (save-buffer))) + +(defun nnmbox-save-active (group-alist active-file) + (let ((nnmail-active-file-coding-system + (or nnmbox-active-file-coding-system-for-write + nnmbox-active-file-coding-system))) + (nnmail-save-active group-alist active-file))) + (deffoo nnmbox-request-scan (&optional group server) (nnmbox-possibly-change-newsgroup group server) (nnmbox-read-mbox) @@ -188,7 +208,7 @@ (lambda () (save-excursion (set-buffer nnmbox-mbox-buffer) - (save-buffer))) + (nnmbox-save-buffer))) (file-name-directory nnmbox-mbox-file) group (lambda () @@ -197,7 +217,7 @@ (set-buffer nnmbox-mbox-buffer) (goto-char (point-max)) (insert-buffer-substring in-buf))) - (nnmail-save-active nnmbox-group-alist nnmbox-active-file)))) + (nnmbox-save-active nnmbox-group-alist nnmbox-active-file)))) (deffoo nnmbox-close-group (group &optional server) t) @@ -207,12 +227,14 @@ (unless (assoc group nnmbox-group-alist) (push (list group (cons 1 0)) nnmbox-group-alist) - (nnmail-save-active nnmbox-group-alist nnmbox-active-file)) + (nnmbox-save-active nnmbox-group-alist nnmbox-active-file)) t) (deffoo nnmbox-request-list (&optional server) (save-excursion - (nnmail-find-file nnmbox-active-file) + (let ((nnmail-file-coding-system + nnmbox-active-file-coding-system)) + (nnmail-find-file nnmbox-active-file)) (setq nnmbox-group-alist (nnmail-get-active)) t)) @@ -223,7 +245,7 @@ (nnheader-report 'nnmbox "LIST NEWSGROUPS is not implemented.")) (deffoo nnmbox-request-expire-articles - (articles newsgroup &optional server force) + (articles newsgroup &optional server force) (nnmbox-possibly-change-newsgroup newsgroup server) (let* ((is-old t) rest) @@ -245,7 +267,7 @@ (nnmbox-delete-mail)) (push (car articles) rest))) (setq articles (cdr articles))) - (save-buffer) + (nnmbox-save-buffer) ;; Find the lowest active article in this group. (let ((active (nth 1 (assoc newsgroup nnmbox-group-alist)))) (goto-char (point-min)) @@ -254,18 +276,17 @@ (<= (car active) (cdr active))) (setcar active (1+ (car active))) (goto-char (point-min)))) - (nnmail-save-active nnmbox-group-alist nnmbox-active-file) + (nnmbox-save-active nnmbox-group-alist nnmbox-active-file) (nconc rest articles)))) (deffoo nnmbox-request-move-article - (article group server accept-form &optional last) + (article group server accept-form &optional last) (let ((buf (get-buffer-create " *nnmbox move*")) result) (and (nnmbox-request-article article group server) (save-excursion (set-buffer buf) - (buffer-disable-undo (current-buffer)) (erase-buffer) (insert-buffer-substring nntp-server-buffer) (goto-char (point-min)) @@ -283,7 +304,7 @@ (goto-char (point-min)) (when (search-forward (nnmbox-article-string article) nil t) (nnmbox-delete-mail)) - (and last (save-buffer)))) + (and last (nnmbox-save-buffer)))) result)) (deffoo nnmbox-request-accept-article (group &optional server last) @@ -323,8 +344,8 @@ (when last (when nnmail-cache-accepted-message-ids (nnmail-cache-close)) - (nnmail-save-active nnmbox-group-alist nnmbox-active-file) - (save-buffer)))) + (nnmbox-save-active nnmbox-group-alist nnmbox-active-file) + (nnmbox-save-buffer)))) result)) (deffoo nnmbox-request-replace-article (article group buffer) @@ -336,7 +357,7 @@ nil (nnmbox-delete-mail t t) (insert-buffer-substring buffer) - (save-buffer) + (nnmbox-save-buffer) t))) (deffoo nnmbox-request-delete-group (group &optional force server) @@ -354,13 +375,13 @@ (setq found t) (nnmbox-delete-mail)) (when found - (save-buffer))))) + (nnmbox-save-buffer))))) ;; Remove the group from all structures. (setq nnmbox-group-alist (delq (assoc group nnmbox-group-alist) nnmbox-group-alist) nnmbox-current-group nil) ;; Save the active file. - (nnmail-save-active nnmbox-group-alist nnmbox-active-file) + (nnmbox-save-active nnmbox-group-alist nnmbox-active-file) t) (deffoo nnmbox-request-rename-group (group new-name &optional server) @@ -375,13 +396,13 @@ (replace-match new-ident t t) (setq found t)) (when found - (save-buffer)))) + (nnmbox-save-buffer)))) (let ((entry (assoc group nnmbox-group-alist))) (when entry (setcar entry new-name)) (setq nnmbox-current-group nil) ;; Save the new group alist. - (nnmail-save-active nnmbox-group-alist nnmbox-active-file) + (nnmbox-save-active nnmbox-group-alist nnmbox-active-file) t)) @@ -425,9 +446,12 @@ (not (buffer-name nnmbox-mbox-buffer))) (save-excursion (set-buffer (setq nnmbox-mbox-buffer - (nnheader-find-file-noselect - nnmbox-mbox-file nil 'raw))) - (buffer-disable-undo (current-buffer)))) + (let ((nnheader-file-coding-system + nnmbox-file-coding-system)) + (nnheader-find-file-noselect + nnmbox-mbox-file nil t)))) + (mm-enable-multibyte) + (buffer-disable-undo))) (when (not nnmbox-group-alist) (nnmail-activate 'nnmbox)) (if newsgroup @@ -496,7 +520,10 @@ (defun nnmbox-create-mbox () (when (not (file-exists-p nnmbox-mbox-file)) - (nnmail-write-region 1 1 nnmbox-mbox-file t 'nomesg))) + (let ((nnmail-file-coding-system + (or nnmbox-file-coding-system-for-write + nnmbox-file-coding-system))) + (nnmail-write-region 1 1 nnmbox-mbox-file t 'nomesg)))) (defun nnmbox-read-mbox () (nnmail-activate 'nnmbox) @@ -512,9 +539,12 @@ (alist nnmbox-group-alist) start end number) (set-buffer (setq nnmbox-mbox-buffer - (nnheader-find-file-noselect - nnmbox-mbox-file nil 'raw))) - (buffer-disable-undo (current-buffer)) + (let ((nnheader-file-coding-system + nnmbox-file-coding-system)) + (nnheader-find-file-noselect + nnmbox-mbox-file nil t)))) + (mm-enable-multibyte) + (buffer-disable-undo) ;; Go through the group alist and compare against ;; the mbox file. @@ -523,26 +553,31 @@ (when (and (re-search-backward (format "^X-Gnus-Newsgroup: %s:\\([0-9]+\\) " (caar alist)) nil t) - (>= (setq number - (string-to-number - (buffer-substring - (match-beginning 1) (match-end 1)))) - (cdadar alist))) - (setcdr (cadar alist) (1+ number))) + (> (setq number + (string-to-number + (buffer-substring + (match-beginning 1) (match-end 1)))) + (cdadar alist))) + (setcdr (cadar alist) number)) (setq alist (cdr alist))) (goto-char (point-min)) (while (re-search-forward delim nil t) (setq start (match-beginning 0)) - (when (not (search-forward "\nX-Gnus-Newsgroup: " - (save-excursion - (setq end - (or - (and - (re-search-forward delim nil t) - (match-beginning 0)) - (point-max)))) - t)) + (unless (search-forward + "\nX-Gnus-Newsgroup: " + (save-excursion + (setq end + (or + (and + ;; skip to end of headers first, since mail + ;; which has been respooled has additional + ;; "From nobody" lines. + (search-forward "\n\n" nil t) + (re-search-forward delim nil t) + (match-beginning 0)) + (point-max)))) + t) (save-excursion (save-restriction (narrow-to-region start end) diff --git a/lisp/gnus/nnmh.el b/lisp/gnus/nnmh.el index 30069a154c2..fb1ad637dc3 100644 --- a/lisp/gnus/nnmh.el +++ b/lisp/gnus/nnmh.el @@ -1,5 +1,7 @@ ;;; nnmh.el --- mhspool access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Masanobu UMEDA @@ -48,7 +50,10 @@ "*Hook run narrowed to an article before saving.") (defvoo nnmh-be-safe nil - "*If non-nil, nnmh will check all articles to make sure whether they are new or not.") + "*If non-nil, nnmh will check all articles to make sure whether they are new or not. +Go through the .nnmh-articles file and compare with the actual +articles in this folder. The articles that are \"new\" will be marked +as unread by Gnus.") @@ -60,7 +65,10 @@ (defvoo nnmh-status-string "") (defvoo nnmh-group-alist nil) -(defvoo nnmh-allow-delete-final nil) +;; Don't even think about setting this variable. It does not exist. +;; Forget about it. Uh-huh. Nope. Nobody here. It's only bound +;; dynamically by certain functions in nndraft. +(defvar nnmh-allow-delete-final nil) @@ -77,8 +85,7 @@ (large (and (numberp nnmail-large-newsgroup) (> number nnmail-large-newsgroup))) (count 0) - (file-name-coding-system 'binary) - (pathname-coding-system 'binary) + (file-name-coding-system nnmail-pathname-coding-system) beg article) (nnmh-possibly-change-directory newsgroup server) ;; We don't support fetching by Message-ID. @@ -106,7 +113,7 @@ (and large (zerop (% count 20)) (nnheader-message 5 "nnmh: Receiving headers... %d%%" - (/ (* count 100) number)))) + (/ (* count 100) number)))) (when large (nnheader-message 5 "nnmh: Receiving headers...done")) @@ -137,8 +144,7 @@ (let ((file (if (stringp id) nil (concat nnmh-current-directory (int-to-string id)))) - (pathname-coding-system 'binary) - (file-name-coding-system 'binary) + (file-name-coding-system nnmail-pathname-coding-system) (nntp-server-buffer (or buffer nntp-server-buffer))) (and (stringp file) (file-exists-p file) @@ -150,8 +156,7 @@ (nnheader-init-server-buffer) (nnmh-possibly-change-directory group server) (let ((pathname (nnmail-group-pathname group nnmh-directory)) - (pathname-coding-system 'binary) - (file-name-coding-system 'binary) + (file-name-coding-system nnmail-pathname-coding-system) dir) (cond ((not (file-directory-p pathname)) @@ -174,16 +179,19 @@ (mapcar (lambda (name) (string-to-int name)) (directory-files pathname nil "^[0-9]+$" t)) '<)) - (cond - (dir - (nnheader-report 'nnmh "Selected group %s" group) - (nnheader-insert - "211 %d %d %d %s\n" (length dir) (car dir) - (progn (while (cdr dir) (setq dir (cdr dir))) (car dir)) - group)) - (t - (nnheader-report 'nnmh "Empty group %s" group) - (nnheader-insert (format "211 0 1 0 %s\n" group)))))))))) + (cond + (dir + (setq nnmh-group-alist + (delq (assoc group nnmh-group-alist) nnmh-group-alist)) + (push (list group (cons (car dir) (car (last dir)))) + nnmh-group-alist) + (nnheader-report 'nnmh "Selected group %s" group) + (nnheader-insert + "211 %d %d %d %s\n" (length dir) (car dir) + (car (last dir)) group)) + (t + (nnheader-report 'nnmh "Empty group %s" group) + (nnheader-insert (format "211 0 1 0 %s\n" group)))))))))) (deffoo nnmh-request-scan (&optional group server) (nnmail-get-new-mail 'nnmh nil nnmh-directory group)) @@ -191,10 +199,9 @@ (deffoo nnmh-request-list (&optional server dir) (nnheader-insert "") (nnmh-possibly-change-directory nil server) - (let* ((pathname-coding-system 'binary) - (file-name-coding-system 'binary) - (nnmh-toplev - (file-truename (or dir (file-name-as-directory nnmh-directory))))) + (let ((file-name-coding-system nnmail-pathname-coding-system) + (nnmh-toplev + (file-truename (or dir (file-name-as-directory nnmh-directory))))) (nnmh-request-list-1 nnmh-toplev)) (setq nnmh-group-alist (nnmail-get-active)) t) @@ -233,8 +240,8 @@ (expand-file-name nnmh-toplev)))) dir) (nnheader-replace-chars-in-string - (gnus-decode-coding-string (substring dir (match-end 0)) - nnmail-pathname-coding-system) + (mm-decode-coding-string (substring dir (match-end 0)) + nnmail-pathname-coding-system) ?/ ?.)) (apply 'max files) (apply 'min files))))))) @@ -275,7 +282,7 @@ t) (deffoo nnmh-request-move-article - (article group server accept-form &optional last) + (article group server accept-form &optional last) (let ((buf (get-buffer-create " *nnmh move*")) result) (and @@ -405,8 +412,7 @@ (nnmh-open-server server)) (when newsgroup (let ((pathname (nnmail-group-pathname newsgroup nnmh-directory)) - (file-name-coding-system 'binary) - (pathname-coding-system 'binary)) + (file-name-coding-system nnmail-pathname-coding-system)) (if (file-directory-p pathname) (setq nnmh-current-directory pathname) (error "No such newsgroup: %s" newsgroup))))) @@ -455,8 +461,8 @@ "Compute the next article number in GROUP." (let ((active (cadr (assoc group nnmh-group-alist))) (dir (nnmail-group-pathname group nnmh-directory)) - (file-name-coding-system 'binary) - (pathname-coding-system 'binary)) + (file-name-coding-system nnmail-pathname-coding-system) + file) (unless active ;; The group wasn't known to nnmh, so we just create an active ;; entry for it. @@ -474,9 +480,15 @@ (when files (setcdr active (car files))))) (setcdr active (1+ (cdr active))) - (while (file-exists-p - (concat (nnmail-group-pathname group nnmh-directory) - (int-to-string (cdr active)))) + (while (or + ;; See whether the file exists... + (file-exists-p + (setq file (concat (nnmail-group-pathname group nnmh-directory) + (int-to-string (cdr active))))) + ;; ... or there is a buffer that will make that file exist + ;; in the future. + (get-file-buffer file)) + ;; Skip past that file. (setcdr active (1+ (cdr active)))) (cdr active))) @@ -539,7 +551,7 @@ (setq articles (sort articles (lambda (art1 art2) (> (car art1) (car art2))))) ;; Finally write this list back to the .nnmh-articles file. - (nnheader-temp-write nnmh-file + (with-temp-file nnmh-file (insert ";; Gnus article active file for " group "\n\n") (insert "(setq nnmh-newsgroup-articles '") (gnus-prin1 articles) diff --git a/lisp/gnus/nnml.el b/lisp/gnus/nnml.el index 59b911f0537..64ca0b2fb4f 100644 --- a/lisp/gnus/nnml.el +++ b/lisp/gnus/nnml.el @@ -1,5 +1,6 @@ ;;; nnml.el --- mail spool access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Masanobu UMEDA @@ -41,11 +42,11 @@ "Spool directory for the nnml mail backend.") (defvoo nnml-active-file - (concat (file-name-as-directory nnml-directory) "active") + (expand-file-name "active" nnml-directory) "Mail active file.") (defvoo nnml-newsgroups-file - (concat (file-name-as-directory nnml-directory) "newsgroups") + (expand-file-name "newsgroups" nnml-directory) "Mail newsgroups description file.") (defvoo nnml-get-new-mail t @@ -86,6 +87,8 @@ all. This may very well take some time.") (defvar nnml-nov-buffer-file-name nil) +(defvoo nnml-file-coding-system nnmail-file-coding-system) + ;;; Interface functions. @@ -100,8 +103,7 @@ all. This may very well take some time.") (let ((file nil) (number (length sequence)) (count 0) - (file-name-coding-system 'binary) - (pathname-coding-system 'binary) + (file-name-coding-system nnmail-pathname-coding-system) beg article) (if (stringp (car sequence)) 'headers @@ -141,9 +143,7 @@ all. This may very well take some time.") (deffoo nnml-open-server (server &optional defs) (nnoo-change-server 'nnml server defs) (when (not (file-exists-p nnml-directory)) - (condition-case () - (make-directory nnml-directory t) - (error))) + (ignore-errors (make-directory nnml-directory t))) (cond ((not (file-exists-p nnml-directory)) (nnml-close-server) @@ -164,8 +164,7 @@ all. This may very well take some time.") (deffoo nnml-request-article (id &optional group server buffer) (nnml-possibly-change-directory group server) (let* ((nntp-server-buffer (or buffer nntp-server-buffer)) - (file-name-coding-system 'binary) - (pathname-coding-system 'binary) + (file-name-coding-system nnmail-pathname-coding-system) path gpath group-num) (if (stringp id) (when (and (setq group-num (nnml-find-group-number id)) @@ -185,7 +184,9 @@ all. This may very well take some time.") (nnheader-report 'nnml "No such file: %s" path)) ((file-directory-p path) (nnheader-report 'nnml "File is a directory: %s" path)) - ((not (save-excursion (nnmail-find-file path))) + ((not (save-excursion (let ((nnmail-file-coding-system + nnml-file-coding-system)) + (nnmail-find-file path)))) (nnheader-report 'nnml "Couldn't read file: %s" path)) (t (nnheader-report 'nnml "Article %s retrieved" id) @@ -194,8 +195,7 @@ all. This may very well take some time.") (string-to-int (file-name-nondirectory path))))))) (deffoo nnml-request-group (group &optional server dont-check) - (let ((pathname-coding-system 'binary) - (file-name-coding-system 'binary)) + (let ((file-name-coding-system nnmail-pathname-coding-system)) (cond ((not (nnml-possibly-change-directory group server)) (nnheader-report 'nnml "Invalid group (no such directory)")) @@ -228,6 +228,7 @@ all. This may very well take some time.") t) (deffoo nnml-request-create-group (group &optional server args) + (nnml-possibly-change-directory nil server) (nnmail-activate 'nnml) (cond ((assoc group nnml-group-alist) @@ -252,10 +253,8 @@ all. This may very well take some time.") (deffoo nnml-request-list (&optional server) (save-excursion (let ((nnmail-file-coding-system nnmail-active-file-coding-system) - (file-name-coding-system 'binary) - (pathname-coding-system 'binary)) - (nnmail-find-file nnml-active-file) - ) + (file-name-coding-system nnmail-pathname-coding-system)) + (nnmail-find-file nnml-active-file)) (setq nnml-group-alist (nnmail-get-active)) t)) @@ -266,8 +265,7 @@ all. This may very well take some time.") (save-excursion (nnmail-find-file nnml-newsgroups-file))) -(deffoo nnml-request-expire-articles (articles group - &optional server force) +(deffoo nnml-request-expire-articles (articles group &optional server force) (nnml-possibly-change-directory group server) (let ((active-articles (nnheader-directory-articles nnml-current-directory)) @@ -288,8 +286,16 @@ all. This may very well take some time.") (nnmail-expired-article-p group mod-time force nnml-inhibit-expiry))) (progn + ;; Allow a special target group. + (unless (eq nnmail-expiry-target 'delete) + (with-temp-buffer + (nnml-request-article number group server + (current-buffer)) + (let ((nnml-current-directory nil)) + (nnmail-expiry-target-group + nnmail-expiry-target group)))) (nnheader-message 5 "Deleting article %s in %s" - article group) + number group) (condition-case () (funcall nnmail-delete-file-function article) (file-error @@ -307,7 +313,7 @@ all. This may very well take some time.") (nconc rest articles))) (deffoo nnml-request-move-article - (article group server accept-form &optional last) + (article group server accept-form &optional last) (let ((buf (get-buffer-create " *nnml move*")) result) (nnml-possibly-change-directory group server) @@ -315,12 +321,15 @@ all. This may very well take some time.") (and (nnml-deletable-article-p group article) (nnml-request-article article group server) - (save-excursion - (set-buffer buf) - (insert-buffer-substring nntp-server-buffer) - (setq result (eval accept-form)) - (kill-buffer (current-buffer)) - result) + (let (nnml-current-directory + nnml-current-group + nnml-article-file-alist) + (save-excursion + (set-buffer buf) + (insert-buffer-substring nntp-server-buffer) + (setq result (eval accept-form)) + (kill-buffer (current-buffer)) + result)) (progn (nnml-possibly-change-directory group server) (condition-case () @@ -368,16 +377,14 @@ all. This may very well take some time.") (let ((chars (nnmail-insert-lines)) (art (concat (int-to-string article) "\t")) headers) - (when (condition-case () - (progn - (nnmail-write-region - (point-min) (point-max) - (or (nnml-article-to-file article) - (concat nnml-current-directory - (int-to-string article))) - nil (if (nnheader-be-verbose 5) nil 'nomesg)) - t) - (error nil)) + (when (ignore-errors + (nnmail-write-region + (point-min) (point-max) + (or (nnml-article-to-file article) + (expand-file-name (int-to-string article) + nnml-current-directory)) + nil (if (nnheader-be-verbose 5) nil 'nomesg)) + t) (setq headers (nnml-parse-head chars article)) ;; Replace the NOV line in the NOV file. (save-excursion @@ -418,9 +425,7 @@ all. This may very well take some time.") (nnheader-message 5 "Deleting article %s in %s..." article group) (funcall nnmail-delete-file-function article)))) ;; Try to delete the directory itself. - (condition-case () - (delete-directory nnml-current-directory) - (error nil))) + (ignore-errors (delete-directory nnml-current-directory))) ;; Remove the group from all structures. (setq nnml-group-alist (delq (assoc group nnml-group-alist) nnml-group-alist) @@ -434,11 +439,9 @@ all. This may very well take some time.") (nnml-possibly-change-directory group server) (let ((new-dir (nnmail-group-pathname new-name nnml-directory)) (old-dir (nnmail-group-pathname group nnml-directory))) - (when (condition-case () - (progn - (make-directory new-dir t) - t) - (error nil)) + (when (ignore-errors + (make-directory new-dir t) + t) ;; We move the articles file by file instead of renaming ;; the directory -- there may be subgroups in this group. ;; One might be more clever, I guess. @@ -453,9 +456,7 @@ all. This may very well take some time.") (when (file-exists-p overview) (rename-file overview (concat new-dir nnml-nov-file-name)))) (when (<= (length (directory-files old-dir)) 2) - (condition-case () - (delete-directory old-dir) - (error nil))) + (ignore-errors (delete-directory old-dir))) ;; That went ok, so we change the internal structures. (let ((entry (assoc group nnml-group-alist))) (when entry @@ -473,7 +474,7 @@ all. This may very well take some time.") ((not (file-exists-p file)) (nnheader-report 'nnml "File %s does not exist" file)) (t - (nnheader-temp-write file + (with-temp-file file (nnheader-insert-file-contents file) (nnmail-replace-status name value)) t)))) @@ -485,7 +486,7 @@ all. This may very well take some time.") (nnml-update-file-alist) (let (file) (if (setq file (cdr (assq article nnml-article-file-alist))) - (concat nnml-current-directory file) + (expand-file-name file nnml-current-directory) ;; Just to make sure nothing went wrong when reading over NFS -- ;; check once more. (when (file-exists-p @@ -507,7 +508,6 @@ all. This may very well take some time.") (defun nnml-find-group-number (id) (save-excursion (set-buffer (get-buffer-create " *nnml id*")) - (buffer-disable-undo (current-buffer)) (let ((alist nnml-group-alist) number) ;; We want to look through all .overview files, but we want to @@ -527,8 +527,8 @@ all. This may very well take some time.") (defun nnml-find-id (group id) (erase-buffer) - (let ((nov (concat (nnmail-group-pathname group nnml-directory) - nnml-nov-file-name)) + (let ((nov (expand-file-name nnml-nov-file-name + (nnmail-group-pathname group nnml-directory))) number found) (when (file-exists-p nov) (nnheader-insert-file-contents nov) @@ -542,15 +542,13 @@ all. This may very well take some time.") (setq found t) ;; We return the article number. (setq number - (condition-case () - (read (current-buffer)) - (error nil))))) + (ignore-errors (read (current-buffer)))))) number))) (defun nnml-retrieve-headers-with-nov (articles &optional fetch-old) (if (or gnus-nov-is-evil nnml-nov-is-evil) nil - (let ((nov (concat nnml-current-directory nnml-nov-file-name))) + (let ((nov (expand-file-name nnml-nov-file-name nnml-current-directory))) (when (file-exists-p nov) (save-excursion (set-buffer nntp-server-buffer) @@ -572,8 +570,7 @@ all. This may very well take some time.") (if (not group) t (let ((pathname (nnmail-group-pathname group nnml-directory)) - (file-name-coding-system 'binary) - (pathname-coding-system 'binary)) + (file-name-coding-system nnmail-pathname-coding-system)) (when (not (equal pathname nnml-current-directory)) (setq nnml-current-directory pathname nnml-current-group group @@ -581,15 +578,10 @@ all. This may very well take some time.") (file-exists-p nnml-current-directory)))) (defun nnml-possibly-create-directory (group) - (let (dir dirs) - (setq dir (nnmail-group-pathname group nnml-directory)) - (while (not (file-directory-p dir)) - (push dir dirs) - (setq dir (file-name-directory (directory-file-name dir)))) - (while dirs - (make-directory (directory-file-name (car dirs))) - (nnheader-message 5 "Creating mail directory %s" (car dirs)) - (setq dirs (cdr dirs))))) + (let ((dir (nnmail-group-pathname group nnml-directory))) + (unless (file-exists-p dir) + (make-directory (directory-file-name dir) t) + (nnheader-message 5 "Creating mail directory %s" dir)))) (defun nnml-save-mail (group-art) "Called narrowed to an article." @@ -652,8 +644,8 @@ all. This may very well take some time.") (push (list group active) nnml-group-alist)) (setcdr active (1+ (cdr active))) (while (file-exists-p - (concat (nnmail-group-pathname group nnml-directory) - (int-to-string (cdr active)))) + (expand-file-name (int-to-string (cdr active)) + (nnmail-group-pathname group nnml-directory))) (setcdr active (1+ (cdr active)))) (cdr active))) @@ -693,8 +685,9 @@ all. This may very well take some time.") (save-excursion (set-buffer buffer) (set (make-local-variable 'nnml-nov-buffer-file-name) - (concat (nnmail-group-pathname group nnml-directory) - nnml-nov-file-name)) + (expand-file-name + nnml-nov-file-name + (nnmail-group-pathname group nnml-directory))) (erase-buffer) (when (file-exists-p nnml-nov-buffer-file-name) (nnheader-insert-file-contents nnml-nov-buffer-file-name))) @@ -738,7 +731,7 @@ all. This may very well take some time.") (let ((dirs (directory-files dir t nil t)) dir) (while (setq dir (pop dirs)) - (when (and (not (member (file-name-nondirectory dir) '("." ".."))) + (when (and (not (string-match "^\\." (file-name-nondirectory dir))) (file-directory-p dir)) (nnml-generate-nov-databases-1 dir seen)))) ;; Do this directory. @@ -778,7 +771,7 @@ all. This may very well take some time.") (save-excursion ;; Init the nov buffer. (set-buffer nov-buffer) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (erase-buffer) (set-buffer nntp-server-buffer) ;; Delete the old NOV file. diff --git a/lisp/gnus/nnoo.el b/lisp/gnus/nnoo.el index 9c27786bf68..45bc275ac47 100644 --- a/lisp/gnus/nnoo.el +++ b/lisp/gnus/nnoo.el @@ -1,5 +1,7 @@ ;;; nnoo.el --- OO Gnus Backends -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -105,11 +107,11 @@ (cdr (assq pbackend (nnoo-parents backend)))) (prog1 (apply function args) - ;; Copy the changed variables back into the child. - (let ((vars (cdr (assq pbackend (nnoo-parents backend))))) - (while vars - (set (cadar vars) (symbol-value (caar vars))) - (setq vars (cdr vars))))))) + ;; Copy the changed variables back into the child. + (let ((vars (cdr (assq pbackend (nnoo-parents backend))))) + (while vars + (set (cadar vars) (symbol-value (caar vars))) + (setq vars (cdr vars))))))) (defun nnoo-execute (backend function &rest args) "Execute FUNCTION on behalf of BACKEND." diff --git a/lisp/gnus/nnsoup.el b/lisp/gnus/nnsoup.el index d228d1698f9..93451d1adaf 100644 --- a/lisp/gnus/nnsoup.el +++ b/lisp/gnus/nnsoup.el @@ -1,5 +1,7 @@ ;;; nnsoup.el --- SOUP access for Gnus -;; Copyright (C) 1995,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Masanobu UMEDA @@ -38,19 +40,22 @@ (defvoo nnsoup-directory "~/SOUP/" "*SOUP packet directory.") -(defvoo nnsoup-tmp-directory temporary-file-directory +(defvoo nnsoup-tmp-directory + (cond ((fboundp 'temp-directory) (temp-directory)) + ((boundp 'temporary-file-directory) temporary-file-directory) + ("/tmp/")) "*Where nnsoup will store temporary files.") -(defvoo nnsoup-replies-directory (concat nnsoup-directory "replies/") +(defvoo nnsoup-replies-directory (expand-file-name "replies/" nnsoup-directory) "*Directory where outgoing packets will be composed.") -(defvoo nnsoup-replies-format-type ?n +(defvoo nnsoup-replies-format-type ?u ;; u is USENET news format. "*Format of the replies packages.") (defvoo nnsoup-replies-index-type ?n "*Index type of the replies packages.") -(defvoo nnsoup-active-file (concat nnsoup-directory "active") +(defvoo nnsoup-active-file (expand-file-name "active" nnsoup-directory) "Active file.") (defvoo nnsoup-packer "tar cf - %s | gzip > $HOME/Soupin%d.tgz" @@ -70,8 +75,8 @@ The SOUP packet file name will be inserted at the %s.") "*Regular expression matching SOUP packets in `nnsoup-packet-directory'.") (defvoo nnsoup-always-save t - "If non nil commit the reply buffer on each message send. -This is necessary if using message mode outside Gnus with nnsoup as a + "If non nil commit the reply buffer on each message send. +This is necessary if using message mode outside Gnus with nnsoup as a backend for the messages.") @@ -252,7 +257,7 @@ backend for the messages.") (nth 1 (nnsoup-article-to-area article nnsoup-current-group)))))) (cond ((= kind ?m) 'mail) - ((= kind ?n) 'news) + ((= kind ?n) 'news) (t 'unknown))))) (deffoo nnsoup-close-group (group &optional server) @@ -310,7 +315,7 @@ backend for the messages.") (setq info (pop infolist) range-list (gnus-uncompress-range (car info)) prefix (gnus-soup-area-prefix (nth 1 info))) - (when ;; All the articles in this file are marked for expiry. + (when;; All the articles in this file are marked for expiry. (and (or (setq mod-time (nth 5 (file-attributes (nnsoup-file prefix)))) (setq mod-time (nth 5 (file-attributes @@ -376,7 +381,7 @@ backend for the messages.") (or force nnsoup-group-alist-touched)) (setq nnsoup-group-alist-touched nil) - (nnheader-temp-write nnsoup-active-file + (with-temp-file nnsoup-active-file (gnus-prin1 `(setq nnsoup-group-alist ',nnsoup-group-alist)) (insert "\n") (gnus-prin1 `(setq nnsoup-current-prefix ,nnsoup-current-prefix)) @@ -419,12 +424,15 @@ backend for the messages.") (setq cur-prefix (nnsoup-next-prefix)) (nnheader-message 5 "Incorporating file %s..." cur-prefix) (when (file-exists-p - (setq file (concat nnsoup-tmp-directory - (gnus-soup-area-prefix area) ".IDX"))) + (setq file + (expand-file-name + (concat (gnus-soup-area-prefix area) ".IDX") + nnsoup-tmp-directory))) (rename-file file (nnsoup-file cur-prefix))) (when (file-exists-p - (setq file (concat nnsoup-tmp-directory - (gnus-soup-area-prefix area) ".MSG"))) + (setq file (expand-file-name + (concat (gnus-soup-area-prefix area) ".MSG") + nnsoup-tmp-directory))) (rename-file file (nnsoup-file cur-prefix t)) (gnus-soup-set-area-prefix area cur-prefix) ;; Find the number of new articles in this area. @@ -473,7 +481,8 @@ backend for the messages.") (goto-char (point-min)) (cond ;; rnews batch format - ((= format ?n) + ((or (= format ?u) + (= format ?n)) ;; Gnus back compatibility. (while (looking-at "^#! *rnews \\(+[0-9]+\\) *$") (forward-line 1) (push (list @@ -527,17 +536,19 @@ backend for the messages.") (let* ((file (concat prefix (if message ".MSG" ".IDX"))) (buffer-name (concat " *nnsoup " file "*"))) (or (get-buffer buffer-name) ; File already loaded. - (when (file-exists-p (concat nnsoup-directory file)) + (when (file-exists-p (expand-file-name file nnsoup-directory)) (save-excursion ; Load the file. (set-buffer (get-buffer-create buffer-name)) - (buffer-disable-undo (current-buffer)) + (buffer-disable-undo) (push (cons nnsoup-current-group (current-buffer)) nnsoup-buffers) - (nnheader-insert-file-contents (concat nnsoup-directory file)) + (nnheader-insert-file-contents + (expand-file-name file nnsoup-directory)) (current-buffer)))))) (defun nnsoup-file (prefix &optional message) (expand-file-name - (concat nnsoup-directory prefix (if message ".MSG" ".IDX")))) + (concat prefix (if message ".MSG" ".IDX")) + nnsoup-directory)) (defun nnsoup-message-buffer (prefix) (nnsoup-index-buffer prefix 'msg)) @@ -587,7 +598,7 @@ backend for the messages.") (let ((format (gnus-soup-encoding-format (gnus-soup-area-encoding (nth 1 area))))) (goto-char end) - (when (or (= format ?n) (= format ?m)) + (when (or (= format ?u) (= format ?n) (= format ?m)) (setq end (progn (forward-line -1) (point)))))) (set-buffer msg-buf)) (widen) @@ -666,8 +677,6 @@ backend for the messages.") (require 'mail-utils) (let ((tembuf (generate-new-buffer " message temp")) (case-fold-search nil) - (real-header-separator mail-header-separator) - (mail-header-separator "") delimline (mailbuf (current-buffer))) (unwind-protect @@ -693,15 +702,11 @@ backend for the messages.") ;; Change header-delimiter to be what sendmail expects. (goto-char (point-min)) (re-search-forward - (concat "^" (regexp-quote real-header-separator) "\n")) + (concat "^" (regexp-quote mail-header-separator) "\n")) (replace-match "\n") (backward-char 1) (setq delimline (point-marker)) - ;; Insert an extra newline if we need it to work around - ;; Sun's bug that swallows newlines. (goto-char (1+ delimline)) - (when (eval message-mailer-swallows-blank-line) - (newline)) (let ((msg-buf (gnus-soup-store nnsoup-replies-directory @@ -724,7 +729,7 @@ backend for the messages.") (unless nnsoup-replies-list (setq nnsoup-replies-list (gnus-soup-parse-replies - (concat nnsoup-replies-directory "REPLIES")))) + (expand-file-name "REPLIES" nnsoup-replies-directory)))) (let ((replies nnsoup-replies-list)) (while (and replies (not (string= kind (gnus-soup-reply-kind (car replies))))) @@ -752,7 +757,6 @@ backend for the messages.") (string-to-int (match-string 1 f2))))))) active group lines ident elem min) (set-buffer (get-buffer-create " *nnsoup work*")) - (buffer-disable-undo (current-buffer)) (while files (nnheader-message 5 "Doing %s..." (car files)) (erase-buffer) @@ -770,13 +774,13 @@ backend for the messages.") (if (not (setq elem (assoc group active))) (push (list group (cons 1 lines) (list (cons 1 lines) - (vector ident group "ncm" "" lines))) + (vector ident group "ucm" "" lines))) active) (nconc elem (list (list (cons (1+ (setq min (cdadr elem))) (+ min lines)) - (vector ident group "ncm" "" lines)))) + (vector ident group "ucm" "" lines)))) (setcdr (cadr elem) (+ min lines))) (setq files (cdr files))) (nnheader-message 5 "") @@ -804,7 +808,8 @@ backend for the messages.") ;; Sort and delete the files. (setq non-files (sort non-files 'string<)) (map-y-or-n-p "Delete file %s? " - (lambda (file) (delete-file (concat nnsoup-directory file))) + (lambda (file) (delete-file + (expand-file-name file nnsoup-directory))) non-files))) (provide 'nnsoup) diff --git a/lisp/gnus/nnspool.el b/lisp/gnus/nnspool.el index da39914f5d4..b33a2dbb50e 100644 --- a/lisp/gnus/nnspool.el +++ b/lisp/gnus/nnspool.el @@ -1,5 +1,7 @@ ;;; nnspool.el --- spool access for GNU Emacs -;; Copyright (C) 1988,89,90,93,94,95,96,97,98 Free Software Foundation, Inc. + +;; Copyright (C) 1988, 1989, 1990, 1993, 1994, 1995, 1996, 1997, 1998, +;; 2000 Free Software Foundation, Inc. ;; Author: Masanobu UMEDA ;; Lars Magne Ingebrigtsen @@ -28,7 +30,6 @@ (require 'nnheader) (require 'nntp) -(require 'timezone) (require 'nnoo) (eval-when-compile (require 'cl)) @@ -85,10 +86,6 @@ there.") (defvoo nnspool-file-coding-system nnheader-file-coding-system "Coding system for nnspool.") -;; 1997/8/14 by MORIOKA Tomohiko -(defvoo nnspool-file-coding-system nnheader-file-coding-system - "Coding system for nnspool.") - (defconst nnspool-version "nnspool 2.0" @@ -141,15 +138,20 @@ there.") (setq beg (point)) (inline (nnheader-insert-head file)) (goto-char beg) - (search-forward "\n\n" nil t) - (forward-char -1) - (insert ".\n") + (if (search-forward "\n\n" nil t) + (progn + (forward-char -1) + (insert ".\n")) + (goto-char (point-max)) + (if (bolp) + (insert ".\n") + (insert "\n.\n"))) (delete-region (point) (point-max))) (and do-message (zerop (% (incf count) 20)) (nnheader-message 5 "nnspool: Receiving headers... %d%%" - (/ (* count 100) number)))) + (/ (* count 100) number)))) (when do-message (nnheader-message 5 "nnspool: Receiving headers...done")) @@ -284,7 +286,7 @@ there.") (while (and (not (looking-at "\\([^ ]+\\) +\\([0-9]+\\)[0-9][0-9][0-9] ")) (zerop (forward-line -1)))) - (let ((seconds (nnspool-seconds-since-epoch date)) + (let ((seconds (time-to-seconds (date-to-time date))) groups) ;; Go through lines and add the latest groups to a list. (while (and (looking-at "\\([^ ]+\\) +[0-9]+ ") @@ -299,8 +301,8 @@ there.") (read (current-buffer))) seconds)) (push (buffer-substring - (match-beginning 1) (match-end 1)) - groups) + (match-beginning 1) (match-end 1)) + groups) (zerop (forward-line -1)))) (erase-buffer) (while groups @@ -424,7 +426,6 @@ there.") (defun nnspool-find-id (id) (save-excursion (set-buffer (get-buffer-create " *nnspool work*")) - (buffer-disable-undo (current-buffer)) (erase-buffer) (ignore-errors (call-process "grep" nil t nil (regexp-quote id) nnspool-history-file)) @@ -439,8 +440,8 @@ there.") (set-buffer nntp-server-buffer) (erase-buffer) (condition-case () - (let ((nnheader-file-coding-system nnspool-file-coding-system)) - (nnheader-insert-file-contents file) + (let ((coding-system-for-read nnspool-file-coding-system)) + (mm-insert-file-contents file) t) (file-error nil))) @@ -457,18 +458,6 @@ there.") "Find the path for GROUP." (nnheader-group-pathname group nnspool-spool-directory article)) -(defun nnspool-seconds-since-epoch (date) - (let* ((tdate (mapcar (lambda (ti) (and ti (string-to-int ti))) - (timezone-parse-date date))) - (ttime (mapcar (lambda (ti) (and ti (string-to-int ti))) - (timezone-parse-time - (aref (timezone-parse-date date) 3)))) - (unix (encode-time (nth 2 ttime) (nth 1 ttime) (nth 0 ttime) - (nth 2 tdate) (nth 1 tdate) (nth 0 tdate) - (nth 4 tdate)))) - (+ (* (car unix) 65536.0) - (cadr unix)))) - (provide 'nnspool) ;;; nnspool.el ends here diff --git a/lisp/gnus/nntp.el b/lisp/gnus/nntp.el index a653c5d65ec..5ced1d77d82 100644 --- a/lisp/gnus/nntp.el +++ b/lisp/gnus/nntp.el @@ -1,5 +1,7 @@ -;;; nntp.el --- nntp access for Gnus Copyright (C) 1987-90,92-97 Free -;;; Software Foundation, Inc. +;;; nntp.el --- nntp access for Gnus +;; Copyright (C) 1987, 1988, 1989, 1990, 1992, 1993, 1994, 1995, 1996, +;; 1997, 1998, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -28,13 +30,9 @@ (require 'nnoo) (require 'gnus-util) -(eval-when-compile (require 'cl)) - (nnoo-declare nntp) -(eval-and-compile - (unless (fboundp 'open-network-stream) - (require 'tcp))) +(eval-when-compile (require 'cl)) (defvoo nntp-address nil "Address of the physical nntp server.") @@ -52,10 +50,10 @@ server spawn an nnrpd server.") It is called with no parameters.") (defvoo nntp-server-action-alist - '(("nntpd 1\\.5\\.11t" - (remove-hook 'nntp-server-opened-hook 'nntp-send-mode-reader)) - ("NNRP server Netscape" - (setq nntp-server-list-active-group nil))) + '(("nntpd 1\\.5\\.11t" + (remove-hook 'nntp-server-opened-hook 'nntp-send-mode-reader)) + ("NNRP server Netscape" + (setq nntp-server-list-active-group nil))) "Alist of regexps to match on server types and actions to be taken. For instance, if you want Gnus to beep every time you connect to innd, you could say something like: @@ -89,7 +87,8 @@ case, this list will be used as the parameter list given to rsh.") (defvoo nntp-rlogin-user-name nil "*User name on remote system when using the rlogin connect method.") -(defvoo nntp-telnet-parameters '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp") +(defvoo nntp-telnet-parameters + '("exec" "telnet" "-8" "${NNTPSERVER:=news}" "nntp") "*Parameters to `nntp-open-telnet'. That function may be used as `nntp-open-connection-function'. In that case, this list will be executed as a command after logging in @@ -177,13 +176,6 @@ server there that you can connect to. See also (const :format "" "password") (string :format "Password: %v"))))))) -;; 1997/5/4 by MORIOKA Tomohiko -(defvoo nntp-coding-system-for-read 'binary - "*Coding system to read from NNTP.") - -(defvoo nntp-coding-system-for-write 'binary - "*Coding system to write to NNTP.") - (defvoo nntp-connection-timeout nil @@ -220,8 +212,18 @@ If this variable is nil, which is the default, no timers are set.") (defvoo nntp-server-xover 'try) (defvoo nntp-server-list-active-group 'try) +(defvar nntp-async-needs-kluge + (string-match "^GNU Emacs 20\\.3\\." (emacs-version)) + "*When non-nil, nntp will poll asynchronous connections +once a second. By default, this is turned on only for Emacs +20.3, which has a bug that breaks nntp's normal method of +noticing asynchronous data.") + +(defvar nntp-async-timer nil) +(defvar nntp-async-process-list nil) + (eval-and-compile - (autoload 'nnmail-read-passwd "nnmail") + (autoload 'mail-source-read-passwd "mail-source") (autoload 'open-ssl-stream "ssl")) @@ -281,9 +283,9 @@ If this variable is nil, which is the default, no timers are set.") (nntp-decode-text (not decode)) (unless discard (save-excursion - (set-buffer buffer) - (goto-char (point-max)) - (insert-buffer-substring (process-buffer process)) + (set-buffer buffer) + (goto-char (point-max)) + (insert-buffer-substring (process-buffer process)) ;; Nix out "nntp reading...." message. (when nntp-have-messaged (setq nntp-have-messaged nil) @@ -292,6 +294,11 @@ If this variable is nil, which is the default, no timers are set.") (unless discard (erase-buffer))))) +(defun nntp-kill-buffer (buffer) + (when (buffer-name buffer) + (kill-buffer buffer) + (nnheader-init-server-buffer))) + (defsubst nntp-find-connection (buffer) "Find the connection delivering to BUFFER." (let ((alist nntp-connection-alist) @@ -304,8 +311,7 @@ If this variable is nil, which is the default, no timers are set.") (when process (if (memq (process-status process) '(open run)) process - (when (buffer-name (process-buffer process)) - (kill-buffer (process-buffer process))) + (nntp-kill-buffer (process-buffer process)) (setq nntp-connection-alist (delq entry nntp-connection-alist)) nil)))) @@ -330,27 +336,23 @@ If this variable is nil, which is the default, no timers are set.") (save-excursion (set-buffer (process-buffer process)) (erase-buffer))) - (when command - (nntp-send-string process command)) - (cond - ((eq callback 'ignore) - t) - ((and callback wait-for) - (save-excursion - (set-buffer (process-buffer process)) - (unless nntp-inside-change-function - (erase-buffer)) - (setq nntp-process-decode decode - nntp-process-to-buffer buffer - nntp-process-wait-for wait-for - nntp-process-callback callback - nntp-process-start-point (point-max) - after-change-functions - (list 'nntp-after-change-function-callback))) - t) - (wait-for - (nntp-wait-for process wait-for buffer decode)) - (t t))))) + (condition-case err + (progn + (when command + (nntp-send-string process command)) + (cond + ((eq callback 'ignore) + t) + ((and callback wait-for) + (nntp-async-wait process wait-for buffer decode callback) + t) + (wait-for + (nntp-wait-for process wait-for buffer decode)) + (t t))) + (error + (nnheader-report 'nntp "Couldn't open connection to %s: %s" + address err)) + (quit nil))))) (defsubst nntp-send-command (wait-for &rest strings) "Send STRINGS to server and wait until WAIT-FOR returns." @@ -407,7 +409,7 @@ If this variable is nil, which is the default, no timers are set.") (cond ;; A result that starts with a 2xx code is terminated by ;; a line with only a "." on it. - ((eq (following-char) ?2) + ((eq (char-after) ?2) (if (re-search-forward "\n\\.\r?\n" nil t) t nil)) @@ -442,36 +444,36 @@ If this variable is nil, which is the default, no timers are set.") (nntp-inhibit-erase t) article) ;; Send HEAD commands. - (while (setq article (pop articles)) - (nntp-send-command - nil - "HEAD" (if (numberp article) - (int-to-string article) - ;; `articles' is either a list of article numbers - ;; or a list of article IDs. - article)) - (incf count) - ;; Every 400 requests we have to read the stream in - ;; order to avoid deadlocks. - (when (or (null articles) ;All requests have been sent. - (zerop (% count nntp-maximum-request))) - (nntp-accept-response) - (while (progn - (set-buffer buf) - (goto-char last-point) - ;; Count replies. - (while (nntp-next-result-arrived-p) - (setq last-point (point)) - (incf received)) - (< received count)) - ;; If number of headers is greater than 100, give - ;; informative messages. - (and (numberp nntp-large-newsgroup) - (> number nntp-large-newsgroup) - (zerop (% received 20)) - (nnheader-message 6 "NNTP: Receiving headers... %d%%" - (/ (* received 100) number))) - (nntp-accept-response)))) + (while (setq article (pop articles)) + (nntp-send-command + nil + "HEAD" (if (numberp article) + (int-to-string article) + ;; `articles' is either a list of article numbers + ;; or a list of article IDs. + article)) + (incf count) + ;; Every 400 requests we have to read the stream in + ;; order to avoid deadlocks. + (when (or (null articles) ;All requests have been sent. + (zerop (% count nntp-maximum-request))) + (nntp-accept-response) + (while (progn + (set-buffer buf) + (goto-char last-point) + ;; Count replies. + (while (nntp-next-result-arrived-p) + (setq last-point (point)) + (incf received)) + (< received count)) + ;; If number of headers is greater than 100, give + ;; informative messages. + (and (numberp nntp-large-newsgroup) + (> number nntp-large-newsgroup) + (zerop (% received 20)) + (nnheader-message 6 "NNTP: Receiving headers... %d%%" + (/ (* received 100) number))) + (nntp-accept-response)))) (and (numberp nntp-large-newsgroup) (> number nntp-large-newsgroup) (nnheader-message 6 "NNTP: Receiving headers...done")) @@ -486,64 +488,75 @@ If this variable is nil, which is the default, no timers are set.") (deffoo nntp-retrieve-groups (groups &optional server) "Retrieve group info on GROUPS." (nntp-possibly-change-group nil server) - (save-excursion - (set-buffer (nntp-find-connection-buffer nntp-server-buffer)) - ;; The first time this is run, this variable is `try'. So we - ;; try. - (when (eq nntp-server-list-active-group 'try) - (nntp-try-list-active (car groups))) - (erase-buffer) - (let ((count 0) - (received 0) - (last-point (point-min)) - (nntp-inhibit-erase t) - (command (if nntp-server-list-active-group "LIST ACTIVE" "GROUP"))) - (while groups - ;; Send the command to the server. - (nntp-send-command nil command (pop groups)) - (incf count) - ;; Every 400 requests we have to read the stream in - ;; order to avoid deadlocks. - (when (or (null groups) ;All requests have been sent. - (zerop (% count nntp-maximum-request))) - (nntp-accept-response) + (when (nntp-find-connection-buffer nntp-server-buffer) + (save-excursion + ;; Erase nntp-server-buffer before nntp-inhibit-erase. + (set-buffer nntp-server-buffer) + (erase-buffer) + (set-buffer (nntp-find-connection-buffer nntp-server-buffer)) + ;; The first time this is run, this variable is `try'. So we + ;; try. + (when (eq nntp-server-list-active-group 'try) + (nntp-try-list-active (car groups))) + (erase-buffer) + (let ((count 0) + (received 0) + (last-point (point-min)) + (nntp-inhibit-erase t) + (buf (nntp-find-connection-buffer nntp-server-buffer)) + (command (if nntp-server-list-active-group "LIST ACTIVE" "GROUP"))) + (while groups + ;; Send the command to the server. + (nntp-send-command nil command (pop groups)) + (incf count) + ;; Every 400 requests we have to read the stream in + ;; order to avoid deadlocks. + (when (or (null groups) ;All requests have been sent. + (zerop (% count nntp-maximum-request))) + (nntp-accept-response) + (while (progn + ;; Search `blue moon' in this file for the + ;; reason why set-buffer here. + (set-buffer buf) + (goto-char last-point) + ;; Count replies. + (while (re-search-forward "^[0-9]" nil t) + (incf received)) + (setq last-point (point)) + (< received count)) + (nntp-accept-response)))) + + ;; Wait for the reply from the final command. + (set-buffer buf) + (goto-char (point-max)) + (re-search-backward "^[0-9]" nil t) + (when (looking-at "^[23]") (while (progn - (goto-char last-point) - ;; Count replies. - (while (re-search-forward "^[0-9]" nil t) - (incf received)) - (setq last-point (point)) - (< received count)) - (nntp-accept-response)))) + (set-buffer buf) + (goto-char (point-max)) + (if (not nntp-server-list-active-group) + (not (re-search-backward "\r?\n" (- (point) 3) t)) + (not (re-search-backward "^\\.\r?\n" (- (point) 4) t)))) + (nntp-accept-response))) - ;; Wait for the reply from the final command. - (goto-char (point-max)) - (re-search-backward "^[0-9]" nil t) - (when (looking-at "^[23]") - (while (progn - (goto-char (point-max)) - (if (not nntp-server-list-active-group) - (not (re-search-backward "\r?\n" (- (point) 3) t)) - (not (re-search-backward "^\\.\r?\n" (- (point) 4) t)))) - (nntp-accept-response))) - - ;; Now all replies are received. We remove CRs. - (goto-char (point-min)) - (while (search-forward "\r" nil t) - (replace-match "" t t)) - - (if (not nntp-server-list-active-group) - (progn - (copy-to-buffer nntp-server-buffer (point-min) (point-max)) - 'group) - ;; We have read active entries, so we just delete the - ;; superfluous gunk. + ;; Now all replies are received. We remove CRs. + (set-buffer buf) (goto-char (point-min)) - (while (re-search-forward "^[.2-5]" nil t) - (delete-region (match-beginning 0) - (progn (forward-line 1) (point)))) - (copy-to-buffer nntp-server-buffer (point-min) (point-max)) - 'active)))) + (while (search-forward "\r" nil t) + (replace-match "" t t)) + + (if (not nntp-server-list-active-group) + (progn + (copy-to-buffer nntp-server-buffer (point-min) (point-max)) + 'group) + ;; We have read active entries, so we just delete the + ;; superfluous gunk. + (goto-char (point-min)) + (while (re-search-forward "^[.2-5]" nil t) + (delete-region (match-beginning 0) + (progn (forward-line 1) (point)))) + (copy-to-buffer nntp-server-buffer (point-min) (point-max)) + 'active))))) (deffoo nntp-retrieve-articles (articles &optional group server) (nntp-possibly-change-group group server) @@ -625,9 +638,14 @@ If this variable is nil, which is the default, no timers are set.") (setq nntp-server-list-active-group t))))) (deffoo nntp-list-active-group (group &optional server) - "Return the active info on GROUP (which can be a regexp." + "Return the active info on GROUP (which can be a regexp)." (nntp-possibly-change-group nil server) - (nntp-send-command "^.*\r?\n" "LIST ACTIVE" group)) + (nntp-send-command "^\\.*\r?\n" "LIST ACTIVE" group)) + +(deffoo nntp-request-group-articles (group &optional server) + "Return the list of existing articles in GROUP." + (nntp-possibly-change-group nil server) + (nntp-send-command "^\\.*\r?\n" "LISTGROUP" group)) (deffoo nntp-request-article (article &optional group server buffer command) (nntp-possibly-change-group group server) @@ -700,8 +718,7 @@ If this variable is nil, which is the default, no timers are set.") ;; QUIT command actually is sent out before we kill ;; the process. (sleep-for 1)))) - (when (buffer-name (process-buffer process)) - (kill-buffer (process-buffer process))) + (nntp-kill-buffer (process-buffer process)) (setq process (car (pop nntp-connection-alist)))) (nnoo-close-server 'nntp))) @@ -717,8 +734,7 @@ If this variable is nil, which is the default, no timers are set.") ;; QUIT command actually is sent out before we kill ;; the process. (sleep-for 1)))) - (when (buffer-name (process-buffer process)) - (kill-buffer (process-buffer process)))))) + (nntp-kill-buffer (process-buffer process))))) (deffoo nntp-request-list (&optional server) (nntp-possibly-change-group nil server) @@ -735,7 +751,7 @@ If this variable is nil, which is the default, no timers are set.") (prog1 (nntp-send-command "^\\.\r?\n" "NEWGROUPS" - (format-time-string "%y%m%d %H%M%S" (nnmail-date-to-time date))) + (format-time-string "%y%m%d %H%M%S" (date-to-time date))) (nntp-decode-text)))) (deffoo nntp-request-post (&optional server) @@ -756,7 +772,7 @@ If this variable is nil, which is the default, no timers are set.") This function is supposed to be called from `nntp-server-opened-hook'. It will make innd servers spawn an nnrpd process to allow actual article reading." - (nntp-send-command "^.*\r?\n" "MODE READER")) + (nntp-send-command "^.*\n" "MODE READER")) (defun nntp-send-authinfo (&optional send-if-force) "Send the AUTHINFO to the nntp server. @@ -767,7 +783,7 @@ and a password. If SEND-IF-FORCE, only send authinfo to the server if the .authinfo file has the FORCE token." (let* ((list (gnus-parse-netrc nntp-authinfo-file)) - (alist (gnus-netrc-machine list nntp-address)) + (alist (gnus-netrc-machine list nntp-address "nntp")) (force (gnus-netrc-get alist "force")) (user (or (gnus-netrc-get alist "login") nntp-authinfo-user)) (passwd (gnus-netrc-get alist "password"))) @@ -779,13 +795,13 @@ If SEND-IF-FORCE, only send authinfo to the server if the (unless (member user '(nil "")) (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user) (when t ;???Should check if AUTHINFO succeeded - (nntp-send-command - "^2.*\r?\n" "AUTHINFO PASS" - (or passwd - nntp-authinfo-password - (setq nntp-authinfo-password - (nnmail-read-passwd (format "NNTP (%s@%s) password: " - user nntp-address)))))))))) + (nntp-send-command + "^2.*\r?\n" "AUTHINFO PASS" + (or passwd + nntp-authinfo-password + (setq nntp-authinfo-password + (mail-source-read-passwd (format "NNTP (%s@%s) password: " + user nntp-address)))))))))) (defun nntp-send-nosy-authinfo () "Send the AUTHINFO to the nntp server." @@ -794,8 +810,8 @@ If SEND-IF-FORCE, only send authinfo to the server if the (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" user) (when t ;???Should check if AUTHINFO succeeded (nntp-send-command "^2.*\r?\n" "AUTHINFO PASS" - (nnmail-read-passwd "NNTP (%s@%s) password: " - user nntp-address)))))) + (mail-source-read-passwd "NNTP (%s@%s) password: " + user nntp-address)))))) (defun nntp-send-authinfo-from-file () "Send the AUTHINFO to the nntp server. @@ -803,7 +819,7 @@ If SEND-IF-FORCE, only send authinfo to the server if the The authinfo login name is taken from the user's login name and the password contained in '~/.nntp-authinfo'." (when (file-exists-p "~/.nntp-authinfo") - (nnheader-temp-write nil + (with-temp-buffer (insert-file-contents "~/.nntp-authinfo") (goto-char (point-min)) (nntp-send-command "^3.*\r?\n" "AUTHINFO USER" (user-login-name)) @@ -832,7 +848,7 @@ password contained in '~/.nntp-authinfo'." (format " *server %s %s %s*" nntp-address nntp-port-number (gnus-buffer-exists-p buffer)))) - (buffer-disable-undo (current-buffer)) + (mm-enable-multibyte) (set (make-local-variable 'after-change-functions) nil) (set (make-local-variable 'nntp-process-wait-for) nil) (set (make-local-variable 'nntp-process-callback) nil) @@ -850,8 +866,7 @@ password contained in '~/.nntp-authinfo'." (nnheader-run-at-time nntp-connection-timeout nil `(lambda () - (when (buffer-name ,pbuffer) - (kill-buffer ,pbuffer)))))) + (nntp-kill-buffer ,pbuffer))))) (process (condition-case () (let ((coding-system-for-read nntp-coding-system-for-read) @@ -877,8 +892,7 @@ password contained in '~/.nntp-authinfo'." (let ((nnheader-callback-function nil)) (run-hooks 'nntp-server-opened-hook) (nntp-send-authinfo t)))) - (when (buffer-name (process-buffer process)) - (kill-buffer (process-buffer process))) + (nntp-kill-buffer (process-buffer process)) nil)))) (defun nntp-open-network-stream (buffer) @@ -910,40 +924,97 @@ password contained in '~/.nntp-authinfo'." (eval (cadr entry)) (funcall (cadr entry))))))) -(defun nntp-after-change-function-callback (beg end len) - (when nntp-process-callback - (save-match-data - (if (and (= beg (point-min)) - (memq (char-after beg) '(?4 ?5))) - ;; Report back error messages. - (save-excursion - (goto-char beg) - (if (looking-at "480") - (nntp-handle-authinfo nntp-process-to-buffer) - (nntp-snarf-error-message) - (funcall nntp-process-callback nil))) - (goto-char end) - (when (and (> (point) nntp-process-start-point) - (re-search-backward nntp-process-wait-for - nntp-process-start-point t)) +(defun nntp-async-wait (process wait-for buffer decode callback) + (save-excursion + (set-buffer (process-buffer process)) + (unless nntp-inside-change-function + (erase-buffer)) + (setq nntp-process-wait-for wait-for + nntp-process-to-buffer buffer + nntp-process-decode decode + nntp-process-callback callback + nntp-process-start-point (point-max)) + (setq after-change-functions '(nntp-after-change-function)) + (if nntp-async-needs-kluge + (nntp-async-kluge process)))) + +(defun nntp-async-kluge (process) + ;; emacs 20.3 bug: process output with encoding 'binary + ;; doesn't trigger after-change-functions. + (unless nntp-async-timer + (setq nntp-async-timer + (nnheader-run-at-time 1 1 'nntp-async-timer-handler))) + (add-to-list 'nntp-async-process-list process)) + +(defun nntp-async-timer-handler () + (mapcar + (lambda (proc) + (if (memq (process-status proc) '(open run)) + (nntp-async-trigger proc) + (nntp-async-stop proc))) + nntp-async-process-list)) + +(defun nntp-async-stop (proc) + (setq nntp-async-process-list (delq proc nntp-async-process-list)) + (when (and nntp-async-timer (not nntp-async-process-list)) + (nnheader-cancel-timer nntp-async-timer) + (setq nntp-async-timer nil))) + +(defun nntp-after-change-function (beg end len) + (unwind-protect + ;; we only care about insertions at eob + (when (and (eq 0 len) (eq (point-max) end)) + (save-match-data + (let ((proc (get-buffer-process (current-buffer)))) + (when proc + (nntp-async-trigger proc))))) + ;; any throw from after-change-functions will leave it + ;; set to nil. so we reset it here, if necessary. + (when quit-flag + (setq after-change-functions '(nntp-after-change-function))))) + +(defun nntp-async-trigger (process) + (save-excursion + (set-buffer (process-buffer process)) + (when nntp-process-callback + ;; do we have an error message? + (goto-char nntp-process-start-point) + (if (memq (following-char) '(?4 ?5)) + ;; wants credentials? + (if (looking-at "480") + (nntp-handle-authinfo nntp-process-to-buffer) + ;; report error message. + (nntp-snarf-error-message) + (nntp-do-callback nil)) + + ;; got what we expect? + (goto-char (point-max)) + (when (re-search-backward + nntp-process-wait-for nntp-process-start-point t) + (nntp-async-stop process) + ;; convert it. (when (gnus-buffer-exists-p nntp-process-to-buffer) - (let ((cur (current-buffer)) - (start nntp-process-start-point)) + (let ((buf (current-buffer)) + (start nntp-process-start-point) + (decode nntp-process-decode)) (save-excursion (set-buffer nntp-process-to-buffer) (goto-char (point-max)) - (let ((b (point))) - (insert-buffer-substring cur start) - (narrow-to-region b (point-max)) - (nntp-decode-text) - (widen))))) - (goto-char end) - (let ((callback nntp-process-callback) - (nntp-inside-change-function t)) - (setq nntp-process-callback nil) - (save-excursion - (funcall callback (buffer-name - (get-buffer nntp-process-to-buffer)))))))))) + (save-restriction + (narrow-to-region (point) (point)) + (insert-buffer-substring buf start) + (when decode + (nntp-decode-text)))))) + ;; report it. + (goto-char (point-max)) + (nntp-do-callback + (buffer-name (get-buffer nntp-process-to-buffer)))))))) + +(defun nntp-do-callback (arg) + (let ((callback nntp-process-callback) + (nntp-inside-change-function t)) + (setq nntp-process-callback nil) + (funcall callback arg))) (defun nntp-snarf-error-message () "Save the error message in the current buffer." @@ -953,7 +1024,7 @@ password contained in '~/.nntp-authinfo'." (nnheader-report 'nntp message) message)) -(defun nntp-accept-process-output (process) +(defun nntp-accept-process-output (process &optional timeout) "Wait for output from PROCESS and message some dots." (save-excursion (set-buffer (or (nntp-find-connection-buffer nntp-server-buffer) @@ -963,7 +1034,7 @@ password contained in '~/.nntp-authinfo'." (unless (< len 10) (setq nntp-have-messaged t) (nnheader-message 7 "nntp read: %dk" len))) - (accept-process-output process 1))) + (accept-process-output process (or timeout 1)))) (defun nntp-accept-response () "Wait for output from the process that outputs to BUFFER." @@ -985,10 +1056,7 @@ password contained in '~/.nntp-authinfo'." (save-excursion (set-buffer (process-buffer (car entry))) (erase-buffer) - (nntp-send-string (car entry) (concat "GROUP " group)) - ;; allow for unexpected responses, since this can be called - ;; from a timer with quit inhibited - (nntp-wait-for-string "^[245].*\n") + (nntp-send-command "^[245].*\n" "GROUP" group) (setcar (cddr entry) group) (erase-buffer)))))) @@ -1051,7 +1119,7 @@ password contained in '~/.nntp-authinfo'." (car (last articles)) 'wait) (goto-char (point-min)) - (when (looking-at "[1-5][0-9][0-9] ") + (when (looking-at "[1-5][0-9][0-9] .*\n") (delete-region (point) (progn (forward-line 1) (point)))) (while (search-forward "\r" nil t) (replace-match "" t t)) @@ -1068,9 +1136,10 @@ password contained in '~/.nntp-authinfo'." ((numberp nntp-nov-gap) (let ((count 0) (received 0) - (last-point (point-min)) + last-point + in-process-buffer-p (buf nntp-server-buffer) - ;;(process-buffer (nntp-find-connection (current-buffer)))) + (process-buffer (nntp-find-connection-buffer nntp-server-buffer)) first) ;; We have to check `nntp-server-xover'. If it gets set to nil, ;; that means that the server does not understand XOVER, but we @@ -1083,40 +1152,58 @@ password contained in '~/.nntp-authinfo'." (< (- (nth 1 articles) (car articles)) nntp-nov-gap)) (setq articles (cdr articles))) - (when (nntp-send-xover-command first (car articles)) - (setq articles (cdr articles) - count (1+ count)) - + (setq in-process-buffer-p (stringp nntp-server-xover)) + (nntp-send-xover-command first (car articles)) + (setq articles (cdr articles)) + + (when (and nntp-server-xover in-process-buffer-p) + ;; Don't count tried request. + (setq count (1+ count)) + ;; Every 400 requests we have to read the stream in ;; order to avoid deadlocks. (when (or (null articles) ;All requests have been sent. (zerop (% count nntp-maximum-request))) - (accept-process-output) - ;; On some Emacs versions the preceding function has - ;; a tendency to change the buffer. Perhaps. It's - ;; quite difficult to reproduce, because it only - ;; seems to happen once in a blue moon. - (set-buffer buf) + + (nntp-accept-response) + ;; On some Emacs versions the preceding function has a + ;; tendency to change the buffer. Perhaps. It's quite + ;; difficult to reproduce, because it only seems to happen + ;; once in a blue moon. + (set-buffer process-buffer) (while (progn - (goto-char last-point) + (goto-char (or last-point (point-min))) ;; Count replies. - (while (re-search-forward "^[0-9][0-9][0-9] " nil t) - (setq received (1+ received))) + (while (re-search-forward "^[0-9][0-9][0-9] .*\n" nil t) + (incf received)) (setq last-point (point)) (< received count)) - (accept-process-output) - (set-buffer buf))))) + (nntp-accept-response) + (set-buffer process-buffer)) + (set-buffer buf)))) (when nntp-server-xover - ;; Wait for the reply from the final command. - (goto-char (point-max)) - (re-search-backward "^[0-9][0-9][0-9] " nil t) - (when (looking-at "^[23]") - (while (progn - (goto-char (point-max)) - (forward-line -1) - (not (looking-at "^\\.\r?\n"))) - (nntp-accept-response))) + (when in-process-buffer-p + (set-buffer process-buffer) + ;; Wait for the reply from the final command. + (goto-char (point-max)) + (while (not (re-search-backward "^[0-9][0-9][0-9] " nil t)) + (nntp-accept-response) + (set-buffer process-buffer) + (goto-char (point-max))) + (when (looking-at "^[23]") + (while (progn + (goto-char (point-max)) + (forward-line -1) + (not (looking-at "^\\.\r?\n"))) + (nntp-accept-response) + (set-buffer process-buffer))) + (set-buffer buf) + (goto-char (point-max)) + (insert-buffer-substring process-buffer) + (set-buffer process-buffer) + (erase-buffer) + (set-buffer buf)) ;; We remove any "." lines and status lines. (goto-char (point-min)) @@ -1124,7 +1211,6 @@ password contained in '~/.nntp-authinfo'." (delete-char -1)) (goto-char (point-min)) (delete-matching-lines "^\\.$\\|^[1-5][0-9][0-9] ") - ;;(copy-to-buffer nntp-server-buffer (point-min) (point-max)) t)))) nntp-server-xover) @@ -1140,7 +1226,7 @@ password contained in '~/.nntp-authinfo'." (nntp-send-command-nodelete "\r?\n\\.\r?\n" nntp-server-xover range) ;; We do not wait for the reply. - (nntp-send-command-nodelete "\r?\n\\.\r?\n" nntp-server-xover range)) + (nntp-send-command-nodelete nil nntp-server-xover range)) (let ((commands nntp-xover-commands)) ;; `nntp-xover-commands' is a list of possible XOVER commands. ;; We try them all until we get at positive response. @@ -1206,9 +1292,8 @@ password contained in '~/.nntp-authinfo'." proc (concat (or nntp-telnet-passwd (setq nntp-telnet-passwd - (nnmail-read-passwd "Password: "))) + (mail-source-read-passwd "Password: "))) "\n")) - (erase-buffer) (nntp-wait-for-string nntp-telnet-shell-prompt) (process-send-string proc (concat (mapconcat 'identity nntp-telnet-parameters " ") "\n")) diff --git a/lisp/gnus/nnvirtual.el b/lisp/gnus/nnvirtual.el index 510cd1b0517..5793573f870 100644 --- a/lisp/gnus/nnvirtual.el +++ b/lisp/gnus/nnvirtual.el @@ -1,5 +1,6 @@ ;;; nnvirtual.el --- virtual newsgroups access for Gnus -;; Copyright (C) 1994,95,96,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: David Moore ;; Lars Magne Ingebrigtsen @@ -45,10 +46,9 @@ (defvoo nnvirtual-always-rescan t "*If non-nil, always scan groups for unread articles when entering a group. -If this variable is nil (which is the default) and you read articles -in a component group after the virtual group has been activated, the -read articles from the component group will show up when you enter the -virtual group.") +If this variable is nil and you read articles in a component group +after the virtual group has been activated, the read articles from the +component group will show up when you enter the virtual group.") (defvoo nnvirtual-component-regexp nil "*Regexp to match component groups.") @@ -63,8 +63,7 @@ virtual group.") (defvoo nnvirtual-current-group nil) (defvoo nnvirtual-mapping-table nil - "Table of rules on how to map between component group and article number -to virtual article number.") + "Table of rules on how to map between component group and article number to virtual article number.") (defvoo nnvirtual-mapping-offsets nil "Table indexed by component group to an offset to be applied to article numbers in that group.") @@ -122,47 +121,47 @@ to virtual article number.") (let ((gnus-use-cache t)) (setq result (gnus-retrieve-headers articles cgroup nil)))) - (set-buffer nntp-server-buffer) - ;; If we got HEAD headers, we convert them into NOV - ;; headers. This is slow, inefficient and, come to think - ;; of it, downright evil. So sue me. I couldn't be - ;; bothered to write a header parse routine that could - ;; parse a mixed HEAD/NOV buffer. - (when (eq result 'headers) - (nnvirtual-convert-headers)) - (goto-char (point-min)) - (while (not (eobp)) - (delete-region (point) - (progn - (setq carticle (read nntp-server-buffer)) - (point))) + (set-buffer nntp-server-buffer) + ;; If we got HEAD headers, we convert them into NOV + ;; headers. This is slow, inefficient and, come to think + ;; of it, downright evil. So sue me. I couldn't be + ;; bothered to write a header parse routine that could + ;; parse a mixed HEAD/NOV buffer. + (when (eq result 'headers) + (nnvirtual-convert-headers)) + (goto-char (point-min)) + (while (not (eobp)) + (delete-region (point) + (progn + (setq carticle (read nntp-server-buffer)) + (point))) - ;; We remove this article from the articles list, if - ;; anything is left in the articles list after going through - ;; the entire buffer, then those articles have been - ;; expired or canceled, so we appropriately update the - ;; component group below. They should be coming up - ;; generally in order, so this shouldn't be slow. - (setq articles (delq carticle articles)) + ;; We remove this article from the articles list, if + ;; anything is left in the articles list after going through + ;; the entire buffer, then those articles have been + ;; expired or canceled, so we appropriately update the + ;; component group below. They should be coming up + ;; generally in order, so this shouldn't be slow. + (setq articles (delq carticle articles)) - (setq article (nnvirtual-reverse-map-article cgroup carticle)) - (if (null article) - ;; This line has no reverse mapping, that means it - ;; was an extra article reference returned by nntp. - (progn - (beginning-of-line) - (delete-region (point) (progn (forward-line 1) (point)))) - ;; Otherwise insert the virtual article number, - ;; and clean up the xrefs. - (princ article nntp-server-buffer) - (nnvirtual-update-xref-header cgroup carticle - prefix system-name) - (forward-line 1)) - ) + (setq article (nnvirtual-reverse-map-article cgroup carticle)) + (if (null article) + ;; This line has no reverse mapping, that means it + ;; was an extra article reference returned by nntp. + (progn + (beginning-of-line) + (delete-region (point) (progn (forward-line 1) (point)))) + ;; Otherwise insert the virtual article number, + ;; and clean up the xrefs. + (princ article nntp-server-buffer) + (nnvirtual-update-xref-header cgroup carticle + prefix system-name) + (forward-line 1)) + ) - (set-buffer vbuf) - (goto-char (point-max)) - (insert-buffer-substring nntp-server-buffer)) + (set-buffer vbuf) + (goto-char (point-max)) + (insert-buffer-substring nntp-server-buffer)) ;; Anything left in articles is expired or canceled. ;; Could be smart and not tell it about articles already known? (when articles @@ -199,8 +198,9 @@ to virtual article number.") (save-excursion (when buffer (set-buffer buffer)) - (let ((method (gnus-find-method-for-group - nnvirtual-last-accessed-component-group))) + (let* ((gnus-override-method nil) + (method (gnus-find-method-for-group + nnvirtual-last-accessed-component-group))) (funcall (gnus-get-function method 'request-article) article nil (nth 1 method) buffer))))) ;; This is a fetch by number. @@ -219,7 +219,9 @@ to virtual article number.") (if buffer (save-excursion (set-buffer buffer) - (gnus-request-article-this-buffer (cdr amap) cgroup)) + ;; We bind this here to avoid double decoding. + (let ((gnus-article-decode-hook nil)) + (gnus-request-article-this-buffer (cdr amap) cgroup))) (gnus-request-article (cdr amap) cgroup)))))))) @@ -283,11 +285,11 @@ to virtual article number.") (deffoo nnvirtual-request-update-mark (group article mark) (let* ((nart (nnvirtual-map-article article)) - (cgroup (car nart)) - ;; The component group might be a virtual group. - (nmark (gnus-request-update-mark cgroup (cdr nart) mark))) + (cgroup (car nart))) (when (and nart - (= mark nmark) + (memq mark gnus-auto-expirable-marks) + ;; The component group might be a virtual group. + (= mark (gnus-request-update-mark cgroup (cdr nart) mark)) (gnus-group-auto-expirable-p cgroup)) (setq mark gnus-expirable-mark))) mark) @@ -359,6 +361,22 @@ to virtual article number.") (cdr gnus-message-group-art))))) (gnus-request-post (gnus-find-method-for-group group))))) + +(deffoo nnvirtual-request-expire-articles (articles group + &optional server force) + (nnvirtual-possibly-change-server server) + (setq nnvirtual-component-groups + (delete (nnvirtual-current-group) nnvirtual-component-groups)) + (let (unexpired) + (dolist (group nnvirtual-component-groups) + (setq unexpired (nconc unexpired + (mapcar + #'(lambda (article) + (nnvirtual-reverse-map-article + group article)) + (gnus-group-expire-articles-1 group))))) + (sort unexpired '<))) + ;;; Internal functions. @@ -385,7 +403,7 @@ to virtual article number.") (insert "\t")) ;; Remove any spaces at the beginning of the Xref field. - (while (= (char-after (1- (point))) ? ) + (while (eq (char-after (1- (point))) ? ) (forward-char -1) (delete-char 1)) @@ -417,7 +435,7 @@ to virtual article number.") ;; Ensure a trailing \t. (end-of-line) - (or (= (char-after (1- (point))) ?\t) + (or (eq (char-after (1- (point))) ?\t) (insert ?\t))) @@ -436,19 +454,24 @@ If UPDATE-P is not nil, call gnus-group-update-group on the components." (nnvirtual-partition-sequence (gnus-list-of-unread-articles (nnvirtual-current-group))))) - (type-marks (mapcar (lambda (ml) - (cons (car ml) - (nnvirtual-partition-sequence (cdr ml)))) - (gnus-info-marks (gnus-get-info - (nnvirtual-current-group))))) + (type-marks + (delq nil + (mapcar (lambda (ml) + (if (eq (car ml) 'score) + nil + (cons (car ml) + (nnvirtual-partition-sequence (cdr ml))))) + (gnus-info-marks (gnus-get-info + (nnvirtual-current-group)))))) mark type groups carticles info entry) ;; Ok, atomically move all of the (un)read info, clear any old ;; marks, and move all of the current marks. This way if someone ;; hits C-g, you won't leave the component groups in a half-way state. - (gnus-atomic-progn + (progn ;; move (un)read - (let ((gnus-newsgroup-active nil)) ;workaround guns-update-read-articles + ;; bind for workaround guns-update-read-articles + (let ((gnus-newsgroup-active nil)) (while (setq entry (pop unreads)) (gnus-update-read-articles (car entry) (cdr entry)))) @@ -457,7 +480,11 @@ If UPDATE-P is not nil, call gnus-group-update-group on the components." (while groups (when (and (setq info (gnus-get-info (pop groups))) (gnus-info-marks info)) - (gnus-info-set-marks info nil))) + (gnus-info-set-marks + info + (if (assq 'score (gnus-info-marks info)) + (list (assq 'score (gnus-info-marks info))) + nil)))) ;; Ok, currently type-marks is an assq list with keys of a mark type, ;; with data of an assq list with keys of component group names @@ -571,7 +598,7 @@ If UPDATE-P is not nil, call gnus-group-update-group on the components." (aref entry 1) (cdr (aref nnvirtual-mapping-offsets group-pos))) )) - )) + )) @@ -629,7 +656,7 @@ then it is left out of the result." "Return an association list of component article numbers. These are indexed by elements of nnvirtual-component-groups, based on the sequence ARTICLES of virtual article numbers. ARTICLES should be -sorted, and can be a compressed sequence. If any of the article +sorted, and can be a compressed sequence. If any of the article numbers has no corresponding component article, then it is left out of the result." (when (numberp (cdr-safe articles)) @@ -692,7 +719,7 @@ based on the marks on the component groups." (setq cnt (1+ cnt) tot (+ tot size) M (max M size)))) - nnvirtual-component-groups) + nnvirtual-component-groups) ;; Number of articles in the virtual group. (setq nnvirtual-mapping-len tot) diff --git a/lisp/gnus/nnweb.el b/lisp/gnus/nnweb.el index c9d866a3a35..54ed5a56fc8 100644 --- a/lisp/gnus/nnweb.el +++ b/lisp/gnus/nnweb.el @@ -1,5 +1,6 @@ ;;; nnweb.el --- retrieving articles via web search engines -;; Copyright (C) 1996,97,98 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Lars Magne Ingebrigtsen ;; Keywords: news @@ -30,23 +31,24 @@ (eval-when-compile (require 'cl)) -(eval-when-compile (require 'cl)) - (require 'nnoo) (require 'message) (require 'gnus-util) (require 'gnus) (require 'nnmail) +(require 'mm-util) (eval-when-compile (ignore-errors - (require 'w3) - (require 'url) - (require 'w3-forms))) + (require 'w3) + (require 'url) + (require 'w3-forms))) + ;; Report failure to find w3 at load time if appropriate. -(eval '(progn - (require 'w3) - (require 'url) - (require 'w3-forms))) +(unless noninteractive + (eval '(progn + (require 'w3) + (require 'url) + (require 'w3-forms)))) (nnoo-declare nnweb) @@ -58,18 +60,19 @@ Valid types include `dejanews', `dejanewsold', `reference', and `altavista'.") -(defvoo nnweb-type-definition +(defvar nnweb-type-definition '((dejanews - (article . nnweb-dejanews-wash-article) + (article . ignore) + (id . "http://search.dejanews.com/msgid.xp?MID=%s&fmt=text") (map . nnweb-dejanews-create-mapping) (search . nnweb-dejanews-search) - (address . "http://x8.dejanews.com/dnquery.xp") + (address . "http://www.deja.com/=dnc/qs.xp") (identifier . nnweb-dejanews-identity)) (dejanewsold - (article . nnweb-dejanews-wash-article) + (article . ignore) (map . nnweb-dejanews-create-mapping) (search . nnweb-dejanewsold-search) - (address . "http://x8.dejanews.com/dnquery.xp") + (address . "http://www.deja.com/dnquery.xp") (identifier . nnweb-dejanews-identity)) (reference (article . nnweb-reference-wash-article) @@ -113,14 +116,14 @@ and `altavista'.") (set-buffer nntp-server-buffer) (erase-buffer) (let (article header) - (while (setq article (pop articles)) - (when (setq header (cadr (assq article nnweb-articles))) - (nnheader-insert-nov header))) + (mm-with-unibyte-current-buffer + (while (setq article (pop articles)) + (when (setq header (cadr (assq article nnweb-articles))) + (nnheader-insert-nov header)))) 'nov))) (deffoo nnweb-request-scan (&optional group server) (nnweb-possibly-change-server group server) - (setq nnweb-hashtb (gnus-make-hashtable 4095)) (funcall (nnweb-definition 'map)) (unless nnweb-ephemeral-p (nnweb-write-active) @@ -132,11 +135,12 @@ and `altavista'.") (not (equal group nnweb-group)) (not nnweb-ephemeral-p)) (let ((info (assoc group nnweb-group-alist))) - (setq nnweb-group group) - (setq nnweb-type (nth 2 info)) - (setq nnweb-search (nth 3 info)) - (unless dont-check - (nnweb-read-overview group)))) + (when info + (setq nnweb-group group) + (setq nnweb-type (nth 2 info)) + (setq nnweb-search (nth 3 info)) + (unless dont-check + (nnweb-read-overview group))))) (cond ((not nnweb-articles) (nnheader-report 'nnweb "No matching articles")) @@ -166,7 +170,8 @@ and `altavista'.") (let* ((header (cadr (assq article nnweb-articles))) (url (and header (mail-header-xref header)))) (when (or (and url - (nnweb-fetch-url url)) + (mm-with-unibyte-current-buffer + (nnweb-fetch-url url))) (and (stringp article) (nnweb-definition 'id t) (let ((fetch (nnweb-definition 'id)) @@ -175,13 +180,14 @@ and `altavista'.") (setq art (match-string 1 article))) (and fetch art - (nnweb-fetch-url - (format fetch article)))))) + (mm-with-unibyte-current-buffer + (nnweb-fetch-url + (format fetch article))))))) (unless nnheader-callback-function (funcall (nnweb-definition 'article)) (nnweb-decode-entities)) (nnheader-report 'nnweb "Fetched article %s" article) - t)))) + (cons group (and (numberp article) article)))))) (deffoo nnweb-close-server (&optional server) (when (and (nnweb-server-opened server) @@ -200,9 +206,7 @@ and `altavista'.") t)) (deffoo nnweb-request-update-info (group info &optional server) - (nnweb-possibly-change-server group server) - ;;(setcar (cddr info) nil) - ) + (nnweb-possibly-change-server group server)) (deffoo nnweb-asynchronous-p () t) @@ -216,7 +220,8 @@ and `altavista'.") (deffoo nnweb-request-delete-group (group &optional force server) (nnweb-possibly-change-server group server) - (gnus-pull group nnweb-group-alist) + (gnus-pull group nnweb-group-alist t) + (nnweb-write-active) (gnus-delete-file (nnweb-overview-file group)) t) @@ -227,7 +232,7 @@ and `altavista'.") (defun nnweb-read-overview (group) "Read the overview of GROUP and build the map." (when (file-exists-p (nnweb-overview-file group)) - (nnheader-temp-write nil + (mm-with-unibyte-buffer (nnheader-insert-file-contents (nnweb-overview-file group)) (goto-char (point-min)) (let (header) @@ -241,7 +246,7 @@ and `altavista'.") (defun nnweb-write-overview (group) "Write the overview file for GROUP." - (nnheader-temp-write (nnweb-overview-file group) + (with-temp-file (nnweb-overview-file group) (let ((articles nnweb-articles)) (while articles (nnheader-insert-nov (cadr (pop articles))))))) @@ -262,7 +267,8 @@ and `altavista'.") (defun nnweb-write-active () "Save the active file." - (nnheader-temp-write (nnheader-concat nnweb-directory "active") + (gnus-make-directory nnweb-directory) + (with-temp-file (nnheader-concat nnweb-directory "active") (prin1 `(setq nnweb-group-alist ',nnweb-group-alist) (current-buffer)))) (defun nnweb-read-active () @@ -287,6 +293,7 @@ and `altavista'.") (when group (when (and (not nnweb-ephemeral-p) (not (equal group nnweb-group))) + (setq nnweb-hashtb (gnus-make-hashtable 4095)) (nnweb-request-group group nil t)))) (defun nnweb-init (server) @@ -294,22 +301,30 @@ and `altavista'.") (unless (gnus-buffer-live-p nnweb-buffer) (setq nnweb-buffer (save-excursion - (nnheader-set-temp-buffer - (format " *nnweb %s %s %s*" nnweb-type nnweb-search server)))))) + (mm-with-unibyte + (nnheader-set-temp-buffer + (format " *nnweb %s %s %s*" + nnweb-type nnweb-search server)) + (current-buffer)))))) (defun nnweb-fetch-url (url) - (save-excursion - (if (not nnheader-callback-function) - (let ((buf (current-buffer))) - (save-excursion - (set-buffer nnweb-buffer) + (let (buf) + (save-excursion + (if (not nnheader-callback-function) + (progn + (with-temp-buffer + (mm-enable-multibyte) + (let ((coding-system-for-read 'binary) + (coding-system-for-write 'binary) + (default-process-coding-system 'binary)) + (nnweb-insert url)) + (setq buf (buffer-string))) (erase-buffer) - (url-insert-file-contents url) - (copy-to-buffer buf (point-min) (point-max)) - t)) - (nnweb-url-retrieve-asynch - url 'nnweb-callback (current-buffer) nnheader-callback-function) - t))) + (insert buf) + t) + (nnweb-url-retrieve-asynch + url 'nnweb-callback (current-buffer) nnheader-callback-function) + t)))) (defun nnweb-callback (buffer callback) (when (gnus-buffer-live-p url-working-buffer) @@ -338,42 +353,6 @@ and `altavista'.") (url-retrieve url)) (setq-default url-be-asynchronous old-asynch))) -(defun nnweb-encode-www-form-urlencoded (pairs) - "Return PAIRS encoded for forms." - (mapconcat - (function - (lambda (data) - (concat (w3-form-encode-xwfu (car data)) "=" - (w3-form-encode-xwfu (cdr data))))) - pairs "&")) - -(defun nnweb-fetch-form (url pairs) - (let ((url-request-data (nnweb-encode-www-form-urlencoded pairs)) - (url-request-method "POST") - (url-request-extra-headers - '(("Content-type" . "application/x-www-form-urlencoded")))) - (url-insert-file-contents url) - (setq buffer-file-name nil)) - t) - -(defun nnweb-decode-entities () - (goto-char (point-min)) - (while (re-search-forward "&\\([a-z]+\\);" nil t) - (replace-match (char-to-string (or (cdr (assq (intern (match-string 1)) - w3-html-entities)) - ?#)) - t t))) - -(defun nnweb-remove-markup () - (goto-char (point-min)) - (while (search-forward "" nil t) - (point-max)))) - (goto-char (point-min)) - (while (re-search-forward "<[^>]+>" nil t) - (replace-match "" t t))) - ;;; ;;; DejaNews functions. ;;; @@ -389,51 +368,46 @@ and `altavista'.") (case-fold-search t) (active (or (cadr (assoc nnweb-group nnweb-group-alist)) (cons 1 0))) - Subject (Score "0") Date Newsgroup Author - map url) + subject date from + map url parse a table group text) (while more ;; Go through all the article hits on this page. (goto-char (point-min)) - (nnweb-decode-entities) - (goto-char (point-min)) - (while (re-search-forward "^

\n" nil t) - (narrow-to-region - (point) - (cond ((re-search-forward "^

\n" nil t) - (match-beginning 0)) - ((search-forward "\n\n" nil t) - (point)) - (t - (point-max)))) - (goto-char (point-min)) - (looking-at ".*HREF=\"\\([^\"]+\\)\"\\(.*\\)") - (setq url (match-string 1)) - (let ((begin (point))) - (nnweb-remove-markup) - (goto-char begin) - (while (search-forward "\t" nil t) - (replace-match " ")) - (goto-char begin) - (end-of-line) - (setq Subject (buffer-substring begin (point))) - (if (re-search-forward - "^ Newsgroup: \\(.*\\)\n Posted on \\([0-9/]+\\) by \\(.*\\)$" nil t) - (setq Newsgroup (match-string 1) - Date (match-string 2) - Author (match-string 3)))) - (widen) - (incf i) - (unless (nnweb-get-hashtb url) - (push - (list - (incf (cdr active)) - (make-full-mail-header - (cdr active) Subject Author Date - (concat "<" (nnweb-identifier url) "@dejanews>") - nil 0 (string-to-int Score) url)) - map) - (nnweb-set-hashtb (cadar map) (car map)))) + (setq parse (w3-parse-buffer (current-buffer)) + table (nth 1 (nnweb-parse-find-all 'table parse))) + (dolist (row (nth 2 (car (nth 2 table)))) + (setq a (nnweb-parse-find 'a row) + url (cdr (assq 'href (nth 1 a))) + text (nreverse (nnweb-text row))) + (when a + (setq subject (nth 4 text) + group (nth 2 text) + date (nth 1 text) + from (nth 0 text)) + (if (string-match "\\([0-9]+\\)/\\([0-9]+\\)/\\([0-9]+\\)" date) + (setq date (format "%s %s 00:00:00 %s" + (car (rassq (string-to-number + (match-string 2 date)) + parse-time-months)) + (match-string 3 date) + (match-string 1 date))) + (setq date "Jan 1 00:00:00 0000")) + (incf i) + (setq url (concat url "&fmt=text")) + (when (string-match "&context=[^&]+" url) + (setq url (replace-match "" t t url))) + (unless (nnweb-get-hashtb url) + (push + (list + (incf (cdr active)) + (make-full-mail-header + (cdr active) (concat subject " (" group ")") from date + (concat "<" (nnweb-identifier url) "@dejanews>") + nil 0 0 url)) + map) + (nnweb-set-hashtb (cadar map) (car map))))) ;; See whether there is a "Get next 20 hits" button here. + (goto-char (point-min)) (if (or (not (re-search-forward "HREF=\"\\([^\"]+\\)\"[<>b]+Next result" nil t)) (>= i nnweb-max-hits)) @@ -446,39 +420,25 @@ and `altavista'.") (setq nnweb-articles (sort (nconc nnweb-articles map) 'car-less-than-car)))))) -(defun nnweb-dejanews-wash-article () - (let ((case-fold-search t)) - (goto-char (point-min)) - (re-search-forward "

" nil t)
-    (delete-region (point-min) (point))
-    (re-search-forward "
" nil t) - (delete-region (point) (point-max)) - (nnweb-remove-markup) - (goto-char (point-min)) - (while (and (looking-at " *$") - (not (eobp))) - (gnus-delete-line)) - (while (looking-at "\\(^[^ ]+:\\) *") - (replace-match "\\1 " t) - (forward-line 1)) - (when (re-search-forward "\n\n+" nil t) - (replace-match "\n" t t)) - (goto-char (point-min)) - (when (search-forward "[More Headers]" nil t) - (replace-match "" t t)))) - (defun nnweb-dejanews-search (search) - (nnweb-fetch-form - (nnweb-definition 'address) - `(("query" . ,search) - ("defaultOp" . "AND") - ("svcclass" . "dncurrent") - ("maxhits" . "100") - ("format" . "verbose2") - ("threaded" . "0") - ("showsort" . "date") - ("agesign" . "1") - ("ageweight" . "1"))) + (nnweb-insert + (concat + (nnweb-definition 'address) + "?" + (nnweb-encode-www-form-urlencoded + `(("ST" . "PS") + ("svcclass" . "dnyr") + ("QRY" . ,search) + ("defaultOp" . "AND") + ("DBS" . "1") + ("OP" . "dnquery.xp") + ("LNG" . "ALL") + ("maxhits" . "100") + ("threaded" . "0") + ("format" . "verbose2") + ("showsort" . "date") + ("agesign" . "1") + ("ageweight" . "1"))))) t) (defun nnweb-dejanewsold-search (search) @@ -497,7 +457,7 @@ and `altavista'.") (defun nnweb-dejanews-identity (url) "Return an unique identifier based on URL." - (if (string-match "recnum=\\([0-9]+\\)" url) + (if (string-match "AN=\\([0-9]+\\)" url) (match-string 1 url) url)) @@ -523,7 +483,6 @@ and `altavista'.") (goto-char (point-min)) (search-forward "
" nil t) (delete-region (point-min) (point)) - ;(nnweb-decode-entities) (goto-char (point-min)) (while (re-search-forward "^ +[0-9]+\\." nil t) (narrow-to-region @@ -719,6 +678,145 @@ and `altavista'.") (setq buffer-file-name nil) t) +;;; +;;; General web/w3 interface utility functions +;;; + +(defun nnweb-insert-html (parse) + "Insert HTML based on a w3 parse tree." + (if (stringp parse) + (insert parse) + (insert "<" (symbol-name (car parse)) " ") + (insert (mapconcat + (lambda (param) + (concat (symbol-name (car param)) "=" + (prin1-to-string + (if (consp (cdr param)) + (cadr param) + (cdr param))))) + (nth 1 parse) + " ")) + (insert ">\n") + (mapcar 'nnweb-insert-html (nth 2 parse)) + (insert "\n"))) + +(defun nnweb-encode-www-form-urlencoded (pairs) + "Return PAIRS encoded for forms." + (mapconcat + (function + (lambda (data) + (concat (w3-form-encode-xwfu (car data)) "=" + (w3-form-encode-xwfu (cdr data))))) + pairs "&")) + +(defun nnweb-fetch-form (url pairs) + "Fetch a form from URL with PAIRS as the data using the POST method." + (let ((url-request-data (nnweb-encode-www-form-urlencoded pairs)) + (url-request-method "POST") + (url-request-extra-headers + '(("Content-type" . "application/x-www-form-urlencoded")))) + (url-insert-file-contents url) + (setq buffer-file-name nil)) + t) + +(defun nnweb-decode-entities () + "Decode all HTML entities." + (goto-char (point-min)) + (while (re-search-forward "&\\(#[0-9]+\\|[a-z]+\\);" nil t) + (replace-match (char-to-string + (if (eq (aref (match-string 1) 0) ?\#) + (let ((c + (string-to-number (substring + (match-string 1) 1)))) + (if (mm-char-or-char-int-p c) c 32)) + (or (cdr (assq (intern (match-string 1)) + w3-html-entities)) + ?#))) + t t))) + +(defun nnweb-decode-entities-string (str) + (with-temp-buffer + (insert str) + (nnweb-decode-entities) + (buffer-substring (point-min) (point-max)))) + +(defun nnweb-remove-markup () + "Remove all HTML markup, leaving just plain text." + (goto-char (point-min)) + (while (search-forward "" nil t) + (point-max)))) + (goto-char (point-min)) + (while (re-search-forward "<[^>]+>" nil t) + (replace-match "" t t))) + +(defun nnweb-insert (url &optional follow-refresh) + "Insert the contents from an URL in the current buffer. +If FOLLOW-REFRESH is non-nil, redirect refresh url in META." + (let ((name buffer-file-name)) + (if follow-refresh + (save-restriction + (narrow-to-region (point) (point)) + (url-insert-file-contents url) + (goto-char (point-min)) + (when (re-search-forward + "]*URL=\\([^\"]+\\)\"" nil t) + (let ((url (match-string 1))) + (delete-region (point-min) (point-max)) + (nnweb-insert url t)))) + (url-insert-file-contents url)) + (setq buffer-file-name name))) + +(defun nnweb-parse-find (type parse &optional maxdepth) + "Find the element of TYPE in PARSE." + (catch 'found + (nnweb-parse-find-1 type parse maxdepth))) + +(defun nnweb-parse-find-1 (type contents maxdepth) + (when (or (null maxdepth) + (not (zerop maxdepth))) + (when (consp contents) + (when (eq (car contents) type) + (throw 'found contents)) + (when (listp (cdr contents)) + (dolist (element contents) + (when (consp element) + (nnweb-parse-find-1 type element + (and maxdepth (1- maxdepth))))))))) + +(defun nnweb-parse-find-all (type parse) + "Find all elements of TYPE in PARSE." + (catch 'found + (nnweb-parse-find-all-1 type parse))) + +(defun nnweb-parse-find-all-1 (type contents) + (let (result) + (when (consp contents) + (if (eq (car contents) type) + (push contents result) + (when (listp (cdr contents)) + (dolist (element contents) + (when (consp element) + (setq result + (nconc result (nnweb-parse-find-all-1 type element)))))))) + result)) + +(defvar nnweb-text) +(defun nnweb-text (parse) + "Return a list of text contents in PARSE." + (let ((nnweb-text nil)) + (nnweb-text-1 parse) + (nreverse nnweb-text))) + +(defun nnweb-text-1 (contents) + (dolist (element contents) + (if (stringp element) + (push element nnweb-text) + (when (and (consp element) + (listp (cdr element))) + (nnweb-text-1 element))))) + (provide 'nnweb) ;;; nnweb.el ends here diff --git a/lisp/gnus/parse-time.el b/lisp/gnus/parse-time.el index 64c768c74d7..d00325131a9 100644 --- a/lisp/gnus/parse-time.el +++ b/lisp/gnus/parse-time.el @@ -1,6 +1,6 @@ ;;; parse-time.el --- Parsing time strings -;; Copyright (C) 1996 by Free Software Foundation, Inc. +;; Copyright (C) 1996, 2000 by Free Software Foundation, Inc. ;; Author: Erik Naggum ;; Keywords: util @@ -36,12 +36,10 @@ ;;; Code: -(eval-when-compile (require 'cl)) ;and ah ain't kiddin' 'bout it +(eval-when-compile (require 'cl)) ;and ah ain't kiddin' 'bout it -(put 'parse-time-syntax 'char-table-extra-slots 0) - -(defvar parse-time-syntax (make-char-table 'parse-time-syntax)) -(defvar parse-time-digits (make-char-table 'parse-time-syntax)) +(defvar parse-time-syntax (make-vector 256 nil)) +(defvar parse-time-digits (make-vector 256 nil)) ;; Byte-compiler warnings (defvar elt) @@ -49,18 +47,18 @@ (unless (aref parse-time-digits ?0) (loop for i from ?0 to ?9 - do (set-char-table-range parse-time-digits i (- i ?0)))) + do (aset parse-time-digits i (- i ?0)))) (unless (aref parse-time-syntax ?0) (loop for i from ?0 to ?9 - do (set-char-table-range parse-time-syntax i ?0)) + do (aset parse-time-syntax i ?0)) (loop for i from ?A to ?Z - do (set-char-table-range parse-time-syntax i ?A)) + do (aset parse-time-syntax i ?A)) (loop for i from ?a to ?z - do (set-char-table-range parse-time-syntax i ?a)) - (set-char-table-range parse-time-syntax ?+ 1) - (set-char-table-range parse-time-syntax ?- -1) - (set-char-table-range parse-time-syntax ?: ?d) + do (aset parse-time-syntax i ?a)) + (aset parse-time-syntax ?+ 1) + (aset parse-time-syntax ?- -1) + (aset parse-time-syntax ?: ?d) ) (defsubst digit-char-p (char) @@ -89,7 +87,8 @@ (setq integer (+ (* integer 10) digit) index (1+ index))) (if (/= index end) - (signal 'parse-error `("not an integer" ,(substring string (or start 0) end))) + (signal 'parse-error `("not an integer" + ,(substring string (or start 0) end))) (* sign integer)))))) (defun parse-time-tokenize (string) @@ -114,24 +113,24 @@ list))) (nreverse list))) -(defvar parse-time-months '(("Jan" . 1) ("Feb" . 2) ("Mar" . 3) - ("Apr" . 4) ("May" . 5) ("Jun" . 6) - ("Jul" . 7) ("Aug" . 8) ("Sep" . 9) - ("Oct" . 10) ("Nov" . 11) ("Dec" . 12))) -(defvar parse-time-weekdays '(("Sun" . 0) ("Mon" . 1) ("Tue" . 2) - ("Wed" . 3) ("Thu" . 4) ("Fri" . 5) ("Sat" . 6))) -(defvar parse-time-zoneinfo `(("Z" 0) ("UT" 0) ("GMT" 0) - ("PST" ,(* -8 3600)) ("PDT" ,(* -7 3600) t) - ("MST" ,(* -7 3600)) ("MDT" ,(* -6 3600) t) - ("CST" ,(* -6 3600)) ("CDT" ,(* -5 3600) t) - ("EST" ,(* -5 3600)) ("EDT" ,(* -4 3600) t)) +(defvar parse-time-months '(("jan" . 1) ("feb" . 2) ("mar" . 3) + ("apr" . 4) ("may" . 5) ("jun" . 6) + ("jul" . 7) ("aug" . 8) ("sep" . 9) + ("oct" . 10) ("nov" . 11) ("dec" . 12))) +(defvar parse-time-weekdays '(("sun" . 0) ("mon" . 1) ("tue" . 2) + ("wed" . 3) ("thu" . 4) ("fri" . 5) ("sat" . 6))) +(defvar parse-time-zoneinfo `(("z" 0) ("ut" 0) ("gmt" 0) + ("pst" ,(* -8 3600)) ("pdt" ,(* -7 3600) t) + ("mst" ,(* -7 3600)) ("mdt" ,(* -6 3600) t) + ("cst" ,(* -6 3600)) ("cdt" ,(* -5 3600) t) + ("est" ,(* -5 3600)) ("edt" ,(* -4 3600) t)) "(zoneinfo seconds-off daylight-savings-time-p)") (defvar parse-time-rules `(((6) parse-time-weekdays) ((3) (1 31)) ((4) parse-time-months) - ((5) (1970 2038)) + ((5) (100 4038)) ((2 1 0) ,#'(lambda () (and (stringp elt) (= (length elt) 8) @@ -150,20 +149,34 @@ (* 60 (parse-integer elt 1 3))) (if (= (aref elt 0) ?-) -1 1)))) ((5 4 3) - ,#'(lambda () (and (stringp elt) (= (length elt) 10) (= (aref elt 4) ?-) (= (aref elt 7) ?-))) + ,#'(lambda () (and (stringp elt) + (= (length elt) 10) + (= (aref elt 4) ?-) + (= (aref elt 7) ?-))) [0 4] [5 7] [8 10]) - ((2 1) + ((2 1 0) ,#'(lambda () (and (stringp elt) (= (length elt) 5) (= (aref elt 2) ?:))) - [0 2] [3 5]) - ((5) (70 99) ,#'(lambda () (+ 1900 elt)))) + [0 2] [3 5] ,#'(lambda () 0)) + ((2 1 0) + ,#'(lambda () (and (stringp elt) + (= (length elt) 4) + (= (aref elt 1) ?:))) + [0 1] [2 4] ,#'(lambda () 0)) + ((2 1 0) + ,#'(lambda () (and (stringp elt) + (= (length elt) 7) + (= (aref elt 1) ?:))) + [0 1] [2 4] [5 7]) + ((5) (50 110) ,#'(lambda () (+ 1900 elt))) + ((5) (0 49) ,#'(lambda () (+ 2000 elt)))) "(slots predicate extractor...)") (defun parse-time-string (string) "Parse the time-string STRING into (SEC MIN HOUR DAY MON YEAR DOW DST TZ). The values are identical to those of `decode-time', but any values that are unknown are returned as nil." - (let ((time (list nil nil nil nil nil nil nil nil nil nil)) - (temp (parse-time-tokenize string))) + (let ((time (list nil nil nil nil nil nil nil nil nil)) + (temp (parse-time-tokenize (downcase string)))) (while temp (let ((elt (pop temp)) (rules parse-time-rules) @@ -173,25 +186,27 @@ unknown are returned as nil." (slots (pop rule)) (predicate (pop rule)) (val)) - (if (and (not (nth (car slots) time)) ;not already set - (setq val (cond ((and (consp predicate) - (not (eq (car predicate) 'lambda))) - (and (numberp elt) - (<= (car predicate) elt) - (<= elt (cadr predicate)) - elt)) - ((symbolp predicate) - (cdr (assoc elt (symbol-value predicate)))) - ((funcall predicate))))) - (progn - (setq exit t) - (while slots - (let ((new-val (and rule - (let ((this (pop rule))) - (if (vectorp this) - (parse-integer elt (aref this 0) (aref this 1)) - (funcall this)))))) - (rplaca (nthcdr (pop slots) time) (or new-val val)))))))))) + (when (and (not (nth (car slots) time)) ;not already set + (setq val (cond ((and (consp predicate) + (not (eq (car predicate) + 'lambda))) + (and (numberp elt) + (<= (car predicate) elt) + (<= elt (cadr predicate)) + elt)) + ((symbolp predicate) + (cdr (assoc elt + (symbol-value predicate)))) + ((funcall predicate))))) + (setq exit t) + (while slots + (let ((new-val (and rule + (let ((this (pop rule))) + (if (vectorp this) + (parse-integer + elt (aref this 0) (aref this 1)) + (funcall this)))))) + (rplaca (nthcdr (pop slots) time) (or new-val val))))))))) time)) (provide 'parse-time) diff --git a/lisp/gnus/pop3.el b/lisp/gnus/pop3.el index 9b75b120242..02630b5ed87 100644 --- a/lisp/gnus/pop3.el +++ b/lisp/gnus/pop3.el @@ -1,6 +1,7 @@ ;;; pop3.el --- Post Office Protocol (RFC 1460) interface -;; Copyright (C) 1996, 96, 97, 98, 1999 Free Software Foundation, Inc. +;; Copyright (C) 1996, 1997, 1998, 1999, 2000 +;; Free Software Foundation, Inc. ;; Author: Richard L. Pieri ;; Maintainer: FSF diff --git a/lisp/gnus/score-mode.el b/lisp/gnus/score-mode.el index 24c31f67242..cfc5cc3c624 100644 --- a/lisp/gnus/score-mode.el +++ b/lisp/gnus/score-mode.el @@ -25,9 +25,8 @@ ;;; Code: -(require 'easymenu) -(require 'timezone) (eval-when-compile (require 'cl)) +(require 'mm-util) ; for mm-auto-save-coding-system (defvar gnus-score-mode-hook nil "*Hook run in score mode buffers.") @@ -40,7 +39,8 @@ (defvar gnus-score-mode-map nil) (unless gnus-score-mode-map - (setq gnus-score-mode-map (copy-keymap emacs-lisp-mode-map)) + (setq gnus-score-mode-map (make-sparse-keymap)) + (set-keymap-parent gnus-score-mode-map emacs-lisp-mode-map) (define-key gnus-score-mode-map "\C-c\C-c" 'gnus-score-edit-exit) (define-key gnus-score-mode-map "\C-c\C-d" 'gnus-score-edit-insert-date) (define-key gnus-score-mode-map "\C-c\C-p" 'gnus-score-pretty-print)) @@ -51,6 +51,9 @@ table) "Syntax table used in score-mode buffers.") +;; We need this to cope with non-ASCII scoring. +(defvar score-mode-coding-system mm-auto-save-coding-system) + ;;;###autoload (defun gnus-score-mode () "Mode for editing Gnus score files. @@ -81,7 +84,7 @@ This mode is an extended emacs-lisp mode. (defun gnus-score-edit-insert-date () "Insert date in numerical format." (interactive) - (princ (gnus-score-day-number (current-time)) (current-buffer))) + (princ (time-to-days (current-time)) (current-buffer))) (defun gnus-score-pretty-print () "Format the current score file." @@ -98,7 +101,8 @@ This mode is an extended emacs-lisp mode. (interactive) (unless (file-exists-p (file-name-directory (buffer-file-name))) (make-directory (file-name-directory (buffer-file-name)) t)) - (save-buffer) + (let ((coding-system-for-write score-mode-coding-system)) + (save-buffer)) (bury-buffer (current-buffer)) (let ((buf (current-buffer))) (when gnus-score-edit-exit-function @@ -106,11 +110,6 @@ This mode is an extended emacs-lisp mode. (when (eq buf (current-buffer)) (switch-to-buffer (other-buffer (current-buffer)))))) -(defun gnus-score-day-number (time) - (let ((dat (decode-time time))) - (timezone-absolute-from-gregorian - (nth 4 dat) (nth 3 dat) (nth 5 dat)))) - (provide 'score-mode) ;;; score-mode.el ends here