[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Emacs-diffs] Changes to emacs/lisp/textmodes/tex-mode.el [emacs-unicode
From: |
Dave Love |
Subject: |
[Emacs-diffs] Changes to emacs/lisp/textmodes/tex-mode.el [emacs-unicode-2] |
Date: |
Wed, 10 Sep 2003 13:50:38 -0400 |
Index: emacs/lisp/textmodes/tex-mode.el
diff -c /dev/null emacs/lisp/textmodes/tex-mode.el:1.137.2.1
*** /dev/null Wed Sep 10 13:50:38 2003
--- emacs/lisp/textmodes/tex-mode.el Wed Sep 10 13:50:37 2003
***************
*** 0 ****
--- 1,2415 ----
+ ;;; 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,2003
+ ;; Free Software Foundation, Inc.
+
+ ;; Maintainer: FSF
+ ;; Keywords: tex
+
+ ;; Contributions over the years by William F. Schelter, Dick King,
+ ;; Stephen Gildea, Michael Prange, Jacob Gore, and Edward M. Reingold.
+
+ ;; This file is part of GNU Emacs.
+
+ ;; GNU Emacs is free software; you can redistribute it and/or modify
+ ;; it under the terms of the GNU General Public License as published by
+ ;; the Free Software Foundation; either version 2, or (at your option)
+ ;; any later version.
+
+ ;; 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, Inc., 59 Temple Place - Suite 330,
+ ;; Boston, MA 02111-1307, USA.
+
+ ;;; Commentary:
+
+ ;;; Code:
+
+ ;; Pacify the byte-compiler
+ (eval-when-compile
+ (require 'compare-w)
+ (require 'cl)
+ (require 'skeleton))
+
+ (require 'shell)
+ (require 'compile)
+
+ (defgroup tex-file nil
+ "TeX files and directories"
+ :prefix "tex-"
+ :group 'tex)
+
+ (defgroup tex-run nil
+ "Running external commands from TeX mode"
+ :prefix "tex-"
+ :group 'tex)
+
+ (defgroup tex-view nil
+ "Viewing and printing TeX files"
+ :prefix "tex-"
+ :group 'tex)
+
+ ;;;###autoload
+ (defcustom tex-shell-file-name nil
+ "*If non-nil, the shell file name to run in the subshell used to run TeX."
+ :type '(choice (const :tag "None" nil)
+ string)
+ :group 'tex-run)
+
+ ;;;###autoload
+ (defcustom tex-directory "."
+ "*Directory in which temporary files are written.
+ You can make this `/tmp' if your TEXINPUTS has no relative directories in it
+ and you don't try to apply \\[tex-region] or \\[tex-buffer] when there are
+ `\\input' commands with relative directories."
+ :type 'directory
+ :group 'tex-file)
+
+ ;;;###autoload
+ (defcustom tex-first-line-header-regexp nil
+ "Regexp for matching a first line which `tex-region' should include.
+ If this is non-nil, it should be a regular expression string;
+ if it matches the first line of the file,
+ `tex-region' always includes the first line in the TeX run."
+ :type '(choice (const :tag "None" nil)
+ regexp)
+ :group 'tex-file)
+
+ ;;;###autoload
+ (defcustom tex-main-file nil
+ "*The main TeX source file which includes this buffer's file.
+ The command `tex-file' runs TeX on the file specified by `tex-main-file'
+ if the variable is non-nil."
+ :type '(choice (const :tag "None" nil)
+ file)
+ :group 'tex-file)
+
+ ;;;###autoload
+ (defcustom tex-offer-save t
+ "*If non-nil, ask about saving modified buffers before \\[tex-file] is run."
+ :type 'boolean
+ :group 'tex-file)
+
+ ;;;###autoload
+ (defcustom tex-run-command "tex"
+ "*Command used to run TeX subjob.
+ TeX Mode sets `tex-command' to this string.
+ See the documentation of that variable."
+ :type 'string
+ :group 'tex-run)
+
+ ;;;###autoload
+ (defcustom latex-run-command "latex"
+ "*Command used to run LaTeX subjob.
+ LaTeX Mode sets `tex-command' to this string.
+ See the documentation of that variable."
+ :type 'string
+ :group 'tex-run)
+
+ ;;;###autoload
+ (defcustom slitex-run-command "slitex"
+ "*Command used to run SliTeX subjob.
+ SliTeX Mode sets `tex-command' to this string.
+ See the documentation of that variable."
+ :type 'string
+ :group 'tex-run)
+
+ ;;;###autoload
+ (defcustom tex-start-options ""
+ "*TeX options to use when starting TeX.
+ These immediately precede the commands in `tex-start-commands'
+ and the input file name, with no separating space and are not shell-quoted.
+ If nil, TeX runs with no options. See the documentation of `tex-command'."
+ :type 'string
+ :group 'tex-run
+ :version "21.4")
+
+ ;;;###autoload
+ (defcustom tex-start-commands "\\nonstopmode\\input"
+ "*TeX commands to use when starting TeX.
+ They are shell-quoted and precede the input file name, with a separating
space.
+ If nil, no commands are used. See the documentation of `tex-command'."
+ :type '(radio (const :tag "Interactive \(nil\)" nil)
+ (const :tag "Nonstop \(\"\\nonstopmode\\input\"\)"
+ "\\nonstopmode\\input")
+ (string :tag "String at your choice"))
+ :group 'tex-run
+ :version "21.4")
+
+ (defvar latex-standard-block-names
+ '("abstract" "array" "center" "description"
+ "displaymath" "document" "enumerate" "eqnarray"
+ "eqnarray*" "equation" "figure" "figure*"
+ "flushleft" "flushright" "itemize" "letter"
+ "list" "minipage" "picture" "quotation"
+ "quote" "slide" "sloppypar" "tabbing"
+ "table" "table*" "tabular" "tabular*"
+ "thebibliography" "theindex*" "titlepage" "trivlist"
+ "verbatim" "verbatim*" "verse" "math")
+ "Standard LaTeX block names.")
+
+ ;;;###autoload
+ (defcustom latex-block-names nil
+ "*User defined LaTeX block names.
+ Combined with `latex-standard-block-names' for minibuffer completion."
+ :type '(repeat string)
+ :group 'tex-run)
+
+ ;;;###autoload
+ (defcustom tex-bibtex-command "bibtex"
+ "*Command used by `tex-bibtex-file' to gather bibliographic data.
+ 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."
+ :type 'string
+ :group 'tex-run)
+
+ ;;;###autoload
+ (defcustom tex-dvi-print-command "lpr -d"
+ "*Command used by \\[tex-print] to print a .dvi file.
+ 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."
+ :type 'string
+ :group 'tex-view)
+
+ ;;;###autoload
+ (defcustom tex-alt-dvi-print-command "lpr -d"
+ "*Command used by \\[tex-print] with a prefix arg to print a .dvi file.
+ 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.
+
+ If two printers are not enough of a choice, you can set the variable
+ `tex-alt-dvi-print-command' to an expression that asks what you want;
+ for example,
+
+ (setq tex-alt-dvi-print-command
+ '(format \"lpr -P%s\" (read-string \"Use printer: \")))
+
+ would tell \\[tex-print] with a prefix argument to ask you which printer to
+ use."
+ :type '(choice (string :tag "Command")
+ (sexp :tag "Expression"))
+ :group 'tex-view)
+
+ ;;;###autoload
+ (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 a space, is added at the end.
+
+ 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
+ (defcustom tex-show-queue-command "lpq"
+ "*Command used by \\[tex-show-print-queue] to show the print queue.
+ Should show the queue(s) that \\[tex-print] puts jobs on."
+ :type 'string
+ :group 'tex-view)
+
+ ;;;###autoload
+ (defcustom tex-default-mode 'latex-mode
+ "*Mode to enter for a new file that might be either TeX or LaTeX.
+ This variable is used when it can't be determined whether the file
+ is plain TeX or LaTeX or what because the file contains no commands.
+ Normally set to either `plain-tex-mode' or `latex-mode'."
+ :type 'function
+ :group 'tex)
+
+ ;;;###autoload
+ (defcustom tex-open-quote "``"
+ "*String inserted by typing \\[tex-insert-quote] to open a quotation."
+ :type 'string
+ :options '("``" "\"<" "\"`" "<<" "«")
+ :group 'tex)
+
+ ;;;###autoload
+ (defcustom tex-close-quote "''"
+ "*String inserted by typing \\[tex-insert-quote] to close a quotation."
+ :type 'string
+ :options '("''" "\">" "\"'" ">>" "»")
+ :group 'tex)
+
+ (defvar tex-last-temp-file nil
+ "Latest temporary file generated by \\[tex-region] and \\[tex-buffer].
+ Deleted when the \\[tex-region] or \\[tex-buffer] is next run, or when the
+ tex shell terminates.")
+
+ (defvar tex-command "tex"
+ "*Command to run TeX.
+ If this string contains an asterisk \(`*'\), that is replaced by the file
name;
+ otherwise the value of `tex-start-options', the \(shell-quoted\)
+ value of `tex-start-commands', and the file name are added at the end
+ with blanks as separators.
+
+ In TeX, LaTeX, and SliTeX Mode this variable becomes buffer local.
+ In these modes, use \\[set-variable] if you want to change it for the
+ current buffer.")
+
+ (defvar tex-trailer nil
+ "String appended after the end of a region sent to TeX by \\[tex-region].")
+
+ (defvar tex-start-of-header nil
+ "Regular expression used by \\[tex-region] to find start of file's header.")
+
+ (defvar tex-end-of-header nil
+ "Regular expression used by \\[tex-region] to find end of file's header.")
+
+ (defvar tex-shell-cd-command "cd"
+ "Command to give to shell running TeX to change directory.
+ The value of `tex-directory' is appended to this, separated by a space.")
+
+ (defvar tex-zap-file nil
+ "Temporary file name used for text being sent as input to TeX.
+ Should be a simple file name with no extension or directory specification.")
+
+ (defvar tex-last-buffer-texed nil
+ "Buffer which was last TeXed.")
+
+ (defvar tex-print-file nil
+ "File name that \\[tex-print] prints.
+ Set by \\[tex-region], \\[tex-buffer], and \\[tex-file].")
+
+ (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.")
+
+ ;;;;
+ ;;;; Imenu support
+ ;;;;
+
+ (defcustom latex-imenu-indent-string ". "
+ "*String to add repeated in front of nested sectional units for Imenu.
+ An alternative value is \" . \", if you use a font with a narrow period."
+ :type 'string
+ :group 'tex)
+
+ (defvar latex-section-alist
+ '(("part" . 0) ("chapter" . 1)
+ ("section" . 2) ("subsection" . 3)
+ ("subsubsection" . 4)
+ ("paragraph" . 5) ("subparagraph" . 6)))
+
+ (defvar latex-metasection-list
+ '("documentstyle" "documentclass"
+ "begin{document}" "end{document}"
+ "appendix" "frontmatter" "mainmatter" "backmatter"))
+
+ (defun latex-imenu-create-index ()
+ "Generate an alist for imenu from a LaTeX buffer."
+ (let ((section-regexp
+ (concat "\\\\" (regexp-opt (mapcar 'car latex-section-alist) t)
+ "\\*?[ \t]*{"))
+ (metasection-regexp
+ (concat "\\\\" (regexp-opt latex-metasection-list t)))
+ i0 menu case-fold-search)
+ (save-excursion
+ ;; Find the top-most level in this file but don't allow it to be
+ ;; any deeper than "section" (which is top-level in an article).
+ (goto-char (point-min))
+ (if (search-forward-regexp "\\\\part\\*?[ \t]*{" nil t)
+ (setq i0 0)
+ (if (search-forward-regexp "\\\\chapter\\*?[ \t]*{" nil t)
+ (setq i0 1)
+ (setq i0 2)))
+
+ ;; Look for chapters and sections.
+ (goto-char (point-min))
+ (while (search-forward-regexp section-regexp nil t)
+ (let ((start (match-beginning 0))
+ (here (point))
+ (i (cdr (assoc (buffer-substring-no-properties
+ (match-beginning 1)
+ (match-end 1))
+ latex-section-alist))))
+ (backward-char 1)
+ (condition-case err
+ (progn
+ ;; Using sexps allows some use of matching {...} inside
+ ;; titles.
+ (forward-sexp 1)
+ (push (cons (concat (apply 'concat
+ (make-list
+ (max 0 (- i i0))
+ latex-imenu-indent-string))
+ (buffer-substring-no-properties
+ here (1- (point))))
+ start)
+ menu))
+ (error nil))))
+
+ ;; Look for included material.
+ (goto-char (point-min))
+ (while (search-forward-regexp
+ "\\\\\\(include\\|input\\|verbatiminput\\|bibliography\\)\
+ \[ \t]*{\\([^}\n]+\\)}"
+ nil t)
+ (push (cons (concat "<<" (buffer-substring-no-properties
+ (match-beginning 2)
+ (match-end 2))
+ (if (= (char-after (match-beginning 1)) ?b)
+ ".bbl"
+ ".tex"))
+ (match-beginning 0))
+ menu))
+
+ ;; Look for \frontmatter, \mainmatter, \backmatter, and \appendix.
+ (goto-char (point-min))
+ (while (search-forward-regexp metasection-regexp nil t)
+ (push (cons "--" (match-beginning 0)) menu))
+
+ ;; Sort in increasing buffer position order.
+ (sort menu (function (lambda (a b) (< (cdr a) (cdr b))))))))
+
+ ;;;;
+ ;;;; Outline support
+ ;;;;
+
+ (defvar latex-outline-regexp
+ (concat "\\\\"
+ (regexp-opt (append latex-metasection-list
+ (mapcar 'car latex-section-alist)) t)))
+
+ (defun latex-outline-level ()
+ (if (looking-at latex-outline-regexp)
+ (1+ (or (cdr (assoc (match-string 1) latex-section-alist)) -1))
+ 1000))
+
+ ;;;;
+ ;;;; Font-Lock support
+ ;;;;
+
+ ;(defvar tex-font-lock-keywords
+ ; ;; Regexps updated with help from Ulrik Dickow <address@hidden>.
+ ; '(("\\\\\\(begin\\|end\\|newcommand\\){\\([a-zA-Z0-9\\*]+\\)}"
+ ; 2 font-lock-function-name-face)
+ ; ("\\\\\\(cite\\|label\\|pageref\\|ref\\){\\([^} \t\n]+\\)}"
+ ; 2 font-lock-constant-face)
+ ; ;; It seems a bit dubious to use `bold' and `italic' faces since we might
+ ; ;; not be able to display those fonts.
+ ; ("{\\\\bf\\([^}]+\\)}" 1 'bold keep)
+ ; ("{\\\\\\(em\\|it\\|sl\\)\\([^}]+\\)}" 2 'italic keep)
+ ; ("\\\\\\(address@hidden|.\\)" . font-lock-keyword-face)
+ ; ("^[ address@hidden(\\w+\\)" 1 font-lock-function-name-face keep))
+ ; ;; Rewritten and extended for LaTeX2e by Ulrik Dickow <address@hidden>.
+ ; '(("\\\\\\(begin\\|end\\|newcommand\\){\\([a-zA-Z0-9\\*]+\\)}"
+ ; 2 font-lock-function-name-face)
+ ; ("\\\\\\(cite\\|label\\|pageref\\|ref\\){\\([^} \t\n]+\\)}"
+ ; 2 font-lock-constant-face)
+ ; ("^[ \t]*\\\\def\\\\\\(\\(\\w\\|@\\)+\\)" 1 font-lock-function-name-face)
+ ; "\\\\\\(address@hidden|.\\)"
+ ; ;; It seems a bit dubious to use `bold' and `italic' faces since we might
+ ; ;; not be able to display those fonts.
+ ; ;; LaTeX2e: \emph{This is emphasized}.
+ ; ("\\\\emph{\\([^}]+\\)}" 1 'italic keep)
+ ; ;; LaTeX2e: \textbf{This is bold}, \textit{...}, \textsl{...}
+ ; ("\\\\text\\(\\(bf\\)\\|it\\|sl\\){\\([^}]+\\)}"
+ ; 3 (if (match-beginning 2) 'bold 'italic) keep)
+ ; ;; Old-style bf/em/it/sl. Stop at `\\' and un-escaped `&', for tables.
+ ; ("\\\\\\(\\(bf\\)\\|em\\|it\\|sl\\)\\>\\(\\([^}&\\]\\|\\\\[^\\]\\)+\\)"
+ ; 3 (if (match-beginning 2) 'bold 'italic) keep))
+
+ ;; Rewritten with the help of Alexandra Bac <address@hidden>.
+ (defconst tex-font-lock-keywords-1
+ (eval-when-compile
+ (let* (;; Names of commands whose arg should be fontified as heading, etc.
+ (headings (regexp-opt
+ '("title" "begin" "end" "chapter" "part"
+ "section" "subsection" "subsubsection"
+ "paragraph" "subparagraph" "subsubparagraph"
+ "newcommand" "renewcommand" "providecommand"
+ "newenvironment" "renewenvironment"
+ "newtheorem" "renewtheorem")
+ t))
+ (variables (regexp-opt
+ '("newcounter" "newcounter*" "setcounter" "addtocounter"
+ "setlength" "addtolength" "settowidth")
+ t))
+ (includes (regexp-opt
+ '("input" "include" "includeonly" "bibliography"
+ "epsfig" "psfig" "epsf" "nofiles" "usepackage"
+ "documentstyle" "documentclass" "verbatiminput"
+ "includegraphics" "includegraphics*")
+ t))
+ ;; Miscellany.
+ (slash "\\\\")
+ (opt " *\\(\\[[^]]*\\] *\\)*")
+ ;; This would allow highlighting \newcommand\CMD but requires
+ ;; adapting subgroup numbers below.
+ ;; (arg
"\\(?:{\\(\\(?:[^{}\\]+\\|\\\\.\\|{[^}]*}\\)+\\)\\|\\\\[a-z*]+\\)"))
+ (arg "{\\(\\(?:[^{}\\]+\\|\\\\.\\|{[^}]*}\\)+\\)"))
+ (list
+ ;; display $$ math $$
+ ;; We only mark the match between $$ and $$ because the $$ delimiters
+ ;; themselves have already been marked (along with $..$) by syntactic
+ ;; fontification. Also this is done at the very beginning so as to
+ ;; interact with the other keywords in the same way as $...$ does.
+ (list "\\$\\$\\([^$]+\\)\\$\\$" 1 'tex-math-face)
+ ;; Heading args.
+ (list (concat slash headings "\\*?" opt arg)
+ ;; If ARG ends up matching too much (if the {} don't match, f.ex)
+ ;; jit-lock will do funny things: when updating the buffer
+ ;; the re-highlighting is only done locally so it will just
+ ;; match the local line, but defer-contextually will
+ ;; match more lines at a time, so ARG will end up matching
+ ;; a lot more, which might suddenly include a comment
+ ;; so you get things highlighted bold when you type them
+ ;; but they get turned back to normal a little while later
+ ;; because "there's already a face there".
+ ;; Using `keep' works around this un-intuitive behavior as well
+ ;; 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 "\\(?:provide\\|\\(?:re\\)?new\\)command\\**
*\\(address@hidden)")
+ 1 'font-lock-function-name-face 'keep)
+ ;; Variable args.
+ (list (concat slash variables " *" arg) 2
'font-lock-variable-name-face)
+ ;; Include args.
+ (list (concat slash includes opt arg) 3 'font-lock-builtin-face)
+ ;; Definitions. I think.
+ '("^[ \t]*\\\\def *\\\\\\(\\(\\w\\|@\\)+\\)"
+ 1 font-lock-function-name-face))))
+ "Subdued expressions to highlight in TeX modes.")
+
+ (defconst tex-font-lock-keywords-2
+ (append tex-font-lock-keywords-1
+ (eval-when-compile
+ (let* (;;
+ ;; Names of commands whose arg should be fontified with fonts.
+ (bold (regexp-opt '("textbf" "textsc" "textup"
+ "boldsymbol" "pmb") t))
+ (italic (regexp-opt '("textit" "textsl" "emph") 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
+ '("label" "ref" "pageref" "vref" "eqref"
+ "cite" "nocite" "index" "glossary" "bibitem"
+ ;; These are text, rather than citations.
+ ;; "caption" "footnote" "footnotemark" "footnotetext"
+ )
+ t))
+ ;;
+ ;; Names of commands that should be fontified.
+ (specials-1 (regexp-opt '("\\" "\\*") t)) ;; "-"
+ (specials-2 (regexp-opt
+ '("linebreak" "nolinebreak" "pagebreak" "nopagebreak"
+ "newline" "newpage" "clearpage" "cleardoublepage"
+ "displaybreak" "allowdisplaybreaks"
+ "enlargethispage") t))
+ (general "\\(address@hidden|[^ \t\n]\\)")
+ ;;
+ ;; Miscellany.
+ (slash "\\\\")
+ (opt " *\\(\\[[^]]*\\] *\\)*")
+ (args "\\(\\(?:[^{}&\\]+\\|\\\\.\\|{[^}]*}\\)+\\)")
+ (arg "{\\(\\(?:[^{}\\]+\\|\\\\.\\|{[^}]*}\\)+\\)"))
+ (list
+ ;;
+ ;; Citation args.
+ (list (concat slash citations opt arg) 3 'font-lock-constant-face)
+ ;;
+ ;; Text between `` quotes ''.
+ (cons (concat (regexp-opt `("``" "\"<" "\"`" "<<" "«") t)
+ "[^'\">{]+" ;a bit pessimistic
+ (regexp-opt `("''" "\">" "\"'" ">>" "»") t))
+ 'font-lock-string-face)
+ ;;
+ ;; Command names, special and general.
+ (cons (concat slash specials-1) 'font-lock-warning-face)
+ (list (concat "\\(" slash specials-2 "\\)\\(address@hidden|\\'\\)")
+ 1 'font-lock-warning-face)
+ (concat slash general)
+ ;;
+ ;; Font environments. It seems a bit dubious to use `bold' etc. faces
+ ;; since we might not be able to display those fonts.
+ (list (concat slash bold " *" arg) 2 '(quote bold) 'append)
+ (list (concat slash italic " *" arg) 2 '(quote italic) 'append)
+ ;; (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 "\\\\\\(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 "\\(address@hidden|[^ \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\\**\\(address@hidden)" 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)
+ (let ((char (nth 3 state)))
+ (cond
+ ((not char) font-lock-comment-face)
+ ((eq char ?$) tex-math-face)
+ (t
+ (when (characterp 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))))
+
+
+ (defun tex-define-common-keys (keymap)
+ "Define the keys that we want defined both in TeX mode and in the TeX
shell."
+ (define-key keymap "\C-c\C-k" 'tex-kill-job)
+ (define-key keymap "\C-c\C-l" 'tex-recenter-output-buffer)
+ (define-key keymap "\C-c\C-q" 'tex-show-print-queue)
+ (define-key keymap "\C-c\C-p" 'tex-print)
+ (define-key keymap "\C-c\C-v" 'tex-view)
+
+ (define-key keymap [menu-bar tex] (cons "TeX" (make-sparse-keymap "TeX")))
+
+ (define-key keymap [menu-bar tex tex-kill-job]
+ '(menu-item "Tex Kill" tex-kill-job :enable (tex-shell-running)))
+ (define-key keymap [menu-bar tex tex-recenter-output-buffer]
+ '(menu-item "Tex Recenter" tex-recenter-output-buffer
+ :enable (get-buffer "*tex-shell*")))
+ (define-key keymap [menu-bar tex tex-show-print-queue]
+ '("Show Print Queue" . tex-show-print-queue))
+ (define-key keymap [menu-bar tex tex-alt-print]
+ '(menu-item "Tex Print (alt printer)" tex-alt-print
+ :enable (stringp tex-print-file)))
+ (define-key keymap [menu-bar tex tex-print]
+ '(menu-item "Tex Print" tex-print :enable (stringp tex-print-file)))
+ (define-key keymap [menu-bar tex tex-view]
+ '(menu-item "Tex View" tex-view :enable (stringp tex-print-file))))
+
+ (defvar tex-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map text-mode-map)
+ (tex-define-common-keys map)
+ (define-key map "\"" 'tex-insert-quote)
+ (define-key map "(" 'skeleton-pair-insert-maybe)
+ (define-key map "{" 'skeleton-pair-insert-maybe)
+ (define-key map "[" 'skeleton-pair-insert-maybe)
+ (define-key map "$" 'skeleton-pair-insert-maybe)
+ (define-key map "\n" 'tex-terminate-paragraph)
+ (define-key map "\M-\r" 'latex-insert-item)
+ (define-key map "\C-c}" 'up-list)
+ (define-key map "\C-c{" 'tex-insert-braces)
+ (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" '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)
+ (define-key map [menu-bar tex tex-bibtex-file]
+ '("BibTeX File" . tex-bibtex-file))
+ (define-key map [menu-bar tex tex-validate-region]
+ '(menu-item "Validate Region" tex-validate-region :enable mark-active))
+ (define-key map [menu-bar tex tex-validate-buffer]
+ '("Validate Buffer" . tex-validate-buffer))
+ (define-key map [menu-bar tex tex-region]
+ '(menu-item "TeX Region" tex-region :enable mark-active))
+ (define-key map [menu-bar tex tex-buffer]
+ '("TeX Buffer" . tex-buffer))
+ (define-key map [menu-bar tex tex-file] '("TeX File" . tex-file))
+ map)
+ "Keymap shared by TeX modes.")
+
+ (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'.")
+
+ (defvar plain-tex-mode-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map tex-mode-map)
+ map)
+ "Keymap for `plain-tex-mode'. See also `tex-mode-map'.")
+
+ (defvar tex-shell-map
+ (let ((m (make-sparse-keymap)))
+ (set-keymap-parent m shell-mode-map)
+ (tex-define-common-keys m)
+ m)
+ "Keymap for the TeX shell.
+ Inherits `shell-mode-map' with a few additions.")
+
+ (defvar tex-face-alist
+ '((bold . "{\\bf ")
+ (italic . "{\\it ")
+ (bold-italic . "{\\bi ") ; hypothetical
+ (underline . "\\underline{")
+ (default . "{\\rm "))
+ "Alist of face and TeX font name for facemenu.")
+
+ (defvar tex-latex-face-alist
+ `((italic . "{\\em ")
+ ,@tex-face-alist)
+ "Alist of face and LaTeX font name for facemenu.")
+
+ ;; This would be a lot simpler if we just used a regexp search,
+ ;; but then it would be too slow.
+ (defun tex-guess-mode ()
+ (let ((mode tex-default-mode) slash comment)
+ (save-excursion
+ (goto-char (point-min))
+ (while (and (setq slash (search-forward "\\" nil t))
+ (setq comment (let ((search-end (point)))
+ (save-excursion
+ (beginning-of-line)
+ (search-forward "%" search-end t))))))
+ (when (and slash (not comment))
+ (setq mode
+ (if (looking-at
+ (eval-when-compile
+ (concat
+ (regexp-opt '("documentstyle" "documentclass"
+ "begin" "subsection" "section"
+ "part" "chapter" "newcommand"
+ "renewcommand") 'words)
+ "\\|NeedsTeXFormat{LaTeX")))
+ (if (looking-at
+ "document\\(style\\|class\\)\\(\\[.*\\]\\)?{slides}")
+ 'slitex-mode
+ 'latex-mode)
+ '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
+ (defalias 'plain-TeX-mode 'plain-tex-mode)
+ ;;;###autoload
+ (defalias 'LaTeX-mode 'latex-mode)
+
+ ;;;###autoload
+ (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,
+ and '' when it appears to be the end; it inserts \" only after a \\.
+
+ Use \\[tex-region] to run TeX on the current region, plus a \"header\"
+ copied from the top of the file (containing macro definitions, etc.),
+ running TeX under a special subshell. \\[tex-buffer] does the whole buffer.
+ \\[tex-file] saves the buffer and then processes the file.
+ \\[tex-print] prints the .dvi file made by any of these.
+ \\[tex-view] previews the .dvi file made by any of these.
+ \\[tex-bibtex-file] runs bibtex on the file of the current buffer.
+
+ Use \\[tex-validate-buffer] to check buffer for paragraphs containing
+ mismatched $'s or braces.
+
+ Special commands:
+ \\{plain-tex-mode-map}
+
+ Mode variables:
+ tex-run-command
+ Command string used by \\[tex-region] or \\[tex-buffer].
+ tex-directory
+ Directory in which to create temporary files for TeX jobs
+ run by \\[tex-region] or \\[tex-buffer].
+ tex-dvi-print-command
+ Command string used by \\[tex-print] to print a .dvi file.
+ tex-alt-dvi-print-command
+ Alternative command string used by \\[tex-print] (when given a prefix
+ argument) to print a .dvi file.
+ tex-dvi-view-command
+ Command string used by \\[tex-view] to preview a .dvi file.
+ tex-show-queue-command
+ Command string used by \\[tex-show-print-queue] to show the print
+ queue that \\[tex-print] put your job on.
+
+ 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."
+ (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 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,
+ and '' when it appears to be the end; it inserts \" only after a \\.
+
+ Use \\[tex-region] to run LaTeX on the current region, plus the preamble
+ copied from the top of the file (containing \\documentstyle, etc.),
+ running LaTeX under a special subshell. \\[tex-buffer] does the whole buffer.
+ \\[tex-file] saves the buffer and then processes the file.
+ \\[tex-print] prints the .dvi file made by any of these.
+ \\[tex-view] previews the .dvi file made by any of these.
+ \\[tex-bibtex-file] runs bibtex on the file of the current buffer.
+
+ Use \\[tex-validate-buffer] to check buffer for paragraphs containing
+ mismatched $'s or braces.
+
+ Special commands:
+ \\{latex-mode-map}
+
+ Mode variables:
+ latex-run-command
+ Command string used by \\[tex-region] or \\[tex-buffer].
+ tex-directory
+ Directory in which to create temporary files for LaTeX jobs
+ run by \\[tex-region] or \\[tex-buffer].
+ tex-dvi-print-command
+ Command string used by \\[tex-print] to print a .dvi file.
+ tex-alt-dvi-print-command
+ Alternative command string used by \\[tex-print] (when given a prefix
+ argument) to print a .dvi file.
+ tex-dvi-view-command
+ Command string used by \\[tex-view] to preview a .dvi file.
+ tex-show-queue-command
+ Command string used by \\[tex-show-print-queue] to show the print
+ queue that \\[tex-print] put your job on.
+
+ 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."
+ (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 "[ \t]*\\(\\$\\$\\|"
+ "\\\\[][]\\|"
+ "\\\\" (regexp-opt (append
+ (mapcar 'car latex-section-alist)
+ '("begin" "label" "end"
+ "item" "bibitem" "newline" "noindent"
+ "newpage" "footnote" "marginpar"
+ "parbox" "caption")) t)
+ "\\>\\|\\\\[a-z]*" (regexp-opt '("space" "skip" "page") t)
+ "\\>\\)"))
+ (setq paragraph-separate
+ (concat "[\f%]\\|[ \t]*\\($\\|"
+ "\\\\[][]\\|"
+ "\\\\" (regexp-opt (append
+ (mapcar 'car latex-section-alist)
+ '("begin" "label" "end" )) t)
+ "\\>\\|\\\\\\(" (regexp-opt '("item" "bibitem" "newline"
+ "noindent" "newpage" "footnote"
+ "marginpar" "parbox" "caption"))
+ "\\|\\$\\$\\|[a-z]*\\(space\\|skip\\|page[a-z]*\\)"
+ "\\>\\)[ \t]*\\($\\|%\\)\\)"))
+ (set (make-local-variable 'imenu-create-index-function)
+ 'latex-imenu-create-index)
+ (set (make-local-variable 'tex-face-alist) tex-latex-face-alist)
+ (add-hook 'fill-nobreak-predicate 'latex-fill-nobreak-predicate nil t)
+ (set (make-local-variable 'indent-line-function) 'latex-indent)
+ (set (make-local-variable 'fill-indent-according-to-mode) t)
+ (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))
+
+ ;;;###autoload
+ (define-derived-mode slitex-mode latex-mode "SliTeX"
+ "Major mode for editing files of input for SliTeX.
+ Makes $ and } display the characters they match.
+ Makes \" insert `` when it seems to be the beginning of a quotation,
+ and '' when it appears to be the end; it inserts \" only after a \\.
+
+ Use \\[tex-region] to run SliTeX on the current region, plus the preamble
+ copied from the top of the file (containing \\documentstyle, etc.),
+ running SliTeX under a special subshell. \\[tex-buffer] does the whole
buffer.
+ \\[tex-file] saves the buffer and then processes the file.
+ \\[tex-print] prints the .dvi file made by any of these.
+ \\[tex-view] previews the .dvi file made by any of these.
+ \\[tex-bibtex-file] runs bibtex on the file of the current buffer.
+
+ Use \\[tex-validate-buffer] to check buffer for paragraphs containing
+ mismatched $'s or braces.
+
+ Special commands:
+ \\{slitex-mode-map}
+
+ Mode variables:
+ slitex-run-command
+ Command string used by \\[tex-region] or \\[tex-buffer].
+ tex-directory
+ Directory in which to create temporary files for SliTeX jobs
+ run by \\[tex-region] or \\[tex-buffer].
+ tex-dvi-print-command
+ Command string used by \\[tex-print] to print a .dvi file.
+ tex-alt-dvi-print-command
+ Alternative command string used by \\[tex-print] (when given a prefix
+ argument) to print a .dvi file.
+ tex-dvi-view-command
+ Command string used by \\[tex-view] to preview a .dvi file.
+ tex-show-queue-command
+ Command string used by \\[tex-show-print-queue] to show the print
+ queue that \\[tex-print] put your job on.
+
+ Entering SliTeX mode runs the hook `text-mode-hook', then the hook
+ `tex-mode-hook', then the hook `latex-mode-hook', and finally the hook
+ `slitex-mode-hook'. When the special subshell is initiated, the hook
+ `tex-shell-hook' is run."
+ (setq tex-command slitex-run-command)
+ (setq tex-start-of-header
"\\\\documentstyle{slides}\\|\\\\documentclass{slides}"))
+
+ (defun tex-common-initialization ()
+ ;; 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.
+ (set (make-local-variable 'paragraph-start)
+ "[ \t]*$\\|[\f\\\\%]\\|[ \t]*\\$\\$")
+ ;; A line starting with $$ starts a paragraph,
+ ;; but does not separate paragraphs if it has more stuff on it.
+ (set (make-local-variable 'paragraph-separate)
+ "[ \t]*$\\|[\f\\\\%]\\|[ \t]*\\$\\$[ \t]*$")
+ (set (make-local-variable 'comment-start) "%")
+ (set (make-local-variable 'comment-add) 1)
+ (set (make-local-variable 'comment-start-skip)
+ "\\(\\(^\\|[^\\\n]\\)\\(\\\\\\\\\\)*\\)\\(%+ *\\)")
+ (set (make-local-variable 'parse-sexp-ignore-comments) t)
+ (set (make-local-variable 'compare-windows-whitespace)
+ 'tex-categorize-whitespace)
+ (set (make-local-variable 'facemenu-add-face-function)
+ (lambda (face end)
+ (let ((face-text (cdr (assq face tex-face-alist))))
+ (if face-text
+ face-text
+ (error "Face %s not configured for %s mode" face mode-name)))))
+ (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-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)
+ (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)
+ (make-local-variable 'tex-trailer))
+
+ (defun tex-categorize-whitespace (backward-limit)
+ ;; compare-windows-whitespace is set to this.
+ ;; This is basically a finite-state machine.
+ ;; Returns a symbol telling how TeX would treat
+ ;; the whitespace we are looking at: null, space, or par.
+ (let ((category 'null)
+ (not-finished t))
+ (skip-chars-backward " \t\n\f" backward-limit)
+ (while not-finished
+ (cond ((looking-at "[ \t]+")
+ (goto-char (match-end 0))
+ (if (eq category 'null)
+ (setq category 'space)))
+ ((looking-at "\n")
+ (cond ((eq category 'newline)
+ (setq category 'par)
+ (setq not-finished nil))
+ (t
+ (setq category 'newline) ;a strictly internal state
+ (goto-char (match-end 0)))))
+ ((looking-at "\f+")
+ (setq category 'par)
+ (setq not-finished nil))
+ (t
+ (setq not-finished nil))))
+ (skip-chars-forward " \t\n\f")
+ (if (eq category 'newline)
+ 'space ;TeX doesn't distinguish
+ category)))
+
+ (defun tex-insert-quote (arg)
+ "Insert the appropriate quote marks for TeX.
+ Inserts the value of `tex-open-quote' (normally ``) or `tex-close-quote'
+ \(normally '') depending on the context. With prefix argument, always
+ inserts \" characters."
+ (interactive "*P")
+ (if (or arg (memq (char-syntax (preceding-char)) '(?/ ?\\))
+ (eq (get-text-property (point) 'face) 'tex-verbatim-face)
+ (save-excursion
+ (backward-char (length tex-open-quote))
+ (when (or (looking-at (regexp-quote tex-open-quote))
+ (looking-at (regexp-quote tex-close-quote)))
+ (delete-char (length tex-open-quote))
+ t)))
+ (self-insert-command (prefix-numeric-value arg))
+ (insert (if (memq (char-syntax (preceding-char)) '(?\( ?> ?\ ))
+ tex-open-quote tex-close-quote))))
+
+ (defun tex-validate-buffer ()
+ "Check current buffer for paragraphs containing mismatched braces or $s.
+ Their positions are recorded in the buffer `*Occur*'.
+ To find a particular invalidity from `*Occur*', switch to that buffer
+ and type C-c C-c or click with mouse-2
+ on the line for the invalidity you want to see."
+ (interactive)
+ (let ((buffer (current-buffer))
+ (prevpos (point-min))
+ (linenum nil)
+ (num-matches 0))
+ (with-output-to-temp-buffer "*Occur*"
+ (princ "Mismatches:\n")
+ (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.
+ (setq occur-revert-arguments (list nil 0 (list buffer))))
+ (save-excursion
+ (goto-char (point-max))
+ (while (and (not (bobp)))
+ (let ((end (point))
+ prev-end)
+ ;; Scan the previous paragraph for invalidities.
+ (if (search-backward "\n\n" nil t)
+ (progn
+ (setq prev-end (point))
+ (forward-char 2))
+ (goto-char (setq prev-end (point-min))))
+ (or (tex-validate-region (point) end)
+ (let* ((end (line-beginning-position 2))
+ start tem)
+ (beginning-of-line)
+ (setq start (point))
+ ;; Keep track of line number as we scan,
+ ;; in a cumulative fashion.
+ (if linenum
+ (setq linenum (- linenum (count-lines prevpos (point))))
+ (setq linenum (1+ (count-lines 1 start))))
+ (setq prevpos (point))
+ ;; Mention this mismatch in *Occur*.
+ ;; Since we scan from end of buffer to beginning,
+ ;; add each mismatch at the beginning of *Occur*.
+ (save-excursion
+ (setq tem (point-marker))
+ (set-buffer standard-output)
+ (goto-char (point-min))
+ ;; Skip "Mismatches:" header line.
+ (forward-line 1)
+ (setq num-matches (1+ num-matches))
+ (insert-buffer-substring buffer start end)
+ (let (text-beg (text-end (point-marker)))
+ (forward-char (- start end))
+ (setq text-beg (point-marker))
+ (insert (format "%3d: " linenum))
+ (add-text-properties
+ text-beg (- text-end 1)
+ '(mouse-face highlight
+ help-echo "mouse-2: go to this invalidity"))
+ (put-text-property text-beg (- text-end 1)
+ 'occur-target tem)))))
+ (goto-char prev-end))))
+ (with-current-buffer standard-output
+ (if (eq num-matches 0)
+ (insert "None!\n"))
+ (if (interactive-p)
+ (message "%d mismatches found" num-matches))))))
+
+ (defun tex-validate-region (start end)
+ "Check for mismatched braces or $'s in region.
+ Returns t if no mismatches. Returns nil and moves point to suspect
+ area if a mismatch is found."
+ (interactive "r")
+ (let ((failure-point nil) (max-possible-sexps (- end start)))
+ (save-excursion
+ (condition-case ()
+ (save-restriction
+ (narrow-to-region start end)
+ ;; First check that the open and close parens balance in numbers.
+ (goto-char start)
+ (while (<= 0 (setq max-possible-sexps (1- max-possible-sexps)))
+ (forward-sexp 1))
+ ;; Now check that like matches like.
+ (goto-char start)
+ (while (progn (skip-syntax-forward "^(")
+ (not (eobp)))
+ (let ((match (matching-paren (following-char))))
+ (save-excursion
+ (forward-sexp 1)
+ (or (= (preceding-char) match)
+ (error "Mismatched parentheses"))))
+ (forward-char 1)))
+ (error
+ (skip-syntax-forward " .>")
+ (setq failure-point (point)))))
+ (if failure-point (goto-char failure-point))
+ (not failure-point)))
+
+ (defun tex-terminate-paragraph (inhibit-validation)
+ "Insert two newlines, breaking a paragraph for TeX.
+ Check for mismatched braces or $s in paragraph being terminated.
+ A prefix arg inhibits the checking."
+ (interactive "*P")
+ (or inhibit-validation
+ (save-excursion
+ (tex-validate-region
+ (save-excursion
+ (search-backward "\n\n" nil 'move)
+ (point))
+ (point)))
+ (message "Paragraph being closed appears to contain a mismatch"))
+ (insert "\n\n"))
+
+ (define-skeleton tex-insert-braces
+ "Make a pair of braces and be poised to type inside of them."
+ 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 ()
+ (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.
+ (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
+ latex-standard-block-names)
+ nil nil nil nil latex-block-default)))
+ (setq latex-block-default choice)
+ (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 "}"
+ (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
+ "Insert a \item macro."
+ nil
+ \n "\\item " >)
+
+
+ ;;;;
+ ;;;; 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 (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 (tex-search-noncomment
+ (re-search-forward "\\\\\\(begin\\|end\\)\\s *{[^}]+}"))
+ (save-excursion (goto-char (match-beginning 0))
+ (looking-at "\\\\begin")))
+ (tex-next-unmatched-end)))
+
+ (defun tex-goto-last-unclosed-latex-block ()
+ "Move point to the last unclosed \\begin{...}.
+ Mark is left at original location."
+ (interactive)
+ (let ((spot))
+ (save-excursion
+ (tex-last-unended-begin)
+ (setq spot (point)))
+ (push-mark)
+ (goto-char spot)))
+
+ (defun latex-backward-sexp-1 ()
+ "Like (backward-sexp 1) but aware of multi-char elements."
+ (let ((pos (point))
+ (forward-sexp-function))
+ (backward-sexp 1)
+ (if (looking-at "\\\\begin\\>")
+ (signal 'scan-error
+ (list "Containing expression ends prematurely"
+ (point) (prog1 (point) (goto-char pos))))
+ (when (eq (char-after) ?{)
+ (let ((newpos (point)))
+ (when (ignore-errors (backward-sexp 1) t)
+ (if (looking-at "\\\\end\\>")
+ (tex-last-unended-begin)
+ (goto-char newpos))))))))
+
+ (defun latex-forward-sexp-1 ()
+ "Like (forward-sexp 1) but aware of multi-char elements."
+ (let ((pos (point))
+ (forward-sexp-function))
+ (forward-sexp 1)
+ (let ((newpos (point)))
+ (skip-syntax-backward "/w")
+ (cond
+ ((looking-at "\\\\end\\>")
+ (signal 'scan-error
+ (list "Containing expression ends prematurely"
+ (point)
+ (prog1
+ (progn (ignore-errors (forward-sexp 2)) (point))
+ (goto-char pos)))))
+ ((looking-at "\\\\begin\\>")
+ (goto-char (match-end 0))
+ (tex-next-unmatched-end))
+ (t (goto-char newpos))))))
+
+ (defun latex-forward-sexp (&optional arg)
+ "Like `forward-sexp' but aware of multi-char elements."
+ (interactive "P")
+ (unless arg (setq arg 1))
+ (let ((pos (point)))
+ (condition-case err
+ (while (/= arg 0)
+ (setq arg
+ (if (> arg 0)
+ (progn (latex-forward-sexp-1) (1- arg))
+ (progn (latex-backward-sexp-1) (1+ arg)))))
+ (scan-error
+ (goto-char pos)
+ (signal (car err) (cdr err))))))
+
+ (defun latex-syntax-after ()
+ "Like (char-syntax (char-after)) but aware of multi-char elements."
+ (if (looking-at "\\\\end\\>") ?\) (char-syntax (following-char))))
+
+ (defun latex-skip-close-parens ()
+ "Like (skip-syntax-forward \" )\") but aware of multi-char elements."
+ (let ((forward-sexp-function nil))
+ (while (progn (skip-syntax-forward " )")
+ (looking-at "\\\\end\\>"))
+ (forward-sexp 2))))
+
+ (defun latex-down-list ()
+ "Like (down-list 1) but aware of multi-char elements."
+ (forward-comment (point-max))
+ (let ((forward-sexp-function nil))
+ (if (not (looking-at "\\\\begin\\>"))
+ (down-list 1)
+ (forward-sexp 1)
+ ;; Skip arguments.
+ (while (looking-at "[ \t]*[[{(]")
+ (with-syntax-table tex-mode-syntax-table
+ (forward-sexp))))))
+
+ (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"
+ "documentclass" "usepackage" "label")
+ "TeX commands whose arguments should not be counted as text.")
+
+ (defun tex-count-words (begin end)
+ "Count the number of words in the buffer."
+ (interactive
+ (if (and transient-mark-mode mark-active)
+ (list (region-beginning) (region-end))
+ (list (point-min) (point-max))))
+ ;; TODO: skip comments and math and maybe some environments.
+ (save-excursion
+ (goto-char begin)
+ (let ((count 0))
+ (while (and (< (point) end) (re-search-forward "\\<" end t))
+ (if (not (eq (char-syntax (preceding-char)) ?/))
+ (progn
+ ;; Don't count single-char words.
+ (unless (looking-at ".\\>") (incf count))
+ (forward-char 1))
+ (let ((cmd
+ (buffer-substring-no-properties
+ (point) (progn (when (zerop (skip-chars-forward "a-zA-Z@"))
+ (forward-char 1))
+ (point)))))
+ (when (member cmd tex-discount-args-cmds)
+ (skip-chars-forward "*")
+ (forward-comment (point-max))
+ (when (looking-at "\\[")
+ (forward-sexp 1)
+ (forward-comment (point-max)))
+ (if (not (looking-at "{"))
+ (forward-char 1)
+ (forward-sexp 1))))))
+ (message "%s words" count))))
+
+
+
+ ;;; Invoking TeX in an inferior shell.
+
+ ;; Why use a shell instead of running TeX directly? Because if TeX
+ ;; gets stuck, the user can switch to the shell window and type at it.
+
+ ;; The utility functions:
+
+ (define-derived-mode tex-shell shell-mode "TeX-Shell"
+ (compilation-shell-minor-mode t))
+
+ ;;;###autoload
+ (defun tex-start-shell ()
+ (with-current-buffer
+ (make-comint
+ "tex-shell"
+ (or tex-shell-file-name (getenv "ESHELL") shell-file-name)
+ nil)
+ (let ((proc (get-process "tex-shell")))
+ (set-process-sentinel proc 'tex-shell-sentinel)
+ (process-kill-without-query proc)
+ (tex-shell)
+ (while (zerop (buffer-size))
+ (sleep-for 1)))))
+
+ (defun tex-feed-input ()
+ "Send input to the tex shell process.
+ In the tex buffer this can be used to continue an interactive tex run.
+ In the tex shell buffer this command behaves like `comint-send-input'."
+ (interactive)
+ (set-buffer (tex-shell-buf))
+ (comint-send-input)
+ (tex-recenter-output-buffer nil))
+
+ (defun tex-display-shell ()
+ "Make the TeX shell buffer visible in a window."
+ (display-buffer (tex-shell-buf))
+ (tex-recenter-output-buffer nil))
+
+ (defun tex-shell-sentinel (proc msg)
+ (cond ((null (buffer-name (process-buffer proc)))
+ ;; buffer killed
+ (set-process-buffer proc nil)
+ (tex-delete-last-temp-files))
+ ((memq (process-status proc) '(signal exit))
+ (tex-delete-last-temp-files))))
+
+ (defun tex-set-buffer-directory (buffer directory)
+ "Set BUFFER's default directory to be DIRECTORY."
+ (setq directory (file-name-as-directory (expand-file-name directory)))
+ (if (not (file-directory-p directory))
+ (error "%s is not a directory" directory)
+ (save-excursion
+ (set-buffer buffer)
+ (setq default-directory directory))))
+
+ (defvar tex-send-command-modified-tick 0)
+ (make-variable-buffer-local 'tex-send-command-modified-tick)
+
+ (defun tex-shell-proc ()
+ (or (tex-shell-running) (error "No TeX subprocess")))
+ (defun tex-shell-buf ()
+ (process-buffer (tex-shell-proc)))
+ (defun tex-shell-buf-no-error ()
+ (let ((proc (tex-shell-running)))
+ (and proc (process-buffer proc))))
+
+ (defun tex-send-command (command &optional file background)
+ "Send COMMAND to TeX shell process, substituting optional FILE for *.
+ Do this in background if optional BACKGROUND is t. If COMMAND has no *,
+ FILE will be appended, preceded by a blank, to COMMAND. If FILE is nil, no
+ substitution will be made in COMMAND. COMMAND can be any expression that
+ evaluates to a command string.
+
+ Return the process in which TeX is running."
+ (save-excursion
+ (let* ((cmd (eval command))
+ (proc (tex-shell-proc))
+ (buf (process-buffer proc))
+ (star (string-match "\\*" cmd))
+ (string
+ (concat
+ (if file
+ (if star (concat (substring cmd 0 star)
+ file (substring cmd (1+ star)))
+ (concat cmd " " file))
+ cmd)
+ (if background "&" ""))))
+ ;; Switch to buffer before checking for subproc output in it.
+ (set-buffer buf)
+ ;; If text is unchanged since previous tex-send-command,
+ ;; we haven't got any output. So wait for output now.
+ (if (= (buffer-modified-tick buf) tex-send-command-modified-tick)
+ (accept-process-output proc))
+ (goto-char (process-mark proc))
+ (insert string)
+ (comint-send-input)
+ (setq tex-send-command-modified-tick (buffer-modified-tick buf))
+ proc)))
+
+ (defun tex-delete-last-temp-files (&optional not-all)
+ "Delete any junk files from last temp file.
+ If NOT-ALL is non-nil, save the `.dvi' file."
+ (if tex-last-temp-file
+ (let* ((dir (file-name-directory tex-last-temp-file))
+ (list (and (file-directory-p dir)
+ (file-name-all-completions
+ (file-name-sans-extension
+ (file-name-nondirectory tex-last-temp-file))
+ dir))))
+ (while list
+ (if not-all
+ (and
+ ;; If arg is non-nil, don't delete the .dvi file.
+ (not (string-match "\\.dvi$" (car list)))
+ (delete-file (concat dir (car list))))
+ (delete-file (concat dir (car list))))
+ (setq list (cdr list))))))
+
+ (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. 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 (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 (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))
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (re-search-forward
+ header-re (+ (point) 10000) t))))
+ (throw 'found (expand-file-name buffer-file-name))))))))
+
+ (defun tex-main-file ()
+ "Return the relative name of the main file."
+ (let* ((file (or tex-main-file
+ ;; Compatibility with AUCTeX.
+ (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")
+ (file-relative-name
+ (if (save-excursion
+ (goto-char (point-min))
+ (re-search-forward tex-start-of-header
+ (+ (point) 10000) t))
+ ;; This is the main file.
+ 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."
+ (let* ((star (string-match "\\*" command))
+ (compile-command
+ (if star
+ (concat (substring command 0 star)
+ (comint-quote-filename file)
+ (substring command (1+ star)))
+ (concat command " "
+ tex-start-options
+ (if (< 0 (length tex-start-commands))
+ (concat
+ (shell-quote-argument tex-start-commands) " "))
+ (comint-quote-filename file)))))
+ (tex-send-tex-command compile-command dir)))
+
+ (defun tex-send-tex-command (cmd &optional dir)
+ (unless (or (equal dir (let ((buf (tex-shell-buf-no-error)))
+ (and buf (with-current-buffer buf
+ default-directory))))
+ (not dir))
+ (let (shell-dirtrack-verbose)
+ (tex-send-command tex-shell-cd-command dir)))
+ (with-current-buffer (process-buffer (tex-send-command cmd))
+ (make-local-variable 'compilation-parse-errors-function)
+ (setq compilation-parse-errors-function 'tex-compilation-parse-errors)
+ (setq compilation-last-buffer (current-buffer))
+ (compilation-forget-errors)
+ ;; Don't parse previous compilations.
+ (set-marker compilation-parsing-end (1- (point-max))))
+ (tex-display-shell)
+ (setq tex-last-buffer-texed (current-buffer)))
+
+ (defvar tex-error-parse-syntax-table
+ (let ((st (make-syntax-table)))
+ (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)
+ ;; Single quotations may appear in errors
+ (modify-syntax-entry ?\" "_" st)
+ st)
+ "Syntax-table used while parsing TeX error messages.")
+
+ (defun tex-compilation-parse-errors (limit-search find-at-least)
+ "Parse the current buffer as TeX error messages.
+ See the variable `compilation-parse-errors-function' for the interface it
uses.
+
+ This function parses only the last TeX compilation.
+ It works on TeX compilations only. It is necessary for that purpose,
+ since TeX does not put file names and line numbers on the same line as
+ for the error messages."
+ (require 'thingatpt)
+ (setq compilation-error-list nil)
+ (message "Parsing error messages...")
+ (let ((default-directory ; Perhaps dir has changed meanwhile.
+ (file-name-directory (buffer-file-name tex-last-buffer-texed)))
+ found-desired (num-errors-found 0)
+ last-filename last-linenum last-position
+ begin-of-error end-of-error)
+ ;; Don't reparse messages already seen at last parse.
+ (goto-char compilation-parsing-end)
+ ;; Parse messages.
+ (while (and (not (or found-desired (eobp)))
+ (prog1 (re-search-forward "^! " nil 'move)
+ (setq begin-of-error (match-beginning 0)
+ end-of-error (match-end 0)))
+ (re-search-forward
+ "^l\\.\\([0-9]+\\) \\(\\.\\.\\.\\)?\\(.*\\)$" nil 'move))
+ (let* ((this-error (set-marker (make-marker) begin-of-error))
+ (linenum (string-to-int (match-string 1)))
+ (error-text (regexp-quote (match-string 3)))
+ (filename
+ (save-excursion
+ (with-syntax-table tex-error-parse-syntax-table
+ (backward-up-list 1)
+ (skip-syntax-forward "(_")
+ (while (not (file-readable-p (thing-at-point 'filename)))
+ (skip-syntax-backward "(_")
+ (backward-up-list 1)
+ (skip-syntax-forward "(_"))
+ (thing-at-point 'filename))))
+ (new-file
+ (or (null last-filename)
+ (not (string-equal last-filename filename))))
+ (error-location
+ (save-excursion
+ (if (equal filename (concat tex-zap-file ".tex"))
+ (set-buffer tex-last-buffer-texed)
+ (set-buffer (find-file-noselect filename)))
+ (if new-file
+ (progn (goto-line linenum) (setq last-position nil))
+ (goto-char last-position)
+ (forward-line (- linenum last-linenum)))
+ ;; first try a forward search for the error text,
+ ;; then a backward search limited by the last error.
+ (let ((starting-point (point)))
+ (or (re-search-forward error-text nil t)
+ (re-search-backward error-text last-position t)
+ (goto-char starting-point)))
+ (point-marker))))
+ (goto-char this-error)
+ (if (and compilation-error-list
+ (or (and find-at-least
+ (>= num-errors-found
+ find-at-least))
+ (and limit-search
+ (>= end-of-error limit-search)))
+ new-file)
+ (setq found-desired t)
+ (setq num-errors-found (1+ num-errors-found)
+ last-filename filename
+ last-linenum linenum
+ last-position error-location
+ compilation-error-list ; Add the new error
+ (cons (cons this-error error-location)
+ compilation-error-list))
+ (goto-char end-of-error)))))
+ (set-marker compilation-parsing-end (point))
+ (setq compilation-error-list (nreverse compilation-error-list))
+ (message "Parsing error messages...done"))
+
+ ;;; The commands:
+
+ (defun tex-region (beg end)
+ "Run TeX on the current region, via a temporary file.
+ The file's name comes from the variable `tex-zap-file' and the
+ variable `tex-directory' says where to put it.
+
+ If the buffer has a header, the header is given to TeX before the
+ region itself. The buffer's header is all lines between the strings
+ defined by `tex-start-of-header' and `tex-end-of-header' inclusive.
+ The header must start in the first 100 lines of the buffer.
+
+ The value of `tex-trailer' is given to TeX as input after the region.
+
+ The value of `tex-command' specifies the command to use to run TeX."
+ (interactive "r")
+ (if (tex-shell-running)
+ (tex-kill-job)
+ (tex-start-shell))
+ (or tex-zap-file
+ (setq tex-zap-file (tex-generate-zap-file-name)))
+ ;; Temp file will be written and TeX will be run in zap-directory.
+ ;; If the TEXINPUTS file has relative directories or if the region has
+ ;; \input of files, this must be the same directory as the file for
+ ;; TeX to access the correct inputs. That's why it's safest if
+ ;; tex-directory is ".".
+ (let* ((zap-directory
+ (file-name-as-directory (expand-file-name tex-directory)))
+ (tex-out-file (expand-file-name (concat tex-zap-file ".tex")
+ zap-directory)))
+ ;; Don't delete temp files if we do the same buffer twice in a row.
+ (or (eq (current-buffer) tex-last-buffer-texed)
+ (tex-delete-last-temp-files t))
+ ;; Write the new temp file.
+ (save-excursion
+ (save-restriction
+ (widen)
+ (goto-char (point-min))
+ (forward-line 100)
+ (let ((search-end (point))
+ (default-directory zap-directory)
+ (already-output 0))
+ (goto-char (point-min))
+
+ ;; Maybe copy first line, such as `\input texinfo', to temp file.
+ (and tex-first-line-header-regexp
+ (looking-at tex-first-line-header-regexp)
+ (write-region (point)
+ (progn (forward-line 1)
+ (setq already-output (point)))
+ tex-out-file nil nil))
+
+ ;; Write out the header, if there is one,
+ ;; and any of the specified region which extends before it.
+ ;; But don't repeat anything already written.
+ (if (re-search-forward tex-start-of-header search-end t)
+ (let (hbeg)
+ (beginning-of-line)
+ (setq hbeg (point)) ;mark beginning of header
+ (if (re-search-forward tex-end-of-header nil t)
+ (let (hend)
+ (forward-line 1)
+ (setq hend (point)) ;mark end of header
+ (write-region (max (min hbeg beg) already-output)
+ hend
+ tex-out-file
+ (not (zerop already-output)) nil)
+ (setq already-output hend)))))
+
+ ;; Write out the specified region
+ ;; (but don't repeat anything already written).
+ (write-region (max beg already-output) end
+ tex-out-file
+ (not (zerop already-output)) nil))
+ ;; Write the trailer, if any.
+ ;; Precede it with a newline to make sure it
+ ;; is not hidden in a comment.
+ (if tex-trailer
+ (write-region (concat "\n" tex-trailer) nil
+ tex-out-file t nil))))
+ ;; Record the file name to be deleted afterward.
+ (setq tex-last-temp-file tex-out-file)
+ ;; Use a relative file name here because (1) the proper dir
+ ;; is already current, and (2) the abs file name is sometimes
+ ;; too long and can make tex crash.
+ (tex-start-tex tex-command (concat tex-zap-file ".tex") zap-directory)
+ (setq tex-print-file tex-out-file)))
+
+ (defun tex-buffer ()
+ "Run TeX on current buffer. See \\[tex-region] for more information.
+ Does not save the buffer, so it's useful for trying experimental versions.
+ See \\[tex-file] for an alternative."
+ (interactive)
+ (tex-region (point-min) (point-max)))
+
+ (defun tex-file ()
+ "Prompt to save all buffers and run TeX (or LaTeX) on current buffer's file.
+ This function is more useful than \\[tex-buffer] when you need the
+ `.aux' file of LaTeX to have the correct name."
+ (interactive)
+ (when tex-offer-save
+ (save-some-buffers))
+ (let* ((source-file (tex-main-file))
+ (file-dir (file-name-directory (expand-file-name source-file))))
+ (if (tex-shell-running)
+ (tex-kill-job)
+ (tex-start-shell))
+ (tex-start-tex tex-command source-file file-dir)
+ (setq tex-print-file (expand-file-name source-file))))
+
+ (defun tex-generate-zap-file-name ()
+ "Generate a unique name suitable for use as a file name."
+ ;; Include the shell process number and host name
+ ;; in case there are multiple shells (for same or different user).
+ ;; Dec 1998: There is a report that some versions of xdvi
+ ;; don't work with file names that start with #.
+ (format "_TZ_%d-%s"
+ (process-id (get-buffer-process "*tex-shell*"))
+ (subst-char-in-string ?. ?- (system-name))))
+
+ ;; This will perhaps be useful for modifying TEXINPUTS.
+ ;; Expand each file name, separated by colons, in the string S.
+ (defun tex-expand-files (s)
+ (let (elts (start 0))
+ (while (string-match ":" s start)
+ (setq elts (cons (substring s start (match-beginning 0)) elts))
+ (setq start (match-end 0)))
+ (or (= start 0)
+ (setq elts (cons (substring s start) elts)))
+ (mapconcat (lambda (elt)
+ (if (= (length elt) 0) elt (expand-file-name elt)))
+ (nreverse elts) ":")))
+
+ (defun tex-shell-running ()
+ (let ((proc (get-process "tex-shell")))
+ (when proc
+ (if (and (eq (process-status proc) 'run)
+ (buffer-live-p (process-buffer proc)))
+ ;; return the TeX process on success
+ proc
+ ;; get rid of the process permanently
+ ;; this should get rid of the annoying w32 problem with
+ ;; dead tex-shell buffer and live process
+ (delete-process proc)))))
+
+ (defun tex-kill-job ()
+ "Kill the currently running TeX job."
+ (interactive)
+ ;; `quit-process' leads to core dumps of the tex process (except if
+ ;; coredumpsize has limit 0kb as on many environments). One would
+ ;; like to use (kill-process proc 'lambda), however that construct
+ ;; does not work on some systems and kills the shell itself.
+ (let ((proc (get-process "tex-shell")))
+ (when proc (quit-process proc t))))
+
+ (defun tex-recenter-output-buffer (linenum)
+ "Redisplay buffer of TeX job output so that most recent output can be seen.
+ 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*"))
+ (window))
+ (if (null tex-shell)
+ (message "No TeX output buffer")
+ (setq window (display-buffer tex-shell))
+ (save-selected-window
+ (select-window window)
+ (bury-buffer tex-shell)
+ (goto-char (point-max))
+ (recenter (if linenum
+ (prefix-numeric-value linenum)
+ (/ (window-height) 2)))))))
+
+ (defun tex-print (&optional alt)
+ "Print the .dvi file made by \\[tex-region], \\[tex-buffer] or \\[tex-file].
+ Runs the shell command defined by `tex-dvi-print-command'. If prefix argument
+ is provided, use the alternative command, `tex-alt-dvi-print-command'."
+ (interactive "P")
+ (let ((print-file-name-dvi (tex-append tex-print-file ".dvi"))
+ test-name)
+ (if (and (not (equal (current-buffer) tex-last-buffer-texed))
+ (buffer-file-name)
+ ;; Check that this buffer's printed file is up to date.
+ (file-newer-than-file-p
+ (setq test-name (tex-append (buffer-file-name) ".dvi"))
+ (buffer-file-name)))
+ (setq print-file-name-dvi test-name))
+ (if (not (file-exists-p print-file-name-dvi))
+ (error "No appropriate `.dvi' file could be found")
+ (if (tex-shell-running)
+ (tex-kill-job)
+ (tex-start-shell))
+ (tex-send-command
+ (if alt tex-alt-dvi-print-command tex-dvi-print-command)
+ (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].
+ Runs the shell command defined by `tex-alt-dvi-print-command'."
+ (interactive)
+ (tex-print t))
+
+ (defun tex-view ()
+ "Preview the last `.dvi' file made by running TeX under Emacs.
+ This means, made using \\[tex-region], \\[tex-buffer] or \\[tex-file].
+ The variable `tex-dvi-view-command' specifies the shell command for preview.
+ You must set that variable yourself before using this command,
+ 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 (eval tex-dvi-view-command)))
+ (tex-print)))
+
+ (defun tex-append (file-name suffix)
+ "Append to FILENAME the suffix SUFFIX, using same algorithm TeX uses.
+ Pascal-based TeX scans for the first period, C TeX uses the last.
+ No period is retained immediately before SUFFIX,
+ so normally SUFFIX starts with one."
+ (if (stringp file-name)
+ (let ((file (file-name-nondirectory file-name))
+ trial-name)
+ ;; Try splitting on last period.
+ ;; The first-period split can get fooled when two files
+ ;; named a.tex and a.b.tex are both tex'd;
+ ;; the last-period split must be right if it matches at all.
+ (setq trial-name
+ (concat (file-name-directory file-name)
+ (substring file 0
+ (string-match "\\.[^.]*$" file))
+ suffix))
+ (if (or (file-exists-p trial-name)
+ (file-exists-p (concat trial-name ".aux"))) ;for BibTeX files
+ trial-name
+ ;; Not found, so split on first period.
+ (concat (file-name-directory file-name)
+ (substring file 0
+ (string-match "\\." file))
+ suffix)))
+ " "))
+
+ (defun tex-show-print-queue ()
+ "Show the print queue that \\[tex-print] put your job on.
+ Runs the shell command defined by `tex-show-queue-command'."
+ (interactive)
+ (if (tex-shell-running)
+ (tex-kill-job)
+ (tex-start-shell))
+ (tex-send-command tex-show-queue-command)
+ (tex-display-shell))
+
+ (defun tex-bibtex-file ()
+ "Run BibTeX on the current buffer's file."
+ (interactive)
+ (if (tex-shell-running)
+ (tex-kill-job)
+ (tex-start-shell))
+ (let (shell-dirtrack-verbose
+ (tex-out-file
+ (tex-append (file-name-nondirectory (buffer-file-name)) ""))
+ (file-dir (file-name-directory (buffer-file-name))))
+ (tex-send-command tex-shell-cd-command file-dir)
+ (tex-send-command tex-bibtex-command tex-out-file))
+ (tex-display-shell))
+
+ ;;;;
+ ;;;; LaTeX indentation
+ ;;;;
+
+ (defvar tex-indent-allhanging t)
+ (defvar tex-indent-arg 4)
+ (defvar tex-indent-basic 2)
+ (defvar tex-indent-item tex-indent-basic)
+ (defvar tex-indent-item-re "\\\\\\(bib\\)?item\\>")
+
+ (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)
+ (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.
+ VIRTUAL if non-nil indicates that we're only trying to find the indentation
+ in order to determine the indentation of something else.
+ 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 (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
+ (save-excursion
+ (latex-skip-close-parens)
+ (latex-backward-sexp-1)
+ (latex-find-indent 'virtual))))
+ ;; Default (maybe an argument)
+ (let ((pos (point))
+ (char (char-after))
+ ;; Outdent \item if necessary.
+ (indent (if (looking-at tex-indent-item-re) (- tex-indent-item) 0))
+ up-list-pos)
+ ;; Find the previous point which determines our current indentation.
+ (condition-case err
+ (progn
+ (latex-backward-sexp-1)
+ (while (> (current-column) (current-indentation))
+ (latex-backward-sexp-1)))
+ (scan-error
+ (setq up-list-pos (nth 2 err))))
+ (cond
+ ((= (point-min) pos) 0) ; We're really just indenting the first line.
+ ((integerp up-list-pos)
+ ;; Have to indent relative to the open-paren.
+ (goto-char up-list-pos)
+ (if (and (not tex-indent-allhanging)
+ (> pos (progn (latex-down-list)
+ (forward-comment (point-max))
+ (point))))
+ ;; 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)
+ (+ indent tex-indent-basic (latex-find-indent 'virtual))))
+ ;; We're now at the "beginning" of a line.
+ ((not (and (not virtual) (eq (char-after) ?\\)))
+ ;; 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.
+ (+ indent (current-column) tex-indent-item))
+ (t
+ (let ((col (current-column)))
+ (if (not (eq (char-syntax char) ?\())
+ ;; 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)))
+ ;; 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)
+
+ ;;; tex-mode.el ends here
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Emacs-diffs] Changes to emacs/lisp/textmodes/tex-mode.el [emacs-unicode-2],
Dave Love <=