Add arch taglines
[bpt/emacs.git] / lisp / textmodes / tex-mode.el
index f051359..cbf2f1c 100644 (file)
@@ -1,6 +1,6 @@
 ;;; tex-mode.el --- TeX, LaTeX, and SliTeX mode commands -*- coding: utf-8 -*-
 
-;; Copyright (C) 1985, 86, 89, 92, 94, 95, 96, 97, 98, 1999, 2002
+;; Copyright (C) 1985,86,89,92,94,95,96,97,98,1999,2002,2003
 ;;       Free Software Foundation, Inc.
 
 ;; Maintainer: FSF
@@ -141,7 +141,7 @@ If nil, no commands are used.  See the documentation of `tex-command'."
   :group 'tex-run
   :version "21.4")
 
-(defvar standard-latex-block-names
+(defvar latex-standard-block-names
   '("abstract"         "array"         "center"        "description"
     "displaymath"      "document"      "enumerate"     "eqnarray"
     "eqnarray*"                "equation"      "figure"        "figure*"
@@ -156,7 +156,7 @@ If nil, no commands are used.  See the documentation of `tex-command'."
 ;;;###autoload
 (defcustom latex-block-names nil
   "*User defined LaTeX block names.
-Combined with `standard-latex-block-names' for minibuffer completion."
+Combined with `latex-standard-block-names' for minibuffer completion."
   :type '(repeat string)
   :group 'tex-run)
 
@@ -196,20 +196,14 @@ use."
   :group 'tex-view)
 
 ;;;###autoload
-(defcustom tex-dvi-view-command nil
+(defcustom tex-dvi-view-command '(if (eq window-system 'x) "xdvi" "dvi2tty * | cat -s")
   "*Command used by \\[tex-view] to display a `.dvi' file.
+If it is a string, that specifies the command directly.
 If this string contains an asterisk (`*'), that is replaced by the file name;
-otherwise, the file name, preceded by blank, is added at the end.
-
-This can be set conditionally so that the previewer used is suitable for the
-window system being used.  For example,
+otherwise, the file name, preceded by a space, is added at the end.
 
-    (setq tex-dvi-view-command
-          (if (eq window-system 'x) \"xdvi\" \"dvi2tty * | cat -s\"))
-
-would tell \\[tex-view] to use xdvi under X windows and to use dvi2tty
-otherwise."
-  :type '(choice (const nil) string)
+If the value is a form, it is evaluated to get the command to use."
+  :type '(choice (const nil) string sexp)
   :group 'tex-view)
 
 ;;;###autoload
@@ -282,22 +276,26 @@ Should be a simple file name with no extension or directory specification.")
   "File name that \\[tex-print] prints.
 Set by \\[tex-region], \\[tex-buffer], and \\[tex-file].")
 
-(easy-mmode-defsyntax tex-mode-syntax-table
-  '((?% . "<")
-    (?\n . ">")
-    (?\f . ">")
-    (?\C-@ . "w")
-    (?' . "w")
-    (?@ . "_")
-    (?* . "_")
-    (?\t . " ")
-    (?~ . " ")
-    (?$ . "$$")
-    (?\\ . "/")
-    (?\" . ".")
-    (?& . ".")
-    (?_ . ".")
-    (?^ . "."))
+(defvar tex-mode-syntax-table
+  (let ((st (make-syntax-table)))
+    (modify-syntax-entry ?% "<" st)
+    (modify-syntax-entry ?\n ">" st)
+    (modify-syntax-entry ?\f ">" st)
+    (modify-syntax-entry ?\C-@ "w" st)
+    (modify-syntax-entry ?' "w" st)
+    (modify-syntax-entry ?@ "_" st)
+    (modify-syntax-entry ?* "_" st)
+    (modify-syntax-entry ?\t " " st)
+    ;; ~ is printed by TeX as a space, but it's semantics in the syntax
+    ;; of TeX is not `whitespace' (i.e. it's just like \hspace{foo}).
+    (modify-syntax-entry ?~ "." st)
+    (modify-syntax-entry ?$ "$$" st)
+    (modify-syntax-entry ?\\ "/" st)
+    (modify-syntax-entry ?\" "." st)
+    (modify-syntax-entry ?& "." st)
+    (modify-syntax-entry ?_ "." st)
+    (modify-syntax-entry ?^ "." st)
+    st)
   "Syntax table used while in TeX mode.")
 \f
 ;;;;
@@ -443,8 +441,9 @@ An alternative value is \" . \", if you use a font with a narrow period."
                      '("title"  "begin" "end" "chapter" "part"
                        "section" "subsection" "subsubsection"
                        "paragraph" "subparagraph" "subsubparagraph"
-                       "newcommand" "renewcommand" "newenvironment"
-                       "newtheorem")
+                       "newcommand" "renewcommand" "providecommand"
+                       "newenvironment" "renewenvironment"
+                       "newtheorem" "renewtheorem")
                      t))
           (variables (regexp-opt
                       '("newcounter" "newcounter*" "setcounter" "addtocounter"
@@ -485,8 +484,8 @@ An alternative value is \" . \", if you use a font with a narrow period."
             ;; as improves the behavior in the very rare case where you do
             ;; have a comment in ARG.
             3 'font-lock-function-name-face 'keep)
-       (list (concat slash "\\(re\\)?newcommand\\** *\\(\\\\[A-Za-z@]+\\)")
-            2 'font-lock-function-name-face 'keep)
+       (list (concat slash "\\(?:provide\\|\\(?:re\\)?new\\)command\\** *\\(\\\\[A-Za-z@]+\\)")
+            1 'font-lock-function-name-face 'keep)
        ;; Variable args.
        (list (concat slash variables " *" arg) 2 'font-lock-variable-name-face)
        ;; Include args.
@@ -504,7 +503,8 @@ An alternative value is \" . \", if you use a font with a narrow period."
            (bold (regexp-opt '("textbf" "textsc" "textup"
                                "boldsymbol" "pmb") t))
            (italic (regexp-opt '("textit" "textsl" "emph") t))
-           (type (regexp-opt '("texttt" "textmd" "textrm" "textsf") t))
+           ;; FIXME: unimplemented yet.
+           ;; (type (regexp-opt '("texttt" "textmd" "textrm" "textsf") t))
            ;;
            ;; Names of commands whose arg should be fontified as a citation.
            (citations (regexp-opt
@@ -527,6 +527,7 @@ An alternative value is \" . \", if you use a font with a narrow period."
            ;; Miscellany.
            (slash "\\\\")
            (opt " *\\(\\[[^]]*\\] *\\)*")
+           (args "\\(\\(?:[^{}&\\]+\\|\\\\.\\|{[^}]*}\\)+\\)")
            (arg "{\\(\\(?:[^{}\\]+\\|\\\\.\\|{[^}]*}\\)+\\)"))
        (list
        ;;
@@ -552,23 +553,105 @@ An alternative value is \" . \", if you use a font with a narrow period."
        ;; (list (concat slash type arg) 2 '(quote bold-italic) 'append)
        ;;
        ;; Old-style bf/em/it/sl.  Stop at `\\' and un-escaped `&', for tables.
-       (list (concat "\\\\\\(\\(bf\\)\\|em\\|it\\|sl\\)\\>"
-                     "\\(\\([^}&\\]\\|\\\\[^\\]\\)+\\)")
-             3 '(if (match-beginning 2) 'bold 'italic) 'append)))))
+       (list (concat "\\\\\\(em\\|it\\|sl\\)\\>" args)
+             2 '(quote italic) 'append)
+       ;; This is separate from the previous one because of cases like
+       ;; {\em foo {\bf bar} bla} where both match.
+       (list (concat "\\\\bf\\>" args) 1 '(quote bold) 'append)))))
    "Gaudy expressions to highlight in TeX modes.")
 
