summaryrefslogtreecommitdiff
path: root/lisp
diff options
context:
space:
mode:
authorJim Blandy <jimb@redhat.com>1991-05-09 21:50:45 +0000
committerJim Blandy <jimb@redhat.com>1991-05-09 21:50:45 +0000
commit745bc783eb8bd84b07a7d512660947ec214e71eb (patch)
tree89290135fc261eacb5a368d79face4cd4391db2f /lisp
parent7229064dbf9dfcb873824a6f2a9af0bdb112b550 (diff)
downloademacs-745bc783eb8bd84b07a7d512660947ec214e71eb.tar.gz
Initial revision
Diffstat (limited to 'lisp')
-rw-r--r--lisp/compare-w.el129
-rw-r--r--lisp/emacs-lisp/cl-indent.el466
-rw-r--r--lisp/gnus.el6081
-rw-r--r--lisp/informat.el415
-rw-r--r--lisp/progmodes/awk-mode.el83
-rw-r--r--lisp/progmodes/cplus-md.el966
-rw-r--r--lisp/textmodes/bibtex.el1020
7 files changed, 9160 insertions, 0 deletions
diff --git a/lisp/compare-w.el b/lisp/compare-w.el
new file mode 100644
index 00000000000..0ae9f37673b
--- /dev/null
+++ b/lisp/compare-w.el
@@ -0,0 +1,129 @@
+;; Compare text between windows for Emacs.
+;; Copyright (C) 1986, 1989 Free Software Foundation, Inc.
+
+;; 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 1, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+(provide 'compare-w)
+
+(defvar compare-windows-whitespace " \t\n"
+ "*String of characters considered whitespace for \\[compare-windows].
+Changes in whitespace are optionally ignored.
+
+The value of `compare-windows-whitespace' may instead be a function; this
+function is called in each buffer, with point at the current scanning point.
+The function's job is to categorize any whitespace around (including before)
+point; it should also advance past any whitespace.
+
+The function is passed one argument, the point where `compare-windows'
+was originally called; it should not consider any text before that point.
+If the function returns the same value for both buffers, then the
+whitespace is considered to match, and is skipped.")
+
+(defvar compare-ignore-case nil
+ "*Non-nil means \\[compare-windows] ignores case differences.")
+
+;;;###autoload
+(defun compare-windows (ignore-whitespace)
+ "Compare text in current window with text in next window.
+Compares the text starting at point in each window,
+moving over text in each one as far as they match.
+
+A prefix arg means ignore changes in whitespace.
+The variable `compare-windows-whitespace' controls how whitespace is skipped.
+If `compare-ignore-case' is non-nil, changes in case are also ignored."
+ (interactive "P")
+ (let* (p1 p2 maxp1 maxp2 b1 b2 w2
+ success size
+ (opoint1 (point))
+ opoint2
+ (skip-whitespace (if ignore-whitespace
+ compare-windows-whitespace))
+ (skip-whitespace-regexp (concat "[" skip-whitespace "]+")))
+ (setq p1 (point) b1 (current-buffer))
+ (setq w2 (next-window (selected-window)))
+ (if (eq w2 (selected-window))
+ (error "No other window"))
+ (setq p2 (window-point w2)
+ b2 (window-buffer w2))
+ (setq opoint2 p2)
+ (setq maxp1 (point-max))
+ (save-excursion
+ (set-buffer b2)
+ (setq maxp2 (point-max)))
+
+ (setq success t)
+ (while success
+ (setq success nil)
+ ;; if interrupted, show how far we've gotten
+ (goto-char p1)
+ (set-window-point w2 p2)
+
+ ;; If both buffers have whitespace next to point,
+ ;; optionally skip over it.
+
+ (and skip-whitespace
+ (save-excursion
+ (let (p1a p2a w1 w2 result1 result2)
+ (if (stringp skip-whitespace)
+ (progn
+ (if (not (eobp))
+ (skip-chars-backward skip-whitespace opoint1))
+ (and (looking-at skip-whitespace-regexp)
+ (setq p1a (match-end 0) result1 t)))
+ (setq result1 (funcall skip-whitespace opoint1))
+ (setq p1a (point)))
+ (set-buffer b2)
+ (goto-char p2)
+ (if (stringp skip-whitespace)
+ (progn
+ (if (not (eobp))
+ (skip-chars-backward skip-whitespace opoint2))
+ (and (looking-at skip-whitespace-regexp)
+ (setq p2a (match-end 0) result2 t)))
+ (setq result2 (funcall skip-whitespace opoint2))
+ (setq p2a (point)))
+ (and result1 result2 (eq result1 result2)
+ (setq p1 p1a
+ p2 p2a)))))
+
+ ;; Try advancing comparing 1000 chars at a time.
+ ;; When that fails, go 500 chars at a time, and so on.
+ (let ((size 1000)
+ success-1)
+ (while (> size 0)
+ (setq success-1 t)
+ (while success-1
+ (setq size (min size (- maxp1 p1) (- maxp2 p2)))
+ (save-excursion
+ (set-buffer b2)
+ (setq s2 (buffer-substring p2 (+ size p2))))
+ (setq success-1
+ (and (> size 0)
+ (if compare-ignore-case
+ (let ((case-fold-search t))
+ (save-excursion
+ (search-forward s2 (+ p1 size) t)))
+ (equal (buffer-substring p1 (+ size p1)) s2))))
+ (if success-1
+ (setq p1 (+ p1 size) p2 (+ p2 size)
+ success t)))
+ (setq size (/ size 2)))))
+
+ (goto-char p1)
+ (set-window-point w2 p2)
+ (if (= (point) opoint1)
+ (ding))))
diff --git a/lisp/emacs-lisp/cl-indent.el b/lisp/emacs-lisp/cl-indent.el
new file mode 100644
index 00000000000..4e4543725c8
--- /dev/null
+++ b/lisp/emacs-lisp/cl-indent.el
@@ -0,0 +1,466 @@
+;; Lisp mode, and its idiosyncratic commands.
+;; Copyright (C) 1987 Free Software Foundation, Inc.
+;; Written by Richard Mlynarik July 1987
+
+;; 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 1, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;>> TODO
+;; :foo
+;; bar
+;; :baz
+;; zap
+;; &key (like &body)??
+
+;; &rest 1 in lambda-lists doesn't work
+;; -- really want (foo bar
+;; baz)
+;; not (foo bar
+;; baz)
+;; Need something better than &rest for such cases
+
+
+;;; Hairy lisp indentation.
+
+(defvar lisp-indent-maximum-backtracking 3
+ "*Maximum depth to backtrack out from a sublist for structured indentation.
+If this variable is 0, no backtracking will occur and forms such as flet
+may not be correctly indented.")
+
+(defvar lisp-tag-indentation 1
+ "*Indentation of tags relative to containing list.
+This variable is used by the function `lisp-indent-tagbody'.")
+
+(defvar lisp-tag-body-indentation 3
+ "*Indentation of non-tagged lines relative to containing list.
+This variable is used by the function `lisp-indent-tagbody' to indent normal
+lines (lines without tags).
+The indentation is relative to the indentation of the parenthesis enclosing
+the special form. If the value is t, the body of tags will be indented
+as a block at the same indentation as the first s-expression following
+the tag. In this case, any forms before the first tag are indented
+by `lisp-body-indent'.")
+
+
+;;;###autoload
+(defun common-lisp-indent-function (indent-point state)
+ (let ((normal-indent (current-column)))
+ ;; Walk up list levels until we see something
+ ;; which does special things with subforms.
+ (let ((depth 0)
+ ;; Path describes the position of point in terms of
+ ;; list-structure with respect to contining lists.
+ ;; `foo' has a path of (0 4 1) in `((a b c (d foo) f) g)'
+ (path ())
+ ;; set non-nil when somebody works out the indentation to use
+ calculated
+ (last-point indent-point)
+ ;; the position of the open-paren of the innermost containing list
+ (containing-form-start (elt state 1))
+ ;; the column of the above
+ sexp-column)
+ ;; Move to start of innermost containing list
+ (goto-char containing-form-start)
+ (setq sexp-column (current-column))
+ ;; Look over successively less-deep containing forms
+ (while (and (not calculated)
+ (< depth lisp-indent-maximum-backtracking))
+ (let ((containing-sexp (point)))
+ (forward-char 1)
+ (parse-partial-sexp (point) indent-point 1 t)
+ ;; Move to the car of the relevant containing form
+ (let (tem function method)
+ (if (not (looking-at "\\sw\\|\\s_"))
+ ;; This form doesn't seem to start with a symbol
+ (setq function nil method nil)
+ (setq tem (point))
+ (forward-sexp 1)
+ (setq function (downcase (buffer-substring tem (point))))
+ (goto-char tem)
+ (setq tem (intern-soft function)
+ method (get tem 'common-lisp-indent-function))
+ (cond ((and (null method)
+ (string-match ":[^:]+" function))
+ ;; The pleblisp package feature
+ (setq function (substring function
+ (1+ (match-beginning 0)))
+ method (get (intern-soft function)
+ 'common-lisp-indent-function)))
+ ((and (null method))
+ ;; backwards compatibility
+ (setq method (get tem 'lisp-indent-function)))))
+ (let ((n 0))
+ ;; How far into the containing form is the current form?
+ (if (< (point) indent-point)
+ (while (condition-case ()
+ (progn
+ (forward-sexp 1)
+ (if (>= (point) indent-point)
+ nil
+ (parse-partial-sexp (point)
+ indent-point 1 t)
+ (setq n (1+ n))
+ t))
+ (error nil))))
+ (setq path (cons n path)))
+
+ ;; backwards compatibility.
+ (cond ((null function))
+ ((null method)
+ (if (null (cdr path))
+ ;; (package prefix was stripped off above)
+ (setq method (cond ((string-match "\\`def"
+ function)
+ '(4 (&whole 4 &rest 1) &body))
+ ((string-match "\\`\\(with\\|do\\)-"
+ function)
+ '(4 &body))))))
+ ;; backwards compatibility. Bletch.
+ ((eq method 'defun)
+ (setq method '(4 (&whole 4 &rest 1) &body))))
+
+ (cond ((and (memq (char-after (1- containing-sexp)) '(?\' ?\`))
+ (not (eql (char-after (- containing-sexp 2)) ?\#)))
+ ;; No indentation for "'(...)" elements
+ (setq calculated (1+ sexp-column)))
+ ((or (eql (char-after (1- containing-sexp)) ?\,)
+ (and (eql (char-after (1- containing-sexp)) ?\@)
+ (eql (char-after (- containing-sexp 2)) ?\,)))
+ ;; ",(...)" or ",@(...)"
+ (setq calculated normal-indent))
+ ((eql (char-after (1- containing-sexp)) ?\#)
+ ;; "#(...)"
+ (setq calculated (1+ sexp-column)))
+ ((null method))
+ ((integerp method)
+ ;; convenient top-level hack.
+ ;; (also compatible with lisp-indent-function)
+ ;; The number specifies how many `distinguished'
+ ;; forms there are before the body starts
+ ;; Equivalent to (4 4 ... &body)
+ (setq calculated (cond ((cdr path)
+ normal-indent)
+ ((<= (car path) method)
+ ;; `distinguished' form
+ (list (+ sexp-column 4)
+ containing-form-start))
+ ((= (car path) (1+ method))
+ ;; first body form.
+ (+ sexp-column lisp-body-indent))
+ (t
+ ;; other body form
+ normal-indent))))
+ ((symbolp method)
+ (setq calculated (funcall method
+ path state indent-point
+ sexp-column normal-indent)))
+ (t
+ (setq calculated (lisp-indent-259
+ method path state indent-point
+ sexp-column normal-indent)))))
+ (goto-char containing-sexp)
+ (setq last-point containing-sexp)
+ (if (not calculated)
+ (condition-case ()
+ (progn (backward-up-list 1)
+ (setq depth (1+ depth)))
+ (error (setq depth lisp-indent-maximum-backtracking))))))
+ calculated)))
+
+
+(defun lisp-indent-report-bad-format (m)
+ (error "%s has a badly-formed %s property: %s"
+ ;; Love those free variable references!!
+ function 'common-lisp-indent-function m))
+
+;; Blame the crufty control structure on dynamic scoping
+;; -- not on me!
+(defun lisp-indent-259 (method path state indent-point
+ sexp-column normal-indent)
+ (catch 'exit
+ (let ((p path)
+ (containing-form-start (elt state 1))
+ n tem tail)
+ ;; Isn't tail-recursion wonderful?
+ (while p
+ ;; This while loop is for destructuring.
+ ;; p is set to (cdr p) each iteration.
+ (if (not (consp method)) (lisp-indent-report-bad-format method))
+ (setq n (1- (car p))
+ p (cdr p)
+ tail nil)
+ (while n
+ ;; This while loop is for advancing along a method
+ ;; until the relevant (possibly &rest/&body) pattern
+ ;; is reached.
+ ;; n is set to (1- n) and method to (cdr method)
+ ;; each iteration.
+ (setq tem (car method))
+
+ (or (eq tem 'nil) ;default indentation
+; (eq tem '&lambda) ;abbrev for (&whole 4 (&rest 1))
+ (and (eq tem '&body) (null (cdr method)))
+ (and (eq tem '&rest)
+ (consp (cdr method)) (null (cdr (cdr method))))
+ (integerp tem) ;explicit indentation specified
+ (and (consp tem) ;destructuring
+ (eq (car tem) '&whole)
+ (or (symbolp (car (cdr tem)))
+ (integerp (car (cdr tem)))))
+ (and (symbolp tem) ;a function to call to do the work.
+ (null (cdr method)))
+ (lisp-indent-report-bad-format method))
+
+ (cond ((and tail (not (consp tem)))
+ ;; indent tail of &rest in same way as first elt of rest
+ (throw 'exit normal-indent))
+ ((eq tem '&body)
+ ;; &body means (&rest <lisp-body-indent>)
+ (throw 'exit
+ (if (and (= n 0) ;first body form
+ (null p)) ;not in subforms
+ (+ sexp-column
+ lisp-body-indent)
+ normal-indent)))
+ ((eq tem '&rest)
+ ;; this pattern holds for all remaining forms
+ (setq tail (> n 0)
+ n 0
+ method (cdr method)))
+ ((> n 0)
+ ;; try next element of pattern
+ (setq n (1- n)
+ method (cdr method))
+ (if (< n 0)
+ ;; Too few elements in pattern.
+ (throw 'exit normal-indent)))
+ ((eq tem 'nil)
+ (throw 'exit (list normal-indent containing-form-start)))
+; ((eq tem '&lambda)
+; ;; abbrev for (&whole 4 &rest 1)
+; (throw 'exit
+; (cond ((null p)
+; (list (+ sexp-column 4) containing-form-start))
+; ((null (cdr p))
+; (+ sexp-column 1))
+; (t normal-indent))))
+ ((integerp tem)
+ (throw 'exit
+ (if (null p) ;not in subforms
+ (list (+ sexp-column tem) containing-form-start)
+ normal-indent)))
+ ((symbolp tem) ;a function to call
+ (throw 'exit
+ (funcall tem path state indent-point
+ sexp-column normal-indent)))
+ (t
+ ;; must be a destructing frob
+ (if (not (null p))
+ ;; descend
+ (setq method (cdr (cdr tem))
+ n nil)
+ (setq tem (car (cdr tem)))
+ (throw 'exit
+ (cond (tail
+ normal-indent)
+ ((eq tem 'nil)
+ (list normal-indent
+ containing-form-start))
+ ((integerp tem)
+ (list (+ sexp-column tem)
+ containing-form-start))
+ (t
+ (funcall tem path state indent-point
+ sexp-column normal-indent))))))))))))
+
+(defun lisp-indent-tagbody (path state indent-point sexp-column normal-indent)
+ (if (not (null (cdr path)))
+ normal-indent
+ (save-excursion
+ (goto-char indent-point)
+ (beginning-of-line)
+ (skip-chars-forward " \t")
+ (list (cond ((looking-at "\\sw\\|\\s_")
+ ;; a tagbody tag
+ (+ sexp-column lisp-tag-indentation))
+ ((integerp lisp-tag-body-indentation)
+ (+ sexp-column lisp-tag-body-indentation))
+ ((eq lisp-tag-body-indentation 't)
+ (condition-case ()
+ (progn (backward-sexp 1) (current-column))
+ (error (1+ sexp-column))))
+ (t (+ sexp-column lisp-body-indent)))
+; (cond ((integerp lisp-tag-body-indentation)
+; (+ sexp-column lisp-tag-body-indentation))
+; ((eq lisp-tag-body-indentation 't)
+; normal-indent)
+; (t
+; (+ sexp-column lisp-body-indent)))
+ (elt state 1)
+ ))))
+
+(defun lisp-indent-do (path state indent-point sexp-column normal-indent)
+ (if (>= (car path) 3)
+ (let ((lisp-tag-body-indentation lisp-body-indent))
+ (funcall (function lisp-indent-tagbody)
+ path state indent-point sexp-column normal-indent))
+ (funcall (function lisp-indent-259)
+ '((&whole nil &rest
+ ;; the following causes wierd indentation
+ ;;(&whole 1 1 2 nil)
+ )
+ (&whole nil &rest 1))
+ path state indent-point sexp-column normal-indent)))
+
+(defun lisp-indent-function-lambda-hack (path state indent-point
+ sexp-column normal-indent)
+ ;; indent (function (lambda () <newline> <body-forms>)) kludgily.
+ (if (or (cdr path) ; wtf?
+ (> (car path) 3))
+ ;; line up under previous body form
+ normal-indent
+ ;; line up under function rather than under lambda in order to
+ ;; conserve horizontal space. (Which is what #' is for.)
+ (condition-case ()
+ (save-excursion
+ (backward-up-list 2)
+ (forward-char 1)
+ (if (looking-at "\\(lisp:+\\)?function\\(\\Sw\\|\\S_\\)")
+ (+ lisp-body-indent -1 (current-column))
+ (+ sexp-column lisp-body-indent)))
+ (error (+ sexp-column lisp-body-indent)))))
+
+
+(let ((l '((block 1)
+ (catch 1)
+ (case (4 &rest (&whole 2 &rest 1)))
+ (ccase . case) (ecase . case)
+ (typecase . case) (etypecase . case) (ctypecase . case)
+ (catch 1)
+ (cond (&rest (&whole 2 &rest 1)))
+ (block 1)
+ (defvar (4 2 2))
+ (defconstant . defvar) (defparameter . defvar)
+ (define-modify-macro
+ (4 &body))
+ (define-setf-method
+ (4 (&whole 4 &rest 1) &body))
+ (defsetf (4 (&whole 4 &rest 1) 4 &body))
+ (defun (4 (&whole 4 &rest 1) &body))
+ (defmacro . defun) (deftype . defun)
+ (defstruct ((&whole 4 &rest (&whole 2 &rest 1))
+ &rest (&whole 2 &rest 1)))
+ (destructuring-bind
+ ((&whole 6 &rest 1) 4 &body))
+ (do lisp-indent-do)
+ (do* . do)
+ (dolist ((&whole 4 2 1) &body))
+ (dotimes . dolist)
+ (eval-when 1)
+ (flet ((&whole 4 &rest (&whole 1 (&whole 4 &rest 1) &body))
+ &body))
+ (labels . flet)
+ (macrolet . flet)
+ ;; `else-body' style
+ (if (nil nil &body))
+ ;; single-else style (then and else equally indented)
+ (if (&rest nil))
+ ;(lambda ((&whole 4 &rest 1) &body))
+ (lambda ((&whole 4 &rest 1)
+ &rest lisp-indent-function-lambda-hack))
+ (let ((&whole 4 &rest (&whole 1 1 2)) &body))
+ (let* . let)
+ (compiler-let . let) ;barf
+ (locally 1)
+ ;(loop ...)
+ (multiple-value-bind
+ ((&whole 6 &rest 1) 4 &body))
+ (multiple-value-call
+ (4 &body))
+ (multiple-value-list 1)
+ (multiple-value-prog1 1)
+ (multiple-value-setq
+ (4 2))
+ ;; Combines the worst features of BLOCK, LET and TAGBODY
+ (prog ((&whole 4 &rest 1) &rest lisp-indent-tagbody))
+ (prog* . prog)
+ (prog1 1)
+ (prog2 2)
+ (progn 0)
+ (progv (4 4 &body))
+ (return 0)
+ (return-from (nil &body))
+ (tagbody lisp-indent-tagbody)
+ (throw 1)
+ (unless 1)
+ (unwind-protect
+ (5 &body))
+ (when 1))))
+ (while l
+ (put (car (car l)) 'common-lisp-indent-function
+ (if (symbolp (cdr (car l)))
+ (get (cdr (car l)) 'common-lisp-indent-function)
+ (car (cdr (car l)))))
+ (setq l (cdr l))))
+
+
+;(defun foo (x)
+; (tagbody
+; foo
+; (bar)
+; baz
+; (when (losing)
+; (with-big-loser
+; (yow)
+; ((lambda ()
+; foo)
+; big)))
+; (flet ((foo (bar baz zap)
+; (zip))
+; (zot ()
+; quux))
+; (do ()
+; ((lose)
+; (foo 1))
+; (quux)
+; foo
+; (lose))
+; (cond ((x)
+; (win 1 2
+; (foo)))
+; (t
+; (lose
+; 3))))))
+
+
+;(put 'while 'common-lisp-indent-function 1)
+;(put 'defwrapper'common-lisp-indent-function ...)
+;(put 'def 'common-lisp-indent-function ...)
+;(put 'defflavor 'common-lisp-indent-function ...)
+;(put 'defsubst 'common-lisp-indent-function ...)
+
+;(put 'with-restart 'common-lisp-indent-function '((1 4 ((* 1))) (2 &body)))
+;(put 'restart-case 'common-lisp-indent-function '((1 4) (* 2 ((0 1) (* 1)))))
+;(put 'define-condition 'common-lisp-indent-function '((1 6) (2 6 ((* 1))) (3 4 ((* 1))) (4 &body)))
+;(put 'with-condition-handler 'common-lisp-indent-function '((1 4 ((* 1))) (2 &body)))
+;(put 'condition-case 'common-lisp-indent-function '((1 4) (* 2 ((0 1) (1 3) (2 &body)))))
+
+
+;;;; Turn it on.
+;(setq lisp-indent-function 'common-lisp-indent-function)
+
+;; To disable this stuff, (setq lisp-indent-function 'lisp-indent-function)
+
diff --git a/lisp/gnus.el b/lisp/gnus.el
new file mode 100644
index 00000000000..d37072dd6a6
--- /dev/null
+++ b/lisp/gnus.el
@@ -0,0 +1,6081 @@
+;;; GNUS: an NNTP-based News Reader for GNU Emacs
+;; Copyright (C) 1987, 1988, 1989 Fujitsu Laboratories LTD.
+;; Copyright (C) 1987, 1988, 1989, 1990 Masanobu UMEDA
+;; $Header: gnus.el,v 3.13 90/03/23 13:24:27 umerin Locked $
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY. No author or distributor
+;; accepts responsibility to anyone for the consequences of using it
+;; or for whether it serves any particular purpose or works at all,
+;; unless he says so in writing. Refer to the GNU Emacs General Public
+;; License for full details.
+
+;; Everyone is granted permission to copy, modify and redistribute
+;; GNU Emacs, but only under the conditions described in the
+;; GNU Emacs General Public License. A copy of this license is
+;; supposed to have been given to you along with GNU Emacs so you
+;; can know your rights and responsibilities. It should be in a
+;; file named COPYING. Among other things, the copyright notice
+;; and this notice must be preserved on all copies.
+
+;; GNUS Mailing List:
+;; There are two mailing lists for GNUS lovers in the world:
+;;
+;; info-gnus@flab.fujitsu.co.jp, and
+;; info-gnus-english@tut.cis.ohio-state.edu.
+;;
+;; They are intended to exchange useful information about GNUS, such
+;; as bug fixes, useful hooks, and extensions. The major difference
+;; between the lists is what the official language is. Both Japanese
+;; and English are available in info-gnus, while English is only
+;; available in info-gnus-english. There is no need to subscribe to
+;; info-gnus if you cannot read Japanese messages, because most of the
+;; discussion and important announcements will be sent to
+;; info-gnus-english. Moreover, if you can read gnu.emacs.gnus
+;; newsgroup of USENET, you need not, either. info-gnus-english and
+;; gnu.emacs.gnus are linked each other.
+;;
+;; Please send subscription request to:
+;;
+;; info-gnus-request@flab.fujitsu.co.jp, or
+;; info-gnus-english-request@cis.ohio-state.edu
+
+;; TO DO:
+;; (1) Incremental update of active info.
+;; (2) GNUS own poster.
+;; (3) Multi-GNUS (Talking to many hosts same time).
+;; (4) Asynchronous transmission of large messages.
+
+(provide 'gnus)
+(require 'nntp)
+(require 'mail-utils)
+
+(defvar gnus-nntp-server (or (getenv "NNTPSERVER") gnus-default-nntp-server)
+ "The name of the host running NNTP server.
+If it is a string such as `:DIRECTORY', the user's private DIRECTORY
+is used as a news spool.
+Initialized from the NNTPSERVER environment variable.")
+
+(defvar gnus-signature-file "~/.signature"
+ "*Your .signature file. Use `.signature-DISTRIBUTION' instead if exists.")
+
+(defvar gnus-use-cross-reference t
+ "Specifies what to do with cross references (Xref: field).
+If nil, ignore cross references. If t, mark articles as read in subscribed
+newsgroups. Otherwise, mark articles as read in all newsgroups.")
+
+(defvar gnus-use-followup-to t
+ "*Specifies what to do with Followup-To: field.
+If nil, ignore followup-to: field. If t, use its value execpt for
+`poster'. Otherewise, if not nil nor t, always use its value.")
+
+(defvar gnus-large-newsgroup 50
+ "*The number of articles which indicates a large newsgroup.
+If the number of articles in a newsgroup is greater than the value,
+confirmation is required for selecting the newsgroup.")
+
+(defvar gnus-author-copy (getenv "AUTHORCOPY")
+ "*Filename for saving a copy of an article posted using FCC: field.
+Initialized from the AUTHORCOPY environment variable.
+
+Articles are saved using a function specified by the the variable
+`gnus-author-copy-saver' (`rmail-output' is the default) if a file name
+is given. Instead, if the first character of the name is `|', the
+contents of the article is piped out to the named program. It is
+possible to save an article in an MH folder as follows:
+
+ (setq gnus-author-copy \"|/usr/local/lib/mh/rcvstore +Article\")")
+
+(defvar gnus-author-copy-saver (function rmail-output)
+ "*A function called with a file name to save an author copy to.
+The default function is `rmail-output' which saves in Unix mailbox format.")
+
+(defvar gnus-use-long-file-name
+ (not (memq system-type '(usg-unix-v xenix)))
+ "Non-nil means that a newsgroup name is used as a default file name
+to save articles to. If nil, the directory form of a newsgroup is
+used instead.")
+
+(defvar gnus-article-save-directory (getenv "SAVEDIR")
+ "*The directory in which to save articles; defaults to ~/News.
+Initialized from the SAVEDIR environment variable.")
+
+(defvar gnus-default-article-saver (function gnus-Subject-save-in-rmail)
+ "A function used to save articles in your favorite format.
+The function must be interactively callable (in other words, it must
+be an Emacs command).
+
+GNUS provides the following functions:
+ gnus-Subject-save-in-rmail (in Rmail format)
+ gnus-Subject-save-in-mail (in Unix mail format)
+ gnus-Subject-save-in-folder (in an MH folder)
+ gnus-Subject-save-in-file (in article format).")
+
+(defvar gnus-rmail-save-name (function gnus-plain-save-name)
+ "A function generating a file name to save articles in Rmail format.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
+
+(defvar gnus-mail-save-name (function gnus-plain-save-name)
+ "A function generating a file name to save articles in Unix mail format.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
+
+(defvar gnus-folder-save-name (function gnus-folder-save-name)
+ "A function generating a file name to save articles in MH folder.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FOLDER.")
+
+(defvar gnus-file-save-name (function gnus-numeric-save-name)
+ "A function generating a file name to save articles in article format.
+The function is called with NEWSGROUP, HEADERS, and optional LAST-FILE.")
+
+(defvar gnus-kill-file-name "KILL"
+ "File name of a KILL file.")
+
+(defvar gnus-default-distribution "local"
+ "*Use this value as distribution if no distribution is specified.")
+
+(defvar gnus-novice-user t
+ "*Non-nil means that you are a novice to USENET.
+If non-nil, verbose messages may be displayed or your confirmation
+may be required.")
+
+(defvar gnus-interactive-post t
+ "*Newsgroup, subject, and distribution will be asked for if non-nil.")
+
+(defvar gnus-user-login-name nil
+ "*The login name of the user.
+Uses USER and LOGNAME environment variables if undefined.")
+
+(defvar gnus-user-full-name nil
+ "*The full name of the user.
+Uses from the NAME environment variable if undefined.")
+
+(defvar gnus-show-threads t
+ "*Show conversation threads in Subject Mode if non-nil.")
+
+(defvar gnus-thread-hide-subject t
+ "*Non-nil means hide subjects for thread subtrees.")
+
+(defvar gnus-thread-hide-subtree nil
+ "*Non-nil means hide thread subtrees initially.
+If non-nil, you have to run the command `gnus-Subject-show-thread' by
+hand or by using `gnus-Select-article-hook' to show hidden threads.")
+
+(defvar gnus-thread-hide-killed t
+ "*Non-nil means hide killed thread subtrees automatically.")
+
+(defvar gnus-thread-ignore-subject nil
+ "*Don't take care of subject differences, but only references if non-nil.
+If it is non-nil, some commands work with subjects do not work properly.")
+
+(defvar gnus-thread-indent-level 4
+ "Indentation of thread subtrees.")
+
+(defvar gnus-ignored-headers
+ "^Path:\\|^Posting-Version:\\|^Article-I.D.:\\|^Expires:\\|^Date-Received:\\|^References:\\|^Control:\\|^Xref:\\|^Lines:\\|^Posted:\\|^Relay-Version:\\|^Message-ID:\\|^Nf-ID:\\|^Nf-From:\\|^Approved:\\|^Sender:\\|^In-Reply-To:"
+ "Regexp matching headers not to display in messages.")
+
+(defvar gnus-show-all-headers nil
+ "*Show all headers of an article if non-nil.")
+
+(defvar gnus-save-all-headers nil
+ "*Save all headers of an article if non-nil.")
+
+(defvar gnus-optional-headers (function gnus-optional-lines-and-from)
+ "A function generating a optional string displayed in GNUS Subject
+mode buffer. The function is called with an article HEADER. The
+result must be a string excluding `[' and `]'.")
+
+(defvar gnus-auto-extend-newsgroup t
+ "*Extend visible articles to forward and backward if non-nil.")
+
+(defvar gnus-auto-select-first t
+ "*Select the first unread article automagically if non-nil.
+If you want to prevent automatic selection of the first unread article
+in some newsgroups, set the variable to nil in `gnus-Select-group-hook'
+or `gnus-Apply-kill-hook'.")
+
+(defvar gnus-auto-select-next t
+ "*Select the next newsgroup automagically if non-nil.
+If the value is t and the next newsgroup is empty, GNUS will exit
+Subject mode and go back to Group mode. If the value is neither nil
+nor t, GNUS will select the following unread newsgroup. Especially, if
+the value is the symbol `quietly', the next unread newsgroup will be
+selected without any confirmations.")
+
+(defvar gnus-auto-select-same nil
+ "*Select the next article with the same subject automagically if non-nil.")
+
+(defvar gnus-auto-center-subject t
+ "*Always center the current subject in GNUS Subject mode window if non-nil.")
+
+(defvar gnus-break-pages t
+ "*Break an article into pages if non-nil.
+Page delimiter is specified by the variable `gnus-page-delimiter'.")
+
+(defvar gnus-page-delimiter "^\^L"
+ "*Regexp describing line-beginnings that separate pages of news article.")
+
+(defvar gnus-digest-show-summary t
+ "*Show a summary of undigestified messages if non-nil.")
+
+(defvar gnus-digest-separator "^Subject:[ \t]"
+ "*Regexp that separates messages in a digest article.")
+
+(defvar gnus-use-full-window t
+ "*Non-nil means to take up the entire screen of Emacs.")
+
+(defvar gnus-window-configuration
+ '((SelectNewsgroup (0 1 0))
+ (ExitNewsgroup (1 0 0))
+ (SelectArticle (0 3 10))
+ (ExpandSubject (0 1 0)))
+ "Specify window configurations for each action.
+The format of the variable is a list of (ACTION (G S A)), where
+G, S, and A are the relative height of Group, Subject, and Article
+windows, respectively. ACTION is `SelectNewsgroup', `ExitNewsgroup',
+`SelectArticle', or `ExpandSubject'.")
+
+(defvar gnus-mail-reply-method
+ (function gnus-mail-reply-using-mail)
+ "A function to compose reply mail.
+The function `gnus-mail-reply-using-mail' uses usual the sendmail mail
+program. The function `gnus-mail-reply-using-mhe' uses the mh-e mail
+program. You can use yet another program by customizing this variable.")
+
+(defvar gnus-mail-other-window-method
+ (function gnus-mail-other-window-using-mail)
+ "A function to compose mail in other window.
+The function `gnus-mail-other-window-using-mail' uses usual sendmail
+mail program. The function `gnus-mail-other-window-using-mhe' uses mh-e
+mail program. You can use yet another program by customizing this variable.")
+
+(defvar gnus-subscribe-newsgroup-method
+ (function
+ (lambda (newsgroup)
+ (gnus-subscribe-newsgroup newsgroup
+ (car (car gnus-newsrc-assoc)))))
+ "A function called with a newsgroup name when it is created.")
+
+(defvar gnus-Group-mode-hook nil
+ "A hook for GNUS Group Mode.")
+
+(defvar gnus-Subject-mode-hook nil
+ "A hook for GNUS Subject Mode.")
+
+(defvar gnus-Article-mode-hook nil
+ "A hook for GNUS Article Mode.")
+
+(defvar gnus-Kill-file-mode-hook nil
+ "A hook for GNUS KILL File Mode.")
+
+(defvar gnus-Open-server-hook nil
+ "A hook called just before opening connection to news server.")
+
+(defvar gnus-Startup-hook nil
+ "A hook called at start up time.
+This hook is called after GNUS is connected to the NNTP server.
+So, it is possible to change the behavior of GNUS according to the
+selected NNTP server.")
+
+(defvar gnus-Group-prepare-hook nil
+ "A hook called after newsgroup list is created in the Newsgroup buffer.
+If you want to modify the Newsgroup buffer, you can use this hook.")
+
+(defvar gnus-Subject-prepare-hook nil
+ "A hook called after subject list is created in the Subject buffer.
+If you want to modify the Subject buffer, you can use this hook.")
+
+(defvar gnus-Article-prepare-hook nil
+ "A hook called after an article is prepared in the Article buffer.
+If you want to run a special decoding program like nkf, use this hook.")
+
+(defvar gnus-Select-group-hook nil
+ "A hook called when a newsgroup is selected.
+If you want to sort Subject buffer by date and then by subject, you
+can use the following hook:
+
+(setq gnus-Select-group-hook
+ '(lambda ()
+ ;; First of all, sort by date.
+ (gnus-sort-headers
+ '(lambda (a b)
+ (gnus-date-lessp (gnus-header-date a)
+ (gnus-header-date b))))
+ ;; Then sort by subject string ignoring `Re:'.
+ ;; If case-fold-search is non-nil, case of letters is ignored.
+ (gnus-sort-headers
+ '(lambda (a b)
+ (gnus-string-lessp
+ (gnus-simplify-subject (gnus-header-subject a) 're)
+ (gnus-simplify-subject (gnus-header-subject b) 're)
+ )))))
+
+If you'd like to simplify subjects like the `gnus-Subject-next-same-subject'
+command does, you can use the following hook:
+
+(setq gnus-Select-group-hook
+ '(lambda ()
+ (mapcar (function
+ (lambda (header)
+ (nntp-set-header-subject
+ header
+ (gnus-simplify-subject
+ (gnus-header-subject header) 're-only))))
+ gnus-newsgroup-headers)))
+
+In some newsgroups author name is meaningless. It is possible to
+prevent listing author names in the GNUS Subject buffer as follows:
+
+(setq gnus-Select-group-hook
+ '(lambda ()
+ (cond ((string-equal \"comp.sources.unix\" gnus-newsgroup-name)
+ (setq gnus-optional-headers
+ (function gnus-optional-lines)))
+ (t
+ (setq gnus-optional-headers
+ (function gnus-optional-lines-and-from))))))")
+
+(defvar gnus-Select-article-hook
+ (function (lambda () (gnus-Subject-show-thread)))
+ "Hook called when an article is selected.
+The default hook automatically shows conversation thread subtrees
+of the selected article as follows:
+
+(setq gnus-Select-article-hook
+ '(lambda ()
+ (gnus-Subject-show-thread)))
+
+If you'd like to run RMAIL on a digest article automagically, you can
+use the following hook:
+
+(setq gnus-Select-article-hook
+ '(lambda ()
+ (gnus-Subject-show-thread)
+ (cond ((string-equal \"comp.sys.sun\" gnus-newsgroup-name)
+ (gnus-Subject-rmail-digest))
+ ((and (string-equal \"comp.text\" gnus-newsgroup-name)
+ (string-match \"^TeXhax Digest\"
+ (gnus-header-subject gnus-current-headers)))
+ (gnus-Subject-rmail-digest)
+ ))))")
+
+(defvar gnus-Select-digest-hook
+ (function
+ (lambda ()
+ ;; Reply-To: is required by `undigestify-rmail-message'.
+ (or (mail-position-on-field "Reply-to" t)
+ (progn
+ (mail-position-on-field "Reply-to")
+ (insert (gnus-fetch-field "From"))))))
+ "A hook called when reading digest messages using Rmail.
+This hook can be used to modify incomplete digest articles as follows
+(this is the default):
+
+(setq gnus-Select-digest-hook
+ '(lambda ()
+ ;; Reply-To: is required by `undigestify-rmail-message'.
+ (or (mail-position-on-field \"Reply-to\" t)
+ (progn
+ (mail-position-on-field \"Reply-to\")
+ (insert (gnus-fetch-field \"From\"))))))")
+
+(defvar gnus-Rmail-digest-hook nil
+ "A hook called when reading digest messages using Rmail.
+This hook is intended to customize Rmail mode for reading digest articles.")
+
+(defvar gnus-Apply-kill-hook (function gnus-apply-kill-file)
+ "A hook called when a newsgroup is selected and subject list is prepared.
+This hook is intended to apply a KILL file to the selected newsgroup.
+The function `gnus-apply-kill-file' is called defaultly.
+
+Since a general KILL file is too heavy to use for only a few
+newsgroups, we recommend you use a lighter hook function. For
+example, if you'd like to apply a KILL file to articles which contains
+a string `rmgroup' in subject in newsgroup `control', you can use the
+following hook:
+
+(setq gnus-Apply-kill-hook
+ '(lambda ()
+ (cond ((string-match \"control\" gnus-newsgroup-name)
+ (gnus-kill \"Subject\" \"rmgroup\")
+ (gnus-expunge \"X\")))))")
+
+(defvar gnus-Mark-article-hook
+ (function
+ (lambda ()
+ (or (memq gnus-current-article gnus-newsgroup-marked)
+ (gnus-Subject-mark-as-read gnus-current-article))
+ (gnus-Subject-set-current-mark "+")))
+ "A hook called when an article is selected for the first time.
+The hook is intended to mark an article as read when it is selected.
+If you'd like to mark as unread (-) instead, use the following hook:
+
+(setq gnus-Mark-article-hook
+ '(lambda ()
+ (gnus-Subject-mark-as-unread gnus-current-article)
+ (gnus-Subject-set-current-mark \"+\")))")
+
+(defvar gnus-Inews-article-hook nil
+ "A hook called before posting an article.
+If you'd like to run a special encoding program, use this hook.")
+
+(defvar gnus-Exit-group-hook nil
+ "A hook called when exiting (not quitting) Subject mode.
+If your machine is so slow that exiting from Subject mode takes a
+long time, set the variable `gnus-newsgroup-headers' to nil. This
+inhibits marking articles as read using cross-reference information.")
+
+(defvar gnus-Suspend-gnus-hook nil
+ "A hook called when suspending (not exiting) GNUS.")
+
+(defvar gnus-Exit-gnus-hook nil
+ "A hook called when exiting (not suspending) GNUS.")
+
+(defvar gnus-Save-newsrc-hook nil
+ "A hook called when saving the newsrc file.
+This hook is called before saving .newsrc file.")
+
+(defvar gnus-your-domain nil
+ "*Your domain name without your host name like: \"stars.flab.Fujitsu.CO.JP\"
+The environment variable DOMAINNAME is used instead if defined. If
+the function `system-name' returns the full internet name, there is no
+need to define this variable.")
+
+(defvar gnus-your-organization nil
+ "*Your organization like: \"Fujitsu Laboratories Ltd., Kawasaki, Japan.\"
+The `ORGANIZATION' environment variable is used instead if defined.")
+
+(defvar gnus-use-generic-from nil
+ "*If nil, prepend local host name to the defined domain in the From:
+field; if stringp, use this; if non-nil, strip of the local host name.")
+
+(defvar gnus-use-generic-path nil
+ "*If nil, use the NNTP server name in the Path: field; if stringp,
+use this; if non-nil, use no host name (user name only)")
+
+;; Internal variables.
+
+(defconst gnus-version "GNUS 3.13"
+ "Version numbers of this version of GNUS.")
+
+(defvar gnus-Info-nodes
+ '((gnus-Group-mode . "(gnus)Newsgroup Commands")
+ (gnus-Subject-mode . "(gnus)Subject Commands")
+ (gnus-Article-mode . "(gnus)Article Commands")
+ (gnus-Kill-file-mode . "(gnus)KILL File")
+ (gnus-Browse-killed-mode . "(gnus)Maintenance"))
+ "Assoc list of major modes and related Info nodes.")
+
+(defvar gnus-access-methods
+ '((nntp
+ (gnus-retrieve-headers . nntp-retrieve-headers)
+ (gnus-open-server . nntp-open-server)
+ (gnus-close-server . nntp-close-server)
+ (gnus-server-opened . nntp-server-opened)
+ (gnus-status-message . nntp-status-message)
+ (gnus-request-article . nntp-request-article)
+ (gnus-request-group . nntp-request-group)
+ (gnus-request-list . nntp-request-list)
+ (gnus-request-post . nntp-request-post))
+ (nnspool
+ (gnus-retrieve-headers . nnspool-retrieve-headers)
+ (gnus-open-server . nnspool-open-server)
+ (gnus-close-server . nnspool-close-server)
+ (gnus-server-opened . nnspool-server-opened)
+ (gnus-status-message . nnspool-status-message)
+ (gnus-request-article . nnspool-request-article)
+ (gnus-request-group . nnspool-request-group)
+ (gnus-request-list . nnspool-request-list)
+ (gnus-request-post . nnspool-request-post))
+ (mhspool
+ (gnus-retrieve-headers . mhspool-retrieve-headers)
+ (gnus-open-server . mhspool-open-server)
+ (gnus-close-server . mhspool-close-server)
+ (gnus-server-opened . mhspool-server-opened)
+ (gnus-status-message . mhspool-status-message)
+ (gnus-request-article . mhspool-request-article)
+ (gnus-request-group . mhspool-request-group)
+ (gnus-request-list . mhspool-request-list)
+ (gnus-request-post . mhspool-request-post)))
+ "Access method for NNTP, nnspool, and mhspool.")
+
+(defvar gnus-Group-buffer "*Newsgroup*")
+(defvar gnus-Subject-buffer "*Subject*")
+(defvar gnus-Article-buffer "*Article*")
+(defvar gnus-Digest-buffer "GNUS Digest")
+(defvar gnus-Digest-summary-buffer "GNUS Digest-summary")
+
+(defvar gnus-buffer-list
+ (list gnus-Group-buffer gnus-Subject-buffer gnus-Article-buffer
+ gnus-Digest-buffer gnus-Digest-summary-buffer)
+ "GNUS buffer names which should be killed when exiting.")
+
+(defvar gnus-variable-list
+ '(gnus-newsrc-options
+ gnus-newsrc-options-n-yes gnus-newsrc-options-n-no
+ gnus-newsrc-assoc gnus-killed-assoc gnus-marked-assoc)
+ "GNUS variables saved in the quick startup file.")
+
+(defvar gnus-overload-functions
+ '((news-inews gnus-inews-news "rnewspost")
+ (caesar-region gnus-caesar-region "rnews"))
+ "Functions overloaded by gnus.
+It is a list of `(original overload &optional file)'.")
+
+(defvar gnus-newsrc-options nil
+ "Options line in the .newsrc file.")
+
+(defvar gnus-newsrc-options-n-yes nil
+ "Regexp representing subscribed newsgroups.")
+
+(defvar gnus-newsrc-options-n-no nil
+ "Regexp representing unsubscribed newsgroups.")
+
+(defvar gnus-newsrc-assoc nil
+ "Assoc list of read articles.")
+
+(defvar gnus-killed-assoc nil
+ "Assoc list of newsgroups removed from `gnus-newsrc-assoc'.")
+
+(defvar gnus-marked-assoc nil
+ "Assoc list of articles marked as unread.")
+
+(defvar gnus-unread-hashtb nil
+ "Hashtable of unread articles.")
+
+(defvar gnus-active-hashtb nil
+ "Hashtable of active articles.")
+
+(defvar gnus-octive-hashtb nil
+ "Hashtable of OLD active articles.")
+
+(defvar gnus-current-startup-file nil
+ "Startup file for the current host.")
+
+(defvar gnus-last-search-regexp nil
+ "Default regexp for article search command.")
+
+(defvar gnus-last-shell-command nil
+ "Default shell command on article.")
+
+(defvar gnus-have-all-newsgroups nil)
+
+(defvar gnus-newsgroup-name nil)
+(defvar gnus-newsgroup-begin nil)
+(defvar gnus-newsgroup-end nil)
+(defvar gnus-newsgroup-last-rmail nil)
+(defvar gnus-newsgroup-last-mail nil)
+(defvar gnus-newsgroup-last-folder nil)
+(defvar gnus-newsgroup-last-file nil)
+
+(defvar gnus-newsgroup-unreads nil
+ "List of unread articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-unselected nil
+ "List of unselected unread articles in the current newsgroup.")
+
+(defvar gnus-newsgroup-marked nil
+ "List of marked articles in the current newsgroup (a subset of unread art).")
+
+(defvar gnus-newsgroup-headers nil
+ "List of article headers in the current newsgroup.")
+
+(defvar gnus-current-article nil)
+(defvar gnus-current-headers nil)
+(defvar gnus-current-history nil)
+(defvar gnus-have-all-headers nil)
+(defvar gnus-last-article nil)
+(defvar gnus-current-kill-article nil)
+
+;; Save window configuration.
+(defvar gnus-winconf-kill-file nil)
+
+(defvar gnus-Group-mode-map nil)
+(defvar gnus-Subject-mode-map nil)
+(defvar gnus-Article-mode-map nil)
+(defvar gnus-Kill-file-mode-map nil)
+
+(defvar rmail-last-file (expand-file-name "~/XMBOX"))
+(defvar rmail-last-rmail-file (expand-file-name "~/XNEWS"))
+
+;; Define GNUS Subsystems.
+(autoload 'gnus-Group-post-news "gnuspost"
+ "Post an article." t)
+(autoload 'gnus-Subject-post-news "gnuspost"
+ "Post an article." t)
+(autoload 'gnus-Subject-post-reply "gnuspost"
+ "Post a reply article." t)
+(autoload 'gnus-Subject-post-reply-with-original "gnuspost"
+ "Post a reply article with original article." t)
+(autoload 'gnus-Subject-cancel-article "gnuspost"
+ "Cancel an article you posted." t)
+
+(autoload 'gnus-Subject-mail-reply "gnusmail"
+ "Reply mail to news author." t)
+(autoload 'gnus-Subject-mail-reply-with-original "gnusmail"
+ "Reply mail to news author with original article." t)
+(autoload 'gnus-Subject-mail-other-window "gnusmail"
+ "Compose mail in other window." t)
+
+(autoload 'gnus-Group-kill-group "gnusmisc"
+ "Kill newsgroup on current line." t)
+(autoload 'gnus-Group-yank-group "gnusmisc"
+ "Yank the last killed newsgroup on current line." t)
+(autoload 'gnus-Browse-killed-groups "gnusmisc"
+ "Browse the killed newsgroups." t)
+
+(autoload 'rmail-output "rmailout"
+ "Append this message to Unix mail file named FILE-NAME." t)
+(autoload 'mail-position-on-field "sendmail")
+(autoload 'mh-find-path "mh-e")
+(autoload 'mh-prompt-for-folder "mh-e")
+
+(put 'gnus-Group-mode 'mode-class 'special)
+(put 'gnus-Subject-mode 'mode-class 'special)
+(put 'gnus-Article-mode 'mode-class 'special)
+
+
+;;(put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
+
+(defmacro gnus-eval-in-buffer-window (buffer &rest forms)
+ "Pop to BUFFER, evaluate FORMS, and then returns to original window."
+ (` (let ((GNUSStartBufferWindow (selected-window)))
+ (unwind-protect
+ (progn
+ (pop-to-buffer (, buffer))
+ (,@ forms))
+ (select-window GNUSStartBufferWindow)))))
+
+(defmacro gnus-make-hashtable ()
+ '(make-abbrev-table))
+
+(defmacro gnus-gethash (string hashtable)
+ "Get hash value of STRING in HASHTABLE."
+ ;;(` (symbol-value (abbrev-symbol (, string) (, hashtable))))
+ (` (abbrev-expansion (, string) (, hashtable))))
+
+(defmacro gnus-sethash (string value hashtable)
+ "Set hash value. Arguments are STRING, VALUE, and HASHTABLE."
+ ;; We cannot use define-abbrev since it only accepts string as value.
+ (` (set (intern (, string) (, hashtable)) (, value))))
+
+;; Note: Macros defined here are also defined in nntp.el. I don't like
+;; to put them here, but many users got troubled with the old
+;; definitions in nntp.elc. These codes are NNTP 3.10 version.
+
+(defmacro nntp-header-number (header)
+ "Return article number in HEADER."
+ (` (aref (, header) 0)))
+
+(defmacro nntp-set-header-number (header number)
+ "Set article number of HEADER to NUMBER."
+ (` (aset (, header) 0 (, number))))
+
+(defmacro nntp-header-subject (header)
+ "Return subject string in HEADER."
+ (` (aref (, header) 1)))
+
+(defmacro nntp-set-header-subject (header subject)
+ "Set article subject of HEADER to SUBJECT."
+ (` (aset (, header) 1 (, subject))))
+
+(defmacro nntp-header-from (header)
+ "Return author string in HEADER."
+ (` (aref (, header) 2)))
+
+(defmacro nntp-set-header-from (header from)
+ "Set article author of HEADER to FROM."
+ (` (aset (, header) 2 (, from))))
+
+(defmacro nntp-header-xref (header)
+ "Return xref string in HEADER."
+ (` (aref (, header) 3)))
+
+(defmacro nntp-set-header-xref (header xref)
+ "Set article xref of HEADER to xref."
+ (` (aset (, header) 3 (, xref))))
+
+(defmacro nntp-header-lines (header)
+ "Return lines in HEADER."
+ (` (aref (, header) 4)))
+
+(defmacro nntp-set-header-lines (header lines)
+ "Set article lines of HEADER to LINES."
+ (` (aset (, header) 4 (, lines))))
+
+(defmacro nntp-header-date (header)
+ "Return date in HEADER."
+ (` (aref (, header) 5)))
+
+(defmacro nntp-set-header-date (header date)
+ "Set article date of HEADER to DATE."
+ (` (aset (, header) 5 (, date))))
+
+(defmacro nntp-header-id (header)
+ "Return Id in HEADER."
+ (` (aref (, header) 6)))
+
+(defmacro nntp-set-header-id (header id)
+ "Set article Id of HEADER to ID."
+ (` (aset (, header) 6 (, id))))
+
+(defmacro nntp-header-references (header)
+ "Return references in HEADER."
+ (` (aref (, header) 7)))
+
+(defmacro nntp-set-header-references (header ref)
+ "Set article references of HEADER to REF."
+ (` (aset (, header) 7 (, ref))))
+
+
+;;;
+;;; GNUS Group Mode
+;;;
+
+(if gnus-Group-mode-map
+ nil
+ (setq gnus-Group-mode-map (make-keymap))
+ (suppress-keymap gnus-Group-mode-map)
+ (define-key gnus-Group-mode-map " " 'gnus-Group-read-group)
+ (define-key gnus-Group-mode-map "=" 'gnus-Group-select-group)
+ (define-key gnus-Group-mode-map "j" 'gnus-Group-jump-to-group)
+ (define-key gnus-Group-mode-map "n" 'gnus-Group-next-unread-group)
+ (define-key gnus-Group-mode-map "p" 'gnus-Group-prev-unread-group)
+ (define-key gnus-Group-mode-map "\177" 'gnus-Group-prev-unread-group)
+ (define-key gnus-Group-mode-map "N" 'gnus-Group-next-group)
+ (define-key gnus-Group-mode-map "P" 'gnus-Group-prev-group)
+ (define-key gnus-Group-mode-map "\C-n" 'gnus-Group-next-group)
+ (define-key gnus-Group-mode-map "\C-p" 'gnus-Group-prev-group)
+ (define-key gnus-Group-mode-map "\r" 'next-line)
+ (define-key gnus-Group-mode-map "/" 'isearch-forward)
+ (define-key gnus-Group-mode-map "<" 'beginning-of-buffer)
+ (define-key gnus-Group-mode-map ">" 'end-of-buffer)
+ (define-key gnus-Group-mode-map "u" 'gnus-Group-unsubscribe-current-group)
+ (define-key gnus-Group-mode-map "U" 'gnus-Group-unsubscribe-group)
+ (define-key gnus-Group-mode-map "c" 'gnus-Group-catch-up)
+ (define-key gnus-Group-mode-map "C" 'gnus-Group-catch-up-all)
+ (define-key gnus-Group-mode-map "l" 'gnus-Group-list-groups)
+ (define-key gnus-Group-mode-map "L" 'gnus-Group-list-all-groups)
+ (define-key gnus-Group-mode-map "g" 'gnus-Group-get-new-news)
+ (define-key gnus-Group-mode-map "R" 'gnus-Group-restart)
+ (define-key gnus-Group-mode-map "b" 'gnus-Group-check-bogus-groups)
+ (define-key gnus-Group-mode-map "r" 'gnus-Group-restrict-groups)
+ (define-key gnus-Group-mode-map "a" 'gnus-Group-post-news)
+ (define-key gnus-Group-mode-map "\ek" 'gnus-Group-edit-local-kill)
+ (define-key gnus-Group-mode-map "\eK" 'gnus-Group-edit-global-kill)
+ (define-key gnus-Group-mode-map "\C-k" 'gnus-Group-kill-group)
+ (define-key gnus-Group-mode-map "\C-y" 'gnus-Group-yank-group)
+ (define-key gnus-Group-mode-map "\C-c\C-y" 'gnus-Browse-killed-groups)
+ (define-key gnus-Group-mode-map "V" 'gnus-version)
+ (define-key gnus-Group-mode-map "x" 'gnus-Group-force-update)
+ (define-key gnus-Group-mode-map "s" 'gnus-Group-force-update)
+ (define-key gnus-Group-mode-map "z" 'gnus-Group-suspend)
+ (define-key gnus-Group-mode-map "q" 'gnus-Group-exit)
+ (define-key gnus-Group-mode-map "Q" 'gnus-Group-quit)
+ (define-key gnus-Group-mode-map "?" 'gnus-Group-describe-briefly)
+ (define-key gnus-Group-mode-map "\C-c\C-i" 'gnus-Info-find-node))
+
+(defun gnus-Group-mode ()
+ "Major mode for reading network news.
+All normal editing commands are turned off.
+Instead, these commands are available:
+\\{gnus-Group-mode-map}
+
+ The name of the host running NNTP server is asked for if no default
+host is specified. It is also possible to choose another NNTP server
+even when the default server is defined by giving a prefix argument to
+the command `\\[gnus]'.
+
+ If an NNTP server is preceded by a colon such as `:Mail', the user's
+private directory `~/Mail' is used as a news spool. This makes it
+possible to read mail stored in MH folders or articles saved by GNUS.
+File names of mail or articles must consist of only numeric
+characters. Otherwise, they are ignored.
+
+ If there is a file named `~/.newsrc-SERVER', it is used as the
+startup file instead of standard one when talking to SERVER. It is
+possible to talk to many hosts by using different startup files for
+each.
+
+ Option `-n' of the options line in the startup file is recognized
+properly the same as the Bnews system. For example, if the options
+line is `options -n !talk talk.rumors', newsgroups under the `talk'
+hierarchy except for `talk.rumors' are ignored while checking new
+newsgroups.
+
+ If there is a file named `~/.signature-DISTRIBUTION', it is used as
+signature file instead of standard one when posting a news in
+DISTRIBUTION.
+
+ If an Info file generated from `gnus.texinfo' is installed, you can
+read an appropriate Info node of the Info file according to the
+current major mode of GNUS by \\[gnus-Info-find-node].
+
+ The variable `gnus-version', `nntp-version', `nnspool-version', and
+`mhspool-version' have the version numbers of this version of gnus.el,
+nntp.el, nnspool.el, and mhspoo.el, respectively.
+
+User customizable variables:
+ gnus-nntp-server
+ Specifies the name of the host running the NNTP server. If its
+ value is a string such as `:DIRECTORY', the user's private
+ DIRECTORY is used as a news spool. The variable is initialized
+ from the NNTPSERVER environment variable.
+
+ gnus-nntp-service
+ Specifies a NNTP service name. It is usually \"nntp\" or 119. Nil
+ forces GNUS to use a local news spool if the variable
+ `gnus-nntp-server' is set to the local host name.
+
+ gnus-startup-file
+ Specifies a startup file (.newsrc). If there is a file named
+ `.newsrc-SERVER', it's used instead when talking to SERVER. I
+ recommend you to use the server specific file, if you'd like to
+ talk to many servers. Especially if you'd like to read your
+ private directory, the name of the file must be
+ `.newsrc-:DIRECTORY'.
+
+ gnus-signature-file
+ Specifies a signature file (.signature). If there is a file named
+ `.signature-DISTRIBUTION', it's used instead when posting an
+ article in DISTRIBUTION. Set the variable to nil to prevent
+ appending the file automatically. If you use an NNTP inews which
+ comes with the NNTP package, you may have to set the variable to
+ nil.
+
+ gnus-use-cross-reference
+ Specifies what to do with cross references (Xref: field). If it
+ is nil, cross references are ignored. If it is t, articles in
+ subscribed newsgroups are only marked as read. Otherwise, if it
+ is not nil nor t, articles in all newsgroups are marked as read.
+
+ gnus-use-followup-to
+ Specifies what to do with followup-to: field. If it is nil, its
+ value is ignored. If it is non-nil, its value is used as followup
+ newsgroups. Especially, if it is t and field value is `poster',
+ your confirmation is required.
+
+ gnus-author-copy
+ Specifies a file name to save a copy of article you posted using
+ FCC: field. If the first character of the value is `|', the
+ contents of the article is piped out to a program specified by the
+ rest of the value. The variable is initialized from the
+ AUTHORCOPY environment variable.
+
+ gnus-author-copy-saver
+ Specifies a function to save an author copy. The function is
+ called with a file name. The default function `rmail-output'
+ saves in Unix mail format.
+
+ gnus-kill-file-name
+ Use specified file name as a KILL file (default to `KILL').
+
+ gnus-novice-user
+ Non-nil means that you are a novice to USENET. If non-nil,
+ verbose messages may be displayed or your confirmations may be
+ required.
+
+ gnus-interactive-post
+ Non-nil means that newsgroup, subject and distribution are asked
+ for interactively when posting a new article.
+
+ gnus-use-full-window
+ Non-nil means to take up the entire screen of Emacs.
+
+ gnus-window-configuration
+ Specifies the configuration of Group, Subject, and Article
+ windows. It is a list of (ACTION (G S A)), where G, S, and A are
+ the relative height of Group, Subject, and Article windows,
+ respectively. ACTION is `SelectNewsgroup', `ExitNewsgroup',
+ `SelectArticle', or `ExpandSubject'.
+
+ gnus-subscribe-newsgroup-method
+ Specifies a function called with a newsgroup name when new
+ newsgroup is found. The default definition adds new newsgroup at
+ the beginning of other newsgroups.
+
+Various hooks for customization:
+ gnus-Group-mode-hook
+ Entry to this mode calls the value with no arguments, if that
+ value is non-nil. This hook is called before GNUS is connected to
+ the NNTP server. So, you can change or define the NNTP server in
+ this hook.
+
+ gnus-Startup-hook
+ Called with no arguments after the NNTP server is selected. It is
+ possible to change the behavior of GNUS or initialize the
+ variables according to the selected NNTP server.
+
+ gnus-Group-prepare-hook
+ Called with no arguments after a newsgroup list is created in the
+ Newsgroup buffer, if that value is non-nil.
+
+ gnus-Save-newsrc-hook
+ Called with no arguments when saving newsrc file if that value is
+ non-nil.
+
+ gnus-Inews-article-hook
+ Called with no arguments when posting an article if that value is
+ non-nil. This hook is called just before posting an article, while
+ `news-inews-hook' is called before preparing article headers. If
+ you'd like to convert kanji code of the article, this hook is recommended.
+
+ gnus-Suspend-gnus-hook
+ Called with no arguments when suspending (not exiting) GNUS, if
+ that value is non-nil.
+
+ gnus-Exit-gnus-hook
+ Called with no arguments when exiting (not suspending) GNUS, if
+ that value is non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ ;; Gee. Why don't you upgrade?
+ (cond ((boundp 'mode-line-modified)
+ (setq mode-line-modified "--- "))
+ ((listp (default-value 'mode-line-format))
+ (setq mode-line-format
+ (cons "--- " (cdr (default-value 'mode-line-format)))))
+ (t
+ (setq mode-line-format
+ "--- GNUS: List of Newsgroups %[(%m)%]----%3p-%-")))
+ (setq major-mode 'gnus-Group-mode)
+ (setq mode-name "Newsgroup")
+ (setq mode-line-buffer-identification "GNUS: List of Newsgroups")
+ (setq mode-line-process nil)
+ (use-local-map gnus-Group-mode-map)
+ (buffer-flush-undo (current-buffer))
+ (setq buffer-read-only t) ;Disable modification
+ (run-hooks 'gnus-Group-mode-hook))
+
+;;;###autoload
+(defun gnus (&optional confirm)
+ "Read network news.
+If optional argument CONFIRM is non-nil, ask NNTP server."
+ (interactive "P")
+ (unwind-protect
+ (progn
+ (switch-to-buffer (get-buffer-create gnus-Group-buffer))
+ (gnus-Group-mode)
+ (gnus-start-news-server confirm))
+ (if (not (gnus-server-opened))
+ (gnus-Group-quit)
+ ;; NNTP server is successfully open.
+ (setq mode-line-process (format " {%s}" gnus-nntp-server))
+ (let ((buffer-read-only nil))
+ (erase-buffer)
+ (gnus-Group-startup-message)
+ (sit-for 0))
+ (run-hooks 'gnus-Startup-hook)
+ (gnus-setup-news-info)
+ (if gnus-novice-user
+ (gnus-Group-describe-briefly)) ;Show brief help message.
+ (gnus-Group-list-groups nil)
+ )))
+
+(defun gnus-Group-startup-message ()
+ "Insert startup message in current buffer."
+ ;; Insert the message.
+ (insert "
+ GNUS Version 3.13
+
+ NNTP-based News Reader for GNU Emacs
+
+
+If you have any trouble with this software, please let me
+know. I will fix your problems in the next release.
+
+Comments, suggestions, and bug fixes are welcome.
+
+Masanobu UMEDA
+umerin@tc.Nagasaki.GO.JP")
+ ;; And then hack it.
+ ;; 57 is the longest line.
+ (indent-rigidly (point-min) (point-max) (/ (max (- (window-width) 57) 0) 2))
+ (goto-char (point-min))
+ ;; +4 is fuzzy factor.
+ (insert-char ?\n (/ (max (- (window-height) 18) 0) 2)))
+
+(defun gnus-Group-list-groups (show-all)
+ "List newsgroups in the Newsgroup buffer.
+If argument SHOW-ALL is non-nil, unsubscribed groups are also listed."
+ (interactive "P")
+ (let ((last-group ;Current newsgroup.
+ (gnus-Group-group-name))
+ (next-group ;Next possible newsgroup.
+ (progn
+ (gnus-Group-search-forward nil nil)
+ (gnus-Group-group-name)))
+ (prev-group ;Previous possible newsgroup.
+ (progn
+ (gnus-Group-search-forward t nil)
+ (gnus-Group-group-name))))
+ (gnus-Group-prepare show-all)
+ (if (zerop (buffer-size))
+ (message "No news is good news")
+ ;; Go to last newsgroup if possible. If cannot, try next and
+ ;; previous. If all fail, go to first unread newsgroup.
+ (goto-char (point-min))
+ (or (and last-group
+ (re-search-forward
+ (concat "^.+: " (regexp-quote last-group) "$") nil t))
+ (and next-group
+ (re-search-forward
+ (concat "^.+: " (regexp-quote next-group) "$") nil t))
+ (and prev-group
+ (re-search-forward
+ (concat "^.+: " (regexp-quote prev-group) "$") nil t))
+ (re-search-forward "^[ \t]+[1-9][0-9]*:" nil t))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ )))
+
+(defun gnus-Group-prepare (&optional all)
+ "Prepare list of newsgroups in current buffer.
+If optional argument ALL is non-nil, unsubscribed groups are also listed."
+ (let ((buffer-read-only nil)
+ (newsrc gnus-newsrc-assoc)
+ (group-info nil)
+ (group-name nil)
+ (unread-count 0)
+ ;; This specifies the format of Group buffer.
+ (cntl "%s%s%5d: %s\n"))
+ (erase-buffer)
+ ;; List newsgroups.
+ (while newsrc
+ (setq group-info (car newsrc))
+ (setq group-name (car group-info))
+ (setq unread-count (nth 1 (gnus-gethash group-name gnus-unread-hashtb)))
+ (if (or all
+ (and (nth 1 group-info) ;Subscribed.
+ (> unread-count 0))) ;There are unread articles.
+ ;; Yes, I can use gnus-Group-prepare-line, but this is faster.
+ (insert
+ (format cntl
+ ;; Subscribed or not.
+ (if (nth 1 group-info) " " "U")
+ ;; Has new news?
+ (if (and (> unread-count 0)
+ (>= 0
+ (- unread-count
+ (length
+ (cdr (assoc group-name
+ gnus-marked-assoc))))))
+ "*" " ")
+ ;; Number of unread articles.
+ unread-count
+ ;; Newsgroup name.
+ group-name))
+ )
+ (setq newsrc (cdr newsrc))
+ )
+ (setq gnus-have-all-newsgroups all)
+ (goto-char (point-min))
+ (run-hooks 'gnus-Group-prepare-hook)
+ ))
+
+(defun gnus-Group-prepare-line (info)
+ "Return a string for the Newsgroup buffer from INFO.
+INFO is an element of gnus-newsrc-assoc or gnus-killed-assoc."
+ (let* ((group-name (car info))
+ (unread-count
+ (or (nth 1 (gnus-gethash group-name gnus-unread-hashtb))
+ ;; Not in hash table, so compute it now.
+ (gnus-number-of-articles
+ (gnus-difference-of-range
+ (nth 2 (gnus-gethash group-name gnus-active-hashtb))
+ (nthcdr 2 info)))))
+ ;; This specifies the format of Group buffer.
+ (cntl "%s%s%5d: %s\n"))
+ (format cntl
+ ;; Subscribed or not.
+ (if (nth 1 info) " " "U")
+ ;; Has new news?
+ (if (and (> unread-count 0)
+ (>= 0
+ (- unread-count
+ (length
+ (cdr (assoc group-name gnus-marked-assoc))))))
+ "*" " ")
+ ;; Number of unread articles.
+ unread-count
+ ;; Newsgroup name.
+ group-name
+ )))
+
+(defun gnus-Group-update-group (group &optional visible-only)
+ "Update newsgroup info of GROUP.
+If optional argument VISIBLE-ONLY is non-nil, non displayed group is ignored."
+ (let ((buffer-read-only nil)
+ (visible nil))
+ ;; Buffer may be narrowed.
+ (save-restriction
+ (widen)
+ ;; Search point to modify.
+ (goto-char (point-min))
+ (if (re-search-forward (concat "^.+: " (regexp-quote group) "$") nil t)
+ ;; GROUP is listed in current buffer. So, delete old line.
+ (progn
+ (setq visible t)
+ (beginning-of-line)
+ (delete-region (point) (progn (forward-line 1) (point)))
+ ))
+ (if (or visible (not visible-only))
+ (progn
+ (insert (gnus-Group-prepare-line (assoc group gnus-newsrc-assoc)))
+ (forward-line -1) ;Move point on that line.
+ ))
+ )))
+
+;; GNUS Group mode command
+
+(defun gnus-Group-group-name ()
+ "Get newsgroup name around point."
+ (save-excursion
+ (beginning-of-line)
+ (if (looking-at ".[* \t]*[0-9]+:[ \t]+\\([^ \t\n]+\\)$")
+ (buffer-substring (match-beginning 1) (match-end 1))
+ )))
+
+(defun gnus-Group-read-group (all &optional no-article)
+ "Read news in this newsgroup.
+If argument ALL is non-nil, already read articles become readable.
+If optional argument NO-ARTICLE is non-nil, no article body is displayed."
+ (interactive "P")
+ (let ((group (gnus-Group-group-name))) ;Newsgroup name to read.
+ (if group
+ (gnus-Subject-read-group
+ group
+ (or all
+ ;;(not (nth 1 (assoc group gnus-newsrc-assoc))) ;Unsubscribed
+ (zerop
+ (nth 1 (gnus-gethash group gnus-unread-hashtb)))) ;No unread
+ no-article
+ ))
+ ))
+
+(defun gnus-Group-select-group (all)
+ "Select this newsgroup.
+No article is selected automatically.
+If argument ALL is non-nil, already read articles become readable."
+ (interactive "P")
+ (gnus-Group-read-group all t))
+
+(defun gnus-Group-jump-to-group (group)
+ "Jump to newsgroup GROUP."
+ (interactive
+ (list (completing-read "Newsgroup: " gnus-newsrc-assoc nil 'require-match)))
+ (goto-char (point-min))
+ (or (re-search-forward (concat "^.+: " (regexp-quote group) "$") nil t)
+ (if (assoc group gnus-newsrc-assoc)
+ ;; Add GROUP entry, then seach again.
+ (gnus-Group-update-group group)))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t))
+
+(defun gnus-Group-search-forward (backward any-group)
+ "Search for newsgroup forward.
+If first argument BACKWARD is non-nil, search backward instead.
+If second argument ANY-GROUP is non-nil, unsubscribed or empty
+group may be selected."
+ (let ((func (if backward 're-search-backward 're-search-forward))
+ (regexp
+ (format "^%s[ \t]*\\(%s\\):"
+ (if any-group ".." " [ \t]")
+ (if any-group "[0-9]+" "[1-9][0-9]*")))
+ (found nil))
+ (if backward
+ (beginning-of-line)
+ (end-of-line))
+ (setq found (funcall func regexp nil t))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ ;; Return T if found.
+ found
+ ))
+
+(defun gnus-Group-next-group (n)
+ "Go to next N'th newsgroup."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Group-search-forward nil t))
+ (setq n (1- n)))
+ (or (gnus-Group-search-forward nil t)
+ (message "No more newsgroups")))
+
+(defun gnus-Group-next-unread-group (n)
+ "Go to next N'th unread newsgroup."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Group-search-forward nil nil))
+ (setq n (1- n)))
+ (or (gnus-Group-search-forward nil nil)
+ (message "No more unread newsgroups")))
+
+(defun gnus-Group-prev-group (n)
+ "Go to previous N'th newsgroup."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Group-search-forward t t))
+ (setq n (1- n)))
+ (or (gnus-Group-search-forward t t)
+ (message "No more newsgroups")))
+
+(defun gnus-Group-prev-unread-group (n)
+ "Go to previous N'th unread newsgroup."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Group-search-forward t nil))
+ (setq n (1- n)))
+ (or (gnus-Group-search-forward t nil)
+ (message "No more unread newsgroups")))
+
+(defun gnus-Group-catch-up (all &optional quietly)
+ "Mark all articles not marked as unread in current newsgroup as read.
+If prefix argument ALL is non-nil, all articles are marked as read.
+Cross references (Xref: field) of articles are ignored."
+ (interactive "P")
+ (let* ((group (gnus-Group-group-name))
+ (marked (if (not all)
+ (cdr (assoc group gnus-marked-assoc)))))
+ (and group
+ (or quietly
+ (y-or-n-p
+ (if all
+ "Do you really want to mark everything as read? "
+ "Delete all articles not marked as read? ")))
+ (progn
+ (message "") ;Erase "Yes or No" question.
+ ;; Any marked articles will be preserved.
+ (gnus-update-unread-articles group marked marked)
+ (gnus-Group-update-group group)
+ (gnus-Group-next-group 1)))
+ ))
+
+(defun gnus-Group-catch-up-all (&optional quietly)
+ "Mark all articles in current newsgroup as read.
+Cross references (Xref: field) of articles are ignored."
+ (interactive)
+ (gnus-Group-catch-up t quietly))
+
+(defun gnus-Group-unsubscribe-current-group ()
+ "Toggle subscribe from/to unsubscribe current group."
+ (interactive)
+ (gnus-Group-unsubscribe-group (gnus-Group-group-name))
+ (gnus-Group-next-group 1))
+
+(defun gnus-Group-unsubscribe-group (group)
+ "Toggle subscribe from/to unsubscribe GROUP.
+New newsgroup is added to .newsrc automatically."
+ (interactive
+ (list (completing-read "Newsgroup: "
+ gnus-active-hashtb nil 'require-match)))
+ (let ((newsrc (assoc group gnus-newsrc-assoc)))
+ (cond ((not (null newsrc))
+ ;; Toggle subscription flag.
+ (setcar (nthcdr 1 newsrc) (not (nth 1 newsrc)))
+ (gnus-update-newsrc-buffer group)
+ (gnus-Group-update-group group)
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t))
+ ((and (stringp group)
+ (gnus-gethash group gnus-active-hashtb))
+ ;; Add new newsgroup.
+ (gnus-add-newsgroup group)
+ (gnus-Group-update-group group)
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t))
+ (t (error "No such newsgroup: %s" group)))
+ ))
+
+(defun gnus-Group-list-all-groups ()
+ "List all of newsgroups in the Newsgroup buffer."
+ (interactive)
+ (gnus-Group-list-groups t))
+
+(defun gnus-Group-get-new-news ()
+ "Get newly arrived articles. In fact, read the active file again."
+ (interactive)
+ (gnus-setup-news-info)
+ (gnus-Group-list-groups gnus-have-all-newsgroups))
+
+(defun gnus-Group-restart ()
+ "Force GNUS to read the raw startup file."
+ (interactive)
+ (gnus-save-newsrc-file)
+ (gnus-setup-news-info t) ;Force to read the raw startup file.
+ (gnus-Group-list-groups gnus-have-all-newsgroups))
+
+(defun gnus-Group-check-bogus-groups ()
+ "Check bogus newsgroups."
+ (interactive)
+ (gnus-check-bogus-newsgroups t) ;Require confirmation.
+ (gnus-Group-list-groups gnus-have-all-newsgroups))
+
+(defun gnus-Group-restrict-groups (start end)
+ "Restrict visible newsgroups to the current region (START and END).
+Type \\[widen] to remove restriction."
+ (interactive "r")
+ (save-excursion
+ (narrow-to-region (progn
+ (goto-char start)
+ (beginning-of-line)
+ (point))
+ (progn
+ (goto-char end)
+ (forward-line 1)
+ (point))))
+ (message (substitute-command-keys "Type \\[widen] to remove restriction")))
+
+(defun gnus-Group-edit-global-kill ()
+ "Edit a global KILL file."
+ (interactive)
+ (setq gnus-current-kill-article nil) ;No articles selected.
+ (gnus-Kill-file-edit-file nil) ;Nil stands for global KILL file.
+ (message
+ (substitute-command-keys
+ "Editing a global KILL file (Type \\[gnus-Kill-file-exit] to exit)")))
+
+(defun gnus-Group-edit-local-kill ()
+ "Edit a local KILL file."
+ (interactive)
+ (setq gnus-current-kill-article nil) ;No articles selected.
+ (gnus-Kill-file-edit-file (gnus-Group-group-name))
+ (message
+ (substitute-command-keys
+ "Editing a local KILL file (Type \\[gnus-Kill-file-exit] to exit)")))
+
+(defun gnus-Group-force-update ()
+ "Update .newsrc file."
+ (interactive)
+ (gnus-save-newsrc-file))
+
+(defun gnus-Group-suspend ()
+ "Suspend the current GNUS session.
+In fact, cleanup buffers except for Group Mode buffer.
+The hook `gnus-Suspend-gnus-hook' is called before actually suspending."
+ (interactive)
+ (run-hooks 'gnus-Suspend-gnus-hook)
+ ;; Kill GNUS buffers except for Group Mode buffer.
+ (let ((buffers gnus-buffer-list))
+ (while buffers
+ (and (not (eq (car buffers) gnus-Group-buffer))
+ (get-buffer (car buffers))
+ (kill-buffer (car buffers)))
+ (setq buffers (cdr buffers))
+ ))
+ (bury-buffer))
+
+(defun gnus-Group-exit ()
+ "Quit reading news after updating .newsrc.
+The hook `gnus-Exit-gnus-hook' is called before actually quitting."
+ (interactive)
+ (if (or noninteractive ;For gnus-batch-kill
+ (zerop (buffer-size)) ;No news is good news.
+ (not (gnus-server-opened)) ;NNTP connection closed.
+ (y-or-n-p "Are you sure you want to quit reading news? "))
+ (progn
+ (message "") ;Erase "Yes or No" question.
+ (run-hooks 'gnus-Exit-gnus-hook)
+ (gnus-save-newsrc-file)
+ (gnus-clear-system)
+ (gnus-close-server))
+ ))
+
+(defun gnus-Group-quit ()
+ "Quit reading news without updating .newsrc.
+The hook `gnus-Exit-gnus-hook' is called before actually quitting."
+ (interactive)
+ (if (or (zerop (buffer-size))
+ (not (gnus-server-opened))
+ (yes-or-no-p
+ (format "Quit reading news without saving %s? "
+ (file-name-nondirectory gnus-current-startup-file))))
+ (progn
+ (message "") ;Erase "Yes or No" question.
+ (run-hooks 'gnus-Exit-gnus-hook)
+ (gnus-clear-system)
+ (gnus-close-server))
+ ))
+
+(defun gnus-Group-describe-briefly ()
+ "Describe Group mode commands briefly."
+ (interactive)
+ (message
+ (concat
+ (substitute-command-keys "\\[gnus-Group-read-group]:Select ")
+ (substitute-command-keys "\\[gnus-Group-next-unread-group]:Forward ")
+ (substitute-command-keys "\\[gnus-Group-prev-unread-group]:Backward ")
+ (substitute-command-keys "\\[gnus-Group-exit]:Exit ")
+ (substitute-command-keys "\\[gnus-Info-find-node]:Run Info ")
+ (substitute-command-keys "\\[gnus-Group-describe-briefly]:This help")
+ )))
+
+
+;;;
+;;; GNUS Subject Mode
+;;;
+
+(if gnus-Subject-mode-map
+ nil
+ (setq gnus-Subject-mode-map (make-keymap))
+ (suppress-keymap gnus-Subject-mode-map)
+ (define-key gnus-Subject-mode-map " " 'gnus-Subject-next-page)
+ (define-key gnus-Subject-mode-map "\177" 'gnus-Subject-prev-page)
+ (define-key gnus-Subject-mode-map "\r" 'gnus-Subject-scroll-up)
+ (define-key gnus-Subject-mode-map "n" 'gnus-Subject-next-unread-article)
+ (define-key gnus-Subject-mode-map "p" 'gnus-Subject-prev-unread-article)
+ (define-key gnus-Subject-mode-map "N" 'gnus-Subject-next-article)
+ (define-key gnus-Subject-mode-map "P" 'gnus-Subject-prev-article)
+ (define-key gnus-Subject-mode-map "\e\C-n" 'gnus-Subject-next-same-subject)
+ (define-key gnus-Subject-mode-map "\e\C-p" 'gnus-Subject-prev-same-subject)
+ ;;(define-key gnus-Subject-mode-map "\e\C-n" 'gnus-Subject-next-unread-same-subject)
+ ;;(define-key gnus-Subject-mode-map "\e\C-p" 'gnus-Subject-prev-unread-same-subject)
+ (define-key gnus-Subject-mode-map "\C-c\C-n" 'gnus-Subject-next-digest)
+ (define-key gnus-Subject-mode-map "\C-c\C-p" 'gnus-Subject-prev-digest)
+ (define-key gnus-Subject-mode-map "\C-n" 'gnus-Subject-next-subject)
+ (define-key gnus-Subject-mode-map "\C-p" 'gnus-Subject-prev-subject)
+ (define-key gnus-Subject-mode-map "\en" 'gnus-Subject-next-unread-subject)
+ (define-key gnus-Subject-mode-map "\ep" 'gnus-Subject-prev-unread-subject)
+ ;;(define-key gnus-Subject-mode-map "\C-cn" 'gnus-Subject-next-group)
+ ;;(define-key gnus-Subject-mode-map "\C-cp" 'gnus-Subject-prev-group)
+ (define-key gnus-Subject-mode-map "." 'gnus-Subject-first-unread-article)
+ (define-key gnus-Subject-mode-map "/" 'isearch-forward)
+ (define-key gnus-Subject-mode-map "s" 'gnus-Subject-isearch-article)
+ (define-key gnus-Subject-mode-map "\es" 'gnus-Subject-search-article-forward)
+ (define-key gnus-Subject-mode-map "\eS" 'gnus-Subject-search-article-backward)
+ (define-key gnus-Subject-mode-map "<" 'gnus-Subject-beginning-of-article)
+ (define-key gnus-Subject-mode-map ">" 'gnus-Subject-end-of-article)
+ (define-key gnus-Subject-mode-map "j" 'gnus-Subject-goto-subject)
+ (define-key gnus-Subject-mode-map "J" 'gnus-Subject-goto-article)
+ (define-key gnus-Subject-mode-map "l" 'gnus-Subject-goto-last-article)
+ (define-key gnus-Subject-mode-map "^" 'gnus-Subject-refer-parent-article)
+ (define-key gnus-Subject-mode-map "\er" 'gnus-Subject-refer-article)
+ (define-key gnus-Subject-mode-map "u" 'gnus-Subject-mark-as-unread-forward)
+ (define-key gnus-Subject-mode-map "U" 'gnus-Subject-mark-as-unread-backward)
+ (define-key gnus-Subject-mode-map "d" 'gnus-Subject-mark-as-read-forward)
+ (define-key gnus-Subject-mode-map "D" 'gnus-Subject-mark-as-read-backward)
+ (define-key gnus-Subject-mode-map "\eu" 'gnus-Subject-clear-mark-forward)
+ (define-key gnus-Subject-mode-map "\eU" 'gnus-Subject-clear-mark-backward)
+ (define-key gnus-Subject-mode-map "k" 'gnus-Subject-kill-same-subject-and-select)
+ (define-key gnus-Subject-mode-map "\C-k" 'gnus-Subject-kill-same-subject)
+ (define-key gnus-Subject-mode-map "\e\C-t" 'gnus-Subject-toggle-threads)
+ (define-key gnus-Subject-mode-map "\e\C-s" 'gnus-Subject-show-thread)
+ (define-key gnus-Subject-mode-map "\e\C-h" 'gnus-Subject-hide-thread)
+ (define-key gnus-Subject-mode-map "\e\C-f" 'gnus-Subject-next-thread)
+ (define-key gnus-Subject-mode-map "\e\C-b" 'gnus-Subject-prev-thread)
+ (define-key gnus-Subject-mode-map "\e\C-u" 'gnus-Subject-up-thread)
+ (define-key gnus-Subject-mode-map "\e\C-d" 'gnus-Subject-down-thread)
+ (define-key gnus-Subject-mode-map "\e\C-k" 'gnus-Subject-kill-thread)
+ (define-key gnus-Subject-mode-map "&" 'gnus-Subject-execute-command)
+ ;;(define-key gnus-Subject-mode-map "c" 'gnus-Subject-catch-up)
+ ;;(define-key gnus-Subject-mode-map "c" 'gnus-Subject-catch-up-all)
+ (define-key gnus-Subject-mode-map "c" 'gnus-Subject-catch-up-and-exit)
+ ;;(define-key gnus-Subject-mode-map "c" 'gnus-Subject-catch-up-all-and-exit)
+ (define-key gnus-Subject-mode-map "\C-t" 'gnus-Subject-toggle-truncation)
+ (define-key gnus-Subject-mode-map "x" 'gnus-Subject-delete-marked-as-read)
+ (define-key gnus-Subject-mode-map "X" 'gnus-Subject-delete-marked-with)
+ (define-key gnus-Subject-mode-map "\C-c\C-sn" 'gnus-Subject-sort-by-number)
+ (define-key gnus-Subject-mode-map "\C-c\C-sa" 'gnus-Subject-sort-by-author)
+ (define-key gnus-Subject-mode-map "\C-c\C-ss" 'gnus-Subject-sort-by-subject)
+ (define-key gnus-Subject-mode-map "\C-c\C-sd" 'gnus-Subject-sort-by-date)
+ (define-key gnus-Subject-mode-map "\C-c\C-s\C-n" 'gnus-Subject-sort-by-number)
+ (define-key gnus-Subject-mode-map "\C-c\C-s\C-a" 'gnus-Subject-sort-by-author)
+ (define-key gnus-Subject-mode-map "\C-c\C-s\C-s" 'gnus-Subject-sort-by-subject)
+ (define-key gnus-Subject-mode-map "\C-c\C-s\C-d" 'gnus-Subject-sort-by-date)
+ (define-key gnus-Subject-mode-map "=" 'gnus-Subject-expand-window)
+ (define-key gnus-Subject-mode-map "G" 'gnus-Subject-reselect-current-group)
+ (define-key gnus-Subject-mode-map "w" 'gnus-Subject-stop-page-breaking)
+ (define-key gnus-Subject-mode-map "\C-c\C-r" 'gnus-Subject-caesar-message)
+ (define-key gnus-Subject-mode-map "g" 'gnus-Subject-show-article)
+ (define-key gnus-Subject-mode-map "t" 'gnus-Subject-toggle-header)
+ (define-key gnus-Subject-mode-map "v" 'gnus-Subject-show-all-headers)
+ (define-key gnus-Subject-mode-map "\C-d" 'gnus-Subject-rmail-digest)
+ (define-key gnus-Subject-mode-map "a" 'gnus-Subject-post-news)
+ (define-key gnus-Subject-mode-map "f" 'gnus-Subject-post-reply)
+ (define-key gnus-Subject-mode-map "F" 'gnus-Subject-post-reply-with-original)
+ (define-key gnus-Subject-mode-map "C" 'gnus-Subject-cancel-article)
+ (define-key gnus-Subject-mode-map "r" 'gnus-Subject-mail-reply)
+ (define-key gnus-Subject-mode-map "R" 'gnus-Subject-mail-reply-with-original)
+ (define-key gnus-Subject-mode-map "m" 'gnus-Subject-mail-other-window)
+ (define-key gnus-Subject-mode-map "o" 'gnus-Subject-save-article)
+ (define-key gnus-Subject-mode-map "\C-o" 'gnus-Subject-save-in-mail)
+ (define-key gnus-Subject-mode-map "|" 'gnus-Subject-pipe-output)
+ (define-key gnus-Subject-mode-map "\ek" 'gnus-Subject-edit-local-kill)
+ (define-key gnus-Subject-mode-map "\eK" 'gnus-Subject-edit-global-kill)
+ (define-key gnus-Subject-mode-map "V" 'gnus-version)
+ (define-key gnus-Subject-mode-map "q" 'gnus-Subject-exit)
+ (define-key gnus-Subject-mode-map "Q" 'gnus-Subject-quit)
+ (define-key gnus-Subject-mode-map "?" 'gnus-Subject-describe-briefly)
+ (define-key gnus-Subject-mode-map "\C-c\C-i" 'gnus-Info-find-node))
+
+(defun gnus-Subject-mode ()
+ "Major mode for reading articles in this newsgroup.
+All normal editing commands are turned off.
+Instead, these commands are available:
+\\{gnus-Subject-mode-map}
+
+User customizable variables:
+ gnus-large-newsgroup
+ The number of articles which indicates a large newsgroup. If the
+ number of articles in a newsgroup is greater than the value, the
+ number of articles to be selected is asked for. If the given value
+ N is positive, the last N articles is selected. If N is negative,
+ the first N articles are selected. An empty string means to select
+ all articles.
+
+ gnus-use-long-file-name
+ Non-nil means that a newsgroup name is used as a default file name
+ to save articles to. If it's nil, the directory form of a
+ newsgroup is used instead.
+
+ gnus-default-article-saver
+ Specifies your favorite article saver which is interactively
+ funcallable. Following functions are available:
+
+ gnus-Subject-save-in-rmail (in Rmail format)
+ gnus-Subject-save-in-mail (in Unix mail format)
+ gnus-Subject-save-in-folder (in MH folder)
+ gnus-Subject-save-in-file (in article format).
+
+ gnus-rmail-save-name
+ gnus-mail-save-name
+ gnus-folder-save-name
+ gnus-file-save-name
+ Specifies a function generating a file name to save articles in
+ specified format. The function is called with NEWSGROUP, HEADERS,
+ and optional LAST-FILE. Access macros to the headers are defined
+ as nntp-header-FIELD, and functions are defined as `gnus-header-FIELD'.
+
+ gnus-article-save-directory
+ Specifies a directory name to save articles to using the commands
+ `gnus-Subject-save-in-rmail', `gnus-Subject-save-in-mail' and
+ `gnus-Subject-save-in-file'. The variable is initialized from the
+ SAVEDIR environment variable.
+
+ gnus-show-all-headers
+ Non-nil means that all headers of an article are shown.
+
+ gnus-save-all-headers
+ Non-nil means that all headers of an article are saved in a file.
+
+ gnus-show-threads
+ Non-nil means that conversation threads are shown in tree structure.
+
+ gnus-thread-hide-subject
+ Non-nil means that subjects for thread subtrees are hidden.
+
+ gnus-thread-hide-subtree
+ Non-nil means that thread subtrees are hidden initially.
+
+ gnus-thread-hide-killed
+ Non-nil means that killed thread subtrees are hidden automatically.
+
+ gnus-thread-ignore-subject
+ Non-nil means that subject differences are ignored in constructing
+ thread trees.
+
+ gnus-thread-indent-level
+ Indentation of thread subtrees.
+
+ gnus-optional-headers
+ Specifies a function which generates an optional string displayed
+ in the Subject buffer. The function is called with an article
+ HEADERS. The result must be a string excluding `[' and `]'. The
+ default function returns a string like NNN:AUTHOR, where NNN is
+ the number of lines in an article and AUTHOR is the name of the
+ author.
+
+ gnus-auto-extend-newsgroup
+ Non-nil means visible articles are extended to forward and
+ backward automatically if possible.
+
+ gnus-auto-select-first
+ Non-nil means the first unread article is selected automagically
+ when a newsgroup is selected normally (by gnus-Group-read-group).
+ If you'd like to prevent automatic selection of the first unread
+ article in some newsgroups, set the variable to nil in
+ gnus-Select-group-hook or gnus-Apply-kill-hook.
+
+ gnus-auto-select-next
+ Non-nil means the next newsgroup is selected automagically at the
+ end of the newsgroup. If the value is t and the next newsgroup is
+ empty (no unread articles), GNUS will exit Subject mode and go
+ back to Group mode. If the value is neither nil nor t, GNUS won't
+ exit Subject mode but select the following unread newsgroup.
+ Especially, if the value is the symbol `quietly', the next unread
+ newsgroup will be selected without any confirmations.
+
+ gnus-auto-select-same
+ Non-nil means an article with the same subject as the current
+ article is selected automagically like `rn -S'.
+
+ gnus-auto-center-subject
+ Non-nil means the point of Subject Mode window is always kept
+ centered.
+
+ gnus-break-pages
+ Non-nil means an article is broken into pages at page delimiters.
+ This may not work with some versions of GNU Emacs earlier than
+ version 18.50.
+
+ gnus-page-delimiter
+ Specifies a regexp describing line-beginnings that separate pages
+ of news article.
+
+ [gnus-more-message is obsolete. overlay-arrow-string interfares
+ with other subsystems, such as dbx mode.]
+
+ gnus-digest-show-summary
+ Non-nil means that a summary of digest messages is shown when
+ reading a digest article using `gnus-Subject-rmail-digest' command.
+
+ gnus-digest-separator
+ Specifies a regexp separating messages in a digest article.
+
+ gnus-mail-reply-method
+ gnus-mail-other-window-method
+ Specifies a function to begin composing mail message using
+ commands gnus-Subject-mail-reply and
+ gnus-Subject-mail-other-window. Functions
+ gnus-mail-reply-using-mail and gnus-mail-reply-using-mhe are
+ available for the value of gnus-mail-reply-method. And functions
+ gnus-mail-other-window-using-mail and
+ gnus-mail-other-window-using-mhe are available for the value of
+ gnus-mail-other-window-method.
+
+Various hooks for customization:
+ gnus-Subject-mode-hook
+ Entry to this mode calls the value with no arguments, if that
+ value is non-nil.
+
+ gnus-Select-group-hook
+ Called with no arguments when newsgroup is selected, if that value
+ is non-nil. It is possible to sort subjects in this hook. See the
+ documentation of this variable for more information.
+
+ gnus-Subject-prepare-hook
+ Called with no arguments after a subject list is created in the
+ Subject buffer, if that value is non-nil. If you'd like to modify
+ the buffer, you can use this hook.
+
+ gnus-Select-article-hook
+ Called with no arguments when an article is selected, if that
+ value is non-nil. See the documentation of this variable for
+ more information.
+
+ gnus-Select-digest-hook
+ Called with no arguments when reading digest messages using Rmail,
+ if that value is non-nil. This hook can be used to modify an
+ article so that Rmail can work with it. See the documentation of
+ the variable for more information.
+
+ gnus-Rmail-digest-hook
+ Called with no arguments when reading digest messages using Rmail,
+ if that value is non-nil. This hook is intended to customize Rmail
+ mode.
+
+ gnus-Apply-kill-hook
+ Called with no arguments when a newsgroup is selected and the
+ Subject buffer is prepared. This hook is intended to apply a KILL
+ file to the selected newsgroup. The format of KILL file is
+ completely different from that of version 3.8. You have to rewrite
+ them in the new format. See the documentation of Kill file mode
+ for more information.
+
+ gnus-Mark-article-hook
+ Called with no arguments when an article is selected at the first
+ time. The hook is intended to mark an article as read (or unread)
+ automatically when it is selected. See the documentation of the
+ variable for more information.
+
+ gnus-Exit-group-hook
+ Called with no arguments when exiting the current newsgroup, if
+ that value is non-nil. If your machine is so slow that exiting
+ from Subject mode takes very long time, inhibit marking articles
+ as read using cross-references by setting the variable
+ `gnus-newsgroup-headers' to nil in this hook."
+ (interactive)
+ (kill-all-local-variables)
+ ;; Gee. Why don't you upgrade?
+ (cond ((boundp 'mode-line-modified)
+ (setq mode-line-modified "--- "))
+ ((listp (default-value 'mode-line-format))
+ (setq mode-line-format
+ (cons "--- " (cdr (default-value 'mode-line-format))))))
+ (make-local-variable 'global-mode-string)
+ (setq global-mode-string nil)
+ (setq major-mode 'gnus-Subject-mode)
+ (setq mode-name "Subject")
+ ;;(setq mode-line-process '(" " gnus-newsgroup-name))
+ (make-local-variable 'minor-mode-alist)
+ (or (assq 'gnus-show-threads minor-mode-alist)
+ (setq minor-mode-alist
+ (cons (list 'gnus-show-threads " Thread") minor-mode-alist)))
+ (gnus-Subject-set-mode-line)
+ (use-local-map gnus-Subject-mode-map)
+ (buffer-flush-undo (current-buffer))
+ (setq buffer-read-only t) ;Disable modification
+ (setq truncate-lines t) ;Stop line folding
+ (setq selective-display t)
+ (setq selective-display-ellipses t) ;Display `...'
+ ;;(setq case-fold-search t)
+ (run-hooks 'gnus-Subject-mode-hook))
+
+(defun gnus-Subject-setup-buffer ()
+ "Initialize subject display buffer."
+ (if (get-buffer gnus-Subject-buffer)
+ (set-buffer gnus-Subject-buffer)
+ (set-buffer (get-buffer-create gnus-Subject-buffer))
+ (gnus-Subject-mode)
+ ))
+
+(defun gnus-Subject-read-group (group &optional show-all no-article)
+ "Start reading news in newsgroup GROUP.
+If optional first argument SHOW-ALL is non-nil, already read articles are
+also listed.
+If optional second argument NO-ARTICLE is non-nil, no article is selected
+initially."
+ (message "Retrieving newsgroup: %s..." group)
+ (if (gnus-select-newsgroup group show-all)
+ (progn
+ ;; Don't switch-to-buffer to prevent displaying old contents
+ ;; of the buffer until new subjects list is created.
+ ;; Suggested by Juha Heinanen <jh@tut.fi>
+ (gnus-Subject-setup-buffer)
+ ;; You can change the order of subjects in this hook.
+ (run-hooks 'gnus-Select-group-hook)
+ (gnus-Subject-prepare)
+ ;; Function `gnus-apply-kill-file' must be called in this hook.
+ (run-hooks 'gnus-Apply-kill-hook)
+ (if (zerop (buffer-size))
+ ;; This newsgroup is empty.
+ (progn
+ (gnus-Subject-catch-up-and-exit nil t) ;Without confirmations.
+ (message "No unread news"))
+ ;; Hide conversation thread subtrees. We cannot do this in
+ ;; gnus-Subject-prepare-hook since kill processing may not
+ ;; work with hidden articles.
+ (and gnus-show-threads
+ gnus-thread-hide-subtree
+ (gnus-Subject-hide-all-threads))
+ ;; Show first unread article if requested.
+ (goto-char (point-min))
+ (if (and (not no-article)
+ gnus-auto-select-first
+ (gnus-Subject-first-unread-article))
+ ;; Window is configured automatically.
+ ;; Current buffer may be changed as a result of hook
+ ;; evaluation, especially by gnus-Subject-rmail-digest
+ ;; command, so we should adjust cursor point carefully.
+ (if (eq (current-buffer) (get-buffer gnus-Subject-buffer))
+ (progn
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)))
+ (gnus-configure-windows 'SelectNewsgroup)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-Subject-set-mode-line)
+ ;; I sometime get confused with the old Article buffer.
+ (if (get-buffer gnus-Article-buffer)
+ (if (get-buffer-window gnus-Article-buffer)
+ (save-excursion
+ (set-buffer gnus-Article-buffer)
+ (let ((buffer-read-only nil))
+ (erase-buffer)))
+ (kill-buffer gnus-Article-buffer)))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t))
+ ))
+ ;; Cannot select newsgroup GROUP.
+ (if (gnus-gethash group gnus-active-hashtb)
+ (progn
+ ;; If NNTP is used, nntp_access file may not be installed
+ ;; properly. Otherwise, may be active file problem.
+ (ding)
+ (message "Cannot select %s. May be security or active file problem." group)
+ (sit-for 0))
+ ;; Check bogus newsgroups.
+ ;; We must be in Group Mode buffer.
+ (gnus-Group-check-bogus-groups))
+ ))
+
+(defun gnus-Subject-prepare ()
+ "Prepare subject list of current newsgroup in Subject mode buffer."
+ (let ((buffer-read-only nil))
+ ;; Note: The next codes are not actually used because the user who
+ ;; want it can define them in gnus-Select-group-hook.
+ ;; Print verbose messages if too many articles are selected.
+ ;; (and (numberp gnus-large-newsgroup)
+ ;; (> (length gnus-newsgroup-headers) gnus-large-newsgroup)
+ ;; (message "Preparing headers..."))
+ (erase-buffer)
+ (gnus-Subject-prepare-threads
+ (if gnus-show-threads
+ (gnus-make-threads gnus-newsgroup-headers)
+ gnus-newsgroup-headers) 0)
+ ;; Erase header retrieval message.
+ (message "")
+ ;; Call hooks for modifying Subject mode buffer.
+ ;; Suggested by sven@tde.LTH.Se (Sven Mattisson).
+ (goto-char (point-min))
+ (run-hooks 'gnus-Subject-prepare-hook)
+ ))
+
+;; Basic ideas by Paul Dworkin <paul@media-lab.media.mit.edu>
+
+(defun gnus-Subject-prepare-threads (threads level)
+ "Prepare Subject buffer from THREADS and indentation LEVEL.
+THREADS is a list of `(PARENT [(CHILD1 [(GRANDCHILD ...]...) ...]).'"
+ (let ((thread nil)
+ (header nil)
+ (number nil)
+ ;; `M Indent NUM: [OPT] SUBJECT'
+ (cntl (format "%%s %%s%%%dd: [%%s] %%s\n"
+ (length (prin1-to-string gnus-newsgroup-end)))))
+ (while threads
+ (setq thread (car threads))
+ (setq threads (cdr threads))
+ ;; If thread is a cons, hierarchical threads is given.
+ ;; Otherwise, thread itself is header.
+ (if (consp thread)
+ (setq header (car thread))
+ (setq header thread))
+ ;; Print valid header only.
+ (if (vectorp header) ;Depends on nntp.el.
+ (progn
+ (setq number (nntp-header-number header))
+ (insert
+ (format cntl
+ ;; Read or not.
+ (cond ((memq number gnus-newsgroup-marked) "-")
+ ((memq number gnus-newsgroup-unreads) " ")
+ (t "D"))
+ ;; Thread level.
+ (make-string (* level gnus-thread-indent-level) ? )
+ ;; Article number.
+ number
+ ;; Optional headers.
+ (or (and gnus-optional-headers
+ (funcall gnus-optional-headers header)) "")
+ ;; Its subject string.
+ (concat (if (or (zerop level)
+ (not gnus-thread-hide-subject))
+ nil
+ (make-string (window-width) ? ))
+ (nntp-header-subject header))
+ ))
+ ))
+ ;; Print subthreads.
+ (and (consp thread)
+ (cdr thread)
+ (gnus-Subject-prepare-threads (cdr thread) (1+ level)))
+ )))
+
+(defun gnus-Subject-set-mode-line ()
+ "Set Subject mode line string."
+ ;; The value must be a string to escape %-constructs.
+ (let ((subject
+ (if gnus-current-headers
+ (nntp-header-subject gnus-current-headers) gnus-newsgroup-name)))
+ (setq mode-line-buffer-identification
+ (concat "GNUS: "
+ subject
+ ;; Enough spaces to pad subject to 17 positions.
+ (make-string (max 0 (- 17 (length subject))) ? ))))
+ (set-buffer-modified-p t))
+
+;; GNUS Subject mode command.
+
+(defun gnus-Subject-search-group (&optional backward)
+ "Search for next unread newsgroup.
+If optional argument BACKWARD is non-nil, search backward instead."
+ (save-excursion
+ (set-buffer gnus-Group-buffer)
+ (save-excursion
+ ;; We don't want to alter current point of Group mode buffer.
+ (if (gnus-Group-search-forward backward nil)
+ (gnus-Group-group-name))
+ )))
+
+(defun gnus-Subject-search-subject (backward unread subject)
+ "Search for article forward.
+If first argument BACKWARD is non-nil, search backward.
+If second argument UNREAD is non-nil, only unread article is selected.
+If third argument SUBJECT is non-nil, the article which has
+the same subject will be searched for."
+ (let ((func (if backward 're-search-backward 're-search-forward))
+ (article nil)
+ ;; We have to take care of hidden lines.
+ (regexp
+ (format "^%s[ \t]+\\([0-9]+\\):.\\[[^]\r\n]*\\][ \t]+%s"
+ ;;(if unread " " ".")
+ (cond ((eq unread t) " ") (unread "[ ---]") (t "."))
+ (if subject
+ (concat "\\([Rr][Ee]:[ \t]+\\)*"
+ (regexp-quote (gnus-simplify-subject subject))
+ ;; Ignore words in parentheses.
+ "\\([ \t]*([^\r\n]*)\\)*[ \t]*\\(\r\\|$\\)")
+ "")
+ )))
+ (if backward
+ (beginning-of-line)
+ (end-of-line))
+ (if (funcall func regexp nil t)
+ (setq article
+ (string-to-int
+ (buffer-substring (match-beginning 1) (match-end 1)))))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ ;; This is the result.
+ article
+ ))
+
+(defun gnus-Subject-search-forward (&optional unread subject)
+ "Search for article forward.
+If first optional argument UNREAD is non-nil, only unread article is selected.
+If second optional argument SUBJECT is non-nil, the article which has
+the same subject will be searched for."
+ (gnus-Subject-search-subject nil unread subject))
+
+(defun gnus-Subject-search-backward (&optional unread subject)
+ "Search for article backward.
+If first optional argument UNREAD is non-nil, only unread article is selected.
+If second optional argument SUBJECT is non-nil, the article which has
+the same subject will be searched for."
+ (gnus-Subject-search-subject t unread subject))
+
+(defun gnus-Subject-article-number ()
+ "Article number around point. If nothing, return current number."
+ (save-excursion
+ (beginning-of-line)
+ (if (looking-at ".[ \t]+\\([0-9]+\\):")
+ (string-to-int
+ (buffer-substring (match-beginning 1) (match-end 1)))
+ ;; If search fail, return current article number.
+ gnus-current-article
+ )))
+
+(defun gnus-Subject-subject-string ()
+ "Return current subject string or nil if nothing."
+ (save-excursion
+ ;; It is possible to implement this function using
+ ;; `gnus-Subject-article-number' and `gnus-newsgroup-headers'.
+ (beginning-of-line)
+ ;; We have to take care of hidden lines.
+ (if (looking-at ".[ \t]+[0-9]+:.\\[[^]\r\n]*\\][ \t]+\\([^\r\n]*\\)[\r\n]")
+ (buffer-substring (match-beginning 1) (match-end 1)))
+ ))
+
+(defun gnus-Subject-goto-subject (article)
+ "Move point to ARTICLE's subject."
+ (interactive
+ (list
+ (string-to-int
+ (completing-read "Article number: "
+ (mapcar
+ (function
+ (lambda (headers)
+ (list
+ (int-to-string (nntp-header-number headers)))))
+ gnus-newsgroup-headers)
+ nil 'require-match))))
+ (let ((current (point)))
+ (goto-char (point-min))
+ (or (and article (re-search-forward (format "^.[ \t]+%d:" article) nil t))
+ (progn (goto-char current) nil))
+ ))
+
+(defun gnus-Subject-recenter ()
+ "Center point in Subject mode window."
+ ;; Scroll window so as to cursor comes center of Subject mode window
+ ;; only when article is displayed.
+ ;; Suggested by earle@mahendo.JPL.NASA.GOV (Greg Earle).
+ ;; Recenter only when requested.
+ ;; Suggested by popovich@park.cs.columbia.edu
+ (and gnus-auto-center-subject
+ (get-buffer-window gnus-Article-buffer)
+ (< (/ (- (window-height) 1) 2)
+ (count-lines (point) (point-max)))
+ (recenter (/ (- (window-height) 2) 2))))
+
+;; Walking around Group mode buffer.
+
+(defun gnus-Subject-jump-to-group (newsgroup)
+ "Move point to NEWSGROUP in Group mode buffer."
+ ;; Keep update point of Group mode buffer if visible.
+ (if (eq (current-buffer)
+ (get-buffer gnus-Group-buffer))
+ (save-window-excursion
+ ;; Take care of tree window mode.
+ (if (get-buffer-window gnus-Group-buffer)
+ (pop-to-buffer gnus-Group-buffer))
+ (gnus-Group-jump-to-group newsgroup))
+ (save-excursion
+ ;; Take care of tree window mode.
+ (if (get-buffer-window gnus-Group-buffer)
+ (pop-to-buffer gnus-Group-buffer)
+ (set-buffer gnus-Group-buffer))
+ (gnus-Group-jump-to-group newsgroup))))
+
+(defun gnus-Subject-next-group (no-article)
+ "Exit current newsgroup and then select next unread newsgroup.
+If prefix argument NO-ARTICLE is non-nil, no article is selected initially."
+ (interactive "P")
+ ;; Make sure Group mode buffer point is on current newsgroup.
+ (gnus-Subject-jump-to-group gnus-newsgroup-name)
+ (let ((group (gnus-Subject-search-group)))
+ (if (null group)
+ (progn
+ (message "Exiting %s..." gnus-newsgroup-name)
+ (gnus-Subject-exit)
+ (message ""))
+ (message "Selecting %s..." group)
+ (gnus-Subject-exit t) ;Exit Subject mode temporary.
+ ;; We are now in Group mode buffer.
+ ;; Make sure Group mode buffer point is on GROUP.
+ (gnus-Subject-jump-to-group group)
+ (gnus-Subject-read-group group nil no-article)
+ (or (eq (current-buffer)
+ (get-buffer gnus-Subject-buffer))
+ (eq gnus-auto-select-next t)
+ ;; Expected newsgroup has nothing to read since the articles
+ ;; are marked as read by cross-referencing. So, try next
+ ;; newsgroup. (Make sure we are in Group mode buffer now.)
+ (and (eq (current-buffer)
+ (get-buffer gnus-Group-buffer))
+ (gnus-Group-group-name)
+ (gnus-Subject-read-group
+ (gnus-Group-group-name) nil no-article))
+ )
+ )))
+
+(defun gnus-Subject-prev-group (no-article)
+ "Exit current newsgroup and then select previous unread newsgroup.
+If prefix argument NO-ARTICLE is non-nil, no article is selected initially."
+ (interactive "P")
+ ;; Make sure Group mode buffer point is on current newsgroup.
+ (gnus-Subject-jump-to-group gnus-newsgroup-name)
+ (let ((group (gnus-Subject-search-group t)))
+ (if (null group)
+ (progn
+ (message "Exiting %s..." gnus-newsgroup-name)
+ (gnus-Subject-exit)
+ (message ""))
+ (message "Selecting %s..." group)
+ (gnus-Subject-exit t) ;Exit Subject mode temporary.
+ ;; We are now in Group mode buffer.
+ ;; We have to adjust point of Group mode buffer because current
+ ;; point is moved to next unread newsgroup by exiting.
+ (gnus-Subject-jump-to-group group)
+ (gnus-Subject-read-group group nil no-article)
+ (or (eq (current-buffer)
+ (get-buffer gnus-Subject-buffer))
+ (eq gnus-auto-select-next t)
+ ;; Expected newsgroup has nothing to read since the articles
+ ;; are marked as read by cross-referencing. So, try next
+ ;; newsgroup. (Make sure we are in Group mode buffer now.)
+ (and (eq (current-buffer)
+ (get-buffer gnus-Group-buffer))
+ (gnus-Subject-search-group t)
+ (gnus-Subject-read-group
+ (gnus-Subject-search-group t) nil no-article))
+ )
+ )))
+
+;; Walking around subject lines.
+
+(defun gnus-Subject-next-subject (n &optional unread)
+ "Go to next N'th subject line.
+If optional argument UNREAD is non-nil, only unread article is selected."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Subject-search-forward unread))
+ (setq n (1- n)))
+ (cond ((gnus-Subject-search-forward unread)
+ (gnus-Subject-recenter))
+ (unread
+ (message "No more unread articles"))
+ (t
+ (message "No more articles"))
+ ))
+
+(defun gnus-Subject-next-unread-subject (n)
+ "Go to next N'th unread subject line."
+ (interactive "p")
+ (gnus-Subject-next-subject n t))
+
+(defun gnus-Subject-prev-subject (n &optional unread)
+ "Go to previous N'th subject line.
+If optional argument UNREAD is non-nil, only unread article is selected."
+ (interactive "p")
+ (while (and (> n 1)
+ (gnus-Subject-search-backward unread))
+ (setq n (1- n)))
+ (cond ((gnus-Subject-search-backward unread)
+ (gnus-Subject-recenter))
+ (unread
+ (message "No more unread articles"))
+ (t
+ (message "No more articles"))
+ ))
+
+(defun gnus-Subject-prev-unread-subject (n)
+ "Go to previous N'th unread subject line."
+ (interactive "p")
+ (gnus-Subject-prev-subject n t))
+
+;; Walking around subject lines with displaying articles.
+
+(defun gnus-Subject-expand-window ()
+ "Expand Subject window to show headers full window."
+ (interactive)
+ (gnus-configure-windows 'ExpandSubject)
+ (pop-to-buffer gnus-Subject-buffer))
+
+(defun gnus-Subject-display-article (article &optional all-header)
+ "Display ARTICLE in Article buffer."
+ (if (null article)
+ nil
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-Article-prepare article all-header)
+ (gnus-Subject-recenter)
+ (gnus-Subject-set-mode-line)
+ (run-hooks 'gnus-Select-article-hook)
+ ;; Successfully display article.
+ t
+ ))
+
+(defun gnus-Subject-select-article (&optional all-headers force)
+ "Select the current article.
+Optional argument ALL-HEADERS is non-nil, show all headers."
+ (let ((article (gnus-Subject-article-number)))
+ (if (or (null gnus-current-article)
+ (/= article gnus-current-article)
+ (and force (not (eq all-headers gnus-have-all-headers))))
+ ;; The selected subject is different from that of the current article.
+ (gnus-Subject-display-article article all-headers)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer))
+ ))
+
+(defun gnus-Subject-set-current-mark (&optional current-mark)
+ "Put `+' at the current article.
+Optional argument specifies CURRENT-MARK instead of `+'."
+ (save-excursion
+ (set-buffer gnus-Subject-buffer)
+ (let ((buffer-read-only nil))
+ (goto-char (point-min))
+ ;; First of all clear mark at last article.
+ (if (re-search-forward "^.[ \t]+[0-9]+:[^ \t]" nil t)
+ (progn
+ (delete-char -1)
+ (insert " ")
+ (goto-char (point-min))))
+ (if (re-search-forward (format "^.[ \t]+%d:" gnus-current-article) nil t)
+ (progn
+ (delete-char 1)
+ (insert (or current-mark "+"))))
+ )))
+
+(defun gnus-Subject-next-article (unread &optional subject)
+ "Select article after current one.
+If argument UNREAD is non-nil, only unread article is selected."
+ (interactive "P")
+ (let ((header nil))
+ (cond ((gnus-Subject-display-article
+ (gnus-Subject-search-forward unread subject)))
+ ((and subject
+ gnus-auto-select-same
+ (gnus-set-difference gnus-newsgroup-unreads
+ gnus-newsgroup-marked)
+ (memq this-command
+ '(gnus-Subject-next-unread-article
+ gnus-Subject-next-page
+ gnus-Subject-kill-same-subject-and-select
+ ;;gnus-Subject-next-article
+ ;;gnus-Subject-next-same-subject
+ ;;gnus-Subject-next-unread-same-subject
+ )))
+ ;; Wrap article pointer if there are unread articles.
+ ;; Hook function, such as gnus-Subject-rmail-digest, may
+ ;; change current buffer, so need check.
+ (let ((buffer (current-buffer))
+ (last-point (point)))
+ ;; No more articles with same subject, so jump to the first
+ ;; unread article.
+ (gnus-Subject-first-unread-article)
+ ;;(and (eq buffer (current-buffer))
+ ;; (= (point) last-point)
+ ;; ;; Ignore given SUBJECT, and try again.
+ ;; (gnus-Subject-next-article unread nil))
+ (and (eq buffer (current-buffer))
+ (< (point) last-point)
+ (message "Wrapped"))
+ ))
+ ((and (not unread)
+ gnus-auto-extend-newsgroup
+ (setq header (gnus-more-header-forward)))
+ ;; Extend to next article if possible.
+ ;; Basic ideas by himacdonald@watdragon.waterloo.edu
+ (gnus-extend-newsgroup header nil)
+ ;; Threads feature must be turned off.
+ (let ((buffer-read-only nil))
+ (goto-char (point-max))
+ (gnus-Subject-prepare-threads (list header) 0))
+ (gnus-Subject-goto-article gnus-newsgroup-end))
+ (t
+ ;; Select next newsgroup automatically if requested.
+ (let ((cmd (string-to-char (this-command-keys)))
+ (group (gnus-Subject-search-group))
+ (auto-select
+ (and gnus-auto-select-next
+ ;;(null (gnus-set-difference gnus-newsgroup-unreads
+ ;; gnus-newsgroup-marked))
+ (memq this-command
+ '(gnus-Subject-next-unread-article
+ gnus-Subject-next-article
+ gnus-Subject-next-page
+ gnus-Subject-next-same-subject
+ gnus-Subject-next-unread-same-subject
+ gnus-Subject-kill-same-subject
+ gnus-Subject-kill-same-subject-and-select
+ ))
+ ;; Ignore characters typed ahead.
+ (not (input-pending-p))
+ )))
+ (message "No more%s articles%s"
+ (if unread " unread" "")
+ (if (and auto-select
+ (not (eq gnus-auto-select-next 'quietly)))
+ (if group
+ (format " (Type %s to %s [%d])"
+ (key-description (char-to-string cmd))
+ group
+ (nth 1 (gnus-gethash group
+ gnus-unread-hashtb)))
+ (format " (Type %s to exit %s)"
+ (key-description (char-to-string cmd))
+ gnus-newsgroup-name
+ ))
+ ""))
+ ;; Select next unread newsgroup automagically.
+ (cond ((and auto-select
+ (eq gnus-auto-select-next 'quietly))
+ ;; Select quietly.
+ (gnus-Subject-next-group nil))
+ (auto-select
+ ;; Confirm auto selection.
+ (let ((char (read-char)))
+ (if (= char cmd)
+ (gnus-Subject-next-group nil)
+ (setq unread-command-char char))))
+ )
+ ))
+ )))
+
+(defun gnus-Subject-next-unread-article ()
+ "Select unread article after current one."
+ (interactive)
+ (gnus-Subject-next-article t (and gnus-auto-select-same
+ (gnus-Subject-subject-string))))
+
+(defun gnus-Subject-prev-article (unread &optional subject)
+ "Select article before current one.
+If argument UNREAD is non-nil, only unread article is selected."
+ (interactive "P")
+ (let ((header nil))
+ (cond ((gnus-Subject-display-article
+ (gnus-Subject-search-backward unread subject)))
+ ((and subject
+ gnus-auto-select-same
+ (gnus-set-difference gnus-newsgroup-unreads
+ gnus-newsgroup-marked)
+ (memq this-command
+ '(gnus-Subject-prev-unread-article
+ ;;gnus-Subject-prev-page
+ ;;gnus-Subject-prev-article
+ ;;gnus-Subject-prev-same-subject
+ ;;gnus-Subject-prev-unread-same-subject
+ )))
+ ;; Ignore given SUBJECT, and try again.
+ (gnus-Subject-prev-article unread nil))
+ (unread
+ (message "No more unread articles"))
+ ((and gnus-auto-extend-newsgroup
+ (setq header (gnus-more-header-backward)))
+ ;; Extend to previous article if possible.
+ ;; Basic ideas by himacdonald@watdragon.waterloo.edu
+ (gnus-extend-newsgroup header t)
+ (let ((buffer-read-only nil))
+ (goto-char (point-min))
+ (gnus-Subject-prepare-threads (list header) 0))
+ (gnus-Subject-goto-article gnus-newsgroup-begin))
+ (t
+ (message "No more articles"))
+ )))
+
+(defun gnus-Subject-prev-unread-article ()
+ "Select unred article before current one."
+ (interactive)
+ (gnus-Subject-prev-article t (and gnus-auto-select-same
+ (gnus-Subject-subject-string))))
+
+(defun gnus-Subject-next-page (lines)
+ "Show next page of selected article.
+If end of artile, select next article.
+Argument LINES specifies lines to be scrolled up."
+ (interactive "P")
+ (let ((article (gnus-Subject-article-number))
+ (endp nil))
+ (if (or (null gnus-current-article)
+ (/= article gnus-current-article))
+ ;; Selected subject is different from current article's.
+ (gnus-Subject-display-article article)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (setq endp (gnus-Article-next-page lines)))
+ (cond ((and endp lines)
+ (message "End of message"))
+ ((and endp (null lines))
+ (gnus-Subject-next-unread-article)))
+ )))
+
+(defun gnus-Subject-prev-page (lines)
+ "Show previous page of selected article.
+Argument LINES specifies lines to be scrolled down."
+ (interactive "P")
+ (let ((article (gnus-Subject-article-number)))
+ (if (or (null gnus-current-article)
+ (/= article gnus-current-article))
+ ;; Selected subject is different from current article's.
+ (gnus-Subject-display-article article)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (gnus-Article-prev-page lines))
+ )))
+
+(defun gnus-Subject-scroll-up (lines)
+ "Scroll up (or down) one line current article.
+Argument LINES specifies lines to be scrolled up (or down if negative)."
+ (interactive "p")
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (cond ((> lines 0)
+ (if (gnus-Article-next-page lines)
+ (message "End of message")))
+ ((< lines 0)
+ (gnus-Article-prev-page (- 0 lines))))
+ ))
+
+(defun gnus-Subject-next-same-subject ()
+ "Select next article which has the same subject as current one."
+ (interactive)
+ (gnus-Subject-next-article nil (gnus-Subject-subject-string)))
+
+(defun gnus-Subject-prev-same-subject ()
+ "Select previous article which has the same subject as current one."
+ (interactive)
+ (gnus-Subject-prev-article nil (gnus-Subject-subject-string)))
+
+(defun gnus-Subject-next-unread-same-subject ()
+ "Select next unread article which has the same subject as current one."
+ (interactive)
+ (gnus-Subject-next-article t (gnus-Subject-subject-string)))
+
+(defun gnus-Subject-prev-unread-same-subject ()
+ "Select previous unread article which has the same subject as current one."
+ (interactive)
+ (gnus-Subject-prev-article t (gnus-Subject-subject-string)))
+
+(defun gnus-Subject-refer-parent-article (child)
+ "Refer parent article of current article.
+If a prefix argument CHILD is non-nil, go back to the child article
+using internally maintained articles history.
+NOTE: This command may not work with nnspool.el."
+ (interactive "P")
+ (gnus-Subject-select-article t t) ;Request all headers.
+ (let ((referenced-id nil)) ;Message-id of parent or child article.
+ (if child
+ ;; Go back to child article using history.
+ (gnus-Subject-refer-article nil)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ ;; Look for parent Message-ID.
+ ;; We cannot use gnus-current-headers to get references
+ ;; because we may be looking at parent or refered article.
+ (let ((references (gnus-fetch-field "References")))
+ ;; Get the last message-id in the references.
+ (and references
+ (string-match "\\(<[^<>]+>\\)[^>]*\\'" references)
+ (setq referenced-id
+ (substring references
+ (match-beginning 1) (match-end 1))))
+ ))
+ (if (stringp referenced-id)
+ (gnus-Subject-refer-article referenced-id)
+ (error "No more parents"))
+ )))
+
+(defun gnus-Subject-refer-article (message-id)
+ "Refer article specified by MESSAGE-ID.
+If MESSAGE-ID is nil or an empty string, it is popped from an
+internally maintained articles history.
+NOTE: This command may not work with nnspool.el."
+ (interactive "sMessage-ID: ")
+ ;; Make sure that this command depends on the fact that article
+ ;; related information is not updated when an article is retrieved
+ ;; by Message-ID.
+ (gnus-Subject-select-article t t) ;Request all headers.
+ (if (and (stringp message-id)
+ (> (length message-id) 0))
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ ;; Construct the correct Message-ID if necessary.
+ ;; Suggested by tale@pawl.rpi.edu.
+ (or (string-match "^<" message-id)
+ (setq message-id (concat "<" message-id)))
+ (or (string-match ">$" message-id)
+ (setq message-id (concat message-id ">")))
+ ;; Push current message-id on history.
+ ;; We cannot use gnus-current-headers to get current
+ ;; message-id because we may be looking at parent or refered
+ ;; article.
+ (let ((current (gnus-fetch-field "Message-ID")))
+ (or (equal current message-id) ;Nothing to do.
+ (equal current (car gnus-current-history))
+ (setq gnus-current-history
+ (cons current gnus-current-history)))
+ ))
+ ;; Pop message-id from history.
+ (setq message-id (car gnus-current-history))
+ (setq gnus-current-history (cdr gnus-current-history)))
+ (if (stringp message-id)
+ ;; Retrieve article by message-id. This may not work with nnspool.
+ (gnus-Article-prepare message-id t)
+ (error "No such references"))
+ )
+
+(defun gnus-Subject-next-digest (nth)
+ "Move to head of NTH next digested message."
+ (interactive "p")
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (gnus-Article-next-digest (or nth 1))
+ ))
+
+(defun gnus-Subject-prev-digest (nth)
+ "Move to head of NTH previous digested message."
+ (interactive "p")
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (gnus-Article-prev-digest (or nth 1))
+ ))
+
+(defun gnus-Subject-first-unread-article ()
+ "Select first unread article. Return non-nil if successfully selected."
+ (interactive)
+ (let ((begin (point)))
+ (goto-char (point-min))
+ (if (re-search-forward "^ [ \t]+[0-9]+:" nil t)
+ (gnus-Subject-display-article (gnus-Subject-article-number))
+ ;; If there is no unread articles, stay there.
+ (goto-char begin)
+ ;;(gnus-Subject-display-article (gnus-Subject-article-number))
+ (message "No more unread articles")
+ nil
+ )
+ ))
+
+(defun gnus-Subject-isearch-article ()
+ "Do incremental search forward on current article."
+ (interactive)
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (call-interactively 'isearch-forward)
+ ))
+
+(defun gnus-Subject-search-article-forward (regexp)
+ "Search for an article containing REGEXP forward.
+`gnus-Select-article-hook' is not called during the search."
+ (interactive
+ (list (read-string
+ (concat "Search forward (regexp): "
+ (if gnus-last-search-regexp
+ (concat "(default " gnus-last-search-regexp ") "))))))
+ (if (string-equal regexp "")
+ (setq regexp (or gnus-last-search-regexp ""))
+ (setq gnus-last-search-regexp regexp))
+ (if (gnus-Subject-search-article regexp nil)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (recenter 0)
+ ;;(sit-for 1)
+ )
+ (error "Search failed: \"%s\"" regexp)
+ ))
+
+(defun gnus-Subject-search-article-backward (regexp)
+ "Search for an article containing REGEXP backward.
+`gnus-Select-article-hook' is not called during the search."
+ (interactive
+ (list (read-string
+ (concat "Search backward (regexp): "
+ (if gnus-last-search-regexp
+ (concat "(default " gnus-last-search-regexp ") "))))))
+ (if (string-equal regexp "")
+ (setq regexp (or gnus-last-search-regexp ""))
+ (setq gnus-last-search-regexp regexp))
+ (if (gnus-Subject-search-article regexp t)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (recenter 0)
+ ;;(sit-for 1)
+ )
+ (error "Search failed: \"%s\"" regexp)
+ ))
+
+(defun gnus-Subject-search-article (regexp &optional backward)
+ "Search for an article containing REGEXP.
+Optional argument BACKWARD means do search for backward.
+`gnus-Select-article-hook' is not called during the search."
+ (let ((gnus-Select-article-hook nil) ;Disable hook.
+ (gnus-Mark-article-hook nil) ;Inhibit marking as read.
+ (re-search
+ (if backward
+ (function re-search-backward) (function re-search-forward)))
+ (found nil)
+ (last nil))
+ ;; Hidden thread subtrees must be searched for ,too.
+ (gnus-Subject-show-all-threads)
+ ;; First of all, search current article.
+ ;; We don't want to read article again from NNTP server nor reset
+ ;; current point.
+ (gnus-Subject-select-article)
+ (message "Searching article: %d..." gnus-current-article)
+ (setq last gnus-current-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ ;; Begin search from current point.
+ (setq found (funcall re-search regexp nil t))))
+ ;; Then search next articles.
+ (while (and (not found)
+ (gnus-Subject-display-article
+ (gnus-Subject-search-subject backward nil nil)))
+ (message "Searching article: %d..." gnus-current-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ (goto-char (if backward (point-max) (point-min)))
+ (setq found (funcall re-search regexp nil t)))
+ ))
+ (message "")
+ ;; Adjust article pointer.
+ (or (eq last gnus-current-article)
+ (setq gnus-last-article last))
+ ;; Return T if found such article.
+ found
+ ))
+
+(defun gnus-Subject-execute-command (field regexp command &optional backward)
+ "If FIELD of article header matches REGEXP, execute COMMAND string.
+If FIELD is an empty string (or nil), entire article body is searched for.
+If optional (prefix) argument BACKWARD is non-nil, do backward instead."
+ (interactive
+ (list (let ((completion-ignore-case t))
+ (completing-read "Field name: "
+ '(("Number")("Subject")("From")
+ ("Lines")("Date")("Id")
+ ("Xref")("References"))
+ nil 'require-match))
+ (read-string "Regexp: ")
+ (read-key-sequence "Command: ")
+ current-prefix-arg))
+ ;; Hidden thread subtrees must be searched for ,too.
+ (gnus-Subject-show-all-threads)
+ ;; We don't want to change current point nor window configuration.
+ (save-excursion
+ (save-window-excursion
+ (message "Executing %s..." (key-description command))
+ ;; We'd like to execute COMMAND interactively so as to give arguments.
+ (gnus-execute field regexp
+ (` (lambda ()
+ (call-interactively '(, (key-binding command)))))
+ backward)
+ (message "Executing %s... done" (key-description command)))))
+
+(defun gnus-Subject-beginning-of-article ()
+ "Go to beginning of article body"
+ (interactive)
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (widen)
+ (beginning-of-buffer)
+ (if gnus-break-pages
+ (gnus-narrow-to-page))
+ ))
+
+(defun gnus-Subject-end-of-article ()
+ "Go to end of article body"
+ (interactive)
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (widen)
+ (end-of-buffer)
+ (if gnus-break-pages
+ (gnus-narrow-to-page))
+ ))
+
+(defun gnus-Subject-goto-article (article &optional all-headers)
+ "Read ARTICLE if exists.
+Optional argument ALL-HEADERS means all headers are shown."
+ (interactive
+ (list
+ (string-to-int
+ (completing-read "Article number: "
+ (mapcar
+ (function
+ (lambda (headers)
+ (list
+ (int-to-string (nntp-header-number headers)))))
+ gnus-newsgroup-headers)
+ nil 'require-match))))
+ (if (gnus-Subject-goto-subject article)
+ (gnus-Subject-display-article article all-headers)))
+
+(defun gnus-Subject-goto-last-article ()
+ "Go to last subject line."
+ (interactive)
+ (if gnus-last-article
+ (gnus-Subject-goto-article gnus-last-article)))
+
+(defun gnus-Subject-show-article ()
+ "Force to show current article."
+ (interactive)
+ ;; The following is a trick to force to read the current article again.
+ (setq gnus-have-all-headers (not gnus-have-all-headers))
+ (gnus-Subject-select-article (not gnus-have-all-headers) t))
+
+(defun gnus-Subject-toggle-header (arg)
+ "Show original header if pruned header currently shown, or vice versa.
+With arg, show original header iff arg is positive."
+ (interactive "P")
+ ;; Variable gnus-show-all-headers must be NIL to toggle really.
+ (let ((gnus-show-all-headers nil)
+ (all-headers
+ (if (null arg) (not gnus-have-all-headers)
+ (> (prefix-numeric-value arg) 0))))
+ (gnus-Subject-select-article all-headers t)))
+
+(defun gnus-Subject-show-all-headers ()
+ "Show original article header."
+ (interactive)
+ (gnus-Subject-select-article t t))
+
+(defun gnus-Subject-stop-page-breaking ()
+ "Stop page breaking by linefeed temporary (Widen article buffer)."
+ (interactive)
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (widen)))
+
+(defun gnus-Subject-kill-same-subject-and-select (unmark)
+ "Mark articles which has the same subject as read, and then select next.
+If argument UNMARK is positive, remove any kinds of marks.
+If argument UNMARK is negative, mark articles as unread instead."
+ (interactive "P")
+ (if unmark
+ (setq unmark (prefix-numeric-value unmark)))
+ (let ((count
+ (gnus-Subject-mark-same-subject
+ (gnus-Subject-subject-string) unmark)))
+ ;; Select next unread article. If auto-select-same mode, should
+ ;; select the first unread article.
+ (gnus-Subject-next-article t (and gnus-auto-select-same
+ (gnus-Subject-subject-string)))
+ (message "%d articles are marked as %s"
+ count (if unmark "unread" "read"))
+ ))
+
+(defun gnus-Subject-kill-same-subject (unmark)
+ "Mark articles which has the same subject as read.
+If argument UNMARK is positive, remove any kinds of marks.
+If argument UNMARK is negative, mark articles as unread instead."
+ (interactive "P")
+ (if unmark
+ (setq unmark (prefix-numeric-value unmark)))
+ (let ((count
+ (gnus-Subject-mark-same-subject
+ (gnus-Subject-subject-string) unmark)))
+ ;; If marked as read, go to next unread subject.
+ (if (null unmark)
+ ;; Go to next unread subject.
+ (gnus-Subject-next-subject 1 t))
+ (message "%d articles are marked as %s"
+ count (if unmark "unread" "read"))
+ ))
+
+(defun gnus-Subject-mark-same-subject (subject &optional unmark)
+ "Mark articles with same SUBJECT as read, and return marked number.
+If optional argument UNMARK is positive, remove any kinds of marks.
+If optional argument UNMARK is negative, mark articles as unread instead."
+ (let ((count 1))
+ (save-excursion
+ (cond ((null unmark)
+ (gnus-Subject-mark-as-read nil "K"))
+ ((> unmark 0)
+ (gnus-Subject-mark-as-unread nil t))
+ (t
+ (gnus-Subject-mark-as-unread)))
+ (while (and subject
+ (gnus-Subject-search-forward nil subject))
+ (cond ((null unmark)
+ (gnus-Subject-mark-as-read nil "K"))
+ ((> unmark 0)
+ (gnus-Subject-mark-as-unread nil t))
+ (t
+ (gnus-Subject-mark-as-unread)))
+ (setq count (1+ count))
+ ))
+ ;; Hide killed thread subtrees. Does not work properly always.
+ ;;(and (null unmark)
+ ;; gnus-thread-hide-killed
+ ;; (gnus-Subject-hide-thread))
+ ;; Return number of articles marked as read.
+ count
+ ))
+
+(defun gnus-Subject-mark-as-unread-forward (count)
+ "Mark current article as unread, and then go forward.
+Argument COUNT specifies number of articles marked as unread."
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-unread nil nil)
+ (gnus-Subject-next-subject 1 nil)
+ (setq count (1- count))))
+
+(defun gnus-Subject-mark-as-unread-backward (count)
+ "Mark current article as unread, and then go backward.
+Argument COUNT specifies number of articles marked as unread."
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-unread nil nil)
+ (gnus-Subject-prev-subject 1 nil)
+ (setq count (1- count))))
+
+(defun gnus-Subject-mark-as-unread (&optional article clear-mark)
+ "Mark current article as unread.
+Optional first argument ARTICLE specifies article number to be
+marked as unread. Optional second argument CLEAR-MARK removes
+any kind of mark."
+ (save-excursion
+ (set-buffer gnus-Subject-buffer)
+ ;; First of all, show hidden thread subtrees.
+ (gnus-Subject-show-thread)
+ (let* ((buffer-read-only nil)
+ (current (gnus-Subject-article-number))
+ (article (or article current)))
+ (gnus-mark-article-as-unread article clear-mark)
+ (if (or (eq article current)
+ (gnus-Subject-goto-subject article))
+ (progn
+ (beginning-of-line)
+ (delete-char 1)
+ (insert (if clear-mark " " "-"))))
+ )))
+
+(defun gnus-Subject-mark-as-read-forward (count)
+ "Mark current article as read, and then go forward.
+Argument COUNT specifies number of articles marked as read"
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-read)
+ (gnus-Subject-next-subject 1 'unread-only)
+ (setq count (1- count))))
+
+(defun gnus-Subject-mark-as-read-backward (count)
+ "Mark current article as read, and then go backward.
+Argument COUNT specifies number of articles marked as read"
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-read)
+ (gnus-Subject-prev-subject 1 'unread-only)
+ (setq count (1- count))))
+
+(defun gnus-Subject-mark-as-read (&optional article mark)
+ "Mark current article as read.
+Optional first argument ARTICLE specifies article number to be marked as read.
+Optional second argument MARK specifies a string inserted at beginning of line.
+Any kind of string (length 1) except for a space and `-' is ok."
+ (save-excursion
+ (set-buffer gnus-Subject-buffer)
+ ;; First of all, show hidden thread subtrees.
+ (gnus-Subject-show-thread)
+ (let* ((buffer-read-only nil)
+ (mark (or mark "D")) ;Default mark is `D'.
+ (current (gnus-Subject-article-number))
+ (article (or article current)))
+ (gnus-mark-article-as-read article)
+ (if (or (eq article current)
+ (gnus-Subject-goto-subject article))
+ (progn
+ (beginning-of-line)
+ (delete-char 1)
+ (insert mark)))
+ )))
+
+(defun gnus-Subject-clear-mark-forward (count)
+ "Remove current article's mark, and go forward.
+Argument COUNT specifies number of articles unmarked"
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-unread nil t)
+ (gnus-Subject-next-subject 1 nil)
+ (setq count (1- count))))
+
+(defun gnus-Subject-clear-mark-backward (count)
+ "Remove current article's mark, and go backward.
+Argument COUNT specifies number of articles unmarked"
+ (interactive "p")
+ (while (> count 0)
+ (gnus-Subject-mark-as-unread nil t)
+ (gnus-Subject-prev-subject 1 nil)
+ (setq count (1- count))))
+
+(defun gnus-Subject-delete-marked-as-read ()
+ "Delete lines which are marked as read."
+ (interactive)
+ (if gnus-newsgroup-unreads
+ (let ((buffer-read-only nil))
+ (save-excursion
+ (goto-char (point-min))
+ (delete-non-matching-lines "^[ ---]"))
+ ;; Adjust point.
+ (if (eobp)
+ (gnus-Subject-prev-subject 1)
+ (beginning-of-line)
+ (search-forward ":" nil t)))
+ ;; It is not so good idea to make the buffer empty.
+ (message "All articles are marked as read")
+ ))
+
+(defun gnus-Subject-delete-marked-with (marks)
+ "Delete lines which are marked with MARKS (e.g. \"DK\")."
+ (interactive "sMarks: ")
+ (let ((buffer-read-only nil))
+ (save-excursion
+ (goto-char (point-min))
+ (delete-matching-lines (concat "^[" marks "]")))
+ ;; Adjust point.
+ (or (zerop (buffer-size))
+ (if (eobp)
+ (gnus-Subject-prev-subject 1)
+ (beginning-of-line)
+ (search-forward ":" nil t)))
+ ))
+
+;; Thread-based commands.
+
+(defun gnus-Subject-toggle-threads (arg)
+ "Toggle showing conversation threads.
+With arg, turn showing conversation threads on iff arg is positive."
+ (interactive "P")
+ (let ((current (gnus-Subject-article-number)))
+ (setq gnus-show-threads
+ (if (null arg) (not gnus-show-threads)
+ (> (prefix-numeric-value arg) 0)))
+ (gnus-Subject-prepare)
+ (gnus-Subject-goto-subject current)
+ ))
+
+(defun gnus-Subject-show-all-threads ()
+ "Show all thread subtrees."
+ (interactive)
+ (if gnus-show-threads
+ (save-excursion
+ (let ((buffer-read-only nil))
+ (subst-char-in-region (point-min) (point-max) ?\^M ?\n t)
+ ))))
+
+(defun gnus-Subject-show-thread ()
+ "Show thread subtrees."
+ (interactive)
+ (if gnus-show-threads
+ (save-excursion
+ (let ((buffer-read-only nil))
+ (subst-char-in-region (progn
+ (beginning-of-line) (point))
+ (progn
+ (end-of-line) (point))
+ ?\^M ?\n t)
+ ))))
+
+(defun gnus-Subject-hide-all-threads ()
+ "Hide all thread subtrees."
+ (interactive)
+ (if gnus-show-threads
+ (save-excursion
+ ;; Adjust cursor point.
+ (goto-char (point-min))
+ (search-forward ":" nil t)
+ (let ((level (current-column)))
+ (gnus-Subject-hide-thread)
+ (while (gnus-Subject-search-forward)
+ (and (>= level (current-column))
+ (gnus-Subject-hide-thread)))
+ ))))
+
+(defun gnus-Subject-hide-thread ()
+ "Hide thread subtrees."
+ (interactive)
+ (if gnus-show-threads
+ (save-excursion
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((buffer-read-only nil)
+ (init (point))
+ (last (point))
+ (level (current-column)))
+ (while (and (gnus-Subject-search-forward)
+ (< level (current-column)))
+ ;; Interested in lower levels.
+ (if (< level (current-column))
+ (progn
+ (setq last (point))
+ ))
+ )
+ (subst-char-in-region init last ?\n ?\^M t)
+ ))))
+
+(defun gnus-Subject-next-thread (n)
+ "Go to the same level next thread.
+Argument N specifies the number of threads."
+ (interactive "p")
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((init (point))
+ (last (point))
+ (level (current-column)))
+ (while (and (> n 0)
+ (gnus-Subject-search-forward)
+ (<= level (current-column)))
+ ;; We have to skip lower levels.
+ (if (= level (current-column))
+ (progn
+ (setq last (point))
+ (setq n (1- n))
+ ))
+ )
+ ;; Return non-nil if successfully move to the next.
+ (prog1 (not (= init last))
+ (goto-char last))
+ ))
+
+(defun gnus-Subject-prev-thread (n)
+ "Go to the same level previous thread.
+Argument N specifies the number of threads."
+ (interactive "p")
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((init (point))
+ (last (point))
+ (level (current-column)))
+ (while (and (> n 0)
+ (gnus-Subject-search-backward)
+ (<= level (current-column)))
+ ;; We have to skip lower levels.
+ (if (= level (current-column))
+ (progn
+ (setq last (point))
+ (setq n (1- n))
+ ))
+ )
+ ;; Return non-nil if successfully move to the previous.
+ (prog1 (not (= init last))
+ (goto-char last))
+ ))
+
+(defun gnus-Subject-down-thread (d)
+ "Go downward current thread.
+Argument D specifies the depth goes down."
+ (interactive "p")
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((last (point))
+ (level (current-column)))
+ (while (and (> d 0)
+ (gnus-Subject-search-forward)
+ (<= level (current-column))) ;<= can be <. Which do you like?
+ ;; We have to skip the same levels.
+ (if (< level (current-column))
+ (progn
+ (setq last (point))
+ (setq level (current-column))
+ (setq d (1- d))
+ ))
+ )
+ (goto-char last)
+ ))
+
+(defun gnus-Subject-up-thread (d)
+ "Go upward current thread.
+Argument D specifies the depth goes up."
+ (interactive "p")
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (let ((last (point))
+ (level (current-column)))
+ (while (and (> d 0)
+ (gnus-Subject-search-backward))
+ ;; We have to skip the same levels.
+ (if (> level (current-column))
+ (progn
+ (setq last (point))
+ (setq level (current-column))
+ (setq d (1- d))
+ ))
+ )
+ (goto-char last)
+ ))
+
+(defun gnus-Subject-kill-thread (unmark)
+ "Mark articles under current thread as read.
+If argument UNMARK is positive, remove any kinds of marks.
+If argument UNMARK is negative, mark articles as unread instead."
+ (interactive "P")
+ (if unmark
+ (setq unmark (prefix-numeric-value unmark)))
+ ;; Adjust cursor point.
+ (beginning-of-line)
+ (search-forward ":" nil t)
+ (save-excursion
+ (let ((level (current-column)))
+ ;; Mark current article.
+ (cond ((null unmark)
+ (gnus-Subject-mark-as-read nil "K"))
+ ((> unmark 0)
+ (gnus-Subject-mark-as-unread nil t))
+ (t
+ (gnus-Subject-mark-as-unread))
+ )
+ ;; Mark following articles.
+ (while (and (gnus-Subject-search-forward)
+ (< level (current-column)))
+ (cond ((null unmark)
+ (gnus-Subject-mark-as-read nil "K"))
+ ((> unmark 0)
+ (gnus-Subject-mark-as-unread nil t))
+ (t
+ (gnus-Subject-mark-as-unread))
+ ))
+ ))
+ ;; Hide killed subtrees.
+ (and (null unmark)
+ gnus-thread-hide-killed
+ (gnus-Subject-hide-thread))
+ ;; If marked as read, go to next unread subject.
+ (if (null unmark)
+ ;; Go to next unread subject.
+ (gnus-Subject-next-subject 1 t))
+ )
+
+(defun gnus-Subject-toggle-truncation (arg)
+ "Toggle truncation of subject lines.
+With ARG, turn line truncation on iff ARG is positive."
+ (interactive "P")
+ (setq truncate-lines
+ (if (null arg) (not truncate-lines)
+ (> (prefix-numeric-value arg) 0)))
+ (redraw-display))
+
+(defun gnus-Subject-sort-by-number (reverse)
+ "Sort subject display buffer by article number.
+Argument REVERSE means reverse order."
+ (interactive "P")
+ (gnus-Subject-sort-subjects
+ (function
+ (lambda (a b)
+ (< (nntp-header-number a) (nntp-header-number b))))
+ reverse
+ ))
+
+(defun gnus-Subject-sort-by-author (reverse)
+ "Sort subject display buffer by author name alphabetically.
+If case-fold-search is non-nil, case of letters is ignored.
+Argument REVERSE means reverse order."
+ (interactive "P")
+ (gnus-Subject-sort-subjects
+ (function
+ (lambda (a b)
+ (gnus-string-lessp (nntp-header-from a) (nntp-header-from b))))
+ reverse
+ ))
+
+(defun gnus-Subject-sort-by-subject (reverse)
+ "Sort subject display buffer by subject alphabetically. `Re:'s are ignored.
+If case-fold-search is non-nil, case of letters is ignored.
+Argument REVERSE means reverse order."
+ (interactive "P")
+ (gnus-Subject-sort-subjects
+ (function
+ (lambda (a b)
+ (gnus-string-lessp
+ (gnus-simplify-subject (nntp-header-subject a) 're-only)
+ (gnus-simplify-subject (nntp-header-subject b) 're-only))))
+ reverse
+ ))
+
+(defun gnus-Subject-sort-by-date (reverse)
+ "Sort subject display buffer by posted date.
+Argument REVERSE means reverse order."
+ (interactive "P")
+ (gnus-Subject-sort-subjects
+ (function
+ (lambda (a b)
+ (gnus-date-lessp (nntp-header-date a) (nntp-header-date b))))
+ reverse
+ ))
+
+(defun gnus-Subject-sort-subjects (predicate &optional reverse)
+ "Sort subject display buffer by PREDICATE.
+Optional argument REVERSE means reverse order."
+ (let ((current (gnus-Subject-article-number)))
+ (gnus-sort-headers predicate reverse)
+ (gnus-Subject-prepare)
+ (gnus-Subject-goto-subject current)
+ ))
+
+(defun gnus-Subject-reselect-current-group (show-all)
+ "Once exit and then reselect the current newsgroup.
+Prefix argument SHOW-ALL means to select all articles."
+ (interactive "P")
+ (let ((current-subject (gnus-Subject-article-number)))
+ (gnus-Subject-exit t)
+ ;; We have to adjust the point of Group mode buffer because the
+ ;; current point was moved to the next unread newsgroup by
+ ;; exiting.
+ (gnus-Subject-jump-to-group gnus-newsgroup-name)
+ (gnus-Group-read-group show-all t)
+ (gnus-Subject-goto-subject current-subject)
+ ))
+
+(defun gnus-Subject-caesar-message (rotnum)
+ "Caesar rotates all letters of current message by 13/47 places.
+With prefix arg, specifies the number of places to rotate each letter forward.
+Caesar rotates Japanese letters by 47 places in any case."
+ (interactive "P")
+ (gnus-Subject-select-article)
+ (gnus-overload-functions)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ ;; We don't want to jump to the beginning of the message.
+ ;; `save-excursion' does not do its job.
+ (move-to-window-line 0)
+ (let ((last (point)))
+ (news-caesar-buffer-body rotnum)
+ (goto-char last)
+ (recenter 0)
+ ))
+ ))
+
+(defun gnus-Subject-rmail-digest ()
+ "Run RMAIL on current digest article.
+`gnus-Select-digest-hook' will be called with no arguments, if that
+value is non-nil. It is possible to modify the article so that Rmail
+can work with it.
+
+`gnus-Rmail-digest-hook' will be called with no arguments, if that value
+is non-nil. The hook is intended to customize Rmail mode."
+ (interactive)
+ (gnus-Subject-select-article)
+ (require 'rmail)
+ (let ((artbuf gnus-Article-buffer)
+ (digbuf (get-buffer-create gnus-Digest-buffer))
+ (mail-header-separator ""))
+ (set-buffer digbuf)
+ (buffer-flush-undo (current-buffer))
+ (setq buffer-read-only nil)
+ (erase-buffer)
+ (insert-buffer-substring artbuf)
+ (run-hooks 'gnus-Select-digest-hook)
+ (gnus-convert-article-to-rmail)
+ (goto-char (point-min))
+ ;; Rmail initializations.
+ (rmail-insert-rmail-file-header)
+ (rmail-mode)
+ (rmail-set-message-counters)
+ (rmail-show-message)
+ (condition-case ()
+ (progn
+ (undigestify-rmail-message)
+ (rmail-expunge) ;Delete original message.
+ ;; File name is meaningless but `save-buffer' requires it.
+ (setq buffer-file-name "GNUS Digest")
+ (setq mode-line-buffer-identification
+ (concat "Digest: "
+ (nntp-header-subject gnus-current-headers)))
+ ;; There is no need to write this buffer to a file.
+ (make-local-variable 'write-file-hooks)
+ (setq write-file-hooks
+ (list (function
+ (lambda ()
+ (set-buffer-modified-p nil)
+ (message "(No changes need to be saved)")
+ 'no-need-to-write-this-buffer))))
+ ;; Default file name saving digest messages.
+ (setq rmail-last-rmail-file
+ (funcall gnus-rmail-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-rmail
+ ))
+ (setq rmail-last-file
+ (funcall gnus-mail-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-mail
+ ))
+ ;; Prevent generating new buffer named ***<N> each time.
+ (setq rmail-summary-buffer
+ (get-buffer-create gnus-Digest-summary-buffer))
+ (run-hooks 'gnus-Rmail-digest-hook)
+ ;; Take all windows safely.
+ (gnus-configure-windows '(1 0 0))
+ (pop-to-buffer gnus-Group-buffer)
+ ;; Use Subject and Article windows for Digest summary and
+ ;; Digest buffers.
+ (if gnus-digest-show-summary
+ (let ((gnus-Subject-buffer gnus-Digest-summary-buffer)
+ (gnus-Article-buffer gnus-Digest-buffer))
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Digest-buffer)
+ (rmail-summary)
+ (pop-to-buffer gnus-Digest-summary-buffer)
+ (message (substitute-command-keys
+ "Type \\[rmail-summary-quit] to return to GNUS")))
+ (let ((gnus-Subject-buffer gnus-Digest-buffer))
+ (gnus-configure-windows 'ExpandSubject)
+ (pop-to-buffer gnus-Digest-buffer)
+ (message (substitute-command-keys
+ "Type \\[rmail-quit] to return to GNUS")))
+ )
+ ;; Move the buffers to the end of buffer list.
+ (bury-buffer gnus-Article-buffer)
+ (bury-buffer gnus-Group-buffer)
+ (bury-buffer gnus-Digest-summary-buffer)
+ (bury-buffer gnus-Digest-buffer))
+ (error (set-buffer-modified-p nil)
+ (kill-buffer digbuf)
+ ;; This command should not signal an error because the
+ ;; command is called from hooks.
+ (ding) (message "Article is not a digest")))
+ ))
+
+(defun gnus-Subject-save-article ()
+ "Save this article using default saver function.
+Variable `gnus-default-article-saver' specifies the saver function."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (if gnus-default-article-saver
+ (call-interactively gnus-default-article-saver)
+ (error "No default saver is defined.")))
+
+(defun gnus-Subject-save-in-rmail (&optional filename)
+ "Append this article to Rmail file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory' which
+is initialized from the SAVEDIR environment variable."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let ((default-name
+ (funcall gnus-rmail-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-rmail
+ )))
+ (or filename
+ (setq filename
+ (read-file-name
+ (concat "Save article in Rmail file: (default "
+ (file-name-nondirectory default-name)
+ ") ")
+ (file-name-directory default-name)
+ default-name)))
+ (gnus-make-directory (file-name-directory filename))
+ (gnus-output-to-rmail filename)
+ ;; Remember the directory name to save articles.
+ (setq gnus-newsgroup-last-rmail filename)
+ )))
+ ))
+
+(defun gnus-Subject-save-in-mail (&optional filename)
+ "Append this article to Unix mail file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory' which
+is initialized from the SAVEDIR environment variable."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let ((default-name
+ (funcall gnus-mail-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-mail
+ )))
+ (or filename
+ (setq filename
+ (read-file-name
+ (concat "Save article in Unix mail file: (default "
+ (file-name-nondirectory default-name)
+ ") ")
+ (file-name-directory default-name)
+ default-name)))
+ (gnus-make-directory (file-name-directory filename))
+ (rmail-output filename)
+ ;; Remember the directory name to save articles.
+ (setq gnus-newsgroup-last-mail filename)
+ )))
+ ))
+
+(defun gnus-Subject-save-in-file (&optional filename)
+ "Append this article to file.
+Optional argument FILENAME specifies file name.
+Directory to save to is default to `gnus-article-save-directory' which
+is initialized from the SAVEDIR environment variable."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-excursion
+ (save-restriction
+ (widen)
+ (let ((default-name
+ (funcall gnus-file-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-file
+ )))
+ (or filename
+ (setq filename
+ (read-file-name
+ (concat "Save article in file: (default "
+ (file-name-nondirectory default-name)
+ ") ")
+ (file-name-directory default-name)
+ default-name)))
+ (gnus-make-directory (file-name-directory filename))
+ (gnus-output-to-file filename)
+ ;; Remember the directory name to save articles.
+ (setq gnus-newsgroup-last-file filename)
+ )))
+ ))
+
+(defun gnus-Subject-save-in-folder (&optional folder)
+ "Save this article to MH folder (using `rcvstore' in MH library).
+Optional argument FOLDER specifies folder name."
+ (interactive)
+ (gnus-Subject-select-article
+ (not (null gnus-save-all-headers)) gnus-save-all-headers)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ ;; Thanks to yuki@flab.Fujitsu.JUNET and ohm@kaba.junet.
+ (mh-find-path)
+ (let ((folder
+ (or folder
+ (mh-prompt-for-folder "Save article in"
+ (funcall gnus-folder-save-name
+ gnus-newsgroup-name
+ gnus-current-headers
+ gnus-newsgroup-last-folder
+ )
+ t
+ )))
+ (errbuf (get-buffer-create " *GNUS rcvstore*")))
+ (unwind-protect
+ (call-process-region (point-min) (point-max)
+ (expand-file-name "rcvstore" mh-lib)
+ nil errbuf nil folder)
+ (set-buffer errbuf)
+ (if (zerop (buffer-size))
+ (message "Article saved in folder: %s" folder)
+ (message "%s" (buffer-string)))
+ (kill-buffer errbuf)
+ (setq gnus-newsgroup-last-folder folder))
+ ))
+ ))
+
+(defun gnus-Subject-pipe-output ()
+ "Pipe this article to subprocess."
+ (interactive)
+ ;; Ignore `gnus-save-all-headers' since this is not save command.
+ (gnus-Subject-select-article)
+ (gnus-eval-in-buffer-window gnus-Article-buffer
+ (save-restriction
+ (widen)
+ (let ((command (read-string "Shell command on article: "
+ gnus-last-shell-command)))
+ (if (string-equal command "")
+ (setq command gnus-last-shell-command))
+ (shell-command-on-region (point-min) (point-max) command nil)
+ (setq gnus-last-shell-command command)
+ ))
+ ))
+
+(defun gnus-Subject-catch-up (all &optional quietly)
+ "Mark all articles not marked as unread in this newsgroup as read.
+If prefix argument ALL is non-nil, all articles are marked as read."
+ (interactive "P")
+ (if (or quietly
+ (y-or-n-p
+ (if all
+ "Do you really want to mark everything as read? "
+ "Delete all articles not marked as unread? ")))
+ (let ((unmarked
+ (gnus-set-difference gnus-newsgroup-unreads
+ (if (not all) gnus-newsgroup-marked))))
+ (message "") ;Erase "Yes or No" question.
+ (while unmarked
+ (gnus-Subject-mark-as-read (car unmarked) "C")
+ (setq unmarked (cdr unmarked))
+ ))
+ ))
+
+(defun gnus-Subject-catch-up-all (&optional quietly)
+ "Mark all articles in this newsgroup as read."
+ (interactive)
+ (gnus-Subject-catch-up t quietly))
+
+(defun gnus-Subject-catch-up-and-exit (all &optional quietly)
+ "Mark all articles not marked as unread in this newsgroup as read, then exit.
+If prefix argument ALL is non-nil, all articles are marked as read."
+ (interactive "P")
+ (if (or quietly
+ (y-or-n-p
+ (if all
+ "Do you really want to mark everything as read? "
+ "Delete all articles not marked as unread? ")))
+ (let ((unmarked
+ (gnus-set-difference gnus-newsgroup-unreads
+ (if (not all) gnus-newsgroup-marked))))
+ (message "") ;Erase "Yes or No" question.
+ (while unmarked
+ (gnus-mark-article-as-read (car unmarked))
+ (setq unmarked (cdr unmarked)))
+ ;; Select next newsgroup or exit.
+ (cond ((eq gnus-auto-select-next 'quietly)
+ ;; Select next newsgroup quietly.
+ (gnus-Subject-next-group nil))
+ (t
+ (gnus-Subject-exit)))
+ )))
+
+(defun gnus-Subject-catch-up-all-and-exit (&optional quietly)
+ "Mark all articles in this newsgroup as read, and then exit."
+ (interactive)
+ (gnus-Subject-catch-up-and-exit t quietly))
+
+(defun gnus-Subject-edit-global-kill ()
+ "Edit a global KILL file."
+ (interactive)
+ (setq gnus-current-kill-article (gnus-Subject-article-number))
+ (gnus-Kill-file-edit-file nil) ;Nil stands for global KILL file.
+ (message
+ (substitute-command-keys
+ "Editing a global KILL file (Type \\[gnus-Kill-file-exit] to exit)")))
+
+(defun gnus-Subject-edit-local-kill ()
+ "Edit a local KILL file applied to the current newsgroup."
+ (interactive)
+ (setq gnus-current-kill-article (gnus-Subject-article-number))
+ (gnus-Kill-file-edit-file gnus-newsgroup-name)
+ (message
+ (substitute-command-keys
+ "Editing a local KILL file (Type \\[gnus-Kill-file-exit] to exit)")))
+
+(defun gnus-Subject-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."
+ (interactive)
+ (let ((updated nil)
+ (gnus-newsgroup-headers gnus-newsgroup-headers)
+ (gnus-newsgroup-unreads gnus-newsgroup-unreads)
+ (gnus-newsgroup-unselected gnus-newsgroup-unselected)
+ (gnus-newsgroup-marked gnus-newsgroup-marked))
+ ;; Important internal variables are save, so we can reenter
+ ;; Subject Mode buffer even if hook changes them.
+ (run-hooks 'gnus-Exit-group-hook)
+ (gnus-update-unread-articles gnus-newsgroup-name
+ (append gnus-newsgroup-unselected
+ gnus-newsgroup-unreads)
+ gnus-newsgroup-marked)
+ ;; T means ignore unsubscribed newsgroups.
+ (if gnus-use-cross-reference
+ (setq updated
+ (gnus-mark-as-read-by-xref gnus-newsgroup-name
+ gnus-newsgroup-headers
+ gnus-newsgroup-unreads
+ (eq gnus-use-cross-reference t)
+ )))
+ ;; Do not switch windows but change the buffer to work.
+ (set-buffer gnus-Group-buffer)
+ ;; Update cross referenced group info.
+ (while updated
+ (gnus-Group-update-group (car updated) t) ;Ignore invisible group.
+ (setq updated (cdr updated)))
+ (gnus-Group-update-group gnus-newsgroup-name))
+ ;; Make sure where I was, and go to next newsgroup.
+ (gnus-Group-jump-to-group gnus-newsgroup-name)
+ (gnus-Group-next-unread-group 1)
+ (if temporary
+ ;; If exiting temporary, caller should adjust Group mode
+ ;; buffer point by itself.
+ nil ;Nothing to do.
+ ;; Return to Group mode buffer.
+ (if (get-buffer gnus-Subject-buffer)
+ (bury-buffer gnus-Subject-buffer))
+ (if (get-buffer gnus-Article-buffer)
+ (bury-buffer gnus-Article-buffer))
+ (gnus-configure-windows 'ExitNewsgroup)
+ (pop-to-buffer gnus-Group-buffer)))
+
+(defun gnus-Subject-quit ()
+ "Quit reading current newsgroup without updating read article info."
+ (interactive)
+ (if (y-or-n-p "Do you really wanna quit reading this group? ")
+ (progn
+ (message "") ;Erase "Yes or No" question.
+ ;; Return to Group selection mode.
+ (if (get-buffer gnus-Subject-buffer)
+ (bury-buffer gnus-Subject-buffer))
+ (if (get-buffer gnus-Article-buffer)
+ (bury-buffer gnus-Article-buffer))
+ (gnus-configure-windows 'ExitNewsgroup)
+ (pop-to-buffer gnus-Group-buffer)
+ (gnus-Group-jump-to-group gnus-newsgroup-name) ;Make sure where I was.
+ (gnus-Group-next-group 1) ;(gnus-Group-next-unread-group 1)
+ )))
+
+(defun gnus-Subject-describe-briefly ()
+ "Describe Subject mode commands briefly."
+ (interactive)
+ (message
+ (concat
+ (substitute-command-keys "\\[gnus-Subject-next-page]:Select ")
+ (substitute-command-keys "\\[gnus-Subject-next-unread-article]:Forward ")
+ (substitute-command-keys "\\[gnus-Subject-prev-unread-article]:Backward ")
+ (substitute-command-keys "\\[gnus-Subject-exit]:Exit ")
+ (substitute-command-keys "\\[gnus-Info-find-node]:Run Info ")
+ (substitute-command-keys "\\[gnus-Subject-describe-briefly]:This help")
+ )))
+
+
+;;;
+;;; GNUS Article Mode
+;;;
+
+(if gnus-Article-mode-map
+ nil
+ (setq gnus-Article-mode-map (make-keymap))
+ (suppress-keymap gnus-Article-mode-map)
+ (define-key gnus-Article-mode-map " " 'gnus-Article-next-page)
+ (define-key gnus-Article-mode-map "\177" 'gnus-Article-prev-page)
+ (define-key gnus-Article-mode-map "r" 'gnus-Article-refer-article)
+ (define-key gnus-Article-mode-map "o" 'gnus-Article-pop-article)
+ (define-key gnus-Article-mode-map "h" 'gnus-Article-show-subjects)
+ (define-key gnus-Article-mode-map "s" 'gnus-Article-show-subjects)
+ (define-key gnus-Article-mode-map "?" 'gnus-Article-describe-briefly)
+ (define-key gnus-Article-mode-map "\C-c\C-i" 'gnus-Info-find-node))
+
+(defun gnus-Article-mode ()
+ "Major mode for browsing through an article.
+All normal editing commands are turned off.
+Instead, these commands are available:
+\\{gnus-Article-mode-map}
+
+Various hooks for customization:
+ gnus-Article-mode-hook
+ Entry to this mode calls the value with no arguments, if that
+ value is non-nil.
+
+ gnus-Article-prepare-hook
+ Called with no arguments after an article is prepared for reading,
+ if that value is non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ ;; Gee. Why don't you upgrade?
+ (cond ((boundp 'mode-line-modified)
+ (setq mode-line-modified "--- "))
+ ((listp (default-value 'mode-line-format))
+ (setq mode-line-format
+ (cons "--- " (cdr (default-value 'mode-line-format))))))
+ (make-local-variable 'global-mode-string)
+ (setq global-mode-string nil)
+ (setq major-mode 'gnus-Article-mode)
+ (setq mode-name "Article")
+ (gnus-Article-set-mode-line)
+ (use-local-map gnus-Article-mode-map)
+ (make-local-variable 'page-delimiter)
+ (setq page-delimiter gnus-page-delimiter)
+ (make-local-variable 'mail-header-separator)
+ (setq mail-header-separator "") ;For caesar function.
+ (buffer-flush-undo (current-buffer))
+ (setq buffer-read-only t) ;Disable modification
+ (run-hooks 'gnus-Article-mode-hook))
+
+(defun gnus-Article-setup-buffer ()
+ "Initialize Article mode buffer."
+ (or (get-buffer gnus-Article-buffer)
+ (save-excursion
+ (set-buffer (get-buffer-create gnus-Article-buffer))
+ (gnus-Article-mode))
+ ))
+
+(defun gnus-Article-prepare (article &optional all-headers)
+ "Prepare ARTICLE in Article mode buffer.
+If optional argument ALL-HEADERS is non-nil, all headers are inserted."
+ (save-excursion
+ (set-buffer gnus-Article-buffer)
+ (let ((buffer-read-only nil))
+ (erase-buffer)
+ (if (gnus-request-article article)
+ (progn
+ ;; Prepare article buffer
+ (insert-buffer-substring nntp-server-buffer)
+ (setq gnus-have-all-headers (or all-headers gnus-show-all-headers))
+ (if (and (numberp article)
+ (not (eq article gnus-current-article)))
+ ;; Seems me that a new article is selected.
+ (progn
+ ;; gnus-current-article must be an article number.
+ (setq gnus-last-article gnus-current-article)
+ (setq gnus-current-article article)
+ (setq gnus-current-headers
+ (gnus-find-header-by-number gnus-newsgroup-headers
+ gnus-current-article))
+ ;; Clear articles history only when articles are
+ ;; retrieved by article numbers.
+ (setq gnus-current-history nil)
+ (run-hooks 'gnus-Mark-article-hook)
+ ))
+ ;; Hooks for modifying contents of the article. This hook
+ ;; must be called before being narrowed.
+ (run-hooks 'gnus-Article-prepare-hook)
+ ;; Delete unnecessary headers.
+ (or gnus-have-all-headers
+ (gnus-Article-delete-headers))
+ ;; Do page break.
+ (goto-char (point-min))
+ (if gnus-break-pages
+ (gnus-narrow-to-page))
+ ;; Next function must be called after setting
+ ;; `gnus-current-article' variable and narrowed to page.
+ (gnus-Article-set-mode-line)
+ )
+ (if (numberp article)
+ (gnus-Subject-mark-as-read article))
+ (ding) (message "No such article (may be canceled)"))
+ )))
+
+(defun gnus-Article-show-all-headers ()
+ "Show all article headers in Article mode buffer."
+ (or gnus-have-all-headers
+ (gnus-Article-prepare gnus-current-article t)))
+
+;;(defun gnus-Article-set-mode-line ()
+;; "Set Article mode line string."
+;; (setq mode-line-buffer-identification
+;; (list 17
+;; (format "GNUS: %s {%d-%d} %d"
+;; gnus-newsgroup-name
+;; gnus-newsgroup-begin
+;; gnus-newsgroup-end
+;; gnus-current-article
+;; )))
+;; (set-buffer-modified-p t))
+
+(defun gnus-Article-set-mode-line ()
+ "Set Article mode line string."
+ (let ((unmarked
+ (- (length gnus-newsgroup-unreads)
+ (length (gnus-intersection
+ gnus-newsgroup-unreads gnus-newsgroup-marked))))
+ (unselected
+ (- (length gnus-newsgroup-unselected)
+ (length (gnus-intersection
+ gnus-newsgroup-unselected gnus-newsgroup-marked)))))
+ (setq mode-line-buffer-identification
+ (list 17
+ (format "GNUS: %s{%d} %s"
+ gnus-newsgroup-name
+ gnus-current-article
+ ;; This is proposed by tale@pawl.rpi.edu.
+ (cond ((and (zerop unmarked)
+ (zerop unselected))
+ " ")
+ ((zerop unselected)
+ (format "%d more" unmarked))
+ (t
+ (format "%d(+%d) more" unmarked unselected)))
+ ))))
+ (set-buffer-modified-p t))
+
+(defun gnus-Article-delete-headers ()
+ "Delete unnecessary headers."
+ (save-excursion
+ (save-restriction
+ (goto-char (point-min))
+ (narrow-to-region (point-min)
+ (progn (search-forward "\n\n" nil 'move) (point)))
+ (goto-char (point-min))
+ (and (stringp gnus-ignored-headers)
+ (while (re-search-forward gnus-ignored-headers nil t)
+ (beginning-of-line)
+ (delete-region (point)
+ (progn (re-search-forward "\n[^ \t]")
+ (forward-char -1)
+ (point)))))
+ )))
+
+;; Working on article's buffer
+
+(defun gnus-Article-next-page (lines)
+ "Show next page of current article.
+If end of article, return non-nil. Otherwise return nil.
+Argument LINES specifies lines to be scrolled up."
+ (interactive "P")
+ (move-to-window-line -1)
+ ;; Fixed by enami@ptgd.sony.co.jp (enami tsugutomo)
+ (if (save-excursion
+ (end-of-line)
+ (and (pos-visible-in-window-p) ;Not continuation line.
+ (eobp)))
+ ;; Nothing in this page.
+ (if (or (not gnus-break-pages)
+ (save-excursion
+ (save-restriction
+ (widen) (forward-line 1) (eobp)))) ;Real end-of-buffer?
+ t ;Nothing more.
+ (gnus-narrow-to-page 1) ;Go to next page.
+ nil
+ )
+ ;; More in this page.
+ (condition-case ()
+ (scroll-up lines)
+ (end-of-buffer
+ ;; Long lines may cause an end-of-buffer error.
+ (goto-char (point-max))))
+ nil
+ ))
+
+(defun gnus-Article-prev-page (lines)
+ "Show previous page of current article.
+Argument LINES specifies lines to be scrolled down."
+ (interactive "P")
+ (move-to-window-line 0)
+ (if (and gnus-break-pages
+ (bobp)
+ (not (save-restriction (widen) (bobp)))) ;Real beginning-of-buffer?
+ (progn
+ (gnus-narrow-to-page -1) ;Go to previous page.
+ (goto-char (point-max))
+ (recenter -1))
+ (scroll-down lines)))
+
+(defun gnus-Article-next-digest (nth)
+ "Move to head of NTH next digested message.
+Set mark at end of digested message."
+ ;; Stop page breaking in digest mode.
+ (widen)
+ (end-of-line)
+ ;; Skip NTH - 1 digest.
+ ;; Suggested by Khalid Sattar <admin@cs.exeter.ac.uk>.
+ ;; Digest separator is customizable.
+ ;; Suggested by Skip Montanaro <montanaro@sprite.crd.ge.com>.
+ (while (and (> nth 1)
+ (re-search-forward gnus-digest-separator nil 'move))
+ (setq nth (1- nth)))
+ (if (re-search-forward gnus-digest-separator nil t)
+ (let ((begin (point)))
+ ;; Search for end of this message.
+ (end-of-line)
+ (if (re-search-forward gnus-digest-separator nil t)
+ (progn
+ (search-backward "\n\n") ;This may be incorrect.
+ (forward-line 1))
+ (goto-char (point-max)))
+ (push-mark) ;Set mark at end of digested message.
+ (goto-char begin)
+ (beginning-of-line)
+ ;; Show From: and Subject: fields.
+ (recenter 1))
+ (message "End of message")
+ ))
+
+(defun gnus-Article-prev-digest (nth)
+ "Move to head of NTH previous digested message."
+ ;; Stop page breaking in digest mode.
+ (widen)
+ (beginning-of-line)
+ ;; Skip NTH - 1 digest.
+ ;; Suggested by Khalid Sattar <admin@cs.exeter.ac.uk>.
+ ;; Digest separator is customizable.
+ ;; Suggested by Skip Montanaro <montanaro@sprite.crd.ge.com>.
+ (while (and (> nth 1)
+ (re-search-backward gnus-digest-separator nil 'move))
+ (setq nth (1- nth)))
+ (if (re-search-backward gnus-digest-separator nil t)
+ (let ((begin (point)))
+ ;; Search for end of this message.
+ (end-of-line)
+ (if (re-search-forward gnus-digest-separator nil t)
+ (progn
+ (search-backward "\n\n") ;This may be incorrect.
+ (forward-line 1))
+ (goto-char (point-max)))
+ (push-mark) ;Set mark at end of digested message.
+ (goto-char begin)
+ ;; Show From: and Subject: fields.
+ (recenter 1))
+ (goto-char (point-min))
+ (message "Top of message")
+ ))
+
+(defun gnus-Article-refer-article ()
+ "Read article specified by message-id around point."
+ (interactive)
+ (save-window-excursion
+ (save-excursion
+ (re-search-forward ">" nil t) ;Move point to end of "<....>".
+ (if (re-search-backward "\\(<[^<> \t\n]+>\\)" nil t)
+ (let ((message-id
+ (buffer-substring (match-beginning 1) (match-end 1))))
+ (set-buffer gnus-Subject-buffer)
+ (gnus-Subject-refer-article message-id))
+ (error "No references around point"))
+ )))
+
+(defun gnus-Article-pop-article ()
+ "Pop up article history."
+ (interactive)
+ (save-window-excursion
+ (set-buffer gnus-Subject-buffer)
+ (gnus-Subject-refer-article nil)))
+
+(defun gnus-Article-show-subjects ()
+ "Reconfigure windows to show headers."
+ (interactive)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Subject-buffer)
+ (gnus-Subject-goto-subject gnus-current-article))
+
+(defun gnus-Article-describe-briefly ()
+ "Describe Article mode commands briefly."
+ (interactive)
+ (message
+ (concat
+ (substitute-command-keys "\\[gnus-Article-next-page]:Next page ")
+ (substitute-command-keys "\\[gnus-Article-prev-page]:Prev page ")
+ (substitute-command-keys "\\[gnus-Article-show-subjects]:Show headers ")
+ (substitute-command-keys "\\[gnus-Info-find-node]:Run Info ")
+ (substitute-command-keys "\\[gnus-Article-describe-briefly]:This help")
+ )))
+
+
+;;;
+;;; GNUS KILL-File Mode
+;;;
+
+(if gnus-Kill-file-mode-map
+ nil
+ (setq gnus-Kill-file-mode-map (copy-keymap emacs-lisp-mode-map))
+ (define-key gnus-Kill-file-mode-map "\C-c\C-k\C-s" 'gnus-Kill-file-kill-by-subject)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-k\C-a" 'gnus-Kill-file-kill-by-author)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-a" 'gnus-Kill-file-apply-buffer)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-e" 'gnus-Kill-file-apply-last-sexp)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-c" 'gnus-Kill-file-exit)
+ (define-key gnus-Kill-file-mode-map "\C-c\C-i" 'gnus-Info-find-node))
+
+(defun gnus-Kill-file-mode ()
+ "Major mode for editing KILL file.
+
+In addition to Emacs-Lisp Mode, the following commands are available:
+
+\\[gnus-Kill-file-kill-by-subject] Insert KILL command for current subject.
+\\[gnus-Kill-file-kill-by-author] Insert KILL command for current author.
+\\[gnus-Kill-file-apply-buffer] Apply current buffer to selected newsgroup.
+\\[gnus-Kill-file-apply-last-sexp] Apply sexp before point to selected newsgroup.
+\\[gnus-Kill-file-exit] Save file and exit editing KILL file.
+\\[gnus-Info-find-node] Read Info about KILL file.
+
+ A KILL file contains lisp expressions to be applied to a selected
+newsgroup. The purpose is to mark articles as read on the basis of
+some set of regexps. A global KILL file is applied to every newsgroup,
+and a local KILL file is applied to a specified newsgroup. Since a
+global KILL file is applied to every newsgroup, for better performance
+use a local one.
+
+ A KILL file can contain any kind of Emacs lisp expressions expected
+to be evaluated in the Subject buffer. Writing lisp programs for this
+purpose is not so easy because the internal working of GNUS must be
+well-known. For this reason, GNUS provides a general function which
+does this easily for non-Lisp programmers.
+
+ The `gnus-kill' function executes commands available in Subject Mode
+by their key sequences. `gnus-kill' should be called with FIELD,
+REGEXP and optional COMMAND and ALL. FIELD is a string representing
+the header field or an empty string. If FIELD is an empty string, the
+entire article body is searched for. REGEXP is a string which is
+compared with FIELD value. COMMAND is a string representing a valid
+key sequence in Subject Mode or Lisp expression. COMMAND is default to
+'(gnus-Subject-mark-as-read nil \"X\"). Make sure that COMMAND is
+executed in the Subject buffer. If the second optional argument ALL
+is non-nil, the COMMAND is applied to articles which are already
+marked as read or unread. Articles which are marked are skipped over
+by default.
+
+ For example, if you want to mark articles of which subjects contain
+the string `AI' as read, a possible KILL file may look like:
+
+ (gnus-kill \"Subject\" \"AI\")
+
+ If you want to mark articles with `D' instead of `X', you can use
+the following expression:
+
+ (gnus-kill \"Subject\" \"AI\" \"d\")
+
+In this example it is assumed that the command
+`gnus-Subject-mark-as-read-forward' is assigned to `d' in Subject Mode.
+
+ It is possible to delete unnecessary headers which are marked with
+`X' in a KILL file as follows:
+
+ (gnus-expunge \"X\")
+
+ If the Subject buffer is empty after applying KILL files, GNUS will
+exit the selected newsgroup normally. If headers which are marked
+with `D' are deleted in a KILL file, it is impossible to read articles
+which are marked as read in the previous GNUS sessions. Marks other
+than `D' should be used for articles which should really be deleted.
+
+Entry to this mode calls emacs-lisp-mode-hook and
+gnus-Kill-file-mode-hook with no arguments, if that value is non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ (use-local-map gnus-Kill-file-mode-map)
+ (set-syntax-table emacs-lisp-mode-syntax-table)
+ (setq major-mode 'gnus-Kill-file-mode)
+ (setq mode-name "KILL-File")
+ (lisp-mode-variables nil)
+ (run-hooks 'emacs-lisp-mode-hook 'gnus-Kill-file-mode-hook))
+
+(defun gnus-Kill-file-edit-file (newsgroup)
+ "Begin editing a KILL file of NEWSGROUP.
+If NEWSGROUP is nil, the global KILL file is selected."
+ (interactive "sNewsgroup: ")
+ (let ((file (gnus-newsgroup-kill-file newsgroup)))
+ (gnus-make-directory (file-name-directory file))
+ ;; Save current window configuration if this is first invocation.
+ (or (and (get-file-buffer file)
+ (get-buffer-window (get-file-buffer file)))
+ (setq gnus-winconf-kill-file (current-window-configuration)))
+ ;; Hack windows.
+ (let ((buffer (find-file-noselect file)))
+ (cond ((get-buffer-window buffer)
+ (pop-to-buffer buffer))
+ ((eq major-mode 'gnus-Group-mode)
+ (gnus-configure-windows '(1 0 0)) ;Take all windows.
+ (pop-to-buffer gnus-Group-buffer)
+ (let ((gnus-Subject-buffer buffer))
+ (gnus-configure-windows '(1 1 0)) ;Split into two.
+ (pop-to-buffer buffer)))
+ ((eq major-mode 'gnus-Subject-mode)
+ (gnus-configure-windows 'SelectArticle)
+ (pop-to-buffer gnus-Article-buffer)
+ (bury-buffer gnus-Article-buffer)
+ (switch-to-buffer buffer))
+ (t ;No good rules.
+ (find-file-other-window file))
+ ))
+ (gnus-Kill-file-mode)
+ ))
+
+(defun gnus-Kill-file-kill-by-subject ()
+ "Insert KILL command for current subject."
+ (interactive)
+ (insert
+ (format "(gnus-kill \"Subject\" %s)\n"
+ (prin1-to-string
+ (if gnus-current-kill-article
+ (regexp-quote
+ (nntp-header-subject
+ (gnus-find-header-by-number gnus-newsgroup-headers
+ gnus-current-kill-article)))
+ "")))))
+
+(defun gnus-Kill-file-kill-by-author ()
+ "Insert KILL command for current author."
+ (interactive)
+ (insert
+ (format "(gnus-kill \"From\" %s)\n"
+ (prin1-to-string
+ (if gnus-current-kill-article
+ (regexp-quote
+ (nntp-header-from
+ (gnus-find-header-by-number gnus-newsgroup-headers
+ gnus-current-kill-article)))
+ "")))))
+
+(defun gnus-Kill-file-apply-buffer ()
+ "Apply current buffer to current newsgroup."
+ (interactive)
+ (if (and gnus-current-kill-article
+ (get-buffer gnus-Subject-buffer))
+ ;; Assume newsgroup is selected.
+ (let ((string (concat "(progn \n" (buffer-string) "\n)" )))
+ (save-excursion
+ (save-window-excursion
+ (pop-to-buffer gnus-Subject-buffer)
+ (eval (car (read-from-string string))))))
+ (ding) (message "No newsgroup is selected.")))
+
+(defun gnus-Kill-file-apply-last-sexp ()
+ "Apply sexp before point in current buffer to current newsgroup."
+ (interactive)
+ (if (and gnus-current-kill-article
+ (get-buffer gnus-Subject-buffer))
+ ;; Assume newsgroup is selected.
+ (let ((string
+ (buffer-substring
+ (save-excursion (forward-sexp -1) (point)) (point))))
+ (save-excursion
+ (save-window-excursion
+ (pop-to-buffer gnus-Subject-buffer)
+ (eval (car (read-from-string string))))))
+ (ding) (message "No newsgroup is selected.")))
+
+(defun gnus-Kill-file-exit ()
+ "Save a KILL file, then return to the previous buffer."
+ (interactive)
+ (save-buffer)
+ (let ((killbuf (current-buffer)))
+ ;; We don't want to return to Article buffer.
+ (and (get-buffer gnus-Article-buffer)
+ (bury-buffer (get-buffer gnus-Article-buffer)))
+ ;; Delete the KILL file windows.
+ (delete-windows-on killbuf)
+ ;; Restore last window configuration if available.
+ (and gnus-winconf-kill-file
+ (set-window-configuration gnus-winconf-kill-file))
+ (setq gnus-winconf-kill-file nil)
+ ;; Kill the KILL file buffer. Suggested by tale@pawl.rpi.edu.
+ (kill-buffer killbuf)))
+
+
+;;;
+;;; Utility functions
+;;;
+
+;; Basic ideas by emv@math.lsa.umich.edu (Edward Vielmetti)
+
+(defun gnus-batch-kill ()
+ "Run batched KILL.
+Usage: emacs -batch -l gnus -f gnus-batch-kill NEWSGROUP ..."
+ (if (not noninteractive)
+ (error "gnus-batch-kill is to be used only with -batch"))
+ (let* ((group nil)
+ (subscribed nil)
+ (newsrc nil)
+ (yes-and-no
+ (gnus-parse-n-options
+ (apply (function concat)
+ (mapcar (function (lambda (g) (concat g " ")))
+ command-line-args-left))))
+ (yes (car yes-and-no))
+ (no (cdr yes-and-no))
+ ;; Disable verbose message.
+ (gnus-novice-user nil)
+ (gnus-large-newsgroup nil)
+ (nntp-large-newsgroup nil))
+ ;; Eat all arguments.
+ (setq command-line-args-left nil)
+ ;; Startup GNUS.
+ (gnus)
+ ;; Apply kills to specified newsgroups in command line arguments.
+ (setq newsrc (copy-sequence gnus-newsrc-assoc))
+ (while newsrc
+ (setq group (car (car newsrc)))
+ (setq subscribed (nth 1 (car newsrc)))
+ (setq newsrc (cdr newsrc))
+ (if (and subscribed
+ (not (zerop (nth 1 (gnus-gethash group gnus-unread-hashtb))))
+ (if yes
+ (string-match yes group) t)
+ (or (null no)
+ (not (string-match no group))))
+ (progn
+ (gnus-Subject-read-group group nil t)
+ (if (eq (current-buffer) (get-buffer gnus-Subject-buffer))
+ (gnus-Subject-exit t))
+ ))
+ )
+ ;; Finally, exit Emacs.
+ (set-buffer gnus-Group-buffer)
+ (gnus-Group-exit)
+ ))
+
+(defun gnus-Numeric-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 nil, it is ~/News/News.group/num.
+Otherwise, it is like ~/News/news/group/num."
+ (let ((default
+ (expand-file-name
+ (concat (if gnus-use-long-file-name
+ (capitalize newsgroup)
+ (gnus-newsgroup-directory-form newsgroup))
+ "/" (int-to-string (nntp-header-number headers)))
+ (or gnus-article-save-directory "~/News"))))
+ (if (and last-file
+ (string-equal (file-name-directory default)
+ (file-name-directory last-file))
+ (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
+ default
+ (or last-file default))))
+
+(defun gnus-numeric-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 nil, it is ~/News/news.group/num.
+Otherwise, it is like ~/News/news/group/num."
+ (let ((default
+ (expand-file-name
+ (concat (if gnus-use-long-file-name
+ newsgroup
+ (gnus-newsgroup-directory-form newsgroup))
+ "/" (int-to-string (nntp-header-number headers)))
+ (or gnus-article-save-directory "~/News"))))
+ (if (and last-file
+ (string-equal (file-name-directory default)
+ (file-name-directory last-file))
+ (string-match "^[0-9]+$" (file-name-nondirectory last-file)))
+ 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 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
+ (capitalize newsgroup)
+ (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
+ (or gnus-article-save-directory "~/News"))))
+
+(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 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
+ newsgroup
+ (concat (gnus-newsgroup-directory-form newsgroup) "/news"))
+ (or gnus-article-save-directory "~/News"))))
+
+(defun gnus-Folder-save-name (newsgroup headers &optional last-folder)
+ "Generate folder name from NEWSGROUP, HEADERS, and optional LAST-FOLDER.
+If variable `gnus-use-long-file-name' is nil, it is +News.group.
+Otherwise, it is like +news/group."
+ (or last-folder
+ (concat "+"
+ (if gnus-use-long-file-name
+ (capitalize newsgroup)
+ (gnus-newsgroup-directory-form newsgroup)))))
+
+(defun gnus-folder-save-name (newsgroup headers &optional last-folder)
+ "Generate folder name from NEWSGROUP, HEADERS, and optional LAST-FOLDER.
+If variable `gnus-use-long-file-name' is nil, it is +news.group.
+Otherwise, it is like +news/group."
+ (or last-folder
+ (concat "+"
+ (if gnus-use-long-file-name
+ newsgroup
+ (gnus-newsgroup-directory-form newsgroup)))))
+
+(defun gnus-apply-kill-file ()
+ "Apply KILL file to the current newsgroup."
+ ;; Apply the global KILL file.
+ (load (gnus-newsgroup-kill-file nil) t nil t)
+ ;; And then apply the local KILL file.
+ (load (gnus-newsgroup-kill-file gnus-newsgroup-name) t nil t))
+
+(defun gnus-Newsgroup-kill-file (newsgroup)
+ "Return the name of a KILL file of NEWSGROUP.
+If NEWSGROUP is nil, return the global KILL file instead."
+ (cond ((or (null newsgroup)
+ (string-equal newsgroup ""))
+ ;; The global KILL file is placed at top of the directory.
+ (expand-file-name gnus-kill-file-name
+ (or gnus-article-save-directory "~/News")))
+ (gnus-use-long-file-name
+ ;; Append ".KILL" to capitalized newsgroup name.
+ (expand-file-name (concat (capitalize newsgroup)
+ "." gnus-kill-file-name)
+ (or gnus-article-save-directory "~/News")))
+ (t
+ ;; Place "KILL" under the hierarchical directory.
+ (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
+ "/" gnus-kill-file-name)
+ (or gnus-article-save-directory "~/News")))
+ ))
+
+(defun gnus-newsgroup-kill-file (newsgroup)
+ "Return the name of a KILL file of NEWSGROUP.
+If NEWSGROUP is nil, return the global KILL file instead."
+ (cond ((or (null newsgroup)
+ (string-equal newsgroup ""))
+ ;; The global KILL file is placed at top of the directory.
+ (expand-file-name gnus-kill-file-name
+ (or gnus-article-save-directory "~/News")))
+ (gnus-use-long-file-name
+ ;; Append ".KILL" to newsgroup name.
+ (expand-file-name (concat newsgroup "." gnus-kill-file-name)
+ (or gnus-article-save-directory "~/News")))
+ (t
+ ;; Place "KILL" under the hierarchical directory.
+ (expand-file-name (concat (gnus-newsgroup-directory-form newsgroup)
+ "/" gnus-kill-file-name)
+ (or gnus-article-save-directory "~/News")))
+ ))
+
+(defun gnus-newsgroup-directory-form (newsgroup)
+ "Make hierarchical directory name from NEWSGROUP name."
+ (let ((newsgroup (substring newsgroup 0)) ;Copy string.
+ (len (length newsgroup))
+ (idx 0))
+ ;; Replace all occurence of `.' with `/'.
+ (while (< idx len)
+ (if (= (aref newsgroup idx) ?.)
+ (aset newsgroup idx ?/))
+ (setq idx (1+ idx)))
+ newsgroup
+ ))
+
+(defun gnus-make-directory (directory)
+ "Make DIRECTORY recursively."
+ (let ((directory (expand-file-name directory default-directory)))
+ (or (file-exists-p directory)
+ (gnus-make-directory-1 "" directory))
+ ))
+
+(defun gnus-make-directory-1 (head tail)
+ (cond ((string-match "^/\\([^/]+\\)" tail)
+ (setq head
+ (concat (file-name-as-directory head)
+ (substring tail (match-beginning 1) (match-end 1))))
+ (or (file-exists-p head)
+ (call-process "mkdir" nil nil nil head))
+ (gnus-make-directory-1 head (substring tail (match-end 1))))
+ ((string-equal tail "") t)
+ ))
+
+(defun gnus-simplify-subject (subject &optional re-only)
+ "Remove `Re:' and words in parentheses.
+If optional argument RE-ONLY is non-nil, strip `Re:' only."
+ (let ((case-fold-search t)) ;Ignore case.
+ ;; Remove `Re:' and `Re^N:'.
+ (if (string-match "\\`\\(re\\(\\^[0-9]+\\)?:[ \t]+\\)+" subject)
+ (setq subject (substring subject (match-end 0))))
+ ;; Remove words in parentheses from end.
+ (or re-only
+ (while (string-match "[ \t\n]*([^()]*)[ \t\n]*\\'" subject)
+ (setq subject (substring subject 0 (match-beginning 0)))))
+ ;; Return subject string.
+ subject
+ ))
+
+(defun gnus-optional-lines-and-from (header)
+ "Return a string like `NNN:AUTHOR' from HEADER."
+ (let ((name-length (length "umerin@photon")))
+ (substring (format "%3d:%s"
+ ;; Lines of the article.
+ ;; Suggested by dana@bellcore.com.
+ (nntp-header-lines header)
+ ;; Its author.
+ (concat (mail-strip-quoted-names
+ (nntp-header-from header))
+ (make-string name-length ? )))
+ ;; 4 stands for length of `NNN:'.
+ 0 (+ 4 name-length))))
+
+(defun gnus-optional-lines (header)
+ "Return a string like `NNN' from HEADER."
+ (format "%4d" (nntp-header-lines header)))
+
+(defun gnus-sort-headers (predicate &optional reverse)
+ "Sort current group headers by PREDICATE safely.
+*Safely* means C-g quitting is disabled during sorting.
+Optional argument REVERSE means reverse order."
+ (let ((inhibit-quit t))
+ (setq gnus-newsgroup-headers
+ (if reverse
+ (nreverse (sort (nreverse gnus-newsgroup-headers) predicate))
+ (sort gnus-newsgroup-headers predicate)))
+ ))
+
+(defun gnus-string-lessp (a b)
+ "Return T if first arg string is less than second in lexicographic order.
+If case-fold-search is non-nil, case of letters is ignored."
+ (if case-fold-search
+ (string-lessp (downcase a) (downcase b)) (string-lessp a b)))
+
+(defun gnus-date-lessp (date1 date2)
+ "Return T if DATE1 is earlyer than DATE2."
+ (string-lessp (gnus-comparable-date date1)
+ (gnus-comparable-date date2)))
+
+(defun gnus-comparable-date (date)
+ "Make comparable string by string-lessp from DATE."
+ (let ((month '(("JAN" . " 1")("FEB" . " 2")("MAR" . " 3")
+ ("APR" . " 4")("MAY" . " 5")("JUN" . " 6")
+ ("JUL" . " 7")("AUG" . " 8")("SEP" . " 9")
+ ("OCT" . "10")("NOV" . "11")("DEC" . "12")))
+ (date (or date "")))
+ ;; Can understand the following styles:
+ ;; (1) 14 Apr 89 03:20:12 GMT
+ ;; (2) Fri, 17 Mar 89 4:01:33 GMT
+ (if (string-match
+ "\\([0-9]+\\) \\([^ ,]+\\) \\([0-9]+\\) \\([0-9:]+\\)" date)
+ (concat
+ ;; Year
+ (substring date (match-beginning 3) (match-end 3))
+ ;; Month
+ (cdr
+ (assoc
+ (upcase (substring date (match-beginning 2) (match-end 2))) month))
+ ;; Day
+ (format "%2d" (string-to-int
+ (substring date
+ (match-beginning 1) (match-end 1))))
+ ;; Time
+ (substring date (match-beginning 4) (match-end 4)))
+ ;; Cannot understand DATE string.
+ date
+ )
+ ))
+
+(defun gnus-fetch-field (field)
+ "Return the value of the header FIELD of current article."
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (narrow-to-region (point-min)
+ (progn (search-forward "\n\n" nil 'move) (point)))
+ (mail-fetch-field field))))
+
+(fset 'gnus-expunge 'gnus-Subject-delete-marked-with)
+
+(defun gnus-kill (field regexp &optional command all)
+ "If FIELD of an article matches REGEXP, execute COMMAND.
+Optional third argument COMMAND is default to
+ (gnus-Subject-mark-as-read nil \"X\").
+If optional fourth argument ALL is non-nil, articles marked are also applied
+to. If FIELD is an empty string (or nil), entire article body is searched for.
+COMMAND must be a lisp expression or a string representing a key sequence."
+ ;; We don't want to change current point nor window configuration.
+ (save-excursion
+ (save-window-excursion
+ ;; Selected window must be Subject mode buffer to execute
+ ;; keyboard macros correctly. See command_loop_1.
+ (switch-to-buffer gnus-Subject-buffer 'norecord)
+ (goto-char (point-min)) ;From the beginning.
+ (if (null command)
+ (setq command '(gnus-Subject-mark-as-read nil "X")))
+ (gnus-execute field regexp command nil (not all))
+ )))
+
+(defun gnus-execute (field regexp form &optional backward ignore-marked)
+ "If FIELD of article header matches REGEXP, execute lisp FORM (or a string).
+If FIELD is an empty string (or nil), entire article body is searched for.
+If optional fifth argument BACKWARD is non-nil, do backward instead.
+If optional sixth argument IGNORE-MARKED is non-nil, articles which are
+marked as read or unread are ignored."
+ (let ((function nil)
+ (header nil)
+ (article nil))
+ (if (string-equal field "")
+ (setq field nil))
+ (if (null field)
+ nil
+ (or (stringp field)
+ (setq field (symbol-name field)))
+ ;; Get access function of header filed.
+ (setq function (intern-soft (concat "gnus-header-" (downcase field))))
+ (if (and function (fboundp function))
+ (setq function (symbol-function function))
+ (error "Unknown header field: \"%s\"" field)))
+ ;; Make FORM funcallable.
+ (if (and (listp form) (not (eq (car form) 'lambda)))
+ (setq form (list 'lambda nil form)))
+ ;; Starting from the current article.
+ (or (and ignore-marked
+ ;; Articles marked as read and unread should be ignored.
+ (setq article (gnus-Subject-article-number))
+ (or (not (memq article gnus-newsgroup-unreads)) ;Marked as read.
+ (memq article gnus-newsgroup-marked) ;Marked as unread.
+ ))
+ (gnus-execute-1 function regexp form))
+ (while (gnus-Subject-search-subject backward ignore-marked nil)
+ (gnus-execute-1 function regexp form))
+ ))
+
+(defun gnus-execute-1 (function regexp form)
+ (save-excursion
+ ;; The point of Subject mode buffer must be saved during execution.
+ (let ((article (gnus-Subject-article-number)))
+ (if (null article)
+ nil ;Nothing to do.
+ (if function
+ ;; Compare with header field.
+ (let ((header (gnus-find-header-by-number
+ gnus-newsgroup-headers article))
+ (value nil))
+ (and header
+ (progn
+ (setq value (funcall function header))
+ ;; Number (Lines:) or symbol must be converted to string.
+ (or (stringp value)
+ (setq value (prin1-to-string value)))
+ (string-match regexp value))
+ (if (stringp form) ;Keyboard macro.
+ (execute-kbd-macro form)
+ (funcall form))))
+ ;; Search article body.
+ (let ((gnus-current-article nil) ;Save article pointer.
+ (gnus-last-article nil)
+ (gnus-break-pages nil) ;No need to break pages.
+ (gnus-Mark-article-hook nil)) ;Inhibit marking as read.
+ (message "Searching for article: %d..." article)
+ (gnus-Article-setup-buffer)
+ (gnus-Article-prepare article t)
+ (if (save-excursion
+ (set-buffer gnus-Article-buffer)
+ (goto-char (point-min))
+ (re-search-forward regexp nil t))
+ (if (stringp form) ;Keyboard macro.
+ (execute-kbd-macro form)
+ (funcall form))))
+ ))
+ )))
+
+;;; caesar-region written by phr@prep.ai.mit.edu Nov 86
+;;; modified by tower@prep Nov 86
+;;; Modified by umerin@flab.flab.Fujitsu.JUNET for ROT47.
+
+(defun gnus-caesar-region (&optional n)
+ "Caesar rotation of region by N, default 13, for decrypting netnews.
+ROT47 will be performed for Japanese text in any case."
+ (interactive (if current-prefix-arg ; Was there a prefix arg?
+ (list (prefix-numeric-value current-prefix-arg))
+ (list nil)))
+ (cond ((not (numberp n)) (setq n 13))
+ ((< n 0) (setq n (- 26 (% (- n) 26))))
+ (t (setq n (% n 26)))) ;canonicalize N
+ (if (not (zerop n)) ; no action needed for a rot of 0
+ (progn
+ (if (or (not (boundp 'caesar-translate-table))
+ (/= (aref caesar-translate-table ?a) (+ ?a n)))
+ (let ((i 0) (lower "abcdefghijklmnopqrstuvwxyz") upper)
+ (message "Building caesar-translate-table...")
+ (setq caesar-translate-table (make-vector 256 0))
+ (while (< i 256)
+ (aset caesar-translate-table i i)
+ (setq i (1+ i)))
+ (setq lower (concat lower lower) upper (upcase lower) i 0)
+ (while (< i 26)
+ (aset caesar-translate-table (+ ?a i) (aref lower (+ i n)))
+ (aset caesar-translate-table (+ ?A i) (aref upper (+ i n)))
+ (setq i (1+ i)))
+ ;; ROT47 for Japanese text.
+ ;; Thanks to ichikawa@flab.fujitsu.junet.
+ (setq i 161)
+ (let ((t1 (logior ?O 128))
+ (t2 (logior ?! 128))
+ (t3 (logior ?~ 128)))
+ (while (< i 256)
+ (aset caesar-translate-table i
+ (let ((v (aref caesar-translate-table i)))
+ (if (<= v t1) (if (< v t2) v (+ v 47))
+ (if (<= v t3) (- v 47) v))))
+ (setq i (1+ i))))
+ (message "Building caesar-translate-table... done")))
+ (let ((from (region-beginning))
+ (to (region-end))
+ (i 0) str len)
+ (setq str (buffer-substring from to))
+ (setq len (length str))
+ (while (< i len)
+ (aset str i (aref caesar-translate-table (aref str i)))
+ (setq i (1+ i)))
+ (goto-char from)
+ (delete-region from to)
+ (insert str)))))
+
+;; Functions accessing headers.
+;; Functions are more convenient than macros in some case.
+
+(defun gnus-header-number (header)
+ "Return article number in HEADER."
+ (nntp-header-number header))
+
+(defun gnus-header-subject (header)
+ "Return subject string in HEADER."
+ (nntp-header-subject header))
+
+(defun gnus-header-from (header)
+ "Return author string in HEADER."
+ (nntp-header-from header))
+
+(defun gnus-header-xref (header)
+ "Return xref string in HEADER."
+ (nntp-header-xref header))
+
+(defun gnus-header-lines (header)
+ "Return lines in HEADER."
+ (nntp-header-lines header))
+
+(defun gnus-header-date (header)
+ "Return date in HEADER."
+ (nntp-header-date header))
+
+(defun gnus-header-id (header)
+ "Return Id in HEADER."
+ (nntp-header-id header))
+
+(defun gnus-header-references (header)
+ "Return references in HEADER."
+ (nntp-header-references header))
+
+
+;;;
+;;; Article savers.
+;;;
+
+(defun gnus-output-to-rmail (file-name)
+ "Append the current article to an Rmail file named FILE-NAME."
+ (require 'rmail)
+ ;; Most of these codes are borrowed from rmailout.el.
+ (setq file-name (expand-file-name file-name))
+ (setq rmail-last-rmail-file file-name)
+ (let ((artbuf (current-buffer))
+ (tmpbuf (get-buffer-create " *GNUS-output*")))
+ (save-excursion
+ (or (get-file-buffer file-name)
+ (file-exists-p file-name)
+ (if (yes-or-no-p
+ (concat "\"" file-name "\" does not exist, create it? "))
+ (let ((file-buffer (create-file-buffer file-name)))
+ (save-excursion
+ (set-buffer file-buffer)
+ (rmail-insert-rmail-file-header)
+ (let ((require-final-newline nil))
+ (write-region (point-min) (point-max) file-name t 1)))
+ (kill-buffer file-buffer))
+ (error "Output file does not exist")))
+ (set-buffer tmpbuf)
+ (buffer-flush-undo (current-buffer))
+ (erase-buffer)
+ (insert-buffer-substring artbuf)
+ (gnus-convert-article-to-rmail)
+ ;; Decide whether to append to a file or to an Emacs buffer.
+ (let ((outbuf (get-file-buffer file-name)))
+ (if (not outbuf)
+ (append-to-file (point-min) (point-max) file-name)
+ ;; File has been visited, in buffer OUTBUF.
+ (set-buffer outbuf)
+ (let ((buffer-read-only nil)
+ (msg (and (boundp 'rmail-current-message)
+ rmail-current-message)))
+ ;; If MSG is non-nil, buffer is in RMAIL mode.
+ (if msg
+ (progn (widen)
+ (narrow-to-region (point-max) (point-max))))
+ (insert-buffer-substring tmpbuf)
+ (if msg
+ (progn
+ (goto-char (point-min))
+ (widen)
+ (search-backward "\^_")
+ (narrow-to-region (point) (point-max))
+ (goto-char (1+ (point-min)))
+ (rmail-count-new-messages t)
+ (rmail-show-message msg))))))
+ )
+ (kill-buffer tmpbuf)
+ ))
+
+(defun gnus-output-to-file (file-name)
+ "Append the current article to a file named FILE-NAME."
+ (setq file-name (expand-file-name file-name))
+ (let ((artbuf (current-buffer))
+ (tmpbuf (get-buffer-create " *GNUS-output*")))
+ (save-excursion
+ (set-buffer tmpbuf)
+ (buffer-flush-undo (current-buffer))
+ (erase-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))
+ (kill-buffer tmpbuf)
+ ))
+
+(defun gnus-convert-article-to-rmail ()
+ "Convert article in current buffer to Rmail message format."
+ (let ((buffer-read-only nil))
+ ;; Convert article directly into Babyl format.
+ ;; Suggested by Rob Austein <sra@lcs.mit.edu>
+ (goto-char (point-min))
+ (insert "\^L\n0, unseen,,\n*** EOOH ***\n")
+ (while (search-forward "\n\^_" nil t) ;single char
+ (replace-match "\n^_")) ;2 chars: "^" and "_"
+ (goto-char (point-max))
+ (insert "\^_")))
+
+;;(defun gnus-convert-article-to-rmail ()
+;; "Convert article in current buffer to Rmail message format."
+;; (let ((buffer-read-only nil))
+;; ;; Insert special header of Unix mail.
+;; (goto-char (point-min))
+;; (insert "From "
+;; (or (mail-strip-quoted-names (mail-fetch-field "from"))
+;; "unknown")
+;; " " (current-time-string) "\n")
+;; ;; Stop quoting `From' since this seems unnecessary in most cases.
+;; ;; ``Quote'' "\nFrom " as "\n>From "
+;; ;;(while (search-forward "\nFrom " nil t)
+;; ;; (forward-char -5)
+;; ;; (insert ?>))
+;; ;; Convert article to babyl format.
+;; (rmail-convert-to-babyl-format)
+;; ))
+
+
+;;;
+;;; Internal functions.
+;;;
+
+(defun gnus-start-news-server (&optional confirm)
+ "Open network stream to remote NNTP server.
+If optional argument CONFIRM is non-nil, ask you host that NNTP server
+is running even if it is defined.
+Run gnus-Open-server-hook just before opening news server."
+ (if (gnus-server-opened)
+ ;; Stream is already opened.
+ nil
+ ;; Open NNTP server.
+ (if (or confirm
+ (null gnus-nntp-server))
+ (if (and (boundp 'gnus-secondary-servers) gnus-secondary-servers)
+ ;; Read server name with completion.
+ (setq gnus-nntp-server
+ (completing-read "NNTP server: "
+ (cons (list gnus-nntp-server)
+ gnus-secondary-servers)
+ nil nil gnus-nntp-server))
+ (setq gnus-nntp-server
+ (read-string "NNTP server: " gnus-nntp-server))))
+ ;; If no server name is given, local host is assumed.
+ (if (string-equal gnus-nntp-server "")
+ (setq gnus-nntp-server (system-name)))
+ (cond ((string-match ":" gnus-nntp-server)
+ ;; :DIRECTORY
+ (require 'mhspool)
+ (gnus-define-access-method 'mhspool)
+ (message "Looking up private directory..."))
+ ((and (null gnus-nntp-service)
+ (string-equal gnus-nntp-server (system-name)))
+ (require 'nnspool)
+ (gnus-define-access-method 'nnspool)
+ (message "Looking up local news spool..."))
+ (t
+ (gnus-define-access-method 'nntp)
+ (message "Connecting to NNTP server on %s..." gnus-nntp-server)))
+ (run-hooks 'gnus-Open-server-hook)
+ (cond ((gnus-open-server gnus-nntp-server gnus-nntp-service))
+ ((and (stringp (gnus-status-message))
+ (> (length (gnus-status-message)) 0))
+ ;; Show valuable message if available.
+ (error (gnus-status-message)))
+ (t (error "Cannot open NNTP server on %s" gnus-nntp-server)))
+ ))
+
+;; Dummy functions used only once. Should return nil.
+(defun gnus-server-opened () nil)
+(defun gnus-close-server () nil)
+
+(defun gnus-define-access-method (method &optional access-methods)
+ "Define access functions for the access METHOD.
+Methods defintion is taken from optional argument ACCESS-METHODS or
+the variable gnus-access-methods."
+ (let ((bindings
+ (cdr (assoc method (or access-methods gnus-access-methods)))))
+ (if (null bindings)
+ (error "Unknown access method: %s" method)
+ ;; Should not use symbol-function here since overload does not work.
+ (while bindings
+ (fset (car (car bindings)) (cdr (car bindings)))
+ (setq bindings (cdr bindings)))
+ )))
+
+(defun gnus-select-newsgroup (group &optional show-all)
+ "Select newsgroup GROUP.
+If optional argument SHOW-ALL is non-nil, all of articles in the group
+are selected."
+ (if (gnus-request-group group)
+ (let ((articles nil))
+ (setq gnus-newsgroup-name group)
+ (setq gnus-newsgroup-unreads
+ (gnus-uncompress-sequence
+ (nthcdr 2 (gnus-gethash group gnus-unread-hashtb))))
+ (cond (show-all
+ ;; Select all active articles.
+ (setq articles
+ (gnus-uncompress-sequence
+ (nthcdr 2 (gnus-gethash group gnus-active-hashtb)))))
+ (t
+ ;; Select unread articles only.
+ (setq articles gnus-newsgroup-unreads)))
+ ;; Require confirmation if selecting large newsgroup.
+ (setq gnus-newsgroup-unselected nil)
+ (if (not (numberp gnus-large-newsgroup))
+ nil
+ (let ((selected nil)
+ (number (length articles)))
+ (if (> number gnus-large-newsgroup)
+ (progn
+ (condition-case ()
+ (let ((input
+ (read-string
+ (format
+ "How many articles from %s (default %d): "
+ gnus-newsgroup-name number))))
+ (setq selected
+ (if (string-equal input "")
+ number (string-to-int input))))
+ (quit
+ (setq selected 0)))
+ (cond ((and (> selected 0)
+ (< selected number))
+ ;; Select last N articles.
+ (setq articles (nthcdr (- number selected) articles)))
+ ((and (< selected 0)
+ (< (- 0 selected) number))
+ ;; Select first N articles.
+ (setq selected (- 0 selected))
+ (setq articles (copy-sequence articles))
+ (setcdr (nthcdr (1- selected) articles) nil))
+ ((zerop selected)
+ (setq articles nil))
+ ;; Otherwise select all.
+ )
+ ;; Get unselected unread articles.
+ (setq gnus-newsgroup-unselected
+ (gnus-set-difference gnus-newsgroup-unreads articles))
+ ))
+ ))
+ ;; Get headers list.
+ (setq gnus-newsgroup-headers (gnus-retrieve-headers articles))
+ ;; UNREADS may contain expired articles, so we have to remove
+ ;; them from the list.
+ (setq gnus-newsgroup-unreads
+ (gnus-intersection gnus-newsgroup-unreads
+ (mapcar
+ (function
+ (lambda (header)
+ (nntp-header-number header)))
+ gnus-newsgroup-headers)))
+ ;; Marked article must be a subset of unread articles.
+ (setq gnus-newsgroup-marked
+ (gnus-intersection (append gnus-newsgroup-unselected
+ gnus-newsgroup-unreads)
+ (cdr (assoc group gnus-marked-assoc))))
+ ;; First and last article in this newsgroup.
+ (setq gnus-newsgroup-begin
+ (if gnus-newsgroup-headers
+ (nntp-header-number (car gnus-newsgroup-headers))
+ 0
+ ))
+ (setq gnus-newsgroup-end
+ (if gnus-newsgroup-headers
+ (nntp-header-number
+ (gnus-last-element gnus-newsgroup-headers))
+ 0
+ ))
+ ;; File name that an article was saved last.
+ (setq gnus-newsgroup-last-rmail nil)
+ (setq gnus-newsgroup-last-mail nil)
+ (setq gnus-newsgroup-last-folder nil)
+ (setq gnus-newsgroup-last-file nil)
+ ;; Reset article pointer etc.
+ (setq gnus-current-article nil)
+ (setq gnus-current-headers nil)
+ (setq gnus-current-history nil)
+ (setq gnus-have-all-headers nil)
+ (setq gnus-last-article nil)
+ ;; GROUP is successfully selected.
+ t
+ )
+ ))
+
+(defun gnus-more-header-backward ()
+ "Find new header backward."
+ (let ((first
+ (car (nth 2 (gnus-gethash gnus-newsgroup-name gnus-active-hashtb))))
+ (artnum gnus-newsgroup-begin)
+ (header nil))
+ (while (and (not header)
+ (> artnum first))
+ (setq artnum (1- artnum))
+ (setq header (car (gnus-retrieve-headers (list artnum)))))
+ header
+ ))
+
+(defun gnus-more-header-forward ()
+ "Find new header forward."
+ (let ((last
+ (cdr (nth 2 (gnus-gethash gnus-newsgroup-name gnus-active-hashtb))))
+ (artnum gnus-newsgroup-end)
+ (header nil))
+ (while (and (not header)
+ (< artnum last))
+ (setq artnum (1+ artnum))
+ (setq header (car (gnus-retrieve-headers (list artnum)))))
+ header
+ ))
+
+(defun gnus-extend-newsgroup (header &optional backward)
+ "Extend newsgroup selection with HEADER.
+Optional argument BACKWARD means extend toward backward."
+ (if header
+ (let ((artnum (nntp-header-number header)))
+ (setq gnus-newsgroup-headers
+ (if backward
+ (cons header gnus-newsgroup-headers)
+ (append gnus-newsgroup-headers (list header))))
+ ;; We have to update unreads and unselected, but don't have to
+ ;; care about gnus-newsgroup-marked.
+ (if (memq artnum gnus-newsgroup-unselected)
+ (setq gnus-newsgroup-unreads
+ (cons artnum gnus-newsgroup-unreads)))
+ (setq gnus-newsgroup-unselected
+ (delq artnum gnus-newsgroup-unselected))
+ (setq gnus-newsgroup-begin (min gnus-newsgroup-begin artnum))
+ (setq gnus-newsgroup-end (max gnus-newsgroup-end artnum))
+ )))
+
+(defun gnus-mark-article-as-read (article)
+ "Remember that ARTICLE is marked as read."
+ ;; Remove from unread and marked list.
+ (setq gnus-newsgroup-unreads
+ (delq article gnus-newsgroup-unreads))
+ (setq gnus-newsgroup-marked
+ (delq article gnus-newsgroup-marked)))
+
+(defun gnus-mark-article-as-unread (article &optional clear-mark)
+ "Remember that ARTICLE is marked as unread.
+Optional argument CLEAR-MARK means ARTICLE should not be remembered
+that it was marked as read once."
+ ;; Add to unread list.
+ (or (memq article gnus-newsgroup-unreads)
+ (setq gnus-newsgroup-unreads
+ (cons article gnus-newsgroup-unreads)))
+ ;; If CLEAR-MARK is non-nil, the article must be removed from marked
+ ;; list. Otherwise, it must be added to the list.
+ (if clear-mark
+ (setq gnus-newsgroup-marked
+ (delq article gnus-newsgroup-marked))
+ (or (memq article gnus-newsgroup-marked)
+ (setq gnus-newsgroup-marked
+ (cons article gnus-newsgroup-marked)))))
+
+(defun gnus-clear-system ()
+ "Clear all variables and buffer."
+ ;; Clear GNUS variables.
+ (let ((variables gnus-variable-list))
+ (while variables
+ (set (car variables) nil)
+ (setq variables (cdr variables))))
+ ;; Clear other internal variables.
+ (setq gnus-active-hashtb nil)
+ (setq gnus-unread-hashtb nil)
+ ;; Kill the startup file.
+ (and gnus-current-startup-file
+ (get-file-buffer gnus-current-startup-file)
+ (kill-buffer (get-file-buffer gnus-current-startup-file)))
+ (setq gnus-current-startup-file nil)
+ ;; Kill GNUS buffers.
+ (let ((buffers gnus-buffer-list))
+ (while buffers
+ (if (get-buffer (car buffers))
+ (kill-buffer (car buffers)))
+ (setq buffers (cdr buffers))
+ )))
+
+(defun gnus-configure-windows (action)
+ "Configure GNUS windows according to the next ACTION.
+The ACTION is either a symbol, such as `SelectNewsgroup', or a
+configuration list such as `(1 1 2)'. If ACTION is not a list,
+configuration list is got from the variable gnus-window-configuration."
+ (let* ((windows
+ (if (listp action)
+ action (car (cdr (assq action gnus-window-configuration)))))
+ (grpwin (get-buffer-window gnus-Group-buffer))
+ (subwin (get-buffer-window gnus-Subject-buffer))
+ (artwin (get-buffer-window gnus-Article-buffer))
+ (winsum nil)
+ (height nil)
+ (grpheight 0)
+ (subheight 0)
+ (artheight 0))
+ (if (or (null windows) ;No configuration is specified.
+ (and (eq (null grpwin)
+ (zerop (nth 0 windows)))
+ (eq (null subwin)
+ (zerop (nth 1 windows)))
+ (eq (null artwin)
+ (zerop (nth 2 windows)))))
+ ;; No need to change window configuration.
+ nil
+ (select-window (or grpwin subwin artwin (selected-window)))
+ ;; First of all, compute the height of each window.
+ (cond (gnus-use-full-window
+ ;; Take up the entire screen.
+ (delete-other-windows)
+ (setq height (window-height (selected-window))))
+ (t
+ (setq height (+ (if grpwin (window-height grpwin) 0)
+ (if subwin (window-height subwin) 0)
+ (if artwin (window-height artwin) 0)))))
+ ;; The Newsgroup buffer exits always. So, use it to extend the
+ ;; Group window so as to get enough window space.
+ (switch-to-buffer gnus-Group-buffer 'norecord)
+ (and (get-buffer gnus-Subject-buffer)
+ (delete-windows-on gnus-Subject-buffer))
+ (and (get-buffer gnus-Article-buffer)
+ (delete-windows-on gnus-Article-buffer))
+ ;; Compute expected window height.
+ (setq winsum (apply (function +) windows))
+ (if (not (zerop (nth 0 windows)))
+ (setq grpheight (max window-min-height
+ (/ (* height (nth 0 windows)) winsum))))
+ (if (not (zerop (nth 1 windows)))
+ (setq subheight (max window-min-height
+ (/ (* height (nth 1 windows)) winsum))))
+ (if (not (zerop (nth 2 windows)))
+ (setq artheight (max window-min-height
+ (/ (* height (nth 2 windows)) winsum))))
+ (setq height (+ grpheight subheight artheight))
+ (enlarge-window (max 0 (- height (window-height (selected-window)))))
+ ;; Then split the window.
+ (and (not (zerop artheight))
+ (or (not (zerop grpheight))
+ (not (zerop subheight)))
+ (split-window-vertically (+ grpheight subheight)))
+ (and (not (zerop grpheight))
+ (not (zerop subheight))
+ (split-window-vertically grpheight))
+ ;; Then select buffers in each window.
+ (and (not (zerop grpheight))
+ (progn
+ (switch-to-buffer gnus-Group-buffer 'norecord)
+ (other-window 1)))
+ (and (not (zerop subheight))
+ (progn
+ (switch-to-buffer gnus-Subject-buffer 'norecord)
+ (other-window 1)))
+ (and (not (zerop artheight))
+ (progn
+ ;; If Article buffer does not exist, it will be created
+ ;; and initialized.
+ (gnus-Article-setup-buffer)
+ (switch-to-buffer gnus-Article-buffer 'norecord)))
+ )
+ ))
+
+(defun gnus-find-header-by-number (headers number)
+ "Return a header which is a element of HEADERS and has NUMBER."
+ (let ((found nil))
+ (while (and headers (not found))
+ ;; We cannot use `=' to accept non-numeric NUMBER.
+ (if (eq number (nntp-header-number (car headers)))
+ (setq found (car headers)))
+ (setq headers (cdr headers)))
+ found
+ ))
+
+(defun gnus-find-header-by-id (headers id)
+ "Return a header which is a element of HEADERS and has Message-ID."
+ (let ((found nil))
+ (while (and headers (not found))
+ (if (string-equal id (nntp-header-id (car headers)))
+ (setq found (car headers)))
+ (setq headers (cdr headers)))
+ found
+ ))
+
+(defun gnus-version ()
+ "Version numbers of this version of GNUS."
+ (interactive)
+ (cond ((and (boundp 'mhspool-version) (boundp 'nnspool-version))
+ (message "%s; %s; %s; %s"
+ gnus-version nntp-version nnspool-version mhspool-version))
+ ((boundp 'mhspool-version)
+ (message "%s; %s; %s"
+ gnus-version nntp-version mhspool-version))
+ ((boundp 'nnspool-version)
+ (message "%s; %s; %s"
+ gnus-version nntp-version nnspool-version))
+ (t
+ (message "%s; %s" gnus-version nntp-version))))
+
+(defun gnus-Info-find-node ()
+ "Find Info documentation of GNUS."
+ (interactive)
+ (require 'info)
+ ;; Enlarge info window if needed.
+ (cond ((eq major-mode 'gnus-Group-mode)
+ (gnus-configure-windows '(1 0 0)) ;Take all windows.
+ (pop-to-buffer gnus-Group-buffer))
+ ((eq major-mode 'gnus-Subject-mode)
+ (gnus-configure-windows '(0 1 0)) ;Take all windows.
+ (pop-to-buffer gnus-Subject-buffer)))
+ (Info-goto-node (cdr (assq major-mode gnus-Info-nodes))))
+
+(defun gnus-overload-functions (&optional overloads)
+ "Overload functions specified by optional argument OVERLOADS.
+If nothing is specified, use the variable gnus-overload-functions."
+ (let ((defs nil)
+ (overloads (or overloads gnus-overload-functions)))
+ (while overloads
+ (setq defs (car overloads))
+ (setq overloads (cdr overloads))
+ ;; Load file before overloading function if necessary. Make
+ ;; sure we cannot use `requre' always.
+ (and (not (fboundp (car defs)))
+ (car (cdr (cdr defs)))
+ (load (car (cdr (cdr defs))) nil 'nomessage))
+ (fset (car defs) (car (cdr defs)))
+ )))
+
+(defun gnus-make-threads (newsgroup-headers)
+ "Make conversation threads tree from NEWSGROUP-HEADERS."
+ (let ((headers newsgroup-headers)
+ (h nil)
+ (d nil)
+ (roots nil)
+ (dependencies nil))
+ ;; Make message dependency alist.
+ (while headers
+ (setq h (car headers))
+ (setq headers (cdr headers))
+ ;; Ignore invalid headers.
+ (if (vectorp h) ;Depends on nntp.el.
+ (progn
+ ;; Ignore broken references, e.g "<123@a.b.c".
+ (setq d (and (nntp-header-references h)
+ (string-match "\\(<[^<>]+>\\)[^>]*$"
+ (nntp-header-references h))
+ (gnus-find-header-by-id
+ newsgroup-headers
+ (substring (nntp-header-references h)
+ (match-beginning 1) (match-end 1)))))
+ ;; Check subject equality.
+ (or gnus-thread-ignore-subject
+ (null d)
+ (string-equal (gnus-simplify-subject
+ (nntp-header-subject h) 're)
+ (gnus-simplify-subject
+ (nntp-header-subject d) 're))
+ ;; H should be a thread root.
+ (setq d nil))
+ ;; H depends on D.
+ (setq dependencies
+ (cons (cons h d) dependencies))
+ ;; H is a thread root.
+ (if (null d)
+ (setq roots (cons h roots)))
+ ))
+ )
+ ;; Make complete threads from the roots.
+ ;; Note: dependencies are in reverse order, but
+ ;; gnus-make-threads-1 processes it in reverse order again. So,
+ ;; we don't have to worry about it.
+ (mapcar
+ (function
+ (lambda (root)
+ (gnus-make-threads-1 root dependencies))) (nreverse roots))
+ ))
+
+(defun gnus-make-threads-1 (parent dependencies)
+ (let ((children nil)
+ (d nil)
+ (depends dependencies))
+ ;; Find children.
+ (while depends
+ (setq d (car depends))
+ (setq depends (cdr depends))
+ (and (cdr d)
+ (eq (nntp-header-id parent) (nntp-header-id (cdr d)))
+ (setq children (cons (car d) children))))
+ ;; Go down.
+ (cons parent
+ (mapcar
+ (function
+ (lambda (child)
+ (gnus-make-threads-1 child dependencies))) children))
+ ))
+
+(defun gnus-narrow-to-page (&optional arg)
+ "Make text outside current page invisible except for page delimiter.
+A numeric arg specifies to move forward or backward by that many pages,
+thus showing a page other than the one point was originally in."
+ (interactive "P")
+ (setq arg (if arg (prefix-numeric-value arg) 0))
+ (save-excursion
+ (forward-page -1) ;Beginning of current page.
+ (widen)
+ (if (> arg 0)
+ (forward-page arg)
+ (if (< arg 0)
+ (forward-page (1- arg))))
+ ;; Find the end of the page.
+ (forward-page)
+ ;; If we stopped due to end of buffer, stay there.
+ ;; If we stopped after a page delimiter, put end of restriction
+ ;; at the beginning of that line.
+ ;; These are commented out.
+ ;; (if (save-excursion (beginning-of-line)
+ ;; (looking-at page-delimiter))
+ ;; (beginning-of-line))
+ (narrow-to-region (point)
+ (progn
+ ;; Find the top of the page.
+ (forward-page -1)
+ ;; If we found beginning of buffer, stay there.
+ ;; If extra text follows page delimiter on same line,
+ ;; include it.
+ ;; Otherwise, show text starting with following line.
+ (if (and (eolp) (not (bobp)))
+ (forward-line 1))
+ (point)))
+ ))
+
+(defun gnus-last-element (list)
+ "Return last element of LIST."
+ (let ((last nil))
+ (while list
+ (if (null (cdr list))
+ (setq last (car list)))
+ (setq list (cdr list)))
+ last
+ ))
+
+(defun gnus-set-difference (list1 list2)
+ "Return a list of elements of LIST1 that do not appear in LIST2."
+ (let ((list1 (copy-sequence list1)))
+ (while list2
+ (setq list1 (delq (car list2) list1))
+ (setq list2 (cdr list2)))
+ list1
+ ))
+
+(defun gnus-intersection (list1 list2)
+ "Return a list of elements that appear in both LIST1 and LIST2."
+ (let ((result nil))
+ (while list2
+ (if (memq (car list2) list1)
+ (setq result (cons (car list2) result)))
+ (setq list2 (cdr list2)))
+ result
+ ))
+
+
+;;;
+;;; Get information about active articles, already read articles, and
+;;; still unread articles.
+;;;
+
+;; GNUS internal format of gnus-newsrc-assoc and gnus-killed-assoc:
+;; (("general" t (1 . 1))
+;; ("misc" t (1 . 10) (12 . 15))
+;; ("test" nil (1 . 99)) ...)
+;; GNUS internal format of gnus-marked-assoc:
+;; (("general" 1 2 3)
+;; ("misc" 2) ...)
+;; GNUS internal format of gnus-active-hashtb:
+;; (("general" t (1 . 1))
+;; ("misc" t (1 . 10))
+;; ("test" nil (1 . 99)) ...)
+;; GNUS internal format of gnus-unread-hashtb:
+;; (("general" 1 (1 . 1))
+;; ("misc" 14 (1 . 10) (12 . 15))
+;; ("test" 99 (1 . 99)) ...)
+
+(defun gnus-setup-news-info (&optional rawfile)
+ "Setup news information.
+If optional argument RAWFILE is non-nil, force to read raw startup file."
+ (let ((init (not (and gnus-newsrc-assoc
+ gnus-active-hashtb
+ gnus-unread-hashtb
+ (not rawfile)
+ ))))
+ ;; We have to clear some variables to re-initialize news info.
+ (if init
+ (setq gnus-newsrc-assoc nil
+ gnus-active-hashtb nil
+ gnus-unread-hashtb nil))
+ (if init
+ (gnus-read-newsrc-file rawfile))
+ (gnus-read-active-file)
+ (gnus-expire-marked-articles)
+ (gnus-get-unread-articles)
+ ;; Check new newsgroups and subscribe them.
+ (if init
+ (let ((new-newsgroups (gnus-find-new-newsgroups)))
+ (while new-newsgroups
+ (funcall gnus-subscribe-newsgroup-method (car new-newsgroups))
+ (setq new-newsgroups (cdr new-newsgroups))
+ )))
+ ))
+
+(defun gnus-subscribe-newsgroup (newsgroup &optional next)
+ "Subscribe new NEWSGROUP.
+If optional argument NEXT is non-nil, it is inserted before NEXT."
+ (gnus-insert-newsgroup (list newsgroup t) next)
+ (message "Newsgroup %s is subscribed" newsgroup))
+
+(defun gnus-add-newsgroup (newsgroup)
+ "Subscribe new NEWSGROUP safely and put it at top."
+ (and (null (assoc newsgroup gnus-newsrc-assoc)) ;Really new?
+ (gnus-gethash newsgroup gnus-active-hashtb) ;Really exist?
+ (gnus-insert-newsgroup (or (assoc newsgroup gnus-killed-assoc)
+ (list newsgroup t))
+ (car (car gnus-newsrc-assoc)))))
+
+(defun gnus-find-new-newsgroups ()
+ "Looking for new newsgroups and return names.
+`-n' option of options line in .newsrc file is recognized."
+ (let ((group nil)
+ (new-newsgroups nil))
+ (mapatoms
+ (function
+ (lambda (sym)
+ (setq group (symbol-name sym))
+ ;; Taking account of `-n' option.
+ (and (or (null gnus-newsrc-options-n-no)
+ (not (string-match gnus-newsrc-options-n-no group))
+ (and gnus-newsrc-options-n-yes
+ (string-match gnus-newsrc-options-n-yes group)))
+ (null (assoc group gnus-killed-assoc)) ;Ignore killed.
+ (null (assoc group gnus-newsrc-assoc)) ;Really new.
+ ;; Find new newsgroup.
+ (setq new-newsgroups
+ (cons group new-newsgroups)))
+ ))
+ gnus-active-hashtb)
+ ;; Return new newsgroups.
+ new-newsgroups
+ ))
+
+(defun gnus-kill-newsgroup (group)
+ "Kill GROUP from gnus-newsrc-assoc, .newsrc and gnus-unread-hashtb."
+ (let ((info (assoc group gnus-newsrc-assoc)))
+ (if (null info)
+ nil
+ ;; Delete from gnus-newsrc-assoc
+ (setq gnus-newsrc-assoc (delq info gnus-newsrc-assoc))
+ ;; Add to gnus-killed-assoc.
+ (setq gnus-killed-assoc
+ (cons info
+ (delq (assoc group gnus-killed-assoc) gnus-killed-assoc)))
+ ;; Clear unread hashtable.
+ ;; Thanks cwitty@csli.Stanford.EDU (Carl Witty).
+ (gnus-sethash group nil gnus-unread-hashtb)
+ ;; Then delete from .newsrc
+ (gnus-update-newsrc-buffer group 'delete)
+ ;; Return the deleted newsrc entry.
+ info
+ )))
+
+(defun gnus-insert-newsgroup (info &optional next)
+ "Insert newsrc INFO entry before NEXT.
+If optional argument NEXT is nil, appended to the last."
+ (if (null info)
+ (error "Invalid argument: %s" info))
+ (let* ((group (car info)) ;Newsgroup name.
+ (range
+ (gnus-difference-of-range
+ (nth 2 (gnus-gethash group gnus-active-hashtb)) (nthcdr 2 info))))
+ ;; Check duplication.
+ (if (assoc group gnus-newsrc-assoc)
+ (error "Duplicated: %s" group))
+ ;; Insert to gnus-newsrc-assoc.
+ (if (string-equal next (car (car gnus-newsrc-assoc)))
+ (setq gnus-newsrc-assoc
+ (cons info gnus-newsrc-assoc))
+ (let ((found nil)
+ (rest gnus-newsrc-assoc)
+ (tail (cons nil gnus-newsrc-assoc)))
+ ;; Seach insertion point.
+ (while (and (not found) rest)
+ (if (string-equal next (car (car rest)))
+ (setq found t)
+ (setq rest (cdr rest))
+ (setq tail (cdr tail))
+ ))
+ ;; Find it.
+ (setcdr tail nil)
+ (setq gnus-newsrc-assoc
+ (append gnus-newsrc-assoc (cons info rest)))
+ ))
+ ;; Delete from gnus-killed-assoc.
+ (setq gnus-killed-assoc
+ (delq (assoc group gnus-killed-assoc) gnus-killed-assoc))
+ ;; Then insert to .newsrc.
+ (gnus-update-newsrc-buffer group nil next)
+ ;; Add to gnus-unread-hashtb.
+ (gnus-sethash group
+ (cons group ;Newsgroup name.
+ (cons (gnus-number-of-articles range) range))
+ gnus-unread-hashtb)
+ ))
+
+(defun gnus-check-killed-newsgroups ()
+ "Check consistency between gnus-newsrc-assoc and gnus-killed-assoc."
+ (let ((group nil)
+ (new-killed nil)
+ (old-killed gnus-killed-assoc))
+ (while old-killed
+ (setq group (car (car old-killed)))
+ (and (or (null gnus-newsrc-options-n-no)
+ (not (string-match gnus-newsrc-options-n-no group))
+ (and gnus-newsrc-options-n-yes
+ (string-match gnus-newsrc-options-n-yes group)))
+ (null (assoc group gnus-newsrc-assoc)) ;No duplication.
+ ;; Subscribed in options line and not in gnus-newsrc-assoc.
+ (setq new-killed
+ (cons (car old-killed) new-killed)))
+ (setq old-killed (cdr old-killed))
+ )
+ (setq gnus-killed-assoc (nreverse new-killed))
+ ))
+
+(defun gnus-check-bogus-newsgroups (&optional confirm)
+ "Delete bogus newsgroups.
+If optional argument CONFIRM is non-nil, confirm deletion of newsgroups."
+ (let ((group nil) ;Newsgroup name temporary used.
+ (old-newsrc gnus-newsrc-assoc)
+ (new-newsrc nil)
+ (bogus nil) ;List of bogus newsgroups.
+ (old-killed gnus-killed-assoc)
+ (new-killed nil)
+ (old-marked gnus-marked-assoc)
+ (new-marked nil))
+ (message "Checking bogus newsgroups...")
+ ;; Update gnus-newsrc-assoc.
+ (while old-newsrc
+ (setq group (car (car old-newsrc)))
+ (if (or (gnus-gethash group gnus-active-hashtb)
+ (and confirm
+ (not (y-or-n-p
+ (format "Delete bogus newsgroup: %s " group)))))
+ ;; Active newsgroup.
+ (setq new-newsrc (cons (car old-newsrc) new-newsrc))
+ ;; Found a bogus newsgroup.
+ (setq bogus (cons group bogus)))
+ (setq old-newsrc (cdr old-newsrc))
+ )
+ (setq gnus-newsrc-assoc (nreverse new-newsrc))
+ ;; Update gnus-killed-assoc.
+ ;; The killed newsgroups are deleted without any confirmations.
+ (while old-killed
+ (setq group (car (car old-killed)))
+ (and (gnus-gethash group gnus-active-hashtb)
+ (null (assoc group gnus-newsrc-assoc))
+ ;; Active and really killed newsgroup.
+ (setq new-killed (cons (car old-killed) new-killed)))
+ (setq old-killed (cdr old-killed))
+ )
+ (setq gnus-killed-assoc (nreverse new-killed))
+ ;; Remove BOGUS from .newsrc file.
+ (while bogus
+ (gnus-update-newsrc-buffer (car bogus) 'delete)
+ (setq bogus (cdr bogus)))
+ ;; Update gnus-marked-assoc.
+ (while old-marked
+ (setq group (car (car old-marked)))
+ (if (and (cdr (car old-marked)) ;Non-empty?
+ (assoc group gnus-newsrc-assoc)) ;Not bogus?
+ (setq new-marked (cons (car old-marked) new-marked)))
+ (setq old-marked (cdr old-marked)))
+ (setq gnus-marked-assoc new-marked)
+ (message "Checking bogus newsgroups... done")
+ ))
+
+(defun gnus-get-unread-articles ()
+ "Compute diffs between active and read articles."
+ (let ((read gnus-newsrc-assoc)
+ (group-info nil)
+ (group-name nil)
+ (active nil)
+ (range nil))
+ (message "Checking new news...")
+ (or gnus-unread-hashtb
+ (setq gnus-unread-hashtb (gnus-make-hashtable)))
+ (while read
+ (setq group-info (car read)) ;About one newsgroup
+ (setq group-name (car group-info))
+ (setq active (nth 2 (gnus-gethash group-name gnus-active-hashtb)))
+ (if (and gnus-octive-hashtb
+ ;; Is nothing changed?
+ (equal active
+ (nth 2 (gnus-gethash group-name gnus-octive-hashtb)))
+ ;; Is this newsgroup in the unread hash table?
+ (gnus-gethash group-name gnus-unread-hashtb)
+ )
+ nil ;Nothing to do.
+ (setq range (gnus-difference-of-range active (nthcdr 2 group-info)))
+ (gnus-sethash group-name
+ (cons group-name ;Group name
+ (cons (gnus-number-of-articles range)
+ range)) ;Range of unread articles
+ gnus-unread-hashtb)
+ )
+ (setq read (cdr read))
+ )
+ (message "Checking new news... done")
+ ))
+
+(defun gnus-expire-marked-articles ()
+ "Check expired article which is marked as unread."
+ (let ((marked-assoc gnus-marked-assoc)
+ (updated-assoc nil)
+ (marked nil) ;Current marked info.
+ (articles nil) ;List of marked articles.
+ (updated nil) ;List of real marked.
+ (begin nil))
+ (while marked-assoc
+ (setq marked (car marked-assoc))
+ (setq articles (cdr marked))
+ (setq updated nil)
+ (setq begin
+ (car (nth 2 (gnus-gethash (car marked) gnus-active-hashtb))))
+ (while (and begin articles)
+ (if (>= (car articles) begin)
+ ;; This article is still active.
+ (setq updated (cons (car articles) updated)))
+ (setq articles (cdr articles)))
+ (if updated
+ (setq updated-assoc
+ (cons (cons (car marked) updated) updated-assoc)))
+ (setq marked-assoc (cdr marked-assoc)))
+ (setq gnus-marked-assoc updated-assoc)
+ ))
+
+(defun gnus-mark-as-read-by-xref
+ (group headers unreads &optional subscribed-only)
+ "Mark articles as read using cross references and return updated newsgroups.
+Arguments are GROUP, HEADERS, UNREADS, and optional SUBSCRIBED-ONLY."
+ (let ((xref-list nil)
+ (header nil)
+ (xrefs nil) ;One Xref: field info.
+ (xref nil) ;(NEWSGROUP . ARTICLE)
+ (gname nil) ;Newsgroup name
+ (article nil)) ;Article number
+ (while headers
+ (setq header (car headers))
+ (if (memq (nntp-header-number header) unreads)
+ ;; This article is not yet marked as read.
+ nil
+ (setq xrefs (gnus-parse-xref-field (nntp-header-xref header)))
+ ;; For each cross reference info. in one Xref: field.
+ (while xrefs
+ (setq xref (car xrefs))
+ (setq gname (car xref)) ;Newsgroup name
+ (setq article (cdr xref)) ;Article number
+ (or (string-equal group gname) ;Ignore current newsgroup.
+ ;; Ignore unsubscribed newsgroup if requested.
+ (and subscribed-only
+ (not (nth 1 (assoc gname gnus-newsrc-assoc))))
+ ;; Ignore article marked as unread.
+ (memq article (cdr (assoc gname gnus-marked-assoc)))
+ (let ((group-xref (assoc gname xref-list)))
+ (if group-xref
+ (if (memq article (cdr group-xref))
+ nil ;Alread marked.
+ (setcdr group-xref (cons article (cdr group-xref))))
+ ;; Create new assoc entry for GROUP.
+ (setq xref-list (cons (list gname article) xref-list)))
+ ))
+ (setq xrefs (cdr xrefs))
+ ))
+ (setq headers (cdr headers)))
+ ;; Mark cross referenced articles as read.
+ (gnus-mark-xrefed-as-read xref-list)
+ ;;(message "%s %s" (prin1-to-string unreads) (prin1-to-string xref-list))
+ ;; Return list of updated group name.
+ (mapcar (function car) xref-list)
+ ))
+
+(defun gnus-parse-xref-field (xref-value)
+ "Parse Xref: field value, and return list of `(group . article-id)'."
+ (let ((xref-list nil)
+ (xref-value (or xref-value "")))
+ ;; Remove server host name.
+ (if (string-match "^[ \t]*[^ \t,]+[ \t,]+\\(.*\\)$" xref-value)
+ (setq xref-value (substring xref-value (match-beginning 1)))
+ (setq xref-value nil))
+ ;; Process each xref info.
+ (while xref-value
+ (if (string-match
+ "^[ \t,]*\\([^ \t,]+\\):\\([0-9]+\\)[^0-9]*" xref-value)
+ (progn
+ (setq xref-list
+ (cons
+ (cons
+ ;; Group name
+ (substring xref-value (match-beginning 1) (match-end 1))
+ ;; Article-ID
+ (string-to-int
+ (substring xref-value (match-beginning 2) (match-end 2))))
+ xref-list))
+ (setq xref-value (substring xref-value (match-end 2))))
+ (setq xref-value nil)))
+ ;; Return alist.
+ xref-list
+ ))
+
+(defun gnus-mark-xrefed-as-read (xrefs)
+ "Update unread article information using XREFS alist."
+ (let ((group nil)
+ (idlist nil)
+ (unread nil))
+ (while xrefs
+ (setq group (car (car xrefs)))
+ (setq idlist (cdr (car xrefs)))
+ (setq unread (gnus-uncompress-sequence
+ (nthcdr 2 (gnus-gethash group gnus-unread-hashtb))))
+ (while idlist
+ (setq unread (delq (car idlist) unread))
+ (setq idlist (cdr idlist)))
+ (gnus-update-unread-articles group unread 'ignore)
+ (setq xrefs (cdr xrefs))
+ )))
+
+(defun gnus-update-unread-articles (group unread-list marked-list)
+ "Update unread articles of GROUP using UNREAD-LIST and MARKED-LIST."
+ (let ((active (nth 2 (gnus-gethash group gnus-active-hashtb)))
+ (unread (gnus-gethash group gnus-unread-hashtb)))
+ (if (or (null active) (null unread))
+ ;; Ignore unknown newsgroup.
+ nil
+ ;; Update gnus-unread-hashtb.
+ (if unread-list
+ (setcdr (cdr unread)
+ (gnus-compress-sequence unread-list))
+ ;; All of the articles are read.
+ (setcdr (cdr unread) '((0 . 0))))
+ ;; Number of unread articles.
+ (setcar (cdr unread)
+ (gnus-number-of-articles (nthcdr 2 unread)))
+ ;; Update gnus-newsrc-assoc.
+ (if (> (car active) 0)
+ ;; Articles from 1 to N are not active.
+ (setq active (cons 1 (cdr active))))
+ (setcdr (cdr (assoc group gnus-newsrc-assoc))
+ (gnus-difference-of-range active (nthcdr 2 unread)))
+ ;; Update .newsrc buffer.
+ (gnus-update-newsrc-buffer group)
+ ;; Update gnus-marked-assoc.
+ (if (listp marked-list) ;Includes NIL.
+ (let ((marked (assoc group gnus-marked-assoc)))
+ (cond (marked
+ (setcdr marked marked-list))
+ (marked-list ;Non-NIL.
+ (setq gnus-marked-assoc
+ (cons (cons group marked-list)
+ gnus-marked-assoc)))
+ )))
+ )))
+
+(defun gnus-read-active-file ()
+ "Get active file from NNTP server."
+ (message "Reading active file...")
+ (if (gnus-request-list) ;Get active file from server
+ (save-excursion
+ (set-buffer nntp-server-buffer)
+ ;; Save OLD active info.
+ (setq gnus-octive-hashtb gnus-active-hashtb)
+ (setq gnus-active-hashtb (gnus-make-hashtable))
+ (gnus-active-to-gnus-format)
+ (message "Reading active file... done"))
+ (error "Cannot read active file from NNTP server.")))
+
+(defun gnus-active-to-gnus-format ()
+ "Convert active file format to internal format."
+ ;; Delete unnecessary lines.
+ (goto-char (point-min))
+ (delete-matching-lines "^to\\..*$")
+ ;; Store active file in hashtable.
+ (goto-char (point-min))
+ (while
+ (re-search-forward
+ "^\\([^ \t]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([ymn]\\).*$"
+ nil t)
+ (gnus-sethash
+ (buffer-substring (match-beginning 1) (match-end 1))
+ (list (buffer-substring (match-beginning 1) (match-end 1))
+ (string-equal
+ "y" (buffer-substring (match-beginning 4) (match-end 4)))
+ (cons (string-to-int
+ (buffer-substring (match-beginning 3) (match-end 3)))
+ (string-to-int
+ (buffer-substring (match-beginning 2) (match-end 2)))))
+ gnus-active-hashtb)))
+
+(defun gnus-read-newsrc-file (&optional rawfile)
+ "Read startup FILE.
+If optional argument RAWFILE is non-nil, the raw startup file is read."
+ (setq gnus-current-startup-file
+ (let* ((file (expand-file-name gnus-startup-file nil))
+ (real-file (concat file "-" gnus-nntp-server)))
+ (if (file-exists-p real-file)
+ real-file file)))
+ ;; Reset variables which may be included in the quick startup file.
+ (let ((variables gnus-variable-list))
+ (while variables
+ (set (car variables) nil)
+ (setq variables (cdr variables))))
+ (let* ((newsrc-file gnus-current-startup-file)
+ (quick-file (concat newsrc-file ".el"))
+ (quick-loaded nil)
+ (newsrc-mod (nth 5 (file-attributes newsrc-file)))
+ (quick-mod (nth 5 (file-attributes quick-file))))
+ (save-excursion
+ ;; Prepare .newsrc buffer.
+ (set-buffer (find-file-noselect newsrc-file))
+ ;; It is not so good idea turning off undo.
+ ;;(buffer-flush-undo (current-buffer))
+ ;; Load quick .newsrc to restore gnus-marked-assoc and
+ ;; gnus-killed-assoc even if gnus-newsrc-assoc is out of date.
+ (condition-case nil
+ (setq quick-loaded (load quick-file t t t))
+ (error nil))
+ (cond ((and (not rawfile) ;Not forced to read the raw file.
+ (or (and (fboundp 'file-newer-than-file-p)
+ (file-newer-than-file-p quick-file newsrc-file))
+ (and newsrc-mod quick-mod
+ ;; .newsrc.el is newer than .newsrc.
+ ;; Some older version does not support function
+ ;; `file-newer-than-file-p'.
+ (or (< (car newsrc-mod) (car quick-mod))
+ (and (= (car newsrc-mod) (car quick-mod))
+ (<= (nth 1 newsrc-mod) (nth 1 quick-mod))))
+ ))
+ quick-loaded
+ gnus-newsrc-assoc ;Really loaded?
+ )
+ ;; We don't have to read the raw startup file.
+ )
+ (t
+ ;; Since .newsrc file is newer than quick file, read it.
+ (message "Reading %s..." newsrc-file)
+ (gnus-newsrc-to-gnus-format)
+ (gnus-check-killed-newsgroups)
+ (message "Reading %s... Done" newsrc-file)))
+ )))
+
+(defun gnus-make-newsrc-file (file)
+ "Make server dependent file name by catenating FILE and server host name."
+ (let* ((file (expand-file-name file nil))
+ (real-file (concat file "-" gnus-nntp-server)))
+ (if (file-exists-p real-file)
+ real-file file)
+ ))
+
+(defun gnus-newsrc-to-gnus-format ()
+ "Parse current buffer as .newsrc file."
+ (let ((newsgroup nil)
+ (subscribe nil)
+ (ranges nil)
+ (subrange nil)
+ (read-list nil))
+ ;; We have to re-initialize these variable (except for
+ ;; gnus-marked-assoc and gnus-killed-assoc) because quick startup
+ ;; file may contain bogus values.
+ (setq gnus-newsrc-options nil)
+ (setq gnus-newsrc-options-n-yes nil)
+ (setq gnus-newsrc-options-n-no nil)
+ (setq gnus-newsrc-assoc nil)
+ ;; Save options line to variable.
+ ;; Lines beginning with white spaces are treated as continuation
+ ;; line. Refer man page of newsrc(5).
+ (goto-char (point-min))
+ (if (re-search-forward
+ "^[ \t]*options[ \t]*\\(.*\\(\n[ \t]+.*\\)*\\)[ \t]*$" nil t)
+ (progn
+ ;; Save entire options line.
+ (setq gnus-newsrc-options
+ (buffer-substring (match-beginning 1) (match-end 1)))
+ ;; Compile "-n" option.
+ (if (string-match "\\(^\\|[ \t\n]\\)-n" gnus-newsrc-options)
+ (let ((yes-and-no
+ (gnus-parse-n-options
+ (substring gnus-newsrc-options (match-end 0)))))
+ (setq gnus-newsrc-options-n-yes (car yes-and-no))
+ (setq gnus-newsrc-options-n-no (cdr yes-and-no))
+ ))
+ ))
+ ;; Parse body of .newsrc file
+ ;; Options line continuation lines must be also considered here.
+ ;; Before supporting continuation lines, " newsgroup ! 1-5" was
+ ;; okay, but now it is invalid. It should be "newsgroup! 1-5".
+ (goto-char (point-min))
+ ;; Due to overflows in regex.c, change the following regexp:
+ ;; "^\\([^:! \t\n]+\\)\\([:!]\\)[ \t]*\\(.*\\)$"
+ ;; Suggested by composer@bucsf.bu.edu (Jeff Kellem).
+ (while (re-search-forward
+ "^\\([^:! \t\n]+\\)\\([:!]\\)[ \t]*\\(\\(...\\)*.*\\)$" nil t)
+ (setq newsgroup (buffer-substring (match-beginning 1) (match-end 1)))
+ ;; Check duplications of newsgroups.
+ ;; Note: Checking the duplications takes very long time.
+ (if (assoc newsgroup gnus-newsrc-assoc)
+ (message "Ignore duplicated newsgroup: %s" newsgroup)
+ (setq subscribe
+ (string-equal
+ ":" (buffer-substring (match-beginning 2) (match-end 2))))
+ (setq ranges (buffer-substring (match-beginning 3) (match-end 3)))
+ (setq read-list nil)
+ (while (string-match "^[, \t]*\\([0-9-]+\\)" ranges)
+ (setq subrange (substring ranges (match-beginning 1) (match-end 1)))
+ (setq ranges (substring ranges (match-end 1)))
+ (cond ((string-match "^\\([0-9]+\\)-\\([0-9]+\\)$" subrange)
+ (setq read-list
+ (cons
+ (cons (string-to-int
+ (substring subrange
+ (match-beginning 1) (match-end 1)))
+ (string-to-int
+ (substring subrange
+ (match-beginning 2) (match-end 2))))
+ read-list)))
+ ((string-match "^[0-9]+$" subrange)
+ (setq read-list
+ (cons (cons (string-to-int subrange)
+ (string-to-int subrange))
+ read-list)))
+ (t
+ (ding) (message "Ignoring bogus lines of %s" newsgroup)
+ (sit-for 0))
+ ))
+ (setq gnus-newsrc-assoc
+ (cons (cons newsgroup (cons subscribe (nreverse read-list)))
+ gnus-newsrc-assoc))
+ ))
+ (setq gnus-newsrc-assoc
+ (nreverse gnus-newsrc-assoc))
+ ))
+
+(defun gnus-parse-n-options (options)
+ "Parse -n NEWSGROUPS options and return a cons of YES and NO regexps."
+ (let ((yes nil)
+ (no nil)
+ (yes-or-no nil) ;`!' or not.
+ (newsgroup nil))
+ ;; Parse each newsgroup description such as "comp.all". Commas
+ ;; and white spaces can be a newsgroup separator.
+ (while
+ (string-match "^[ \t\n,]*\\(!?\\)\\([^--- \t\n,][^ \t\n,]*\\)" options)
+ (setq yes-or-no
+ (substring options (match-beginning 1) (match-end 1)))
+ (setq newsgroup
+ (regexp-quote
+ (substring options
+ (match-beginning 2) (match-end 2))))
+ (setq options (substring options (match-end 2)))
+ ;; Rewrite "all" to ".+" not ".*". ".+" requires at least one
+ ;; character.
+ (while (string-match "\\(^\\|\\\\[.]\\)all\\(\\\\[.]\\|$\\)" newsgroup)
+ (setq newsgroup
+ (concat (substring newsgroup 0 (match-end 1))
+ ".+"
+ (substring newsgroup (match-beginning 2)))))
+ (cond ((string-equal yes-or-no "!")
+ (setq no (cons newsgroup no)))
+ ((string-equal newsgroup ".+")) ;Ignore `all'.
+ (t
+ (setq yes (cons newsgroup yes)))
+ ))
+ ;; Make a cons of regexps from parsing result.
+ (cons (if yes
+ (concat "^\\("
+ (apply (function concat)
+ (mapcar
+ (function
+ (lambda (newsgroup)
+ (concat newsgroup "\\|")))
+ (cdr yes)))
+ (car yes) "\\)"))
+ (if no
+ (concat "^\\("
+ (apply (function concat)
+ (mapcar
+ (function
+ (lambda (newsgroup)
+ (concat newsgroup "\\|")))
+ (cdr no)))
+ (car no) "\\)")))
+ ))
+
+(defun gnus-save-newsrc-file ()
+ "Save to .newsrc FILE."
+ ;; Note: We cannot save .newsrc file if all newsgroups are removed
+ ;; from the variable gnus-newsrc-assoc.
+ (and (or gnus-newsrc-assoc gnus-killed-assoc)
+ gnus-current-startup-file
+ (save-excursion
+ ;; A buffer containing .newsrc file may be deleted.
+ (set-buffer (find-file-noselect gnus-current-startup-file))
+ (if (not (buffer-modified-p))
+ (message "(No changes need to be saved)")
+ (message "Saving %s..." gnus-current-startup-file)
+ (let ((make-backup-files t)
+ (version-control nil)
+ (require-final-newline t)) ;Don't ask even if requested.
+ ;; Make backup file of master newsrc.
+ ;; You can stop or change version control of backup file.
+ ;; Suggested by jason@violet.berkeley.edu.
+ (run-hooks 'gnus-Save-newsrc-hook)
+ (save-buffer))
+ ;; Quickly loadable .newsrc.
+ (set-buffer (get-buffer-create " *GNUS-newsrc*"))
+ (buffer-flush-undo (current-buffer))
+ (erase-buffer)
+ (gnus-gnus-to-quick-newsrc-format)
+ (let ((make-backup-files nil)
+ (version-control nil)
+ (require-final-newline t)) ;Don't ask even if requested.
+ (write-file (concat gnus-current-startup-file ".el")))
+ (kill-buffer (current-buffer))
+ (message "Saving %s... Done" gnus-current-startup-file)
+ ))
+ ))
+
+(defun gnus-update-newsrc-buffer (group &optional delete next)
+ "Incrementally update .newsrc buffer about GROUP.
+If optional second argument DELETE is non-nil, delete the group.
+If optional third argument NEXT is non-nil, inserted before it."
+ (save-excursion
+ ;; Taking account of the killed startup file.
+ ;; Suggested by tale@pawl.rpi.edu.
+ (set-buffer (or (get-file-buffer gnus-current-startup-file)
+ (find-file-noselect gnus-current-startup-file)))
+ ;; Options line continuation lines must be also considered here.
+ ;; Before supporting continuation lines, " newsgroup ! 1-5" was
+ ;; okay, but now it is invalid. It should be "newsgroup! 1-5".
+ (let ((deleted nil)
+ (buffer-read-only nil)) ;May be not modifiable.
+ ;; Delete ALL entries which match for GROUP.
+ (goto-char (point-min))
+ (while (re-search-forward
+ (concat "^" (regexp-quote group) "[:!]") nil t)
+ (beginning-of-line)
+ (delete-region (point) (progn (forward-line 1) (point)))
+ (setq deleted t) ;Old entry is deleted.
+ )
+ (if delete
+ nil
+ ;; Insert group entry.
+ (let ((newsrc (assoc group gnus-newsrc-assoc)))
+ (if (null newsrc)
+ nil
+ ;; Find insertion point.
+ (cond (deleted nil) ;Insert here.
+ ((and (stringp next)
+ (progn
+ (goto-char (point-min))
+ (re-search-forward
+ (concat "^" (regexp-quote next) "[:!]") nil t)))
+ (beginning-of-line))
+ (t
+ (goto-char (point-max))
+ (or (bolp)
+ (insert "\n"))))
+ ;; Insert after options line.
+ (if (looking-at "^[ \t]*options\\([ \t]\\|$\\)")
+ (progn
+ (forward-line 1)
+ ;; Skip continuation lines.
+ (while (and (not (eobp))
+ (looking-at "^[ \t]+"))
+ (forward-line 1))))
+ (insert group ;Group name
+ (if (nth 1 newsrc) ": " "! ")) ;Subscribed?
+ (gnus-ranges-to-newsrc-format (nthcdr 2 newsrc)) ;Read articles
+ (insert "\n")
+ )))
+ )))
+
+(defun gnus-gnus-to-quick-newsrc-format ()
+ "Insert GNUS variables such as `gnus-newsrc-assoc' in Lisp format."
+ (insert ";; GNUS internal format of .newsrc.\n")
+ (insert ";; Touch .newsrc instead if you think to remove this file.\n")
+ (let ((variable nil)
+ (variables gnus-variable-list)
+ ;; Temporary rebind to make changes invisible.
+ (gnus-killed-assoc gnus-killed-assoc))
+ ;; Remove duplicated or unsubscribed newsgroups in gnus-killed-assoc.
+ (gnus-check-killed-newsgroups)
+ ;; Then, insert lisp expressions.
+ (while variables
+ (setq variable (car variables))
+ (and (boundp variable)
+ (symbol-value variable)
+ (insert "(setq " (symbol-name variable) " '"
+ (prin1-to-string (symbol-value variable))
+ ")\n"))
+ (setq variables (cdr variables)))
+ ))
+
+(defun gnus-ranges-to-newsrc-format (ranges)
+ "Insert ranges of read articles."
+ (let ((range nil)) ;Range is a pair of BEGIN and END.
+ (while ranges
+ (setq range (car ranges))
+ (setq ranges (cdr ranges))
+ (cond ((= (car range) (cdr range))
+ (if (= (car range) 0)
+ (setq ranges nil) ;No unread articles.
+ (insert (int-to-string (car range)))
+ (if ranges (insert ","))
+ ))
+ (t
+ (insert (int-to-string (car range))
+ "-"
+ (int-to-string (cdr range)))
+ (if ranges (insert ","))
+ ))
+ )))
+
+(defun gnus-compress-sequence (numbers)
+ "Convert list of sorted numbers to ranges."
+ (let* ((numbers (sort (copy-sequence numbers) (function <)))
+ (first (car numbers))
+ (last (car numbers))
+ (result nil))
+ (while numbers
+ (cond ((= last (car numbers)) nil) ;Omit duplicated number
+ ((= (1+ last) (car numbers)) ;Still in sequence
+ (setq last (car numbers)))
+ (t ;End of one sequence
+ (setq result (cons (cons first last) result))
+ (setq first (car numbers))
+ (setq last (car numbers)))
+ )
+ (setq numbers (cdr numbers))
+ )
+ (nreverse (cons (cons first last) result))
+ ))
+
+(defun gnus-uncompress-sequence (ranges)
+ "Expand compressed format of sequence."
+ (let ((first nil)
+ (last nil)
+ (result nil))
+ (while ranges
+ (setq first (car (car ranges)))
+ (setq last (cdr (car ranges)))
+ (while (< first last)
+ (setq result (cons first result))
+ (setq first (1+ first)))
+ (setq result (cons first result))
+ (setq ranges (cdr ranges))
+ )
+ (nreverse result)
+ ))
+
+(defun gnus-number-of-articles (range)
+ "Compute number of articles from RANGE `((beg1 . end1) (beg2 . end2) ...)'."
+ (let ((count 0))
+ (while range
+ (if (/= (cdr (car range)) 0)
+ ;; If end1 is 0, it must be skipped. Usually no articles in
+ ;; this group.
+ (setq count (+ count 1 (- (cdr (car range)) (car (car range))))))
+ (setq range (cdr range))
+ )
+ count ;Result
+ ))
+
+(defun gnus-difference-of-range (src obj)
+ "Compute (SRC - OBJ) on range.
+Range of SRC is expressed as `(beg . end)'.
+Range of OBJ is expressed as `((beg1 . end1) (beg2 . end2) ...)."
+ (let ((beg (car src))
+ (end (cdr src))
+ (range nil)) ;This is result.
+ ;; Src may be nil.
+ (while (and src obj)
+ (let ((beg1 (car (car obj)))
+ (end1 (cdr (car obj))))
+ (cond ((> beg end)
+ (setq obj nil)) ;Terminate loop
+ ((< beg beg1)
+ (setq range (cons (cons beg (min (1- beg1) end)) range))
+ (setq beg (1+ end1)))
+ ((>= beg beg1)
+ (setq beg (max beg (1+ end1))))
+ )
+ (setq obj (cdr obj)) ;Next OBJ
+ ))
+ ;; Src may be nil.
+ (if (and src (<= beg end))
+ (setq range (cons (cons beg end) range)))
+ ;; Result
+ (if range
+ (nreverse range)
+ (list (cons 0 0)))
+ ))
+
+
+;;Local variables:
+;;eval: (put 'gnus-eval-in-buffer-window 'lisp-indent-function 1)
+;;end:
diff --git a/lisp/informat.el b/lisp/informat.el
new file mode 100644
index 00000000000..1f91cb5b8be
--- /dev/null
+++ b/lisp/informat.el
@@ -0,0 +1,415 @@
+;; Info support functions package for Emacs
+;; Copyright (C) 1986 Free Software Foundation, Inc.
+
+;; 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 1, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+(require 'info)
+
+;;;###autoload
+(defun Info-tagify ()
+ "Create or update Info-file tag table in current buffer."
+ (interactive)
+ ;; Save and restore point and restrictions.
+ ;; save-restrictions would not work
+ ;; because it records the old max relative to the end.
+ ;; We record it relative to the beginning.
+ (message "Tagifying %s ..." (file-name-nondirectory (buffer-file-name)))
+ (let ((omin (point-min))
+ (omax (point-max))
+ (nomax (= (point-max) (1+ (buffer-size))))
+ (opoint (point)))
+ (unwind-protect
+ (progn
+ (widen)
+ (goto-char (point-min))
+ (if (search-forward "\^_\nIndirect:\n" nil t)
+ (message "Cannot tagify split info file")
+ (let ((regexp "Node:[ \t]*\\([^,\n\t]\\)*[,\t\n]")
+ (case-fold-search t)
+ list)
+ (while (search-forward "\n\^_" nil t)
+ (forward-line 1)
+ (let ((beg (point)))
+ (forward-line 1)
+ (if (re-search-backward regexp beg t)
+ (setq list
+ (cons (list (buffer-substring
+ (match-beginning 1)
+ (match-end 1))
+ beg)
+ list)))))
+ (goto-char (point-max))
+ (forward-line -8)
+ (let ((buffer-read-only nil))
+ (if (search-forward "\^_\nEnd tag table\n" nil t)
+ (let ((end (point)))
+ (search-backward "\nTag table:\n")
+ (beginning-of-line)
+ (delete-region (point) end)))
+ (goto-char (point-max))
+ (insert "\^_\f\nTag table:\n")
+ (move-marker Info-tag-table-marker (point))
+ (setq list (nreverse list))
+ (while list
+ (insert "Node: " (car (car list)) ?\177)
+ (princ (car (cdr (car list))) (current-buffer))
+ (insert ?\n)
+ (setq list (cdr list)))
+ (insert "\^_\nEnd tag table\n")))))
+ (goto-char opoint)
+ (narrow-to-region omin (if nomax (1+ (buffer-size))
+ (min omax (point-max))))))
+ (message "Tagifying %s ... done" (file-name-nondirectory (buffer-file-name))))
+
+;;;###autoload
+(defun Info-split ()
+ "Split an info file into an indirect file plus bounded-size subfiles.
+Each subfile will be up to 50,000 characters plus one node.
+
+To use this command, first visit a large Info file that has a tag
+table. The buffer is modified into a (small) indirect info file which
+should be saved in place of the original visited file.
+
+The subfiles are written in the same directory the original file is
+in, with names generated by appending `-' and a number to the original
+file name. The indirect file still functions as an Info file, but it
+contains just the tag table and a directory of subfiles."
+
+ (interactive)
+ (if (< (buffer-size) 70000)
+ (error "This is too small to be worth splitting"))
+ (goto-char (point-min))
+ (search-forward "\^_")
+ (forward-char -1)
+ (let ((start (point))
+ (chars-deleted 0)
+ subfiles
+ (subfile-number 1)
+ (case-fold-search t)
+ (filename (file-name-sans-versions buffer-file-name)))
+ (goto-char (point-max))
+ (forward-line -8)
+ (setq buffer-read-only nil)
+ (or (search-forward "\^_\nEnd tag table\n" nil t)
+ (error "Tag table required; use M-x Info-tagify"))
+ (search-backward "\nTag table:\n")
+ (if (looking-at "\nTag table:\n\^_")
+ (error "Tag table is just a skeleton; use M-x Info-tagify"))
+ (beginning-of-line)
+ (forward-char 1)
+ (save-restriction
+ (narrow-to-region (point-min) (point))
+ (goto-char (point-min))
+ (while (< (1+ (point)) (point-max))
+ (goto-char (min (+ (point) 50000) (point-max)))
+ (search-forward "\^_" nil 'move)
+ (setq subfiles
+ (cons (list (+ start chars-deleted)
+ (concat (file-name-nondirectory filename)
+ (format "-%d" subfile-number)))
+ subfiles))
+ ;; Put a newline at end of split file, to make Unix happier.
+ (insert "\n")
+ (write-region (point-min) (point)
+ (concat filename (format "-%d" subfile-number)))
+ (delete-region (1- (point)) (point))
+ ;; Back up over the final ^_.
+ (forward-char -1)
+ (setq chars-deleted (+ chars-deleted (- (point) start)))
+ (delete-region start (point))
+ (setq subfile-number (1+ subfile-number))))
+ (while subfiles
+ (goto-char start)
+ (insert (nth 1 (car subfiles))
+ (format ": %d" (car (car subfiles)))
+ "\n")
+ (setq subfiles (cdr subfiles)))
+ (goto-char start)
+ (insert "\^_\nIndirect:\n")
+ (search-forward "\nTag Table:\n")
+ (insert "(Indirect)\n")))
+
+;;;###autoload
+(defun Info-validate ()
+ "Check current buffer for validity as an Info file.
+Check that every node pointer points to an existing node."
+ (interactive)
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (if (search-forward "\nTag table:\n(Indirect)\n" nil t)
+ (error "Don't yet know how to validate indirect info files: \"%s\""
+ (buffer-name (current-buffer))))
+ (goto-char (point-min))
+ (let ((allnodes '(("*")))
+ (regexp "Node:[ \t]*\\([^,\n\t]*\\)[,\t\n]")
+ (case-fold-search t)
+ (tags-losing nil)
+ (lossages ()))
+ (while (search-forward "\n\^_" nil t)
+ (forward-line 1)
+ (let ((beg (point)))
+ (forward-line 1)
+ (if (re-search-backward regexp beg t)
+ (let ((name (downcase
+ (buffer-substring
+ (match-beginning 1)
+ (progn
+ (goto-char (match-end 1))
+ (skip-chars-backward " \t")
+ (point))))))
+ (if (assoc name allnodes)
+ (setq lossages
+ (cons (list name "Duplicate node-name" nil)
+ lossages))
+ (setq allnodes
+ (cons (list name
+ (progn
+ (end-of-line)
+ (and (re-search-backward
+ "prev[ious]*:" beg t)
+ (progn
+ (goto-char (match-end 0))
+ (downcase
+ (Info-following-node-name)))))
+ beg)
+ allnodes)))))))
+ (goto-char (point-min))
+ (while (search-forward "\n\^_" nil t)
+ (forward-line 1)
+ (let ((beg (point))
+ thisnode next)
+ (forward-line 1)
+ (if (re-search-backward regexp beg t)
+ (save-restriction
+ (search-forward "\n\^_" nil 'move)
+ (narrow-to-region beg (point))
+ (setq thisnode (downcase
+ (buffer-substring
+ (match-beginning 1)
+ (progn
+ (goto-char (match-end 1))
+ (skip-chars-backward " \t")
+ (point)))))
+ (end-of-line)
+ (and (search-backward "next:" nil t)
+ (setq next (Info-validate-node-name "invalid Next"))
+ (assoc next allnodes)
+ (if (equal (car (cdr (assoc next allnodes)))
+ thisnode)
+ ;; allow multiple `next' pointers to one node
+ (let ((tem lossages))
+ (while tem
+ (if (and (equal (car (cdr (car tem)))
+ "should have Previous")
+ (equal (car (car tem))
+ next))
+ (setq lossages (delq (car tem) lossages)))
+ (setq tem (cdr tem))))
+ (setq lossages
+ (cons (list next
+ "should have Previous"
+ thisnode)
+ lossages))))
+ (end-of-line)
+ (if (re-search-backward "prev[ious]*:" nil t)
+ (Info-validate-node-name "invalid Previous"))
+ (end-of-line)
+ (if (search-backward "up:" nil t)
+ (Info-validate-node-name "invalid Up"))
+ (if (re-search-forward "\n* Menu:" nil t)
+ (while (re-search-forward "\n\\* " nil t)
+ (Info-validate-node-name
+ (concat "invalid menu item "
+ (buffer-substring (point)
+ (save-excursion
+ (skip-chars-forward "^:")
+ (point))))
+ (Info-extract-menu-node-name))))
+ (goto-char (point-min))
+ (while (re-search-forward "\\*note[ \n]*[^:\t]*:" nil t)
+ (goto-char (+ (match-beginning 0) 5))
+ (skip-chars-forward " \n")
+ (Info-validate-node-name
+ (concat "invalid reference "
+ (buffer-substring (point)
+ (save-excursion
+ (skip-chars-forward "^:")
+ (point))))
+ (Info-extract-menu-node-name "Bad format cross-reference")))))))
+ (setq tags-losing (not (Info-validate-tags-table)))
+ (if (or lossages tags-losing)
+ (with-output-to-temp-buffer " *problems in info file*"
+ (while lossages
+ (princ "In node \"")
+ (princ (car (car lossages)))
+ (princ "\", ")
+ (let ((tem (nth 1 (car lossages))))
+ (cond ((string-match "\n" tem)
+ (princ (substring tem 0 (match-beginning 0)))
+ (princ "..."))
+ (t
+ (princ tem))))
+ (if (nth 2 (car lossages))
+ (progn
+ (princ ": ")
+ (let ((tem (nth 2 (car lossages))))
+ (cond ((string-match "\n" tem)
+ (princ (substring tem 0 (match-beginning 0)))
+ (princ "..."))
+ (t
+ (princ tem))))))
+ (terpri)
+ (setq lossages (cdr lossages)))
+ (if tags-losing (princ "\nTags table must be recomputed\n")))
+ ;; Here if info file is valid.
+ ;; If we already made a list of problems, clear it out.
+ (save-excursion
+ (if (get-buffer " *problems in info file*")
+ (progn
+ (set-buffer " *problems in info file*")
+ (kill-buffer (current-buffer)))))
+ (message "File appears valid"))))))
+
+(defun Info-validate-node-name (kind &optional name)
+ (if name
+ nil
+ (goto-char (match-end 0))
+ (skip-chars-forward " \t")
+ (if (= (following-char) ?\()
+ nil
+ (setq name
+ (buffer-substring
+ (point)
+ (progn
+ (skip-chars-forward "^,\t\n")
+ (skip-chars-backward " ")
+ (point))))))
+ (if (null name)
+ nil
+ (setq name (downcase name))
+ (or (and (> (length name) 0) (= (aref name 0) ?\())
+ (assoc name allnodes)
+ (setq lossages
+ (cons (list thisnode kind name) lossages))))
+ name)
+
+(defun Info-validate-tags-table ()
+ (goto-char (point-min))
+ (if (not (search-forward "\^_\nEnd tag table\n" nil t))
+ t
+ (not (catch 'losing
+ (let* ((end (match-beginning 0))
+ (start (progn (search-backward "\nTag table:\n")
+ (1- (match-end 0))))
+ tem)
+ (setq tem allnodes)
+ (while tem
+ (goto-char start)
+ (or (equal (car (car tem)) "*")
+ (search-forward (concat "Node: "
+ (car (car tem))
+ "\177")
+ end t)
+ (throw 'losing 'x))
+ (setq tem (cdr tem)))
+ (goto-char (1+ start))
+ (while (looking-at ".*Node: \\(.*\\)\177\\([0-9]+\\)$")
+ (setq tem (downcase (buffer-substring
+ (match-beginning 1)
+ (match-end 1))))
+ (setq tem (assoc tem allnodes))
+ (if (or (not tem)
+ (< 1000 (progn
+ (goto-char (match-beginning 2))
+ (setq tem (- (car (cdr (cdr tem)))
+ (read (current-buffer))))
+ (if (> tem 0) tem (- tem)))))
+ (throw 'losing 'y)))
+ (forward-line 1))
+ (or (looking-at "End tag table\n")
+ (throw 'losing 'z))
+ nil))))
+
+;;;###autoload
+(defun batch-info-validate ()
+ "Runs `Info-validate' on the files remaining on the command line.
+Must be used only with -batch, and kills Emacs on completion.
+Each file will be processed even if an error occurred previously.
+For example, invoke \"emacs -batch -f batch-info-validate $info/ ~/*.info\""
+ (if (not noninteractive)
+ (error "batch-info-validate may only be used -batch."))
+ (let ((version-control t)
+ (auto-save-default nil)
+ (find-file-run-dired nil)
+ (kept-old-versions 259259)
+ (kept-new-versions 259259))
+ (let ((error 0)
+ file
+ (files ()))
+ (while command-line-args-left
+ (setq file (expand-file-name (car command-line-args-left)))
+ (cond ((not (file-exists-p file))
+ (message ">> %s does not exist!" file)
+ (setq error 1
+ command-line-args-left (cdr command-line-args-left)))
+ ((file-directory-p file)
+ (setq command-line-args-left (nconc (directory-files file)
+ (cdr command-line-args-left))))
+ (t
+ (setq files (cons file files)
+ command-line-args-left (cdr command-line-args-left)))))
+ (while files
+ (setq file (car files)
+ files (cdr files))
+ (let ((lose nil))
+ (condition-case err
+ (progn
+ (if buffer-file-name (kill-buffer (current-buffer)))
+ (find-file file)
+ (buffer-disable-undo (current-buffer))
+ (set-buffer-modified-p nil)
+ (fundamental-mode)
+ (let ((case-fold-search nil))
+ (goto-char (point-max))
+ (cond ((search-backward "\n\^_\^L\nTag table:\n" nil t)
+ (message "%s already tagified" file))
+ ((< (point-max) 30000)
+ (message "%s too small to bother tagifying" file))
+ (t
+ (Info-tagify file))))
+ (let ((loss-name " *problems in info file*"))
+ (message "Checking validity of info file %s..." file)
+ (if (get-buffer loss-name)
+ (kill-buffer loss-name))
+ (Info-validate)
+ (if (not (get-buffer loss-name))
+ nil ;(message "Checking validity of info file %s... OK" file)
+ (message "----------------------------------------------------------------------")
+ (message ">> PROBLEMS IN INFO FILE %s" file)
+ (save-excursion
+ (set-buffer loss-name)
+ (princ (buffer-substring (point-min) (point-max))))
+ (message "----------------------------------------------------------------------")
+ (setq error 1 lose t)))
+ (if (and (buffer-modified-p)
+ (not lose))
+ (progn (message "Saving modified %s" file)
+ (save-buffer))))
+ (error (message ">> Error: %s" (prin1-to-string err))))))
+ (kill-emacs error))))
diff --git a/lisp/progmodes/awk-mode.el b/lisp/progmodes/awk-mode.el
new file mode 100644
index 00000000000..7b70f82b748
--- /dev/null
+++ b/lisp/progmodes/awk-mode.el
@@ -0,0 +1,83 @@
+;; C code editing commands for Emacs
+;; Copyright (C) 1988 Free Software Foundation, Inc.
+
+;; 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 1, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+
+(defvar awk-mode-syntax-table nil
+ "Syntax table in use in Awk-mode buffers.")
+
+(if awk-mode-syntax-table
+ ()
+ (setq awk-mode-syntax-table (make-syntax-table))
+ (modify-syntax-entry ?\\ "\\" awk-mode-syntax-table)
+ (modify-syntax-entry ?\n "> " emacs-lisp-mode-syntax-table)
+ (modify-syntax-entry ?\f "> " emacs-lisp-mode-syntax-table)
+ (modify-syntax-entry ?\# "< " emacs-lisp-mode-syntax-table)
+ (modify-syntax-entry ?/ "." awk-mode-syntax-table)
+ (modify-syntax-entry ?* "." awk-mode-syntax-table)
+ (modify-syntax-entry ?+ "." awk-mode-syntax-table)
+ (modify-syntax-entry ?- "." awk-mode-syntax-table)
+ (modify-syntax-entry ?= "." awk-mode-syntax-table)
+ (modify-syntax-entry ?% "." awk-mode-syntax-table)
+ (modify-syntax-entry ?< "." awk-mode-syntax-table)
+ (modify-syntax-entry ?> "." awk-mode-syntax-table)
+ (modify-syntax-entry ?& "." awk-mode-syntax-table)
+ (modify-syntax-entry ?| "." awk-mode-syntax-table)
+ (modify-syntax-entry ?\' "\"" awk-mode-syntax-table))
+
+(defvar awk-mode-abbrev-table nil
+ "Abbrev table in use in Awk-mode buffers.")
+(define-abbrev-table 'awk-mode-abbrev-table ())
+
+;;;###autoload
+(defun awk-mode ()
+ "Major mode for editing AWK code.
+This is much like C mode except for the syntax of comments. It uses
+the same keymap as C mode and has the same variables for customizing
+indentation. It has its own abbrev table and its own syntax table.
+
+Turning on AWK mode calls the value of the variable `awk-mode-hook'
+with no args, if that value is non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ (use-local-map c-mode-map)
+ (setq major-mode 'awk-mode)
+ (setq mode-name "AWK")
+ (setq local-abbrev-table awk-mode-abbrev-table)
+ (set-syntax-table awk-mode-syntax-table)
+ (make-local-variable 'paragraph-start)
+ (setq paragraph-start (concat "^$\\|" page-delimiter))
+ (make-local-variable 'paragraph-separate)
+ (setq paragraph-separate paragraph-start)
+ (make-local-variable 'paragraph-ignore-fill-prefix)
+ (setq paragraph-ignore-fill-prefix t)
+ (make-local-variable 'indent-line-function)
+ (setq indent-line-function 'awk-indent-line)
+ (make-local-variable 'require-final-newline)
+ (setq require-final-newline t)
+ (make-local-variable 'comment-start)
+ (setq comment-start "# ")
+ (make-local-variable 'comment-end)
+ (setq comment-end "")
+ (make-local-variable 'comment-column)
+ (setq comment-column 32)
+ (make-local-variable 'comment-start-skip)
+ (setq comment-start-skip "#+ *")
+ (make-local-variable 'comment-indent-hook)
+ (setq comment-indent-hook 'c-comment-indent)
+ (run-hooks 'awk-mode-hook))
diff --git a/lisp/progmodes/cplus-md.el b/lisp/progmodes/cplus-md.el
new file mode 100644
index 00000000000..f5082895e1e
--- /dev/null
+++ b/lisp/progmodes/cplus-md.el
@@ -0,0 +1,966 @@
+;; C++ code editing commands for Emacs
+;; 1987 Dave Detlefs (dld@cs.cmu.edu)
+;; and Stewart Clamen (clamen@cs.cmu.edu).
+;; Done by fairly faithful modification of:
+;; c-mode.el, Copyright (C) 1985 Richard M. Stallman.
+;;
+;; Feb, 1990 (Dave Detlefs, dld@cs.cmu.edu)
+;; Fixed electric-c++-terminator to handle double colons, at the
+;; request of John Hagerman.
+;;
+;; Jan, 1990 (Doug Lea, dl@oswego.edu)
+;; Replaced c++-comment-region and c++-uncomment-region with
+;; versions from Igor Metz that avoid potential infinite loops.
+;;
+;; Oct, 1989 (Dave Detlefs, dld@cs.cmu.edu)
+;; Added contribution from Igor Metz <metz@iam.unibe.ch>:
+;; functions c++-comment-region and c++-uncomment-region and
+;; corresponding key-binding.
+;; Also fixed bug in indentation of second line after an empty
+;; arglist with empty-arglist non-null.
+;;
+;; Sept, 1989 (Glen Ditchfield, gjditchfield@violet.uwaterloo.ca):
+;; Textual changes to more closely imitate Emacs 18.55's c-mode.
+;; Fixed handling of "default:", where ":" was the last character in the
+;; buffer. Fixed indentation of comments starting in column 0, and when
+;; previous line contained more than one comment start string. Fixed
+;; handling of "friend class".
+;;
+;; Aug 7, 1989; John Hagerman (hagerman@ece.cmu.edu):
+;; Changed calculate-c++-indent to handle member initializations
+;; more flexibly. Two new variables are used to control behavior:
+;; c++-member-init-indent and c++-continued-member-init-offset.
+;; Note the assumption that member initializations and argument
+;; declarations are not mixed in one function definition.
+;;
+;; June 1989 (Dave Detlefs, dld@cs.cmu.edu)
+;; Fixed calculate-c++-indent to handle continued lines ending in
+;; {'s. (I wasn't following C-mode closely enough, or C-mode
+;; changed.) Made ' a quote character, at the behest of someone
+;; whose mail I apparently deleted (if they send me mail I'll credit
+;; them here in a future revision.)
+;; Dan Weinreb (dlw@odi.com) pointed out that 'c++-mode successively
+;; bound c++-indent-exp and c++-indent-defun to ESC-^q. ESC-^q is
+;; now bound to c++-indent-exp, while, c++-indent-defun is invoked
+;; with ESC-^x.
+
+;; February 1989 (Dave Detlefs, dld@cs.cmu.edu)
+;; Fixed some errors in c++-indent-defun, as pointed out by Sam
+;; Haradhvala (odi!sam@talcott.harvard.edu).
+;; October 1988 (Dave Detlefs, dld@cs.cmu.edu)
+;; It turns out I had only *thought* I had made
+;; beginning(end)-of-defun work. It should work better now -- you
+;; can either attempt to match defun headers "strongly," using a
+;; very complicated regexp, or "weakly," using a simple one. This
+;; is settable by a variable; the default is the cheaper weak
+;; method. (Stewart Clamen was intimately involved in this, too.)
+;;
+;; I made "'" *not* be a string delimiter, because that was causing
+;; comments containing contractions to ("// don't") to mess up paren
+;; balancing.
+;;
+;; I also incorporated another slight indentation fix from Glen
+;; Ditchfield.
+;;
+;; We hope this is will make into version 19 of gnu-emacs.
+;;
+;; September 1988: incorporated changes from Fred Calm at Schlumberger.
+;; Also, made beginning(end)-of-defun, indent-defun work.
+;;
+;; August 1987: incorporated changes done by Glen Ditchfield of Waterloo.
+
+(defvar c++-mode-abbrev-table nil
+ "Abbrev table used in C++ mode.")
+(define-abbrev-table 'c++-mode-abbrev-table ())
+
+(defvar c++-mode-map ()
+ "Keymap used in C++ mode.")
+(if c++-mode-map
+ ()
+ (setq c++-mode-map (make-sparse-keymap))
+ (define-key c++-mode-map "\C-j" 'reindent-then-newline-and-indent)
+ (define-key c++-mode-map "{" 'electric-c++-brace)
+ (define-key c++-mode-map "}" 'electric-c++-brace)
+ (define-key c++-mode-map ";" 'electric-c++-semi)
+ (define-key c++-mode-map "\e\C-h" 'mark-c-function)
+ (define-key c++-mode-map "\e\C-q" 'indent-c++-exp)
+ (define-key c++-mode-map "\177" 'backward-delete-char-untabify)
+ (define-key c++-mode-map "\t" 'c++-indent-command)
+ (define-key c++-mode-map "\C-c\C-i" 'c++-insert-header)
+ (define-key c++-mode-map "\C-c\C-\\" 'c++-macroize-region)
+ (define-key c++-mode-map "\C-c\C-c" 'c++-comment-region)
+ (define-key c++-mode-map "\C-c\C-u" 'c++-uncomment-region)
+ (define-key c++-mode-map "\e\C-a" 'c++-beginning-of-defun)
+ (define-key c++-mode-map "\e\C-e" 'c++-end-of-defun)
+ (define-key c++-mode-map "\e\C-x" 'c++-indent-defun))
+
+(defvar c++-mode-syntax-table nil
+ "Syntax table used in C++ mode.")
+
+(if c++-mode-syntax-table
+ ()
+ (setq c++-mode-syntax-table (copy-syntax-table c-mode-syntax-table))
+ (modify-syntax-entry ?/ ". 12" c++-mode-syntax-table)
+ (modify-syntax-entry ?\n ">" c++-mode-syntax-table)
+ (modify-syntax-entry ?\' "." c++-mode-syntax-table))
+
+(defvar c++-continued-member-init-offset nil
+ "*Extra indent for continuation lines of member inits;
+NIL means to align with previous initializations rather than
+with the colon on the first line.")
+(defvar c++-member-init-indent 0
+ "*Indentation level of member initializations in function declarations.")
+(defvar c++-friend-offset -4
+ "*Offset of C++ friend class declarations relative to member declarations.")
+(defvar c++-electric-colon t
+ "*If t, colon is an electric terminator.")
+(defvar c++-empty-arglist-indent nil
+ "*Indicates how far to indent an line following an empty argument
+list. Nil indicates to just after the paren.")
+
+
+;;;###autoload
+(defun c++-mode ()
+ "Major mode for editing C++ code. Very much like editing C code.
+Expression and list commands understand all C++ brackets.
+Tab at left margin indents for C++ code
+Comments are delimited with /* ... */ {or with // ... <newline>}
+Paragraphs are separated by blank lines only.
+Delete converts tabs to spaces as it moves back.
+\\{c++-mode-map}
+Variables controlling indentation style:
+ c-tab-always-indent
+ Non-nil means TAB in C mode should always reindent the current line,
+ regardless of where in the line point is when the TAB command is used.
+ Default is t.
+ c-auto-newline
+ Non-nil means automatically newline before and after braces,
+ and after colons and semicolons, inserted in C code.
+ c-indent-level
+ Indentation of C statements within surrounding block.
+ The surrounding block's indentation is the indentation
+ of the line on which the open-brace appears.
+ c-continued-statement-offset
+ Extra indentation given to a substatement, such as the
+ then-clause of an if or body of a while.
+ c-continued-brace-offset
+ Extra indentation given to a brace that starts a substatement.
+ This is in addition to c-continued-statement-offset.
+ c-brace-offset
+ Extra indentation for line if it starts with an open brace.
+ c-brace-imaginary-offset
+ An open brace following other text is treated as if it were
+ this far to the right of the start of its line.
+ c-argdecl-indent
+ Indentation level of declarations of C function arguments.
+ c-label-offset
+ Extra indentation for line that is a label, or case or ``default:'', or
+ ``public:'' or ``private:'', or ``protected:''.
+ c++-electric-colon
+ If non-nil at invocation of c++-mode (t is the default) colon electricly
+ indents.
+ c++-empty-arglist-indent
+ If non-nil, a function declaration or invocation which ends a line with a
+ left paren is indented this many extra spaces, instead of flush with the
+ left paren.
+ c++-friend-offset
+ Offset of C++ friend class declarations relative to member declarations.
+ c++-member-init-indent
+ Indentation level of member initializations in function declarations,
+ if they are on a separate line beginning with a colon.
+ c++-continued-member-init-offset
+ Extra indentation for continuation lines of member initializations; NIL
+ means to align with previous initializations rather than with the colon.
+
+Settings for K&R, BSD, and Stroustrup indentation styles are
+ c-indent-level 5 8 4
+ c-continued-statement-offset 5 8 4
+ c-continued-brace-offset 0
+ c-brace-offset -5 -8 0
+ c-brace-imaginary-offset 0
+ c-argdecl-indent 0 8 4
+ c-label-offset -5 -8 -4
+ c++-empty-arglist-indent 4
+ c++-friend-offset 0
+
+Turning on C++ mode calls the value of the variable `c++-mode-hook' with
+no args if that value is non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ (use-local-map c++-mode-map)
+ (set-syntax-table c++-mode-syntax-table)
+ (setq major-mode 'c++-mode
+ mode-name "C++"
+ comment-column 32
+ local-abbrev-table c++-mode-abbrev-table)
+ (set (make-local-variable 'indent-line-function) 'c++-indent-line)
+ (set (make-local-variable 'comment-start) "// ")
+ (set (make-local-variable 'comment-end) "")
+ (set (make-local-variable 'comment-start-skip) "/\\*+ *\\|// *")
+ (set (make-local-variable 'comment-indent-hook) 'c++-comment-indent)
+ (set (make-local-variable 'paragraph-start) (concat "^$\\|" page-delimiter))
+ (set (make-local-variable 'paragraph-separate) paragraph-start)
+ (set (make-local-variable 'paragraph-ignore-fill-prefix) t)
+ (set (make-local-variable 'require-final-newline) t)
+ (set (make-local-variable 'parse-sexp-ignore-comments) nil)
+ (run-hooks 'c++-mode-hook)
+ (if c++-electric-colon
+ (define-key c++-mode-map ":" 'electric-c++-terminator)))
+
+;; This is used by indent-for-comment
+;; to decide how much to indent a comment in C++ code
+;; based on its context.
+(defun c++-comment-indent ()
+ (if (looking-at "^\\(/\\*\\|//\\)")
+ 0 ; Existing comment at bol stays there.
+ (save-excursion
+ (skip-chars-backward " \t")
+ (max
+ ;; leave at least one space on non-empty lines.
+ (if (zerop (current-column)) 0 (1+ (current-column)))
+ (let ((cur-pt (point)))
+ (beginning-of-line 0)
+ ;; If previous line had a comment, use it's indent
+ (if (re-search-forward comment-start-skip cur-pt t)
+ (progn
+ (goto-char (match-beginning 0))
+ (current-column))
+ comment-column)))))) ; otherwise indent at comment column.
+
+(defun electric-c++-brace (arg)
+ "Insert character and correct line's indentation."
+ (interactive "P")
+ (let (insertpos)
+ (if (and (not arg)
+ (eolp)
+ (or (save-excursion
+ (skip-chars-backward " \t")
+ (bolp))
+ (if c-auto-newline (progn (c++-indent-line) (newline) t))))
+ (progn
+ (insert last-command-char)
+ (c++-indent-line)
+ (if c-auto-newline
+ (progn
+ (newline)
+ ;; (newline) may have done auto-fill
+ (setq insertpos (- (point) 2))
+ (c++-indent-line)))
+ (save-excursion
+ (if insertpos (goto-char (1+ insertpos)))
+ (delete-char -1))))
+ (if insertpos
+ (save-excursion
+ (goto-char insertpos)
+ (self-insert-command (prefix-numeric-value arg)))
+ (self-insert-command (prefix-numeric-value arg)))))
+
+(defun electric-c++-semi (arg)
+ "Insert character and correct line's indentation."
+ (interactive "P")
+ (if c-auto-newline
+ (electric-c++-terminator arg)
+ (self-insert-command (prefix-numeric-value arg))))
+
+(defun electric-c++-terminator (arg)
+ "Insert character and correct line's indentation."
+ (interactive "P")
+ (let (insertpos (end (point)))
+ (if (and (not arg) (eolp)
+ (not (save-excursion
+ (beginning-of-line)
+ (skip-chars-forward " \t")
+ (or (= (following-char) ?#)
+ ;; Colon is special only after a label, or
+ ;; case, or another colon.
+ ;; So quickly rule out most other uses of colon
+ ;; and do no indentation for them.
+ (and (eq last-command-char ?:)
+ (not (looking-at "case[ \t]"))
+ (save-excursion
+ (forward-word 1)
+ (skip-chars-forward " \t")
+ (< (point) end))
+ ;; Do re-indent double colons
+ (save-excursion
+ (end-of-line 1)
+ (looking-at ":")))
+ (progn
+ (beginning-of-defun)
+ (let ((pps (parse-partial-sexp (point) end)))
+ (or (nth 3 pps) (nth 4 pps) (nth 5 pps))))))))
+ (progn
+ (insert last-command-char)
+ (c++-indent-line)
+ (and c-auto-newline
+ (not (c-inside-parens-p))
+ (progn
+ ;; the new marker object, used to be just an integer
+ (setq insertpos (make-marker))
+ ;; changed setq to set-marker
+ (set-marker insertpos (1- (point)))
+ ;; do this before the newline, since in auto fill can break
+ (newline)
+ (c-indent-line)))
+ (save-excursion
+ (if insertpos (goto-char (1+ insertpos)))
+ (delete-char -1))))
+ (if insertpos
+ (save-excursion
+ (goto-char insertpos)
+ (self-insert-command (prefix-numeric-value arg)))
+ (self-insert-command (prefix-numeric-value arg)))))
+
+(defun c++-indent-command (&optional whole-exp)
+ "Indent current line as C++ code, or in some cases insert a tab character.
+If `c-tab-always-indent' is non-nil (the default), always indent current
+line. Otherwise, indent the current line only if point is at the left
+margin or in the line's indentation; otherwise insert a tab.
+
+A numeric argument, regardless of its value, means indent rigidly all means
+indent rigidly all the lines of the expression starting after point so that
+this line becomes properly indented. The relative indentation among the
+lines of the expression are preserved."
+ (interactive "P")
+ (if whole-exp
+ ;; If arg, always indent this line as C
+ ;; and shift remaining lines of expression the same amount.
+ (let ((shift-amt (c++-indent-line))
+ beg end)
+ (save-excursion
+ (if c-tab-always-indent
+ (beginning-of-line))
+ (setq beg (point))
+ (forward-sexp 1)
+ (setq end (point))
+ (goto-char beg)
+ (forward-line 1)
+ (setq beg (point)))
+ (if (> end beg)
+ (indent-code-rigidly beg end shift-amt "#")))
+ (if (and (not c-tab-always-indent)
+ (save-excursion
+ (skip-chars-backward " \t")
+ (not (bolp))))
+ (insert-tab)
+ (c++-indent-line))))
+
+(defun c++-indent-line ()
+ "Indent current line as C++ code.
+Return the amount the indentation changed by."
+ (let ((indent (calculate-c++-indent nil))
+ beg shift-amt
+ (case-fold-search nil)
+ (pos (- (point-max) (point))))
+ (beginning-of-line)
+ (setq beg (point))
+ (cond ((eq indent nil)
+ (setq indent (current-indentation)))
+ ((eq indent t)
+ (setq indent (calculate-c-indent-within-comment)))
+ ((looking-at "[ \t]*#")
+ (setq indent 0))
+ (t
+ (skip-chars-forward " \t")
+ (if (listp indent) (setq indent (car indent)))
+ (cond ((looking-at "\\(default\\|public\\|private\\|protected\\):")
+ (setq indent (+ indent c-label-offset)))
+ ((or (looking-at "case\\b")
+ (and (looking-at "[A-Za-z]")
+ (save-excursion
+ (forward-sexp 1)
+ (looking-at ":[^:]"))))
+ (setq indent (max 1 (+ indent c-label-offset))))
+ ((and (looking-at "else\\b")
+ (not (looking-at "else\\s_")))
+ (setq indent (save-excursion
+ (c-backward-to-start-of-if)
+ (current-indentation))))
+ ((looking-at "friend\[ \t]class[ \t]")
+ (setq indent (+ indent c++-friend-offset)))
+ ((= (following-char) ?})
+ (setq indent (- indent c-indent-level)))
+ ((= (following-char) ?{)
+ (setq indent (+ indent c-brace-offset))))))
+ (skip-chars-forward " \t")
+ (setq shift-amt (- indent (current-column)))
+ (if (zerop shift-amt)
+ (if (> (- (point-max) pos) (point))
+ (goto-char (- (point-max) pos)))
+ (delete-region beg (point))
+ (indent-to indent)
+ ;; If initial point was within line's indentation,
+ ;; position after the indentation. Else stay at same point in text.
+ (if (> (- (point-max) pos) (point))
+ (goto-char (- (point-max) pos))))
+ shift-amt))
+
+(defun calculate-c++-indent (&optional parse-start)
+ "Return appropriate indentation for current line as C++ code.
+In usual case returns an integer: the column to indent to.
+Returns nil if line starts inside a string, t if in a comment."
+ (save-excursion
+ (beginning-of-line)
+ (let ((indent-point (point))
+ (case-fold-search nil)
+ state
+ containing-sexp)
+ (if parse-start
+ (goto-char parse-start)
+ (beginning-of-defun))
+ (while (< (point) indent-point)
+ (setq parse-start (point))
+ (setq state (parse-partial-sexp (point) indent-point 0))
+ (setq containing-sexp (car (cdr state))))
+ (cond ((or (nth 3 state) (nth 4 state))
+ ;; return nil or t if should not change this line
+ (nth 4 state))
+ ((null containing-sexp)
+ ;; Line is at top level. May be data or function definition, or
+ ;; may be function argument declaration or member initialization.
+ ;; Indent like the previous top level line unless
+ ;; (1) the previous line ends in a closeparen without semicolon,
+ ;; in which case this line is the first argument declaration or
+ ;; member initialization, or
+ ;; (2) the previous line begins with a colon,
+ ;; in which case this is the second line of member inits.
+ ;; It is assumed that arg decls and member inits are not mixed.
+ (goto-char indent-point)
+ (skip-chars-forward " \t")
+ (if (= (following-char) ?{)
+ 0 ; Unless it starts a function body
+ (c++-backward-to-noncomment (or parse-start (point-min)))
+ (if (= (preceding-char) ?\))
+ (progn ; first arg decl or member init
+ (goto-char indent-point)
+ (skip-chars-forward " \t")
+ (if (= (following-char) ?:)
+ c++-member-init-indent
+ c-argdecl-indent))
+ (if (= (preceding-char) ?\;)
+ (backward-char 1))
+ (if (= (preceding-char) ?})
+ 0
+ (beginning-of-line) ; continued arg decls or member inits
+ (skip-chars-forward " \t")
+ (if (= (following-char) ?:)
+ (if c++-continued-member-init-offset
+ (+ (current-indentation)
+ c++-continued-member-init-offset)
+ (progn
+ (forward-char 1)
+ (skip-chars-forward " \t")
+ (current-column)))
+ (current-indentation)))
+ )))
+ ((/= (char-after containing-sexp) ?{)
+ ;; line is expression, not statement:
+ ;; indent to just after the surrounding open -- unless
+ ;; empty arg list, in which case we do what
+ ;; c++-empty-arglist-indent says to do.
+ (if (and c++-empty-arglist-indent
+ (or (null (nth 2 state)) ;; indicates empty arg
+ ;; list.
+ ;; Use a heuristic: if the first
+ ;; non-whitespace following left paren on
+ ;; same line is not a comment,
+ ;; is not an empty arglist.
+ (save-excursion
+ (goto-char (1+ containing-sexp))
+ (not
+ (looking-at "\\( \\|\t\\)*[^/\n]")))))
+ (progn
+ (goto-char containing-sexp)
+ (beginning-of-line)
+ (skip-chars-forward " \t")
+ (goto-char (min (+ (point) c++-empty-arglist-indent)
+ (1+ containing-sexp)))
+ (current-column))
+ ;; In C-mode, we would always indent to one after the
+ ;; left paren. Here, though, we may have an
+ ;; empty-arglist, so we'll indent to the min of that
+ ;; and the beginning of the first argument.
+ (goto-char (1+ containing-sexp))
+ (current-column)))
+ (t
+ ;; Statement. Find previous non-comment character.
+ (goto-char indent-point)
+ (c++-backward-to-noncomment containing-sexp)
+ (if (not (memq (preceding-char) '(nil ?\, ?\; ?} ?: ?\{)))
+ ;; This line is continuation of preceding line's statement;
+ ;; indent c-continued-statement-offset more than the
+ ;; previous line of the statement.
+ (progn
+ (c-backward-to-start-of-continued-exp containing-sexp)
+ (+ c-continued-statement-offset (current-column)))
+ ;; This line starts a new statement.
+ ;; Position following last unclosed open.
+ (goto-char containing-sexp)
+ ;; Is line first statement after an open-brace?
+ (or
+ ;; If no, find that first statement and indent like it.
+ (save-excursion
+ (forward-char 1)
+ (while (progn (skip-chars-forward " \t\n")
+ (looking-at
+ (concat
+ "#\\|/\\*\\|//"
+ "\\|case[ \t]"
+ "\\|[a-zA-Z0-9_$]*:[^:]"
+ "\\|friend[ \t]class[ \t]")))
+ ;; Skip over comments and labels following openbrace.
+ (cond ((= (following-char) ?\#)
+ (forward-line 1))
+ ((looking-at "/\\*")
+ (search-forward "*/" nil 'move))
+ ((looking-at "//\\|friend[ \t]class[ \t]")
+ (forward-line 1))
+ (t
+ (re-search-forward ":[^:]" nil 'move))))
+ ;; The first following code counts
+ ;; if it is before the line we want to indent.
+ (and (< (point) indent-point)
+ (current-column)))
+ ;; If no previous statement,
+ ;; indent it relative to line brace is on.
+ ;; For open brace in column zero, don't let statement
+ ;; start there too. If c-indent-offset is zero,
+ ;; use c-brace-offset + c-continued-statement-offset instead.
+ ;; For open-braces not the first thing in a line,
+ ;; add in c-brace-imaginary-offset.
+ (+ (if (and (bolp) (zerop c-indent-level))
+ (+ c-brace-offset c-continued-statement-offset)
+ c-indent-level)
+ ;; Move back over whitespace before the openbrace.
+ ;; If openbrace is not first nonwhite thing on the line,
+ ;; add the c-brace-imaginary-offset.
+ (progn (skip-chars-backward " \t")
+ (if (bolp) 0 c-brace-imaginary-offset))
+ ;; If the openbrace is preceded by a parenthesized exp,
+ ;; move to the beginning of that;
+ ;; possibly a different line
+ (progn
+ (if (eq (preceding-char) ?\))
+ (forward-sexp -1))
+ ;; Get initial indentation of the line we are on.
+ (current-indentation))))))))))
+
+(defun c++-backward-to-noncomment (lim)
+ (let (opoint stop)
+ (while (not stop)
+ (skip-chars-backward " \t\n\r\f" lim)
+ (setq opoint (point))
+ (cond ((and (>= (point) (+ 2 lim))
+ (save-excursion
+ (forward-char -2)
+ (looking-at "\\*/")))
+ (search-backward "/*" lim 'move))
+ ((and
+ (search-backward "//" (max (point-bol) lim) 'move)
+ (not (within-string-p (point) opoint))))
+ (t (beginning-of-line)
+ (skip-chars-forward " \t")
+ (if (looking-at "#")
+ (setq stop (<= (point) lim))
+ (setq stop t)
+ (goto-char opoint)))))))
+
+(defun indent-c++-exp ()
+ "Indent each line of the C++ grouping following point."
+ (interactive)
+ (let ((indent-stack (list nil))
+ (contain-stack (list (point)))
+ (case-fold-search nil)
+ restart outer-loop-done inner-loop-done state ostate
+ this-indent last-sexp
+ at-else at-brace
+ (opoint (point))
+ (next-depth 0))
+ (save-excursion
+ (forward-sexp 1))
+ (save-excursion
+ (setq outer-loop-done nil)
+ (while (and (not (eobp)) (not outer-loop-done))
+ (setq last-depth next-depth)
+ ;; Compute how depth changes over this line
+ ;; plus enough other lines to get to one that
+ ;; does not end inside a comment or string.
+ ;; Meanwhile, do appropriate indentation on comment lines.
+ (setq innerloop-done nil)
+ (while (and (not innerloop-done)
+ (not (and (eobp) (setq outer-loop-done t))))
+ (setq ostate state)
+ (setq state (parse-partial-sexp (point) (progn (end-of-line) (point))
+ nil nil state))
+ (setq next-depth (car state))
+ (if (and (car (cdr (cdr state)))
+ (>= (car (cdr (cdr state))) 0))
+ (setq last-sexp (car (cdr (cdr state)))))
+ (if (or (nth 4 ostate))
+ (c++-indent-line))
+ (if (or (nth 3 state))
+ (forward-line 1)
+ (setq innerloop-done t)))
+ (if (<= next-depth 0)
+ (setq outer-loop-done t))
+ (if outer-loop-done
+ nil
+ ;; If this line had ..))) (((.. in it, pop out of the levels
+ ;; that ended anywhere in this line, even if the final depth
+ ;; doesn't indicate that they ended.
+ (while (> last-depth (nth 6 state))
+ (setq indent-stack (cdr indent-stack)
+ contain-stack (cdr contain-stack)
+ last-depth (1- last-depth)))
+ (if (/= last-depth next-depth)
+ (setq last-sexp nil))
+ ;; Add levels for any parens that were started in this line.
+ (while (< last-depth next-depth)
+ (setq indent-stack (cons nil indent-stack)
+ contain-stack (cons nil contain-stack)
+ last-depth (1+ last-depth)))
+ (if (null (car contain-stack))
+ (setcar contain-stack (or (car (cdr state))
+ (save-excursion (forward-sexp -1)
+ (point)))))
+ (forward-line 1)
+ (skip-chars-forward " \t")
+ (if (eolp)
+ nil
+ (if (and (car indent-stack)
+ (>= (car indent-stack) 0))
+ ;; Line is on an existing nesting level.
+ ;; Lines inside parens are handled specially.
+ (if (/= (char-after (car contain-stack)) ?{)
+ (setq this-indent (car indent-stack))
+ ;; Line is at statement level.
+ ;; Is it a new statement? Is it an else?
+ ;; Find last non-comment character before this line
+ (save-excursion
+ (setq at-else (looking-at "else\\W"))
+ (setq at-brace (= (following-char) ?{))
+ (c++-backward-to-noncomment opoint)
+ (if (not (memq (preceding-char) '(nil ?\, ?\; ?} ?: ?{)))
+ ;; Preceding line did not end in comma or semi;
+ ;; indent this line c-continued-statement-offset
+ ;; more than previous.
+ (progn
+ (c-backward-to-start-of-continued-exp
+ (car contain-stack))
+ (setq this-indent
+ (+ c-continued-statement-offset
+ (current-column)
+ (if at-brace c-continued-brace-offset 0))))
+ ;; Preceding line ended in comma or semi;
+ ;; use the standard indent for this level.
+ (if at-else
+ (progn (c-backward-to-start-of-if opoint)
+ (setq this-indent (current-indentation)))
+ (setq this-indent (car indent-stack))))))
+ ;; Just started a new nesting level.
+ ;; Compute the standard indent for this level.
+ (let ((val (calculate-c++-indent
+ (if (car indent-stack)
+ (- (car indent-stack))))))
+ (setcar indent-stack
+ (setq this-indent val))))
+ ;; Adjust line indentation according to its contents
+ (if (looking-at "\\(public\\|private\\|protected\\):")
+ (setq this-indent (- this-indent c-indent-level)))
+ (if (or (looking-at "case[ \t]")
+ (and (looking-at "[A-Za-z]")
+ (save-excursion
+ (forward-sexp 1)
+ (looking-at ":[^:]"))))
+ (setq this-indent (max 1 (+ this-indent c-label-offset))))
+ (if (looking-at "friend[ \t]class[ \t]")
+ (setq this-indent (+ this-indent c++-friend-offset)))
+ (if (= (following-char) ?})
+ (setq this-indent (- this-indent c-indent-level)))
+ (if (= (following-char) ?{)
+ (setq this-indent (+ this-indent c-brace-offset)))
+ ;; Put chosen indentation into effect.
+ (or (= (current-column) this-indent)
+ (= (following-char) ?\#)
+ (progn
+ (delete-region (point) (progn (beginning-of-line) (point)))
+ (indent-to this-indent)))
+ ;; Indent any comment following the text.
+ (or (looking-at comment-start-skip)
+ (if (re-search-forward comment-start-skip
+ (save-excursion (end-of-line)
+ (point)) t)
+ (progn
+ (indent-for-comment)
+ (beginning-of-line))))))))))
+
+(defun fill-C-comment ()
+ (interactive)
+ (save-excursion
+ (let ((save fill-prefix))
+ (beginning-of-line 1)
+ (save-excursion
+ (re-search-forward comment-start-skip
+ (save-excursion (end-of-line) (point))
+ t)
+ (goto-char (match-end 0))
+ (set-fill-prefix))
+ (while (looking-at fill-prefix)
+ (previous-line 1))
+ (next-line 1)
+ (insert-string "\n")
+ (fill-paragraph nil)
+ (delete-char -1)
+ (setq fill-prefix save))))
+
+(defun point-bol ()
+ "Returns the value of the point at the beginning of the current line."
+ (save-excursion
+ (beginning-of-line)
+ (point)))
+
+(defun c++-insert-header ()
+ "Insert header denoting C++ code at top of buffer."
+ (interactive)
+ (save-excursion
+ (goto-char (point-min))
+ (insert "// "
+ "This may look like C code, but it is really "
+ "-*- C++ -*-"
+ "\n\n")))
+
+(defun within-string-p (point1 point2)
+ "Returns true if number of double quotes between two points is odd."
+ (let ((s (buffer-substring point1 point2)))
+ (not (zerop (mod (count-char-in-string ?\" s) 2)))))
+
+(defun count-char-in-string (c s)
+ (let ((count 0)
+ (pos 0))
+ (while (< pos (length s))
+ (setq count (+ count (if (\= (aref s pos) c) 1 0)))
+ (setq pos (1+ pos)))
+ count))
+
+;;; This page covers "macroization;" making C++ parameterized types
+;;; via macros.
+
+(defvar c++-default-macroize-column 78
+ "Place to insert backslashes.")
+
+(defun c++-macroize-region (from to arg)
+ "Insert backslashes at end of every line in region. Useful for defining cpp
+macros. If called with negative argument, will remove trailing backslashes,
+so that indentation will work right."
+ (interactive "r\np")
+ (save-excursion
+ (goto-char from)
+ (beginning-of-line 1)
+ (let ((line (count-lines (point-min) (point)))
+ (to-line (save-excursion (goto-char to)
+ (count-lines (point-min) (point)))))
+ (while (< line to-line)
+ (backslashify-current-line (> arg 0))
+ (next-line 1) (setq line (1+ line))))))
+
+(defun backslashify-current-line (doit)
+ (end-of-line 1)
+ (cond
+ (doit
+ ;; Note that "\\\\" is needed to get one backslash.
+ (if (not (save-excursion (forward-char -1) (looking-at "\\\\")))
+ (progn
+ (if (>= (current-column) c++-default-macroize-column)
+ (insert " \\")
+ (while (<= (current-column) c++-default-macroize-column)
+ (insert "\t") (end-of-line))
+ (delete-char -1)
+ (while (< (current-column) c++-default-macroize-column)
+ (insert " ") (end-of-line))
+ (insert "\\")))))
+ (t
+ (forward-char -1)
+ (if (looking-at "\\\\")
+ (progn (skip-chars-backward " \t")
+ (kill-line))))))
+
+
+;;; This page covers commenting out multiple lines.
+
+(defun c++-comment-region ()
+ "Comment out all lines in a region between mark and current point.
+Inserts \"// \" (`comment-start') in front of each line."
+ (interactive)
+ (let* ((m (if (eq (mark) nil) (error "Mark is not set!") (mark)))
+ (start (if (< (point) m) (point) m))
+ (end (if (> (point) m) (point) m))
+ (mymark (copy-marker end)))
+ (save-excursion
+ (goto-char start)
+ (while (< (point) (marker-position mymark))
+ (beginning-of-line)
+ (insert comment-start)
+ (beginning-of-line)
+ (next-line 1)))))
+
+(defun c++-uncomment-region ()
+ "Uncomment all lines in region between mark and current point.
+Deletes the leading \"// \" (`comment-start') from each line, if any."
+ (interactive)
+ (let* ((m (if (eq (mark) nil) (error "Mark is not set!") (mark)))
+ (start (if (< (point) m) (point) m))
+ (end (if (> (point) m) (point) m))
+ (mymark (copy-marker end))
+ (len (length comment-start))
+ (char (string-to-char comment-start)))
+ (save-excursion
+ (goto-char start)
+ (while (< (point) (marker-position mymark))
+ (beginning-of-line)
+ (if (looking-at (concat " *" comment-start))
+ (progn
+ (zap-to-char 1 char)
+ (delete-char len)))
+ (beginning-of-line)
+ (next-line 1)))))
+
+;;; Below are two regular expressions that attempt to match defuns
+;;; "strongly" and "weakly." The strong one almost reconstructs the
+;;; grammar of C++; the weak one just figures anything id or curly on
+;;; the left begins a defun. The constant "c++-match-header-strongly"
+;;; determines which to use; the default is the weak one.
+
+(defvar c++-match-header-strongly nil
+ "*If nil, use `c++-defun-header-weak' to identify beginning of definitions.
+If non-nil, use `c++-defun-header-strong'.")
+
+(defvar c++-defun-header-strong-struct-equivs "\\(class\\|struct\\|enum\\)"
+ "Regexp to match names of structure declaration blocks in C++.")
+
+(defconst c++-defun-header-strong
+ (let*
+ (; valid identifiers
+ ;; There's a real wierdness here -- if I switch the below
+ (id "\\(\\w\\|_\\)+")
+ ;; to be
+ ;; (id "\\(_\\|\\w\\)+")
+ ;; things no longer work right. Try it and see!
+
+ ; overloadable operators
+ (op-sym1
+ "[---+*/%^&|~!=<>]\\|[---+*/%^&|<>=!]=\\|<<=?\\|>>=?")
+ (op-sym2
+ "&&\\|||\\|\\+\\+\\|--\\|()\\|\\[\\]")
+ (op-sym (concat "\\(" op-sym1 "\\|" op-sym2 "\\)"))
+ ; whitespace
+ (middle "[^\\*]*\\(\\*+[^/\\*][^\\*]*\\)*")
+ (c-comment (concat "/\\*" middle "\\*+/"))
+ (wh (concat "\\(\\s \\|\n\\|//.*$\\|" c-comment "\\)"))
+ (wh-opt (concat wh "*"))
+ (wh-nec (concat wh "+"))
+ (oper (concat "\\(" "operator" "\\("
+ wh-opt op-sym "\\|" wh-nec id "\\)" "\\)"))
+ (dcl-list "([^():]*)")
+ (func-name (concat "\\(" oper "\\|" id "::" id "\\|" id "\\)"))
+ (inits
+ (concat "\\(:"
+ "\\(" wh-opt id "(.*\\()" wh-opt "," "\\)\\)*"
+ wh-opt id "(.*)" wh-opt "{"
+ "\\|" wh-opt "{\\)"))
+ (type-name (concat
+ "\\(" c++-defun-header-strong-struct-equivs wh-nec "\\)?"
+ id))
+ (type (concat "\\(const" wh-nec "\\)?"
+ "\\(" type-name "\\|" type-name wh-opt "\\*+" "\\|"
+ type-name wh-opt "&" "\\)"))
+ (modifier "\\(inline\\|virtual\\|overload\\|auto\\|static\\)")
+ (modifiers (concat "\\(" modifier wh-nec "\\)*"))
+ (func-header
+ ;; type arg-dcl
+ (concat modifiers type wh-nec func-name wh-opt dcl-list wh-opt inits))
+ (inherit (concat "\\(:" wh-opt "\\(public\\|private\\)?"
+ wh-nec id "\\)"))
+ (cs-header (concat
+ c++-defun-header-strong-struct-equivs
+ wh-nec id wh-opt inherit "?" wh-opt "{")))
+ (concat "^\\(" func-header "\\|" cs-header "\\)"))
+ "Strongly-defined regexp to match beginning of structure or function def.")
+
+
+;; This part has to do with recognizing defuns.
+
+;; The weak convention we will use is that a defun begins any time
+;; there is a left curly brace, or some identifier on the left margin,
+;; followed by a left curly somewhere on the line. (This will also
+;; incorrectly match some continued strings, but this is after all
+;; just a weak heuristic.) Suggestions for improvement (short of the
+;; strong scheme shown above) are welcomed.
+
+(defconst c++-defun-header-weak "^{\\|^[_a-zA-Z].*{"
+ "Weakly-defined regexp to match beginning of structure or function def.")
+
+(defun c++-beginning-of-defun (arg)
+ (interactive "p")
+ (let ((c++-defun-header (if c++-match-header-strongly
+ c++-defun-header-strong
+ c++-defun-header-weak)))
+ (cond ((or (= arg 0) (and (> arg 0) (bobp))) nil)
+ ((and (not (looking-at c++-defun-header))
+ (let ((curr-pos (point))
+ (open-pos (if (search-forward "{" nil 'move)
+ (point)))
+ (beg-pos
+ (if (re-search-backward c++-defun-header nil 'move)
+ (match-beginning 0))))
+ (if (and open-pos beg-pos
+ (< beg-pos curr-pos)
+ (> open-pos curr-pos))
+ (progn
+ (goto-char beg-pos)
+ (if (= arg 1) t nil));; Are we done?
+ (goto-char curr-pos)
+ nil))))
+ (t
+ (if (and (looking-at c++-defun-header) (not (bobp)))
+ (forward-char (if (< arg 0) 1 -1)))
+ (and (re-search-backward c++-defun-header nil 'move (or arg 1))
+ (goto-char (match-beginning 0)))))))
+
+
+(defun c++-end-of-defun (arg)
+ (interactive "p")
+ (let ((c++-defun-header (if c++-match-header-strongly
+ c++-defun-header-strong
+ c++-defun-header-weak)))
+ (if (and (eobp) (> arg 0))
+ nil
+ (if (and (> arg 0) (looking-at c++-defun-header)) (forward-char 1))
+ (let ((pos (point)))
+ (c++-beginning-of-defun
+ (if (< arg 0)
+ (- (- arg (if (eobp) 0 1)))
+ arg))
+ (if (and (< arg 0) (bobp))
+ t
+ (if (re-search-forward c++-defun-header nil 'move)
+ (progn (forward-char -1)
+ (forward-sexp)
+ (beginning-of-line 2)))
+ (if (and (= pos (point))
+ (re-search-forward c++-defun-header nil 'move))
+ (c++-end-of-defun 1))))
+ t)))
+
+(defun c++-indent-defun ()
+ "Indents the current function definition, struct or class declaration."
+ (interactive)
+ (let ((restore (point)))
+ (c++-end-of-defun 1)
+ (beginning-of-line 1)
+ (let ((end (point)))
+ (c++-beginning-of-defun 1)
+ (while (<= (point) end)
+ (c++-indent-line)
+ (next-line 1)
+ (beginning-of-line 1)))
+ (goto-char restore)))
diff --git a/lisp/textmodes/bibtex.el b/lisp/textmodes/bibtex.el
new file mode 100644
index 00000000000..17591f658fe
--- /dev/null
+++ b/lisp/textmodes/bibtex.el
@@ -0,0 +1,1020 @@
+;;; BibTeX mode for GNU Emacs
+;; Copyright (C) 1985, 1986, 1987, 1990 Free Software Foundation, Inc.
+
+;; 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 1, or (at your option)
+;; any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING. If not, write to
+;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+;;; Mike Newton (newton@gumby.cs.caltech.edu) 91.1.20
+;;; * bibtex.el/bibtex-mode : updated comments to indicate new use of
+;;; address, add minor explanations and fix small omissions.
+;;; * bibtex.el/bibtex-entry : fixed spelling of variable
+
+;;; Mike Newton (newton@gumby.cs.caltech.edu) 90.11.17
+;;; * Handle items like
+;;; title = poft # "Fifth Tri-quaterly" # random-conf,
+;;; and title = {This title is inside curlies}
+;;; * added user settable, always present, optional fields
+;;; * fixed 'bibtex-find-it's doc string's location
+;;; * bibtex-field-text made more general (it wouldnt handle the # construct)
+;;; and it now handles a small subset of the {} cases
+;;; * put DEA thesis back in (why get rid of good code?)
+;;; * merged into release 19 version code
+;;; * if cross-ref'ing is on, put 'pages' near top of OPTs, as the other
+;;; entries are unlikely to be used.
+;;; * skip-whitespace replaced by skip-chars-forward (also done
+;;; by Marc Shairo)
+
+;;; Bengt Martensson, March 6
+;;; Adapted to Bibtex 0.99 by updating the optional fields according
+;;; to the document BibTeXing, Oren Patashnik, dated January 31, 1988.
+;;; Updated documentation strings accordingly. Added (provide 'bibtex).
+;;; If bibtex-include-OPT-crossref is non-nil, every entry will have
+;;; an OPTcrossref field, analogously for bibtex-include-OPTkey and
+;;; bibtex-include-OPTannote. Added bibtex-preamble, bound to ^C^EP,
+;;; and also found in X- and sun-menus. Cleaned up the sun-menu
+;;; stuff, and made it more uniform with the X-menu stuff. Marc: I
+;;; strongly suspect that I broke your parsing... (Or, more
+;;; correctly, BibTeX 0.99 broke it.)
+;;; Added bibtex-clean-entry-zap-empty-opts, defvar'd to t. If it
+;;; is nil, bibtex-clean-entry will leave empty optional fields alone.
+
+;;; Marc Shapiro 1-feb-89: integrated changes by Bengt Martensson 88-05-06:
+;;; Added Sun menu support. Locally bound to right mouse button in
+;;; bibtex-mode. Emacs 18.49 allows local mouse bindings!!
+;;; Commented out DEAthesis.
+
+;;; Marc Shapiro 6-oct-88
+;;; * use indent-to-column instead of inserting tabs (changes to
+;;; bibtex-entry, bibtex-make-entry, bibtex-make-OPT-entry, renamed to
+;;; bibtex-make-optional-entry)
+;;; * C-c C-k deletes the current OPT entry entirely
+;;; * C-c C-d replaces text of field with ""
+;;; * renamed bibtex-find-it to bibtex-find-text. With arg, now goes to
+;;; start of text. Fixed bugs in it.
+
+;;; Marc Shapiro 23-sep-88
+;;; * bibtex-clean-entry moves past end of entry.
+;;; * bibtex-clean-entry signals mandatory fields left empty.
+
+;;; Marc Shapiro 18-jul-88
+;;; * Moved all the entry type keystrokes to "C-c C-e something" (instead of
+;;; "C-c something" previously) to make room for more. C-c C-e is
+;;; supposed to stand for "entry" [idea taken from mail-mode]. Moved
+;;; bibtex-pop-previous to C-c C-p and bibtex-pop-next to C-c C-n.
+;;; * removed binding for "\e[25~"
+;;; * replaced bibtex-clean-optionals by bibtex-clean-entry, bound to
+;;; C-c C-c
+
+;;; Marc Shapiro 13-jul-88 [based on ideas by Sacha Krakowiak of IMAG]
+;;; * bibtex-pop-previous replaces current field with value of
+;;; similar field in previous entry. May be called n times in a row
+;;; (or with arg n) to pop similar field of n'th previous entry.
+;;; There is also a bibtex-pop-next to get similar field of next
+;;; entry.
+;;; * C-c C-k now kills all empty optional fields of current entry, and
+;;; removes "OPT" for those optional fields which have text.
+
+;;; Marc Shapiro 14-dec-87
+;;; Cosmetic fixes. Fixed small bug in bibtex-move-outside-of-entry.
+;;; Skip Montanaro <steinmetz!sprite!montanaro> 7-dec-87, Shapiro 10-dec-87
+;;; before inserting an entry, make sure we are outside of a bib entry
+;;; Marc Shapiro 3-nov-87
+;;; addition for France: DEAthesis
+;;; Marc Shapiro 19-oct-1987
+;;; add X window menu option; bug fixes. TAB, LFD, C-c " and C-c C-o now
+;;; behave consistently; deletion never occurs blindly.
+;;; Marc Shapiro <shapiro@inria.inria.fr> 15-oct-1986
+;;; align long lines nicely; C-c C-o checks for the "OPT" string;
+;;; TAB goes to the end of the string; use lower case; use
+;;; run-hooks
+
+;;; Bengt Martensson <ubrinf!mond!bengt> 87-06-28
+;;; (Bengt Martensson <bengt@mathematik.uni-Bremen.de> 87-06-28)
+;;; Original version
+
+;;; NOTE by Marc Shapiro, 14-dec-87:
+;;; (bibtex-x-environment) binds an X menu for bibtex mode to x-button-c-right.
+;;; Trouble is, in Emacs 18.44 you can't have a mode-specific mouse binding,
+;;; so it will remain active in all windows. Yuck!
+
+(provide 'bibtex)
+
+(defvar bibtex-mode-syntax-table nil "")
+(defvar bibtex-mode-abbrev-table nil "")
+(define-abbrev-table 'bibtex-mode-abbrev-table ())
+(defvar bibtex-mode-map (make-sparse-keymap) "")
+(defvar bibtex-pop-previous-search-point nil
+ "Next point where `bibtex-pop-previous' should start looking for a similar
+entry.")
+(defvar bibtex-pop-next-search-point nil
+ "Next point where `bibtex-pop-next' should start looking for a similar
+entry.")
+
+(defvar bibtex-clean-entry-zap-empty-opts t
+ "*If non-nil, `bibtex-clean-entry' will delete all empty optional fields.")
+(defvar bibtex-include-OPTcrossref t
+ "*If non-nil, all entries will have an `OPTcrossref' field.")
+(defvar bibtex-include-OPTkey t
+ "*If non-nil, all entries will have an `OPTkey' field.")
+(defvar bibtex-include-OPTannote t
+ "*If non-nil, all entries will have an `OPTannote' field.")
+
+;; note: the user should be allowed to have their own list of always
+;; available optional fields. exs: "keywords" "categories"
+(defvar bibtex-mode-user-optional-fields nil ;no default value
+ "*List of optional fields that user always wants present in a bibtex entry.
+One possibility is for ``keywords''")
+
+
+;;; A bibtex file is a sequence of entries, either string definitions
+;;; or reference entries. A reference entry has a type part, a
+;;; key part, and a comma-separated sequence of fields. A string
+;;; entry has a single field. A field has a left and right part,
+;;; separated by a '='. The left part is the name, the right part is
+;;; the text. Here come the definitions allowing to create and/or parse
+;;; entries and fields:
+
+;;; fields
+(defun bibtex-cfield (name text)
+ "Create a regexp for a bibtex field of name NAME and text TEXT."
+ (concat ",[ \t\n]*\\("
+ name
+ "\\)[ \t\n]*=[ \t\n]*\\("
+ text
+ "\\)"))
+(defconst bibtex-name-in-cfield 1
+ "The regexp subexpression number of the name part in `bibtex-cfield'.")
+(defconst bibtex-text-in-cfield 2
+ "The regexp subexpression number of the text part in `bibtex-cfield'.")
+
+(defconst bibtex-field-name "[A-Za-z][---A-Za-z0-9:_+]*"
+ "Regexp defining the name part of a bibtex field.")
+
+;; bibtex-field-text must be able to handle
+;; title = "Proc. Fifteenth Annual" # STOC,
+;; month = "10~" # jan,
+;; year = "{\noopsort{1973c}}1981",
+;; month = apr # "-" # may,
+;; key = {Volume-2},
+;; note = "Volume~2 is listed under Knuth \cite{book-full}"
+;; i have added a few of these, but not all! -- MON
+
+(defconst bibtex-field-const
+ "[0-9A-Za-z][---A-Za-z0-9:_+]*"
+ "Format of a bibtex field constant.")
+(defconst bibtex-field-string
+ (concat
+ "\"[^\"]*[^\\\\]\"\\|\"\"")
+ "Match either a string or an empty string.")
+(defconst bibtex-field-string-or-const
+ (concat bibtex-field-const "\\|" bibtex-field-string)
+ "Match either `bibtex-field-string' or `bibtex-field-const'.")
+
+;(defconst bibtex-field-text
+; "\"[^\"]*[^\\\\]\"\\|\"\"\\|[0-9A-Za-z][---A-Za-z0-9:_+]*"
+; "Regexp defining the text part of a bibtex field: either a string, or an empty string, or a constant.")
+
+(defconst bibtex-field-text
+ (concat
+ "\\(" bibtex-field-string-or-const "\\)"
+ "\\([ \t\n]+#[ \t\n]+\\(" bibtex-field-string-or-const "\\)\\)*\\|"
+ "{[^{}]*[^\\\\]}")
+ "Regexp defining the text part of a bibtex field: either a string, or
+an empty string, or a constant followed by one or more # / constant pairs.
+Also matches simple {...} patterns.")
+
+(defconst bibtex-field
+ (bibtex-cfield bibtex-field-name bibtex-field-text)
+ "Regexp defining the format of a bibtex field")
+
+(defconst bibtex-name-in-field bibtex-name-in-cfield
+ "The regexp subexpression number of the name part in `bibtex-field'.")
+(defconst bibtex-text-in-field bibtex-text-in-cfield
+ "The regexp subexpression number of the text part in `bibtex-field'.")
+
+;;; references
+(defconst bibtex-reference-type
+ "@[A-Za-z]+"
+ "Regexp defining the type part of a bibtex reference entry.")
+(defconst bibtex-reference-head
+ (concat "^[ \t]*\\("
+ bibtex-reference-type
+ "\\)[ \t]*[({]\\("
+ bibtex-field-name
+ "\\)")
+ "Regexp defining format of the header line of a bibtex reference entry.")
+(defconst bibtex-type-in-head 1
+ "The regexp subexpression number of the type part in `bibtex-reference-head'.")
+(defconst bibtex-key-in-head 2
+ "The regexp subexpression number of the key part in `bibtex-reference-head'.")
+
+(defconst bibtex-reference
+ (concat bibtex-reference-head
+ "\\([ \t\n]*" bibtex-field "\\)*"
+ "[ \t\n]*[})]")
+ "Regexp defining the format of a bibtex reference entry.")
+(defconst bibtex-type-in-reference bibtex-type-in-head
+ "The regexp subexpression number of the type part in `bibtex-reference'.")
+(defconst bibtex-key-in-reference bibtex-key-in-head
+ "The regexp subexpression number of the key part in `bibtex-reference'.")
+
+;;; strings
+(defconst bibtex-string
+ (concat "^[ \t]*@[sS][tT][rR][iI][nN][gG][ \t\n]*[({][ \t\n]*\\("
+ bibtex-field-name
+ "\\)[ \t\n]*=[ \t\n]*\\("
+ bibtex-field-text
+ "\\)[ \t\n]*[})]")
+ "Regexp defining the format of a bibtex string entry.")
+(defconst bibtex-name-in-string 1
+ "The regexp subexpression of the name part in `bibtex-string'.")
+(defconst bibtex-text-in-string 2
+ "The regexp subexpression of the text part in `bibtex-string'.")
+
+(defconst bibtex-name-alignement 2
+ "Alignment for the name part in BibTeX fields.
+Chosen on aesthetic grounds only.")
+
+(defconst bibtex-text-alignment (length " organization = ")
+ "Alignment for the text part in BibTeX fields.
+Equal to the space needed for the longest name part.")
+
+;;; bibtex mode:
+
+;;;###autoload
+(defun bibtex-mode ()
+ "Major mode for editing bibtex files.
+
+\\{bibtex-mode-map}
+
+A command such as \\[bibtex-Book] will outline the fields for a BibTeX book entry.
+
+The optional fields start with the string OPT, and thus ignored by BibTeX.
+The OPT string may be removed from a field with \\[bibtex-remove-OPT].
+\\[bibtex-kill-optional-field] kills the current optional field entirely.
+\\[bibtex-remove-double-quotes] removes the double-quotes around the text of
+the current field. \\[bibtex-empty-field] replaces the text of the current
+field with the default \"\".
+
+The command \\[bibtex-clean-entry] cleans the current entry, i.e. (i) removes
+double-quotes from entirely numerical fields, (ii) removes OPT from all
+non-empty optional fields, (iii) removes all empty optional fields, and (iv)
+checks that no non-optional fields are empty.
+
+Use \\[bibtex-find-text] to position the dot at the end of the current field.
+Use \\[bibtex-next-field] to move to end of the next field.
+
+\\[bibtex-x-environment] binds a mode-specific X menu to control+right
+mouse button.
+\\[bibtex-sun-environment] binds a mode-specific Sun menu to right
+mouse button.
+
+Fields:
+ address
+ Publisher's address, or for conference, location held
+ annote
+ Long annotation used for annotated bibliographies (begins sentence)
+ author
+ Name(s) of author(s), in BibTeX name format
+ booktitle
+ Book title when the thing being referenced isn't the whole book.
+ For book entries, the title field should be used instead.
+ chapter
+ Chapter number (or section or whatever).
+ crossref
+ The database key of the entry being cross referenced.
+ edition
+ Edition of a book (e.g., \"second\")
+ editor
+ Name(s) of editor(s), in BibTeX name format.
+ If there is also an author field, then the editor field should be
+ for the book or collection that the work appears in
+ howpublished
+ How something strange has been published (begins sentence)
+ institution
+ Sponsoring institution
+ journal
+ Journal name (macros are provided for many)
+ key
+ Alphabetizing, labeling and cross-refing key (needed when no
+ author or editor)
+ month
+ Month (macros are provided)
+ note
+ To help the reader find a reference (begins sentence)
+ number
+ Number of a journal or technical report
+ organization
+ Organization (sponsoring a conference)
+ pages
+ Page number or numbers (use `--' to separate a range)
+ publisher
+ Publisher name
+ school
+ School name (for theses)
+ series
+ The name of a series or set of books.
+ An individual book will will also have it's own title
+ title
+ The title of the thing being referenced
+ type
+ Type of a technical report (e.g., \"Research Note\") to be used
+ instead of the default \"Technical Report\"
+ volume
+ Volume of a journal or multivolume work
+ year
+ Year---should contain only numerals
+---------------------------------------------------------
+Entry to this mode calls the value of bibtex-mode-hook if that value is
+non-nil."
+ (interactive)
+ (kill-all-local-variables)
+ (if bibtex-mode-syntax-table
+ (set-syntax-table bibtex-mode-syntax-table)
+ (setq bibtex-mode-syntax-table (make-syntax-table))
+ (set-syntax-table bibtex-mode-syntax-table)
+ (modify-syntax-entry ?\" ".")
+ (modify-syntax-entry ?$ "$$ ")
+ (modify-syntax-entry ?% "< ")
+ (modify-syntax-entry ?' "w ")
+ (modify-syntax-entry ?@ "w ")
+ (modify-syntax-entry ?\\ "\\")
+ (modify-syntax-entry ?\f "> ")
+ (modify-syntax-entry ?\n "> ")
+ (modify-syntax-entry ?~ " "))
+ (use-local-map bibtex-mode-map)
+ (setq major-mode 'bibtex-mode)
+
+
+ (setq mode-name "BibTeX")
+ (set-syntax-table bibtex-mode-syntax-table)
+ (setq local-abbrev-table bibtex-mode-abbrev-table)
+ (make-local-variable 'paragraph-start)
+ (setq paragraph-start "^[ \f\n\t]*$")
+
+ (define-key bibtex-mode-map "\t" 'bibtex-find-text)
+ (define-key bibtex-mode-map "\n" 'bibtex-next-field)
+ (define-key bibtex-mode-map "\C-c\"" 'bibtex-remove-double-quotes)
+ (define-key bibtex-mode-map "\C-c\C-c" 'bibtex-clean-entry)
+ (define-key bibtex-mode-map "\C-c?" 'describe-mode)
+ (define-key bibtex-mode-map "\C-c\C-p" 'bibtex-pop-previous)
+ (define-key bibtex-mode-map "\C-c\C-n" 'bibtex-pop-next)
+ (define-key bibtex-mode-map "\C-c\C-k" 'bibtex-kill-optional-field)
+ (define-key bibtex-mode-map "\C-c\C-d" 'bibtex-empty-field)
+
+ (define-key bibtex-mode-map "\C-c\C-e\C-a" 'bibtex-Article)
+ (define-key bibtex-mode-map "\C-c\C-e\C-b" 'bibtex-Book)
+ (define-key bibtex-mode-map "\C-c\C-e\C-d" 'bibtex-DEAthesis)
+ (define-key bibtex-mode-map "\C-c\C-e\C-c" 'bibtex-InProceedings)
+ (define-key bibtex-mode-map "\C-c\C-e\C-i" 'bibtex-InBook)
+ (define-key bibtex-mode-map "\C-c\C-ei" 'bibtex-InCollection)
+ (define-key bibtex-mode-map "\C-c\C-eI" 'bibtex-InProceedings)
+ (define-key bibtex-mode-map "\C-c\C-e\C-m" 'bibtex-Manual)
+ (define-key bibtex-mode-map "\C-c\C-em" 'bibtex-MastersThesis)
+ (define-key bibtex-mode-map "\C-c\C-eM" 'bibtex-Misc)
+ (define-key bibtex-mode-map "\C-c\C-o" 'bibtex-remove-OPT)
+ (define-key bibtex-mode-map "\C-c\C-e\C-p" 'bibtex-PhdThesis)
+ (define-key bibtex-mode-map "\C-c\C-ep" 'bibtex-Proceedings)
+ (define-key bibtex-mode-map "\C-c\C-eP" 'bibtex-preamble)
+ (define-key bibtex-mode-map "\C-c\C-e\C-t" 'bibtex-TechReport)
+ (define-key bibtex-mode-map "\C-c\C-e\C-s" 'bibtex-string)
+ (define-key bibtex-mode-map "\C-c\C-e\C-u" 'bibtex-Unpublished)
+
+ (auto-fill-mode 1) ; nice alignements
+ (setq left-margin (+ bibtex-text-alignment 1))
+
+ (run-hooks 'bibtex-mode-hook))
+
+(defun bibtex-move-outside-of-entry ()
+ "Make sure we are outside of a bib entry"
+ (cond ((or
+ (= (point) (point-max))
+ (= (point) (point-min))
+ (looking-at "[ \n]*@")
+ )
+ t)
+ (t
+ (backward-paragraph)
+ (forward-paragraph)))
+ (re-search-forward "[ \t\n]*" (point-max) t))
+
+;;
+;; note: this should really take lists of strings OR of lists. in the
+;; second case, one can use either list. (ie:
+;; "name" (("crossref") ("journal" "year")) )
+;;
+
+(defun bibtex-entry (entry-type required optional)
+ (bibtex-move-outside-of-entry)
+ (insert "@" entry-type "{")
+ (mapcar 'bibtex-make-field required)
+ (if bibtex-include-OPTcrossref
+ (bibtex-make-optional-field "crossref"))
+ (if bibtex-include-OPTkey
+ (bibtex-make-optional-field "key"))
+ (mapcar 'bibtex-make-optional-field optional)
+ (if bibtex-mode-user-optional-fields ;MON...
+ (mapcar 'bibtex-make-optional-field
+ bibtex-mode-user-optional-fields))
+ (if bibtex-include-OPTannote
+ (bibtex-make-optional-field "annote"))
+ (insert "\n}\n\n")
+ (forward-char -3)
+ (up-list -1)
+ (forward-char 1))
+
+(defun bibtex-make-field (str)
+ (interactive "sBibTeX entry type: ")
+ (insert ",\n")
+ (indent-to-column bibtex-name-alignement)
+ (insert str " = ")
+ (indent-to-column bibtex-text-alignment)
+ (insert "\"\"")
+ nil)
+
+(defun bibtex-make-optional-field (str)
+ (interactive "sOptional BibTeX entry type: ")
+ (insert ",\n")
+ (indent-to-column bibtex-name-alignement)
+ (insert "OPT" str " = ")
+ (indent-to-column bibtex-text-alignment)
+ (insert "\"\"")
+ nil)
+
+;; What to do about crossref? if present, journal and year are
+;; both optional. Due to this, i move all of them into optional. -- MON
+
+(defun bibtex-Article ()
+ (interactive)
+ (if bibtex-include-OPTcrossref
+ (bibtex-entry "Article" '("author" "title")
+ '("journal" "year" "volume" "number" "pages"
+ "month" "note"))
+ (bibtex-entry "Article" '("author" "title" "journal" "year")
+ '("volume" "number" "pages" "month" "note"))))
+
+(defun bibtex-Book ()
+ (interactive)
+ (bibtex-entry "Book" '("author" "title" "publisher" "year")
+ '("editor" "volume" "number" "series" "address"
+ "edition" "month" "note")))
+
+(defun bibtex-Booklet ()
+ (interactive)
+ (bibtex-entry "Booklet" '("title")
+ '("author" "howpublished" "address" "month" "year" "note")))
+
+;; France: Dipl\^{o}me d'Etudes Approfondies (similar to Master's)
+(defun bibtex-DEAthesis ()
+ (interactive)
+ (bibtex-entry "DEAthesis" '("author" "title" "school" "year")
+ '("address" "month" "note")))
+
+(defun bibtex-InBook ()
+ (interactive)
+ (if bibtex-include-OPTcrossref
+ (bibtex-entry "InBook" '("author" "title" "chapter")
+ '("publisher" "year" "editor" "pages" "volume" "number"
+ "series" "address" "edition" "month" "type" "note"))
+ (bibtex-entry "InBook" '("author" "title" "chapter" "publisher" "year")
+ '("editor" "pages" "volume" "number" "series" "address"
+ "edition" "month" "type" "note"))))
+
+;; In next 2, for crossref case, put pages near beginning of
+;; optionals as it will be used most often -- MON
+(defun bibtex-InCollection ()
+ (interactive)
+ (if bibtex-include-OPTcrossref
+ (bibtex-entry "InCollection" '("author" "title")
+ '("pages" "booktitle" "publisher" "year"
+ "editor" "volume" "number" "series" "type" "chapter"
+ "address" "edition" "month" "note"))
+ (bibtex-entry "InCollection" '("author" "title"
+ "booktitle" "publisher" "year")
+ '("editor" "volume" "number" "series" "type" "chapter"
+ "pages" "address" "edition" "month" "note"))))
+
+(defun bibtex-InProceedings ()
+ (interactive)
+ (if bibtex-include-OPTcrossref
+ (bibtex-entry "InProceedings" '("author" "title")
+ '( "pages" "editor" "volume" "number" "series"
+ "booktitle" "year"
+ "organization" "publisher" "address" "month" "note"))
+ (bibtex-entry "InProceedings" '("author" "title" "booktitle" "year")
+ '("editor" "volume" "number" "series" "pages"
+ "organization" "publisher" "address" "month" "note"))))
+
+(defun bibtex-Manual ()
+ (interactive)
+ (bibtex-entry "Manual" '("title")
+ '("author" "organization" "address" "edition" "year"
+ "month" "note")))
+
+(defun bibtex-MastersThesis ()
+ (interactive)
+ (bibtex-entry "MastersThesis" '("author" "title" "school" "year")
+ '("address" "month" "note" "type")))
+
+(defun bibtex-Misc ()
+ (interactive)
+ (bibtex-entry "Misc" '()
+ '("author" "title" "howpublished" "year" "month" "note")))
+
+(defun bibtex-PhdThesis ()
+ (interactive)
+ (bibtex-entry "PhdThesis" '("author" "title" "school" "year")
+ '("address" "month" "type" "note")))
+
+(defun bibtex-Proceedings ()
+ (interactive)
+ (bibtex-entry "Proceedings" '("title" "year")
+ '("editor" "volume" "number" "series" "publisher"
+ "organization" "address" "month" "note")))
+
+(defun bibtex-TechReport ()
+ (interactive)
+ (bibtex-entry "TechReport" '("author" "title" "institution" "year")
+ '("type" "number" "address" "month" "note")))
+
+
+(defun bibtex-Unpublished ()
+ (interactive)
+ (bibtex-entry "Unpublished" '("author" "title" "note")
+ '("year" "month")))
+
+(defun bibtex-string ()
+ (interactive)
+ (bibtex-move-outside-of-entry)
+ (insert "@string{ = """"}\n")
+ (previous-line 1)
+ (forward-char 8))
+
+(defun bibtex-preamble ()
+ (interactive)
+ (bibtex-move-outside-of-entry)
+ (insert "@Preamble{}\n")
+ (previous-line 1)
+ (forward-char 10))
+
+(defun bibtex-next-field (arg)
+ "Finds end of text of next BibTeX field; with arg, to its beginning"
+ (interactive "P")
+ (bibtex-inside-field)
+ (let ((start (point)))
+ (condition-case ()
+ (progn
+ (bibtex-enclosing-field)
+ (goto-char (match-end 0))
+ (forward-char 2))
+ (error
+ (goto-char start)
+ (end-of-line)
+ (forward-char 1))))
+ (bibtex-find-text arg))
+
+(defun bibtex-find-text (arg)
+ "Go to end of text of current field; with arg, go to beginning."
+ (interactive "P")
+ (bibtex-inside-field)
+ (bibtex-enclosing-field)
+ (if arg
+ (progn
+ (goto-char (match-beginning bibtex-text-in-field))
+ (if (looking-at "\"")
+ (forward-char 1)))
+ (goto-char (match-end bibtex-text-in-field))
+ (if (= (preceding-char) ?\")
+ (forward-char -1))))
+
+(defun bibtex-remove-OPT ()
+ "Removes the 'OPT' starting optional arguments and goes to end of text"
+ (interactive)
+ (bibtex-inside-field)
+ (bibtex-enclosing-field)
+ (save-excursion
+ (goto-char (match-beginning bibtex-name-in-field))
+ (if (looking-at "OPT")
+ (delete-char (length "OPT"))))
+ (bibtex-inside-field))
+
+(defun bibtex-inside-field ()
+ "Try to avoid point being at end of a bibtex field."
+ (interactive)
+ (end-of-line)
+ (skip-chars-backward " \t") ;delete these chars? -- MON
+ (cond ((= (preceding-char) ?,)
+ (forward-char -1)))
+ (cond ((= (preceding-char) ?\")
+ (forward-char -1)))) ;only go back if quote
+
+
+(defun bibtex-remove-double-quotes ()
+ "Removes """" around string."
+ (interactive)
+ (save-excursion
+ (bibtex-inside-field)
+ (bibtex-enclosing-field)
+ (let ((start (match-beginning bibtex-text-in-field))
+ (stop (match-end bibtex-text-in-field)))
+ (goto-char stop)
+ (forward-char -1)
+ (if (looking-at "\"")
+ (delete-char 1))
+ (goto-char start)
+ (if (looking-at "\"")
+ (delete-char 1)))))
+
+(defun bibtex-kill-optional-field ()
+ "Kill the entire enclosing optional BibTeX field"
+ (interactive)
+ (bibtex-inside-field)
+ (bibtex-enclosing-field)
+ (goto-char (match-beginning bibtex-name-in-field))
+ (let ((the-end (match-end 0))
+ (the-beginning (match-beginning 0)))
+ (if (looking-at "OPT")
+ (progn
+ (goto-char the-end)
+ (skip-chars-forward " \t\n,")
+ (kill-region the-beginning the-end))
+ (error "Mandatory fields can't be killed"))))
+
+(defun bibtex-empty-field ()
+ "Delete the text part of the current field, replace with empty text"
+ (interactive)
+ (bibtex-inside-field)
+ (bibtex-enclosing-field)
+ (goto-char (match-beginning bibtex-text-in-field))
+ (kill-region (point) (match-end bibtex-text-in-field))
+ (insert "\"\"")
+ (bibtex-find-text t))
+
+
+(defun bibtex-pop-previous (arg)
+ "Replace text of current field with the text of similar field in previous entry.
+With arg, go up ARG entries. Repeated, goes up so many times. May be
+intermixed with \\[bibtex-pop-next] (bibtex-pop-next)."
+ (interactive "p")
+ (bibtex-inside-field)
+ (save-excursion
+ ; parse current field
+ (bibtex-enclosing-field)
+ (let ((start-old-text (match-beginning bibtex-text-in-field))
+ (stop-old-text (match-end bibtex-text-in-field))
+ (start-name (match-beginning bibtex-name-in-field))
+ (stop-name (match-end bibtex-name-in-field))
+ (new-text))
+ (goto-char start-name)
+ ; construct regexp for previous field with same name as this one
+ (let ((matching-entry
+ (bibtex-cfield
+ (buffer-substring (if (looking-at "OPT")
+ (+ (point) (length "OPT"))
+ (point))
+ stop-name)
+ bibtex-field-text)))
+
+ ; if executed several times in a row, start each search where the
+ ; last one finished
+ (cond ((or (eq last-command 'bibtex-pop-previous)
+ (eq last-command 'bibtex-pop-next))
+ t
+ )
+ (t
+ (bibtex-enclosing-reference)
+ (setq bibtex-pop-previous-search-point (match-beginning 0))
+ (setq bibtex-pop-next-search-point (match-end 0))))
+ (goto-char bibtex-pop-previous-search-point)
+
+ ; Now search for arg'th previous similar field
+ (cond
+ ((re-search-backward matching-entry (point-min) t arg)
+ (setq new-text
+ (buffer-substring (match-beginning bibtex-text-in-cfield)
+ (match-end bibtex-text-in-cfield)))
+ ; Found a matching field. Remember boundaries.
+ (setq bibtex-pop-next-search-point (match-end 0))
+ (setq bibtex-pop-previous-search-point (match-beginning 0))
+ (bibtex-flash-head)
+ ; Go back to where we started, delete old text, and pop new.
+ (goto-char stop-old-text)
+ (delete-region start-old-text stop-old-text)
+ (insert new-text))
+ (t ; search failed
+ (error "No previous matching BibTeX field."))))))
+ (setq this-command 'bibtex-pop-previous))
+
+(defun bibtex-pop-next (arg)
+ "Replace text of current field with the text of similar field in next entry.
+With arg, go up ARG entries. Repeated, goes up so many times. May be
+intermixed with \\[bibtex-pop-previous] (bibtex-pop-previous)."
+ (interactive "p")
+ (bibtex-inside-field)
+ (save-excursion
+ ; parse current field
+ (bibtex-enclosing-field)
+ (let ((start-old-text (match-beginning bibtex-text-in-field))
+ (stop-old-text (match-end bibtex-text-in-field))
+ (start-name (match-beginning bibtex-name-in-field))
+ (stop-name (match-end bibtex-name-in-field))
+ (new-text))
+ (goto-char start-name)
+ ; construct regexp for next field with same name as this one,
+ ; ignoring possible OPT's
+ (let ((matching-entry
+ (bibtex-cfield
+ (buffer-substring (if (looking-at "OPT")
+ (+ (point) (length "OPT"))
+ (point))
+ stop-name)
+ bibtex-field-text)))
+
+ ; if executed several times in a row, start each search where the
+ ; last one finished
+ (cond ((or (eq last-command 'bibtex-pop-next)
+ (eq last-command 'bibtex-pop-previous))
+ t
+ )
+ (t
+ (bibtex-enclosing-reference)
+ (setq bibtex-pop-previous-search-point (match-beginning 0))
+ (setq bibtex-pop-next-search-point (match-end 0))))
+ (goto-char bibtex-pop-next-search-point)
+
+ ; Now search for arg'th next similar field
+ (cond
+ ((re-search-forward matching-entry (point-max) t arg)
+ (setq new-text
+ (buffer-substring (match-beginning bibtex-text-in-cfield)
+ (match-end bibtex-text-in-cfield)))
+ ; Found a matching field. Remember boundaries.
+ (setq bibtex-pop-next-search-point (match-end 0))
+ (setq bibtex-pop-previous-search-point (match-beginning 0))
+ (bibtex-flash-head)
+ ; Go back to where we started, delete old text, and pop new.
+ (goto-char stop-old-text)
+ (delete-region start-old-text stop-old-text)
+ (insert new-text))
+ (t ; search failed
+ (error "No next matching BibTeX field."))))))
+ (setq this-command 'bibtex-pop-next))
+
+(defun bibtex-flash-head ()
+ "Flash at BibTeX reference head before point, if exists. (Moves point)."
+ (let ((flash))
+ (cond ((re-search-backward bibtex-reference-head (point-min) t)
+ (goto-char (match-beginning bibtex-type-in-head))
+ (setq flash (match-end bibtex-key-in-reference)))
+ (t
+ (end-of-line)
+ (skip-chars-backward " \t")
+ (setq flash (point))
+ (beginning-of-line)
+ (skip-chars-forward " \t")))
+ (if (pos-visible-in-window-p (point))
+ (sit-for 1)
+ (message "From: %s"
+ (buffer-substring (point) flash)))))
+
+
+
+(defun bibtex-enclosing-field ()
+ "Search for BibTeX field enclosing point.
+Point moves to end of field; also, use match-beginning and match-end
+to parse the field."
+ (condition-case errname
+ (bibtex-enclosing-regexp bibtex-field)
+ (search-failed
+ (error "Can't find enclosing BibTeX field."))))
+
+(defun bibtex-enclosing-reference ()
+ "Search for BibTeX reference enclosing point.
+Point moves to end of reference; also, use match-beginning and match-end
+to parse the reference."
+ (condition-case errname
+ (bibtex-enclosing-regexp bibtex-reference)
+ (search-failed
+ (error "Can't find enclosing BibTeX reference."))))
+
+(defun bibtex-enclosing-regexp (regexp)
+ "Search for REGEXP enclosing point.
+Point moves to end of REGEXP. See also match-beginning and match-end.
+If an enclosing REGEXP is not found, signals search-failed; point is left in
+an undefined location.
+
+[Doesn't something like this exist already?]"
+
+ (interactive "sRegexp: ")
+ ; compute reasonable limits for the loop
+ (let* ((initial (point))
+ (right (if (re-search-forward regexp (point-max) t)
+ (match-end 0)
+ (point-max)))
+ (left
+ (progn
+ (goto-char initial)
+ (if (re-search-backward regexp (point-min) t)
+ (match-beginning 0)
+ (point-min)))))
+ ; within the prescribed limits, loop until a match is found
+ (goto-char left)
+ (re-search-forward regexp right nil 1)
+ (if (> (match-beginning 0) initial)
+ (signal 'search-failed (list regexp)))
+ (while (<= (match-end 0) initial)
+ (re-search-forward regexp right nil 1)
+ (if (> (match-beginning 0) initial)
+ (signal 'search-failed (list regexp))))
+ ))
+
+(defun bibtex-clean-entry ()
+ "For all optional fields of current BibTeX entry: if empty, kill the whole field; otherwise, remove the \"OPT\" string in the name; if text numerical, remove double-quotes. For all mandatory fields: if empty, signal error."
+ (interactive)
+ (bibtex-enclosing-reference)
+ (goto-char (match-beginning 0))
+ (let ((start (point)))
+ (save-restriction
+ (narrow-to-region start (match-end 0))
+ (while (re-search-forward bibtex-field (point-max) t 1)
+ (let ((begin-field (match-beginning 0))
+ (end-field (match-end 0))
+ (begin-name (match-beginning bibtex-name-in-field))
+ (end-name (match-end bibtex-name-in-field))
+ (begin-text (match-beginning bibtex-text-in-field))
+ (end-text (match-end bibtex-text-in-field))
+ )
+ (goto-char begin-name)
+ (cond ((and
+ (looking-at "OPT")
+ bibtex-clean-entry-zap-empty-opts)
+ (goto-char begin-text)
+ (if (looking-at "\"\"") ; empty: delete whole field
+ (delete-region begin-field end-field)
+ ; otherwise: not empty, delete "OPT"
+ (goto-char begin-name)
+ (delete-char (length "OPT"))
+ (goto-char begin-field) ; and loop to go through next test
+ ))
+ (t
+ (goto-char begin-text)
+ (cond ((looking-at "\"[0-9]+\"") ; if numerical,
+ (goto-char end-text)
+ (delete-char -1) ; delete enclosing double-quotes
+ (goto-char begin-text)
+ (delete-char 1)
+ (goto-char end-field) ; go to end for next search
+ (forward-char -2) ; to compensate for the 2 quotes deleted
+ )
+ ((looking-at "\"\"") ; if empty quotes, complain
+ (forward-char 1)
+ (if (not (or (equal (buffer-substring
+ begin-name
+ (+ begin-name 3))
+ "OPT")
+ (equal (buffer-substring
+ begin-name
+ (+ begin-name 3))
+ "opt")))
+ (error "Mandatory field ``%s'' is empty"
+ (buffer-substring begin-name end-name))))
+ (t
+ (goto-char end-field))))))))
+ (goto-char start)
+ (skip-chars-forward "@a-zA-Z")
+ (bibtex-enclosing-reference)
+ (goto-char (match-end 0))
+ (skip-chars-forward " \t\n ")))
+
+
+
+;;; X window menus for bibtex mode
+
+(defun bibtex-x-help (arg)
+ "Mouse commands for BibTeX mode"
+
+ (let ((selection
+ (x-popup-menu
+ arg
+ '("BibTeX commands"
+ ("BibTeX entry types"
+ (" article in conference Proceedings " . bibtex-InProceedings)
+ (" Article in journal " . bibtex-Article)
+ (" Book " . bibtex-Book)
+ (" Booklet " . bibtex-Booklet)
+ (" Conference " . bibtex-InProceedings)
+ (" Master's Thesis " . bibtex-MastersThesis)
+ (" DEA Thesis " . bibtex-DEAthesis)
+ (" Phd. Thesis " . bibtex-PhdThesis)
+ (" Technical Report " . bibtex-TechReport)
+ (" technical Manual " . bibtex-Manual)
+ (" conference Proceedings " . bibtex-Proceedings)
+ (" a chapter in a Book " . bibtex-InBook)
+ (" an article in a Collection " . bibtex-InCollection)
+ (" miscellaneous " . bibtex-Misc)
+ (" unpublished " . bibtex-Unpublished)
+ (" string " . bibtex-string)
+ (" preamble " . bibtex-preamble)
+ )
+ ("Moving around and editing"
+ (" next field " . bibtex-next-field)
+ (" to end of field " . bibtex-find-text)
+ ("snatch from similar preceding field" . bibtex-pop-previous)
+ ("snatch from similar following field" . bibtex-pop-next)
+ (" remove OPT " . bibtex-remove-OPT)
+ (" remove quotes "
+ . bibtex-remove-double-quotes)
+ (" clean up entry " . bibtex-clean-entry)
+ )
+ ("help"
+ (" describe BibTeX mode " . describe-mode)
+ )))))
+ (and selection (call-interactively selection))))
+
+(defun bibtex-x-environment ()
+ "Set up X menus for BibTeX mode. Call it as bibtex-mode-hook, or interactively"
+ (interactive)
+ (require 'x-mouse)
+ (define-key mouse-map x-button-c-right 'bibtex-x-help)
+ )
+
+
+
+;; Please don't send anything to bug-gnu-emacs about these Sunwindows functions
+;; since we aren't interested. See etc/SUN-SUPPORT for the reasons why
+;; we consider this nothing but a distraction from our work.
+
+(if (fboundp 'defmenu)
+ (progn
+
+(defmenu bibtex-sun-entry-menu
+ ("Article In Conf. Proc."
+ (lambda () (eval-in-window *menu-window* (bibtex-InProceedings))))
+ ("Article In Journal"
+ (lambda () (eval-in-window *menu-window* (bibtex-Article))))
+ ("Book"
+ (lambda () (eval-in-window *menu-window* (bibtex-Book))))
+ ("Booklet"
+ (lambda () (eval-in-window *menu-window* (bibtex-Booklet))))
+ ("Master's Thesis"
+ (lambda () (eval-in-window *menu-window* (bibtex-MastersThesis))))
+ ;;("DEA Thesis" bibtex-DEAthesis)
+ ("PhD. Thesis"
+ (lambda () (eval-in-window *menu-window* (bibtex-PhdThesis))))
+ ("Technical Report"
+ (lambda () (eval-in-window *menu-window* (bibtex-TechReport))))
+ ("Technical Manual"
+ (lambda () (eval-in-window *menu-window* (bibtex-Manual))))
+ ("Conference Proceedings"
+ (lambda () (eval-in-window *menu-window* (bibtex-Proceedings))))
+ ("In A Book"
+ (lambda () (eval-in-window *menu-window* (bibtex-InBook))))
+ ("In A Collection"
+ (lambda () (eval-in-window *menu-window* (bibtex-InCollection))))
+ ("Miscellaneous"
+ (lambda () (eval-in-window *menu-window* (bibtex-Misc))))
+ ("Unpublished"
+ (lambda () (eval-in-window *menu-window* (bibtex-Unpublished)))))
+
+(defmenu bibtex-sun-menu
+ ("BibTeX menu")
+ ("add entry" . bibtex-sun-entry-menu)
+ ("add string"
+ (lambda () (eval-in-window *menu-window* (bibtex-string))))
+ ;("next field" bibtex-next-position)
+ ;("to end of field" bibtex-find-it)
+; ("remove OPT"
+; (lambda () (eval-in-window *menu-window* (bibtex-remove-opt))))
+; ("remove quotes"
+; (lambda () (eval-in-window *menu-window* (bibtex-remove-double-quotes))))
+; ("remove this line"
+; (lambda () (eval-in-window *menu-window* (kill-current-line))))
+ ("describe BibTeX mode"
+ (lambda () (eval-in-window *menu-window* (describe-mode))))
+ ("Main Emacs menu" . emacs-menu))
+
+(defun bibtex-sun-menu-eval (window x y)
+ "Pop-up menu of BibTeX commands."
+ (sun-menu-evaluate window (1+ x) (1- y) 'bibtex-sun-menu))
+
+(defun bibtex-sun-environment ()
+ "Set up sun menus for BibTeX mode. Call it as bibtex-mode-hook, or interactively"
+ (interactive)
+ (local-set-mouse '(text right) 'bibtex-sun-menu-eval))
+
+)) ; matches (if...
+
+;;; ------------- end bibtex-mode.el -------------------------------