;;; 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
: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*"
;;;###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)
: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
"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
;;;;
'("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"
;; 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.
(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
;; Miscellany.
(slash "\\\\")
(opt " *\\(\\[[^]]*\\] *\\)*")
+ (args "\\(\\(?:[^{}&\\]+\\|\\\\.\\|{[^}]*}\\)+\\)")
(arg "{\\(\\(?:[^{}\\]+\\|\\\\.\\|{[^}]*}\\)+\\)"))
(list
;;
;; (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)
(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)
(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'.")
;; 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))
(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}")
'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 '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,
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,
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)
(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"
(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.
(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)
(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.
(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))
(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
;;;; 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)))
(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"
(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))
"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")
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."
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")
(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].
(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)
(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.
(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
(> 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)
;; 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)))
;; 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