+(defun tex-font-lock-suscript (pos)
+  (unless (or (memq (get-text-property pos 'face)
+                   '(font-lock-constant-face font-lock-builtin-face
+                     font-lock-comment-face tex-verbatim-face))
+             ;; Check for backslash quoting
+             (let ((odd nil)
+                   (pos pos))
+               (while (eq (char-before pos) ?\\)
+                 (setq pos (1- pos) odd (not odd)))
+               odd))
+    (if (eq (char-after pos) ?_)
+       '(face subscript display (raise -0.3))
+      '(face superscript display (raise +0.3)))))
+
+(defconst tex-font-lock-keywords-3
+  (append tex-font-lock-keywords-2
+   (eval-when-compile
+     (let ((general "\\([a-zA-Z@]+\\|[^ \t\n]\\)")
+          (slash "\\\\")
+          ;; This is not the same regexp as before: it has a `+' removed.
+          ;; The + makes the matching faster in the above cases (where we can
+          ;; exit as soon as the match fails) but would make this matching
+          ;; degenerate to nasty complexity (because we try to match the
+          ;; closing brace, which forces trying all matching combinations).
+          (arg "{\\(?:[^{}\\]\\|\\\\.\\|{[^}]*}\\)*"))
+       `((,(concat "[_^] *\\([^\n\\{}]\\|" slash general "\\|" arg "}\\)")
+         (1 (tex-font-lock-suscript (match-beginning 0))
+            append))))))
+  "Experimental expressions to highlight in TeX modes.")
+
 (defvar tex-font-lock-keywords tex-font-lock-keywords-1
   "Default expressions to highlight in TeX modes.")
 
+(defvar tex-verbatim-environments
+  '("verbatim" "verbatim*"))
+
+(defvar tex-font-lock-syntactic-keywords
+  (let ((verbs (regexp-opt tex-verbatim-environments t)))
+    `((,(concat "^\\\\begin *{" verbs "}.*\\(\n\\)") 2 "|")
+      (,(concat "^\\\\end *{" verbs "}\\(.?\\)") 2
+       (unless (<= (match-beginning 0) (point-min))
+        (put-text-property (1- (match-beginning 0)) (match-beginning 0)
+                           'syntax-table (string-to-syntax "|"))
+        "<"))
+      ;; ("^\\(\\\\\\)begin *{comment}" 1 "< b")
+      ;; ("^\\\\end *{comment}.*\\(\n\\)" 1 "> b")
+      ("\\\\verb\\**\\([^a-z@*]\\)" 1 "\""))))
+
+(defun tex-font-lock-unfontify-region (beg end)
+  (font-lock-default-unfontify-region beg end)
+  (while (< beg end)
+    (let ((next (next-single-property-change beg 'display nil end))
+         (prop (get-text-property beg 'display)))
+      (if (and (eq (car-safe prop) 'raise)
+              (member (car-safe (cdr prop)) '(-0.3 +0.3))
+              (null (cddr prop)))
+         (put-text-property beg next 'display nil))
+      (setq beg next))))
+
+(defface superscript
+  '((t :height 0.8)) ;; :raise 0.3
+  "Face used for superscripts.")
+(defface subscript
+  '((t :height 0.8)) ;; :raise -0.3
+  "Face used for subscripts.")
 
 (defface tex-math-face
   '((t :inherit font-lock-string-face))
   "Face used to highlight TeX math expressions.")
 (defvar tex-math-face 'tex-math-face)
+(defface tex-verbatim-face
+  ;; '((t :inherit font-lock-string-face))
+  '((t :family "courier"))
+  "Face used to highlight TeX verbatim environments.")
+(defvar tex-verbatim-face 'tex-verbatim-face)
 
 ;; Use string syntax but math face for $...$.
 (defun tex-font-lock-syntactic-face-function (state)
-  (if (nth 3 state) tex-math-face font-lock-comment-face))
+  (let ((char (nth 3 state)))
+    (cond
+     ((not char) font-lock-comment-face)
+     ((eq char ?$) tex-math-face)
+     (t
+      (when (char-valid-p char)
+       ;; This is a \verb?...? construct.  Let's find the end and mark it.
+       (save-excursion
+         (skip-chars-forward (string ?^ char)) ;; Use `end' ?
+         (when (eq (char-syntax (preceding-char)) ?/)
+           (put-text-property (1- (point)) (point) 'syntax-table '(1)))
+         (unless (eobp)
+           (put-text-property (point) (1+ (point)) 'syntax-table '(7)))))
+      tex-verbatim-face))))
 
 \f
 (defun tex-define-common-keys (keymap)
@@ -612,9 +695,10 @@ An alternative value is \" . \", if you use a font with a narrow period."
     (define-key map "\C-c\C-r" 'tex-region)
     (define-key map "\C-c\C-b" 'tex-buffer)
     (define-key map "\C-c\C-f" 'tex-file)
+    (define-key map "\C-c\C-c" 'tex-compile)
     (define-key map "\C-c\C-i" 'tex-bibtex-file)
-    (define-key map "\C-c\C-o" 'tex-latex-block)
-    (define-key map "\C-c\C-e" 'tex-close-latex-block)
+    (define-key map "\C-c\C-o" 'latex-insert-block)
+    (define-key map "\C-c\C-e" 'latex-close-block)
     (define-key map "\C-c\C-u" 'tex-goto-last-unclosed-latex-block)
     (define-key map "\C-c\C-m" 'tex-feed-input)
     (define-key map [(control return)] 'tex-feed-input)
@@ -635,6 +719,7 @@ An alternative value is \" . \", if you use a font with a narrow period."
 (defvar latex-mode-map
   (let ((map (make-sparse-keymap)))
     (set-keymap-parent map tex-mode-map)
+    (define-key map "\C-c\C-s" 'latex-split-block)
     map)
   "Keymap for `latex-mode'.  See also `tex-mode-map'.")
 
@@ -667,15 +752,7 @@ Inherits `shell-mode-map' with a few additions.")
 
 ;; This would be a lot simpler if we just used a regexp search,
 ;; but then it would be too slow.
-;;;###autoload
-(defun tex-mode ()
-  "Major mode for editing files of input for TeX, LaTeX, or SliTeX.
-Tries to determine (by looking at the beginning of the file) whether
-this file is for plain TeX, LaTeX, or SliTeX and calls `plain-tex-mode',
-`latex-mode', or `slitex-mode', respectively.  If it cannot be determined,
-such as if there are no commands in the file, the value of `tex-default-mode'
-says which mode to use."
-  (interactive)
+(defun tex-guess-mode ()
   (let ((mode tex-default-mode) slash comment)
     (save-excursion
       (goto-char (point-min))
@@ -691,7 +768,8 @@ says which mode to use."
                     (concat
                      (regexp-opt '("documentstyle" "documentclass"
                                    "begin" "subsection" "section"
-                                   "part" "chapter" "newcommand") 'words)
+                                   "part" "chapter" "newcommand"
+                                   "renewcommand") 'words)
                      "\\|NeedsTeXFormat{LaTeX")))
                  (if (looking-at
                       "document\\(style\\|class\\)\\(\\[.*\\]\\)?{slides}")
@@ -700,6 +778,32 @@ says which mode to use."
                'plain-tex-mode))))
     (funcall mode)))
 
+;; `tex-mode' plays two roles: it's the parent of several sub-modes
+;; but it's also the function that chooses between those submodes.
+;; To tell the difference between those two cases where the function
+;; might be called, we check `delay-mode-hooks'.
+(define-derived-mode tex-mode text-mode "generic-TeX"
+  (tex-common-initialization))
+;; We now move the function and define it again.  This gives a warning
+;; in the byte-compiler :-( but it's difficult to avoid because
+;; `define-derived-mode' will necessarily define the function once
+;; and we need to define it a second time for `autoload' to get the
+;; proper docstring.
+(defalias 'tex-mode-internal (symbol-function 'tex-mode))
+;;;###autoload
+(defun tex-mode ()
+  "Major mode for editing files of input for TeX, LaTeX, or SliTeX.
+Tries to determine (by looking at the beginning of the file) whether
+this file is for plain TeX, LaTeX, or SliTeX and calls `plain-tex-mode',
+`latex-mode', or `slitex-mode', respectively.  If it cannot be determined,
+such as if there are no commands in the file, the value of `tex-default-mode'
+says which mode to use."
+  (interactive)
+  (if delay-mode-hooks
+      ;; We're called from one of the children already.
+      (tex-mode-internal)
+    (tex-guess-mode)))
+
 ;;;###autoload
 (defalias 'TeX-mode 'tex-mode)
 ;;;###autoload
@@ -708,7 +812,7 @@ says which mode to use."
 (defalias 'LaTeX-mode 'latex-mode)
 
 ;;;###autoload
-(define-derived-mode plain-tex-mode text-mode "TeX"
+(define-derived-mode plain-tex-mode tex-mode "TeX"
   "Major mode for editing files of input for plain TeX.
 Makes $ and } display the characters they match.
 Makes \" insert `` when it seems to be the beginning of a quotation,
@@ -748,15 +852,13 @@ tex-show-queue-command
 Entering Plain-tex mode runs the hook `text-mode-hook', then the hook
 `tex-mode-hook', and finally the hook `plain-tex-mode-hook'.  When the
 special subshell is initiated, the hook `tex-shell-hook' is run."
-  (tex-common-initialization)
-  (setq tex-command tex-run-command)
-  (setq tex-start-of-header "%\\*\\*start of header")
-  (setq tex-end-of-header "%\\*\\*end of header")
-  (setq tex-trailer "\\bye\n")
-  (run-hooks 'tex-mode-hook))
+  (set (make-local-variable 'tex-command) tex-run-command)
+  (set (make-local-variable 'tex-start-of-header) "%\\*\\*start of header")
+  (set (make-local-variable 'tex-end-of-header) "%\\*\\*end of header")
+  (set (make-local-variable 'tex-trailer) "\\bye\n"))
 
 ;;;###autoload
-(define-derived-mode latex-mode text-mode "LaTeX"
+(define-derived-mode latex-mode tex-mode "LaTeX"
   "Major mode for editing files of input for LaTeX.
 Makes $ and } display the characters they match.
 Makes \" insert `` when it seems to be the beginning of a quotation,
@@ -796,16 +898,16 @@ tex-show-queue-command
 Entering Latex mode runs the hook `text-mode-hook', then
 `tex-mode-hook', and finally `latex-mode-hook'.  When the special
 subshell is initiated, `tex-shell-hook' is run."
-  (tex-common-initialization)
-  (setq tex-command latex-run-command)
-  (setq tex-start-of-header "\\\\document\\(style\\|class\\)")
-  (setq tex-end-of-header "\\\\begin\\s-*{document}")
-  (setq tex-trailer "\\end\\s-*{document}\n")
+  (set (make-local-variable 'tex-command) latex-run-command)
+  (set (make-local-variable 'tex-start-of-header)
+       "\\\\document\\(style\\|class\\)")
+  (set (make-local-variable 'tex-end-of-header) "\\\\begin\\s-*{document}")
+  (set (make-local-variable 'tex-trailer) "\\end\\s-*{document}\n")
   ;; A line containing just $$ is treated as a paragraph separator.
   ;; A line starting with $$ starts a paragraph,
   ;; but does not separate paragraphs if it has more stuff on it.
   (setq paragraph-start
-       (concat "[\f%]\\|[ \t]*\\($\\|\\$\\$\\|"
+       (concat "[ \t]*\\(\\$\\$\\|"
                "\\\\[][]\\|"
                "\\\\" (regexp-opt (append
                                    (mapcar 'car latex-section-alist)
@@ -835,8 +937,7 @@ subshell is initiated, `tex-shell-hook' is run."
   (set (make-local-variable 'outline-regexp) latex-outline-regexp)
   (set (make-local-variable 'outline-level) 'latex-outline-level)
   (set (make-local-variable 'forward-sexp-function) 'latex-forward-sexp)
-  (set (make-local-variable 'skeleton-end-hook) nil)
-  (run-hooks 'tex-mode-hook))
+  (set (make-local-variable 'skeleton-end-hook) nil))
 
 ;;;###autoload
 (define-derived-mode slitex-mode latex-mode "SliTeX"
@@ -884,7 +985,6 @@ Entering SliTeX mode runs the hook `text-mode-hook', then the hook
   (setq tex-start-of-header "\\\\documentstyle{slides}\\|\\\\documentclass{slides}"))
 
 (defun tex-common-initialization ()
-  (set-syntax-table tex-mode-syntax-table)
   ;; Regexp isearch should accept newline and formfeed as whitespace.
   (set (make-local-variable 'search-whitespace-regexp) "[ \t\r\n\f]+")
   ;; A line containing just $$ is treated as a paragraph separator.
@@ -910,15 +1010,21 @@ Entering SliTeX mode runs the hook `text-mode-hook', then the hook
   (set (make-local-variable 'facemenu-end-add-face) "}")
   (set (make-local-variable 'facemenu-remove-face-function) t)
   (set (make-local-variable 'font-lock-defaults)
-       '((tex-font-lock-keywords
-         tex-font-lock-keywords-1 tex-font-lock-keywords-2)
+       '((tex-font-lock-keywords tex-font-lock-keywords-1
+         tex-font-lock-keywords-2 tex-font-lock-keywords-3)
         nil nil ((?$ . "\"")) nil
         ;; Who ever uses that anyway ???
         (font-lock-mark-block-function . mark-paragraph)
         (font-lock-syntactic-face-function
-         . tex-font-lock-syntactic-face-function)))
+         . tex-font-lock-syntactic-face-function)
+        (font-lock-unfontify-region-function
+         . tex-font-lock-unfontify-region)
+        (font-lock-syntactic-keywords
+         . tex-font-lock-syntactic-keywords)
+        (parse-sexp-lookup-properties . t)))
   ;; TABs in verbatim environments don't do what you think.
   (set (make-local-variable 'indent-tabs-mode) nil)
+  ;; Other vars that should be buffer-local.
   (make-local-variable 'tex-command)
   (make-local-variable 'tex-start-of-header)
   (make-local-variable 'tex-end-of-header)
@@ -985,8 +1091,7 @@ on the line for the invalidity you want to see."
        (num-matches 0))
     (with-output-to-temp-buffer "*Occur*"
       (princ "Mismatches:\n")
-      (save-excursion
-       (set-buffer standard-output)
+      (with-current-buffer standard-output
        (occur-mode)
        ;; This won't actually work...Really, this whole thing should
        ;; be rewritten instead of being a hack on top of occur.
@@ -1003,8 +1108,7 @@ on the line for the invalidity you want to see."
                  (forward-char 2))
              (goto-char (setq prev-end (point-min))))
            (or (tex-validate-region (point) end)
-               (let* ((oend end)
-                      (end (save-excursion (forward-line 1) (point)))
+               (let* ((end (line-beginning-position 2))
                       start tem)
                  (beginning-of-line)
                  (setq start (point))
@@ -1087,46 +1191,66 @@ A prefix arg inhibits the checking."
       (message "Paragraph being closed appears to contain a mismatch"))
   (insert "\n\n"))
 
-(defun tex-insert-braces ()
+(define-skeleton tex-insert-braces
   "Make a pair of braces and be poised to type inside of them."
-  (interactive "*")
-  (insert ?\{)
-  (save-excursion
-    (insert ?})))
+  nil
+  ?\{ _ ?})
 
 ;; This function is used as the value of fill-nobreak-predicate
 ;; in LaTeX mode.  Its job is to prevent line-breaking inside
 ;; of a \verb construct.
 (defun latex-fill-nobreak-predicate ()
-  (let ((opoint (point))
-       inside)
-    (save-excursion
-      (beginning-of-line)
-      (while (re-search-forward "\\\\verb\\(.\\)" opoint t)
-       (unless (re-search-forward (regexp-quote (match-string 1)) opoint t)
-         (setq inside t))))
-    inside))
+  (save-excursion
+    (skip-chars-backward " ")
+    ;; Don't break after \ since `\ ' has special meaning.
+    (or (and (not (bobp)) (memq (char-syntax (char-before)) '(?\\ ?/)))
+       (let ((opoint (point))
+             inside)
+         (beginning-of-line)
+         (while (re-search-forward "\\\\verb\\(.\\)" opoint t)
+           (unless (re-search-forward (regexp-quote (match-string 1)) opoint t)
+             (setq inside t)))
+         inside))))
 
 (defvar latex-block-default "enumerate")
 
+(defvar latex-block-args-alist
+  '(("array" nil ?\{ (skeleton-read "[options]: ") ?\})
+    ("tabular" nil ?\{ (skeleton-read "[options]: ") ?\}))
+  "Skeleton element to use for arguments to particular environments.
+Every element of the list has the form (NAME . SKEL-ELEM) where NAME is
+the name of the environment and SKEL-ELEM is an element to use in
+a skeleton (see `skeleton-insert').")
+
+(defvar latex-block-body-alist
+  '(("enumerate" nil '(latex-insert-item) > _)
+    ("itemize" nil '(latex-insert-item) > _)
+    ("table" nil "\\caption{" > - "}" > \n _)
+    ("figure" nil  > _ \n "\\caption{" > _ "}" >))
+  "Skeleton element to use for the body of particular environments.
+Every element of the list has the form (NAME . SKEL-ELEM) where NAME is
+the name of the environment and SKEL-ELEM is an element to use in
+a skeleton (see `skeleton-insert').")
+
 ;; Like tex-insert-braces, but for LaTeX.
-(define-skeleton tex-latex-block
-  "Create a matching pair of lines \\begin[OPT]{NAME} and \\end{NAME} at point.
+(defalias 'tex-latex-block 'latex-insert-block)
+(define-skeleton latex-insert-block
+  "Create a matching pair of lines \\begin{NAME} and \\end{NAME} at point.
 Puts point on a blank line between them."
   (let ((choice (completing-read (format "LaTeX block name [%s]: "
                                         latex-block-default)
                                 (append latex-block-names
-                                        standard-latex-block-names)
+                                        latex-standard-block-names)
                                 nil nil nil nil latex-block-default)))
     (setq latex-block-default choice)
-    (unless (or (member choice standard-latex-block-names)
+    (unless (or (member choice latex-standard-block-names)
                (member choice latex-block-names))
       ;; Remember new block names for later completion.
       (push choice latex-block-names))
     choice)
   \n "\\begin{" str "}"
-  ?\[ (skeleton-read "[options]: ") & ?\] | -1
-  > \n _ \n
+  (cdr (assoc str latex-block-args-alist))
+  > \n (or (cdr (assoc str latex-block-body-alist)) '(nil > _)) \n
   "\\end{" str "}" > \n)
 
 (define-skeleton latex-insert-item
@@ -1139,17 +1263,30 @@ Puts point on a blank line between them."
 ;;;; LaTeX syntax navigation
 ;;;;
 
+(defmacro tex-search-noncomment (&rest body)
+  "Execute BODY as long as it return non-nil and point is in a comment.
+Return the value returned by the last execution of BODY."
+  (declare (debug t))
+  (let ((res-sym (make-symbol "result")))
+    `(let (,res-sym)
+       (while
+          (and (setq ,res-sym (progn ,@body))
+               (save-excursion (skip-chars-backward "^\n%") (not (bolp)))))
+       ,res-sym)))
+
 (defun tex-last-unended-begin ()
   "Leave point at the beginning of the last `\\begin{...}' that is unended."
   (condition-case nil
-      (while (and (re-search-backward "\\\\\\(begin\\|end\\)\\s *{")
+      (while (and (tex-search-noncomment
+                  (re-search-backward "\\\\\\(begin\\|end\\)\\s *{"))
                  (looking-at "\\\\end"))
        (tex-last-unended-begin))
     (search-failed (error "Couldn't find unended \\begin"))))
 
 (defun tex-next-unmatched-end ()
   "Leave point at the end of the next `\\end' that is unended."
-  (while (and (re-search-forward "\\\\\\(begin\\|end\\)\\s *{[^}]+}")
+  (while (and (tex-search-noncomment
+              (re-search-forward "\\\\\\(begin\\|end\\)\\s *{[^}]+}"))
              (save-excursion (goto-char (match-beginning 0))
                              (looking-at "\\\\begin")))
     (tex-next-unmatched-end)))
@@ -1235,23 +1372,32 @@ Mark is left at original location."
        (down-list 1)
       (forward-sexp 1)
       ;; Skip arguments.
-      (while (looking-at "[ \t]*\\s(") (forward-sexp)))))
+      (while (looking-at "[ \t]*[[{(]")
+       (with-syntax-table tex-mode-syntax-table
+         (forward-sexp))))))
 
-(defun tex-close-latex-block ()
-  "Creates an \\end{...} to match the last unclosed \\begin{...}."
-  (interactive "*")
-  (let ((new-line-needed (bolp))
-       text indentation)
-    (save-excursion
-      (condition-case nil
-          (tex-last-unended-begin)
-        (error (error "Couldn't find unended \\begin")))
-      (setq indentation (current-column))
-      (re-search-forward "\\\\begin\\(\\s *{[^}\n]*}\\)")
-      (setq text (buffer-substring (match-beginning 1) (match-end 1))))
-    (indent-to indentation)
-    (insert "\\end" text)
-    (if new-line-needed (insert ?\n))))
+(defalias 'tex-close-latex-block 'latex-close-block)
+(define-skeleton latex-close-block
+  "Create an \\end{...} to match the last unclosed \\begin{...}."
+  (save-excursion
+    (tex-last-unended-begin)
+    (if (not (looking-at "\\\\begin\\(\\s *{[^}\n]*}\\)")) '("{" _ "}")
+      (match-string 1)))
+  \n "\\end" str > \n)
+
+(define-skeleton latex-split-block
+  "Split the enclosing environment by inserting \\end{..}\\begin{..} at point."
+  (save-excursion
+    (tex-last-unended-begin)
+    (if (not (looking-at "\\\\begin\\(\\s *{[^}\n]*}\\)")) '("{" _ "}")
+      (prog1 (match-string 1)
+       (goto-char (match-end 1))
+       (setq v1 (buffer-substring (point)
+                                  (progn
+                                    (while (looking-at "[ \t]*[[{]")
+                                      (forward-sexp 1))
+                                    (point)))))))
+  \n "\\end" str > \n _ \n "\\begin" str v1 > \n)
 
 (defconst tex-discount-args-cmds
   '("begin" "end" "input" "special" "cite" "ref" "include" "includeonly"
@@ -1412,23 +1558,84 @@ If NOT-ALL is non-nil, save the `.dvi' file."
 
 (add-hook 'kill-emacs-hook 'tex-delete-last-temp-files)
 
+;;
+;; Machinery to guess the command that the user wants to execute.
+;;
+
+(defvar tex-compile-history nil)
+
+(defvar tex-input-files-re
+  (eval-when-compile
+    (concat "\\." (regexp-opt '("tex" "texi" "texinfo"
+                               "bbl" "ind" "sty" "cls") t)
+           ;; Include files with no dots (for directories).
+           "\\'\\|\\`[^.]+\\'")))
+
+(defcustom tex-use-reftex t
+  "If non-nil, use RefTeX's list of files to determine what command to use."
+  :type 'boolean)
+
+(defvar tex-compile-commands
+  '(((concat "pdf" tex-command
+            " " (if (< 0 (length tex-start-commands))
+                    (shell-quote-argument tex-start-commands)) " %f")
+     t "%r.pdf")
+    ((concat tex-command
+            " " (if (< 0 (length tex-start-commands))
+                    (shell-quote-argument tex-start-commands)) " %f")
+     t "%r.dvi")
+    ("xdvi %r &" "%r.dvi")
+    ("advi %r &" "%r.dvi")
+    ("bibtex %r" "%r.aux" "%r.bbl")
+    ("makeindex %r" "%r.idx" "%r.ind")
+    ("texindex %r.??")
+    ("dvipdfm %r" "%r.dvi" "%r.pdf")
+    ("dvipdf %r" "%r.dvi" "%r.pdf")
+    ("dvips %r" "%r.dvi" "%r.ps")
+    ("gv %r.ps &" "%r.ps")
+    ("gv %r.pdf &" "%r.pdf")
+    ("xpdf %r.pdf &" "%r.pdf")
+    ("lpr %r.ps" "%r.ps"))
+  "List of commands for `tex-compile'.
+Each element should be of the form (FORMAT IN OUT) where
+FORMAT is an expression that evaluates to a string that can contain
+  - `%r' the main file name without extension.
+  - `%f' the main file name.
+IN can be either a string (with the same % escapes in it) indicating
+  the name of the input file, or t to indicate that the input is all
+  the TeX files of the document, or nil if we don't know.
+OUT describes the output file and is either a %-escaped string
+  or nil to indicate that there is no output file.")
+
+;; defsubst* gives better byte-code than defsubst.
+(defsubst* tex-string-prefix-p (str1 str2)
+  "Return non-nil if STR1 is a prefix of STR2"
+  (eq t (compare-strings str2 nil (length str1) str1 nil nil)))
+
 (defun tex-guess-main-file (&optional all)
   "Find a likely `tex-main-file'.
 Looks for hints in other buffers in the same directory or in
-ALL other buffers."
+ALL other buffers.  If ALL is `sub' only look at buffers in parent directories
+of the current buffer."
   (let ((dir default-directory)
        (header-re tex-start-of-header))
     (catch 'found
       ;; Look for a buffer with `tex-main-file' set.
       (dolist (buf (if (consp all) all (buffer-list)))
        (with-current-buffer buf
-         (when (and (or all (equal dir default-directory))
+         (when (and (cond
+                     ((null all) (equal dir default-directory))
+                     ((eq all 'sub) (tex-string-prefix-p default-directory dir))
+                     (t))
                     (stringp tex-main-file))
            (throw 'found (expand-file-name tex-main-file)))))
       ;; Look for a buffer containing the magic `tex-start-of-header'.
       (dolist (buf (if (consp all) all (buffer-list)))
        (with-current-buffer buf
-         (when (and (or all (equal dir default-directory))
+         (when (and (cond
+                     ((null all) (equal dir default-directory))
+                     ((eq all 'sub) (tex-string-prefix-p default-directory dir))
+                     (t))
                     buffer-file-name
                     ;; (or (easy-mmode-derived-mode-p 'latex-mode)
                     ;;          (easy-mmode-derived-mode-p 'plain-tex-mode))
@@ -1444,8 +1651,10 @@ ALL other buffers."
   "Return the relative name of the main file."
   (let* ((file (or tex-main-file
                   ;; Compatibility with AUCTeX.
-                  (and (boundp 'TeX-master) (stringp TeX-master)
-                       (set (make-local-variable 'tex-main-file) TeX-master))
+                  (with-no-warnings
+                   (when (and (boundp 'TeX-master) (stringp TeX-master))
+                     (make-local-variable 'tex-main-file)
+                     (setq tex-main-file TeX-master)))
                   ;; Try to guess the main file.
                   (if (not buffer-file-name)
                       (error "Buffer is not associated with any file")
@@ -1458,10 +1667,188 @@ ALL other buffers."
                          buffer-file-name
                        ;; This isn't the main file, let's try to find better,
                        (or (tex-guess-main-file)
+                           (tex-guess-main-file 'sub)
                            ;; (tex-guess-main-file t)
                            buffer-file-name)))))))
     (if (file-exists-p file) file (concat file ".tex"))))
 
+(defun tex-summarize-command (cmd)
+  (if (not (stringp cmd)) ""
+    (mapconcat 'identity
+              (mapcar (lambda (s) (car (split-string s)))
+                      (split-string cmd "\\s-*\\(?:;\\|&&\\)\\s-*"))
+              "&")))
+
+(defun tex-uptodate-p (file)
+  "Return non-nil if FILE is not uptodate w.r.t the document source files.
+FILE is typically the output DVI or PDF file."
+  ;; We should check all the files included !!!
+  (and
+   ;; Clearly, the target must exist.
+   (file-exists-p file)
+   ;; And the last run must not have asked for a rerun.
+   ;; FIXME: this should check that the last run was done on the same file.
+   (let ((buf (condition-case nil (tex-shell-buf) (error nil))))
+     (when buf
+       (with-current-buffer buf
+        (save-excursion
+          (goto-char (point-max))
+          (and (re-search-backward
+                "(see the transcript file for additional information)" nil t)
+               (> (save-excursion
+                    (or (re-search-backward "\\[[0-9]+\\]" nil t)
+                        (point-min)))
+                  (save-excursion
+                    (or (re-search-backward "Rerun" nil t)
+                        (point-min)))))))))
+   ;; And the input files must not have been changed in the meantime.
+   (let ((files (if (and tex-use-reftex
+                        (fboundp 'reftex-scanning-info-available-p)
+                        (reftex-scanning-info-available-p))
+                   (reftex-all-document-files)
+                 (list (file-name-directory (expand-file-name file)))))
+        (ignored-dirs-re
+         (concat
+          (regexp-opt
+           (delq nil (mapcar (lambda (s) (if (eq (aref s (1- (length s))) ?/)
+                                        (substring s 0 (1- (length s)))))
+                             completion-ignored-extensions))
+           t) "\\'"))
+        (uptodate t))
+     (while (and files uptodate)
+       (let ((f (pop files)))
+        (if (file-directory-p f)
+            (unless (string-match ignored-dirs-re f)
+              (setq files (nconc
+                           (directory-files f t tex-input-files-re)
+                           files)))
+          (when (file-newer-than-file-p f file)
+            (setq uptodate nil)))))
+     uptodate)))
+    
+
+(autoload 'format-spec "format-spec")
+
+(defvar tex-executable-cache nil)
+(defun tex-executable-exists-p (name)
+  "Like `executable-find' but with a cache."
+  (let ((cache (assoc name tex-executable-cache)))
+    (if cache (cdr cache)
+      (let ((executable (executable-find name)))
+       (push (cons name executable) tex-executable-cache)
+       executable))))
+
+(defun tex-command-executable (cmd)
+  (let ((s (if (stringp cmd) cmd (eval (car cmd)))))
+    (substring s 0 (string-match "[ \t]\\|\\'" s))))
+
+(defun tex-command-active-p (cmd fspec)
+  "Return non-nil if the CMD spec might need to be run."
+  (let ((in (nth 1 cmd))
+       (out (nth 2 cmd)))
+    (if (stringp in)
+       (let ((file (format-spec in fspec)))
+         (when (file-exists-p file)
+           (or (not out)
+               (file-newer-than-file-p
+                file (format-spec out fspec)))))
+      (when (and (eq in t) (stringp out))
+       (not (tex-uptodate-p (format-spec out fspec)))))))
+
+(defun tex-compile-default (fspec)
+  "Guess a default command given the format-spec FSPEC."
+  ;; TODO: Learn to do latex+dvips!
+  (let ((cmds nil)
+       (unchanged-in nil))
+    ;; Only consider active commands.
+    (dolist (cmd tex-compile-commands)
+      (when (tex-executable-exists-p (tex-command-executable cmd))
+       (if (tex-command-active-p cmd fspec)
+           (push cmd cmds)
+         (push (nth 1 cmd) unchanged-in))))
+    ;; Remove those commands whose input was considered stable for
+    ;; some other command (typically if (t . "%.pdf") is inactive
+    ;; then we're using pdflatex and the fact that the dvi file
+    ;; is inexistent doesn't matter).
+    (let ((tmp nil))
+      (dolist (cmd cmds)
+       (unless (member (nth 1 cmd) unchanged-in)
+         (push cmd tmp)))
+      ;; Only remove if there's something left.
+      (if tmp (setq cmds tmp)))
+    ;; Remove commands whose input is not uptodate either.
+    (let ((outs (delq nil (mapcar (lambda (x) (nth 2 x)) cmds)))
+         (tmp nil))
+      (dolist (cmd cmds)
+       (unless (member (nth 1 cmd) outs)
+         (push cmd tmp)))
+      ;; Only remove if there's something left.
+      (if tmp (setq cmds tmp)))
+    ;; Select which file we're going to operate on (the latest).
+    (let ((latest (nth 1 (car cmds))))
+      (dolist (cmd (prog1 (cdr cmds) (setq cmds (list (car cmds)))))
+       (if (equal latest (nth 1 cmd))
+           (push cmd cmds)
+         (unless (eq latest t)         ;Can't beat that!
+           (if (or (not (stringp latest))
+                   (eq (nth 1 cmd) t)
+                   (and (stringp (nth 1 cmd))
+                        (file-newer-than-file-p
+                         (format-spec (nth 1 cmd) fspec)
+                         (format-spec latest fspec))))
+               (setq latest (nth 1 cmd) cmds (list cmd)))))))
+    ;; Expand the command spec into the actual text.
+    (dolist (cmd (prog1 cmds (setq cmds nil)))
+      (push (cons (eval (car cmd)) (cdr cmd)) cmds))
+    ;; Select the favorite command from the history.
+    (let ((hist tex-compile-history)
+         re hist-cmd)
+      (while hist
+       (setq hist-cmd (pop hist))
+       (setq re (concat "\\`"
+                        (regexp-quote (tex-command-executable hist-cmd))
+                        "\\([ \t]\\|\\'\\)"))
+       (dolist (cmd cmds)
+         ;; If the hist entry uses the same command and applies to a file
+         ;; of the same type (e.g. `gv %r.pdf' vs `gv %r.ps'), select cmd.
+         (and (string-match re (car cmd))
+              (or (not (string-match "%[fr]\\([-._[:alnum:]]+\\)" (car cmd)))
+                  (string-match (regexp-quote (match-string 1 (car cmd)))
+                                hist-cmd))
+              (setq hist nil cmds (list cmd)))))
+      ;; Substitute and return.
+      (if (and hist-cmd
+              (string-match (concat "[' \t\"]" (format-spec "%r" fspec)
+                                    "\\([;&' \t\"]\\|\\'\\)") hist-cmd))
+         ;; The history command was already applied to the same file,
+         ;; so just reuse it.
+         hist-cmd
+       (if cmds (format-spec (caar cmds) fspec))))))
+
+(defun tex-compile (dir cmd)
+  "Run a command CMD on current TeX buffer's file in DIR."
+  ;; FIXME: Use time-stamps on files to decide the next op.
+  (interactive
+   (let* ((file (tex-main-file))
+         (default-directory
+           (prog1 (file-name-directory (expand-file-name file))
+             (setq file (file-name-nondirectory file))))
+         (root (file-name-sans-extension file))
+         (fspec (list (cons ?r (comint-quote-filename root))
+                      (cons ?f (comint-quote-filename file))))
+         (default (tex-compile-default fspec)))
+     (list default-directory
+          (completing-read
+           (format "Command [%s]: " (tex-summarize-command default))
+           (mapcar (lambda (x)
+                     (list (format-spec (eval (car x)) fspec)))
+                   tex-compile-commands)
+           nil nil nil 'tex-compile-history default))))
+  (save-some-buffers (not compilation-ask-about-save) nil)
+  (if (tex-shell-running)
+      (tex-kill-job)
+    (tex-start-shell))
+  (tex-send-tex-command cmd dir))
 
 (defun tex-start-tex (command file &optional dir)
   "Start a TeX run, using COMMAND on FILE."
@@ -1749,7 +2136,6 @@ The last line of the buffer is displayed on
 line LINE of the window, or centered if LINE is nil."
   (interactive "P")
   (let ((tex-shell (get-buffer "*tex-shell*"))
-       (old-buffer (current-buffer))
        (window))
     (if (null tex-shell)
        (message "No TeX output buffer")
@@ -1783,7 +2169,9 @@ is provided, use the alternative command, `tex-alt-dvi-print-command'."
         (tex-start-shell))
       (tex-send-command
        (if alt tex-alt-dvi-print-command tex-dvi-print-command)
-       print-file-name-dvi t))))
+       (shell-quote-argument
+       print-file-name-dvi)
+       t))))
 
 (defun tex-alt-print ()
   "Print the .dvi file made by \\[tex-region], \\[tex-buffer] or \\[tex-file].
@@ -1800,7 +2188,7 @@ because there is no standard value that would generally work."
   (interactive)
   (or tex-dvi-view-command
       (error "You must set `tex-dvi-view-command'"))
-  (let ((tex-dvi-print-command tex-dvi-view-command))
+  (let ((tex-dvi-print-command (eval tex-dvi-view-command)))
     (tex-print)))
 
 (defun tex-append (file-name suffix)
@@ -1864,24 +2252,28 @@ Runs the shell command defined by `tex-show-queue-command'."
 (defvar tex-indent-item tex-indent-basic)
 (defvar tex-indent-item-re "\\\\\\(bib\\)?item\\>")
 
-(easy-mmode-defsyntax tex-latex-indent-syntax-table
-  '((?$ . ".")
-    (?\( . ".")
-    (?\) . "."))
-  "Syntax table used while computing indentation."
-  :copy tex-mode-syntax-table)
+(defvar tex-latex-indent-syntax-table
+  (let ((st (make-syntax-table tex-mode-syntax-table)))
+    (modify-syntax-entry ?$ "." st)
+    (modify-syntax-entry ?\( "." st)
+    (modify-syntax-entry ?\) "." st)
+    st)
+  "Syntax table used while computing indentation.")
 
 (defun latex-indent (&optional arg)
-  (with-syntax-table tex-latex-indent-syntax-table
-    ;; TODO: Rather than ignore $, we should try to be more clever about it.
-    (let ((indent
-          (save-excursion
-            (beginning-of-line)
-            (latex-find-indent))))
-      (if (< indent 0) (setq indent 0))
-      (if (<= (current-column) (current-indentation))
-         (indent-line-to indent)
-       (save-excursion (indent-line-to indent))))))
+  (if (and (eq (get-text-property (line-beginning-position) 'face)
+              tex-verbatim-face))
+      'noindent
+    (with-syntax-table tex-latex-indent-syntax-table
+      ;; TODO: Rather than ignore $, we should try to be more clever about it.
+      (let ((indent
+            (save-excursion
+              (beginning-of-line)
+              (latex-find-indent))))
+       (if (< indent 0) (setq indent 0))
+       (if (<= (current-column) (current-indentation))
+           (indent-line-to indent)
+         (save-excursion (indent-line-to indent)))))))
 
 (defun latex-find-indent (&optional virtual)
   "Find the proper indentation of text after point.
@@ -1891,9 +2283,15 @@ There might be text before point."
   (save-excursion
     (skip-chars-forward " \t")
     (or
+     ;; Stick the first line at column 0.
+     (and (= (point-min) (line-beginning-position)) 0)
      ;; Trust the current indentation, if such info is applicable.
-     (and virtual (>= (current-indentation) (current-column))
-         (current-indentation))
+     (and virtual (save-excursion (skip-chars-backward " \t&") (bolp))
+         (current-column))
+     ;; Stick verbatim environments to the left margin.
+     (and (looking-at "\\\\\\(begin\\|end\\) *{\\([^\n}]+\\)")
+         (member (match-string 2) tex-verbatim-environments)
+         0)
      ;; Put leading close-paren where the matching open brace would be.
      (and (eq (latex-syntax-after) ?\))
          (ignore-errors
@@ -1924,7 +2322,7 @@ There might be text before point."
                  (> pos (progn (latex-down-list)
                                (forward-comment (point-max))
                                (point))))
-            ;; Align with the first element after the open-paren.
+                ;; Align with the first element after the open-paren.
             (current-column)
           ;; We're the first element after a hanging brace.
           (goto-char up-list-pos)
@@ -1934,8 +2332,8 @@ There might be text before point."
         ;; Nothing particular here: just keep the same indentation.
         (+ indent (current-column)))
        ;; We're now looking at a macro call.
-       ((looking-at tex-indent-item-re)
-        ;; Indenting relative to an item, have to re-add the outdenting.
+         ((looking-at tex-indent-item-re)
+          ;; Indenting relative to an item, have to re-add the outdenting.
         (+ indent (current-column) tex-indent-item))
        (t
         (let ((col (current-column)))
@@ -1943,18 +2341,76 @@ There might be text before point."
               ;; If the first char was not an open-paren, there's
               ;; a risk that this is really not an argument to the
               ;; macro at all.
-              (+ indent col)
-            (forward-sexp 1)
-            (if (< (line-end-position)
-                   (save-excursion (forward-comment (point-max))
-                                   (point)))
+                (+ indent col)
+              (forward-sexp 1)
+              (if (< (line-end-position)
+                     (save-excursion (forward-comment (point-max))
+                                     (point)))
                 ;; we're indenting the first argument.
                 (min (current-column) (+ tex-indent-arg col))
               (skip-syntax-forward " ")
               (current-column))))))))))
+;;; DocTeX support
+
+(defun doctex-font-lock-^^A ()
+  (if (eq (char-after (line-beginning-position)) ?\%)
+      (progn
+       (put-text-property
+        (1- (match-beginning 1)) (match-beginning 1)
+        'syntax-table
+        (if (= (1+ (line-beginning-position)) (match-beginning 1))
+            ;; The `%' is a single-char comment, which Emacs
+            ;; syntax-table can't deal with.  We could turn it
+            ;; into a non-comment, or use `\n%' or `%^' as the comment.
+            ;; Instead, we include it in the ^^A comment.
+            (eval-when-compile (string-to-syntax "< b"))
+          (eval-when-compile (string-to-syntax ">"))))
+       (let ((end (line-end-position)))
+         (if (< end (point-max))
+             (put-text-property
+              end (1+ end)
+              'syntax-table
+              (eval-when-compile (string-to-syntax "> b")))))
+       (eval-when-compile (string-to-syntax "< b")))))
+
+(defun doctex-font-lock-syntactic-face-function (state)
+  ;; Mark DocTeX documentation, which is parsed as a style A comment
+  ;; starting in column 0.
+  (if (or (nth 3 state) (nth 7 state)
+         (not (memq (char-before (nth 8 state))
+                    '(?\n nil))))
+      ;; Anything else is just as for LaTeX.
+      (tex-font-lock-syntactic-face-function state)
+    font-lock-doc-face))
+
+(defvar doctex-font-lock-syntactic-keywords
+  (append
+   tex-font-lock-syntactic-keywords
+   ;; For DocTeX comment-in-doc.
+   `(("\\(\\^\\)\\^A" (1 (doctex-font-lock-^^A))))))
+
+(defvar doctex-font-lock-keywords
+  (append tex-font-lock-keywords
+         '(("^%<[^>]*>" (0 font-lock-preprocessor-face t)))))
+
+;;;###autoload
+(define-derived-mode doctex-mode latex-mode "DocTeX"
+  "Major mode to edit DocTeX files."
+  (setq font-lock-defaults
+       (cons (append (car font-lock-defaults) '(doctex-font-lock-keywords))
+             (mapcar
+              (lambda (x)
+                (case (car-safe x)
+                  (font-lock-syntactic-keywords
+                   (cons (car x) 'doctex-font-lock-syntactic-keywords))
+                  (font-lock-syntactic-face-function
+                   (cons (car x) 'doctex-font-lock-syntactic-face-function))
+                  (t x)))
+              (cdr font-lock-defaults)))))
 
 (run-hooks 'tex-mode-load-hook)
 
 (provide 'tex-mode)
 
+;;; arch-tag: c0a680b1-63aa-4547-84b9-4193c29c0080
 ;;; tex-mode.el ends here