X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/53c4fe47866097b20949ffc87ed93a6b09387cdf..ceb4c4d3039f5b70d0e49c343bce0bd36cfc1dc8:/lisp/textmodes/tex-mode.el?ds=sidebyside diff --git a/lisp/textmodes/tex-mode.el b/lisp/textmodes/tex-mode.el index b66cd6470a..97153e31a2 100644 --- a/lisp/textmodes/tex-mode.el +++ b/lisp/textmodes/tex-mode.el @@ -1,7 +1,7 @@ -;;; tex-mode.el --- TeX, LaTeX, and SliTeX mode commands. +;;; tex-mode.el --- TeX, LaTeX, and SliTeX mode commands -*- coding: utf-8 -*- -;; Copyright (C) 1985, 86, 89, 92, 94, 95, 96, 97, 98, 1999 -;; Free Software Foundation, Inc. +;; Copyright (C) 1985, 1986, 1989, 1992, 1994, 1995, 1996, 1997, 1998, 1999, +;; 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc. ;; Maintainer: FSF ;; Keywords: tex @@ -23,31 +23,37 @@ ;; 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. +;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: ;;; Code: ;; Pacify the byte-compiler (eval-when-compile (require 'compare-w) + (require 'cl) (require 'skeleton)) +(defvar font-lock-comment-face) +(defvar font-lock-doc-face) + (require 'shell) (require 'compile) (defgroup tex-file nil - "TeX files and directories" + "TeX files and directories." :prefix "tex-" :group 'tex) (defgroup tex-run nil - "Running external commands from TeX mode" + "Running external commands from TeX mode." :prefix "tex-" :group 'tex) (defgroup tex-view nil - "Viewing and printing TeX files" + "Viewing and printing TeX files." :prefix "tex-" :group 'tex) @@ -117,33 +123,43 @@ See the documentation of that variable." :group 'tex-run) ;;;###autoload -(defcustom tex-start-options-string "\\nonstopmode\\input" - "*TeX options to use when running TeX. -These precede the input file name. If nil, TeX runs without option. -See the documentation of `tex-command'." +(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 "22.1") + +;;;###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 "20.4") - -(defvar standard-latex-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") + :version "22.1") + +(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 `standard-latex-block-names' for minibuffer completion." +Combined with `latex-standard-block-names' for minibuffer completion." :type '(repeat string) :group 'tex-run) @@ -183,20 +199,18 @@ use." :group 'tex-view) ;;;###autoload -(defcustom tex-dvi-view-command nil +(defcustom tex-dvi-view-command + '(cond + ((eq window-system 'x) "xdvi") + ((eq window-system 'w32) "yap") + (t "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, - - (setq tex-dvi-view-command - (if (eq window-system 'x) \"xdvi\" \"dvi2tty * | cat -s\")) +otherwise, the file name, preceded by a space, is added at the end. -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 @@ -219,12 +233,14 @@ Normally set to either `plain-tex-mode' or `latex-mode'." (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 @@ -232,11 +248,12 @@ Normally set to either `plain-tex-mode' or `latex-mode'." Deleted when the \\[tex-region] or \\[tex-buffer] is next run, or when the tex shell terminates.") -(defvar tex-command nil +(defvar tex-command "tex" "*Command to run TeX. -If this string contains an asterisk \(`*'\), that is replaced by the file name\; -otherwise the \(shell-quoted\) value of `tex-start-options-string' and -the file name are added at the end, with blanks as separators. +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 @@ -266,7 +283,26 @@ Should be a simple file name with no extension or directory specification.") "File name that \\[tex-print] prints. Set by \\[tex-region], \\[tex-buffer], and \\[tex-file].") -(defvar tex-mode-syntax-table nil +(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.") ;;;; @@ -412,8 +448,9 @@ An alternative value is \" . \", if you use a font with a narrow period." '("title" "begin" "end" "chapter" "part" "section" "subsection" "subsubsection" "paragraph" "subparagraph" "subsubparagraph" - "newcommand" "renewcommand" "newenvironment" - "newtheorem") + "newcommand" "renewcommand" "providecommand" + "newenvironment" "renewenvironment" + "newtheorem" "renewtheorem") t)) (variables (regexp-opt '("newcounter" "newcounter*" "setcounter" "addtocounter" @@ -422,81 +459,253 @@ An alternative value is \" . \", if you use a font with a narrow period." (includes (regexp-opt '("input" "include" "includeonly" "bibliography" "epsfig" "psfig" "epsf" "nofiles" "usepackage" - "includegraphics" "includegraphics*") + "documentstyle" "documentclass" "verbatiminput" + "includegraphics" "includegraphics*" + "url" "nolinkurl") t)) ;; Miscellany. (slash "\\\\") - (opt "\\(\\[[^]]*\\]\\)?") - (arg "{\\(\\(?:[^{}]+\\(?:{[^}]*}\\)?\\)+\\)")) + (opt " *\\(\\[[^]]*\\] *\\)*") + ;; This would allow highlighting \newcommand\CMD but requires + ;; adapting subgroup numbers below. + ;; (arg "\\(?:{\\(\\(?:[^{}\\]+\\|\\\\.\\|{[^}]*}\\)+\\)\\|\\\\[a-z*]+\\)")) + (arg "{\\(\\(?:[^{}\\]+\\|\\\\.\\|{[^}]*}\\)+\\)")) (list + ;; font-lock-syntactic-keywords causes the \ of \end{verbatim} to be + ;; highlighted as tex-verbatim face. Let's undo that. + ;; This is ugly and brittle :-( --Stef + '("^\\(\\\\\\)end" (1 (get-text-property (match-end 1) 'face) t)) + ;; 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) - 3 'font-lock-function-name-face 'prepend) + ;; 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\\** *\\(\\\\[A-Za-z@]+\\)") + 1 'font-lock-function-name-face 'keep) ;; Variable args. - (list (concat slash variables arg) 2 'font-lock-variable-name-face) + (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) - ))) + '("^[ \t]*\\\\def *\\\\\\(\\(\\w\\|@\\)+\\)" + 1 font-lock-function-name-face)))) "Subdued expressions to highlight in TeX modes.") +(defun tex-font-lock-append-prop (prop) + (unless (memq (get-text-property (match-end 1) 'face) + '(font-lock-comment-face tex-verbatim)) + prop)) + (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 '("bf" "textbf" "textsc" "textup" + (bold (regexp-opt '("textbf" "textsc" "textup" "boldsymbol" "pmb") t)) - (italic (regexp-opt '("it" "textit" "textsl" "emph") t)) - (type (regexp-opt '("texttt" "textmd" "textrm" "textsf") 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" "caption" "index" "glossary" - "footnote" "footnotemark" "footnotetext") + "cite" "nocite" "index" "glossary" "bibitem" + ;; These are text, rather than citations. + ;; "caption" "footnote" "footnotemark" "footnotetext" + ) t)) ;; ;; Names of commands that should be fontified. - (specials (regexp-opt - '("\\" - "linebreak" "nolinebreak" "pagebreak" "nopagebreak" - "newline" "newpage" "clearpage" "cleardoublepage" - "displaybreak" "allowdisplaybreaks" "enlargethispage") - t)) + (specials-1 (regexp-opt '("\\" "\\*") t)) ;; "-" + (specials-2 (regexp-opt + '("linebreak" "nolinebreak" "pagebreak" "nopagebreak" + "newline" "newpage" "clearpage" "cleardoublepage" + "displaybreak" "allowdisplaybreaks" + "enlargethispage") t)) (general "\\([a-zA-Z@]+\\**\\|[^ \t\n]\\)") ;; ;; Miscellany. (slash "\\\\") - (opt "\\(\\[[^]]*\\]\\)?") - (arg "{\\(\\(?:[^{}]+\\(?:{[^}]*}\\)?\\)+\\)")) + (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) 'font-lock-warning-face) + (cons (concat slash specials-1) 'font-lock-warning-face) + (list (concat "\\(" slash specials-2 "\\)\\([^a-zA-Z@]\\|\\'\\)") + 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) + (list (concat slash bold " *" arg) 2 + '(tex-font-lock-append-prop 'bold) 'append) + (list (concat slash italic " *" arg) 2 + '(tex-font-lock-append-prop '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 "\\\\\\(\\(bf\\)\\|em\\|it\\|sl\\)\\>" - "\\(\\([^}&\\]\\|\\\\[^\\]\\)+\\)") - 3 '(if (match-beginning 2) 'bold 'italic) 'append) - )))) + (list (concat "\\\\\\(em\\|it\\|sl\\)\\>" args) + 2 '(tex-font-lock-append-prop 'italic) 'append) + ;; This is separate from the previous one because of cases like + ;; {\em foo {\bf bar} bla} where both match. + (list (concat "\\\\\\(bf\\(series\\)?\\)\\>" args) + 3 '(tex-font-lock-append-prop '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)) + ;; 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 "\\|#[0-9]\\|" 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 "|") + ;; Technically, we'd like to put the "|" property on the \n preceding + ;; the \end, but this would have 2 disadvantages: + ;; 1 - it's wrong if the verbatim env is empty (the same \n is used to + ;; start and end the fenced-string). + ;; 2 - font-lock considers the preceding \n as being part of the + ;; preceding line, so things gets screwed every time the previous + ;; line is re-font-locked on its own. + ;; There's a hack in tex-font-lock-keywords-1 to remove the verbatim + ;; face from the \ but C-M-f still jumps to the wrong spot :-( --Stef + (,(concat "^\\(\\\\\\)end *{" verbs "}\\(.?\\)") (1 "|") (3 "<")) + ;; ("^\\(\\\\\\)begin *{comment}" 1 "< b") + ;; ("^\\\\end *{comment}.*\\(\n\\)" 1 "> b") + ("\\\\verb\\**\\([^a-z@*]\\)" + ;; Do it last, because it uses syntax-ppss which needs the + ;; syntax-table properties of previous entries. + 1 (tex-font-lock-verb (match-end 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." + :group 'tex) +(defface subscript + '((t :height 0.8)) ;; :raise -0.3 + "Face used for subscripts." + :group 'tex) + +(defface tex-math + '((t :inherit font-lock-string-face)) + "Face used to highlight TeX math expressions." + :group 'tex) +;; backward-compatibility alias +(put 'tex-math-face 'face-alias 'tex-math) +(defvar tex-math-face 'tex-math) + +(defface tex-verbatim + ;; '((t :inherit font-lock-string-face)) + '((t :family "courier")) + "Face used to highlight TeX verbatim environments." + :group 'tex) +;; backward-compatibility alias +(put 'tex-verbatim-face 'face-alias 'tex-verbatim) +(defvar tex-verbatim-face 'tex-verbatim) + +(defun tex-font-lock-verb (end) + "Place syntax-table properties on the \verb construct. +END is the position of the first delimiter after \verb." + (unless (nth 8 (syntax-ppss end)) + ;; Do nothing if the \verb construct is itself inside a comment or + ;; verbatim env. + (save-excursion + ;; Let's find the end and mark it. + ;; We used to do it inside tex-font-lock-syntactic-face-function, but + ;; this leads to funny effects when jumping to the end of the buffer, + ;; because font-lock applies font-lock-syntactic-keywords to the whole + ;; preceding text but font-lock-syntactic-face-function only to the + ;; actually displayed text. + (goto-char end) + (let ((char (char-before))) + (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)) + ;; Cause the rest of the buffer to be re-fontified. + ;; (remove-text-properties (1+ (point)) (point-max) '(fontified)) + ))) + "\"")) + +;; 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 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." @@ -508,55 +717,70 @@ An alternative value is \" . \", if you use a font with a narrow period." (define-key keymap [menu-bar tex] (cons "TeX" (make-sparse-keymap "TeX"))) - (define-key keymap [menu-bar tex tex-kill-job] '("Tex Kill" . tex-kill-job)) + (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] - '("Tex Recenter" . 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] - '("Tex Print (alt printer)" . tex-alt-print)) - (define-key keymap [menu-bar tex tex-print] '("Tex Print" . tex-print)) - (define-key keymap [menu-bar tex tex-view] '("Tex View" . tex-view)) - ) - -(defvar tex-mode-map nil "Keymap for TeX mode.") - -(if tex-mode-map - nil - (setq tex-mode-map (make-sparse-keymap)) - (tex-define-common-keys tex-mode-map) - (define-key tex-mode-map "\"" 'tex-insert-quote) - (define-key tex-mode-map "\n" 'tex-terminate-paragraph) - (define-key tex-mode-map "\C-c}" 'up-list) - (define-key tex-mode-map "\C-c{" 'tex-insert-braces) - (define-key tex-mode-map "\C-c\C-r" 'tex-region) - (define-key tex-mode-map "\C-c\C-b" 'tex-buffer) - (define-key tex-mode-map "\C-c\C-f" 'tex-file) - (define-key tex-mode-map "\C-c\C-i" 'tex-bibtex-file) - (define-key tex-mode-map "\C-c\C-o" 'tex-latex-block) - (define-key tex-mode-map "\C-c\C-e" 'tex-close-latex-block) - (define-key tex-mode-map "\C-c\C-u" 'tex-goto-last-unclosed-latex-block) - (define-key tex-mode-map "\C-c\C-m" 'tex-feed-input) - (define-key tex-mode-map [(control return)] 'tex-feed-input) - (define-key tex-mode-map [menu-bar tex tex-bibtex-file] - '("BibTeX File" . tex-bibtex-file)) - (define-key tex-mode-map [menu-bar tex tex-validate-region] - '("Validate Region" . tex-validate-region)) - (define-key tex-mode-map [menu-bar tex tex-validate-buffer] - '("Validate Buffer" . tex-validate-buffer)) - (define-key tex-mode-map [menu-bar tex tex-region] - '("TeX Region" . tex-region)) - (define-key tex-mode-map [menu-bar tex tex-buffer] - '("TeX Buffer" . tex-buffer)) - (define-key tex-mode-map [menu-bar tex tex-file] '("TeX File" . tex-file))) - -(put 'tex-region 'menu-enable 'mark-active) -(put 'tex-validate-region 'menu-enable 'mark-active) -(put 'tex-print 'menu-enable '(stringp tex-print-file)) -(put 'tex-alt-print 'menu-enable '(stringp tex-print-file)) -(put 'tex-view 'menu-enable '(stringp tex-print-file)) -(put 'tex-recenter-output-buffer 'menu-enable '(get-buffer "*tex-shell*")) -(put 'tex-kill-job 'menu-enable '(tex-shell-running)) + '(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))) @@ -579,17 +803,9 @@ Inherits `shell-mode-map' with a few additions.") ,@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. -;;;###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) +;; 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)) @@ -598,15 +814,59 @@ says which mode to use." (save-excursion (beginning-of-line) (search-forward "%" search-end t)))))) - (if (and slash (not comment)) - (setq mode (if (looking-at "documentstyle\\|documentclass\\|begin\\b\\|NeedsTeXFormat{LaTeX") - (if (looking-at - "document\\(style\\|class\\)\\(\\[.*\\]\\)?{slides}") - 'slitex-mode - 'latex-mode) - 'plain-tex-mode)))) + (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" "RequirePackage") 'words) + "\\|NeedsTeXFormat{LaTeX"))) + (if (and (looking-at + "document\\(style\\|class\\)\\(\\[.*\\]\\)?{slides}") + ;; SliTeX is almost never used any more nowadays. + (tex-executable-exists-p slitex-run-command)) + '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))) + +;; The following three autoloaded aliases appear to conflict with +;; AUCTeX. However, even though AUCTeX uses the mixed case variants +;; for all mode relevant variables and hooks, the invocation function +;; and setting of `major-mode' themselves need to be lowercase for +;; AUCTeX to provide a fully functional user-level replacement. So +;; these aliases should remain as they are, in particular since AUCTeX +;; users are likely to use them. + ;;;###autoload (defalias 'TeX-mode 'tex-mode) ;;;###autoload @@ -615,7 +875,7 @@ says which mode to use." (defalias 'LaTeX-mode 'latex-mode) ;;;###autoload -(define-derived-mode plain-tex-mode text-mode "TeX" +(define-derived-mode plain-tex-mode tex-mode "TeX" "Major mode for editing files of input for plain TeX. Makes $ and } display the characters they match. Makes \" insert `` when it seems to be the beginning of a quotation, @@ -633,7 +893,7 @@ Use \\[tex-validate-buffer] to check buffer for paragraphs containing mismatched $'s or braces. Special commands: -\\{tex-mode-map} +\\{plain-tex-mode-map} Mode variables: tex-run-command @@ -655,15 +915,13 @@ tex-show-queue-command Entering Plain-tex mode runs the hook `text-mode-hook', then the hook `tex-mode-hook', and finally the hook `plain-tex-mode-hook'. When the special subshell is initiated, the hook `tex-shell-hook' is run." - (tex-common-initialization) - (setq tex-command tex-run-command) - (setq tex-start-of-header "%\\*\\*start of header") - (setq tex-end-of-header "%\\*\\*end of header") - (setq tex-trailer "\\bye\n") - (run-hooks 'tex-mode-hook)) + (set (make-local-variable 'tex-command) tex-run-command) + (set (make-local-variable 'tex-start-of-header) "%\\*\\*start of header") + (set (make-local-variable 'tex-end-of-header) "%\\*\\*end of header") + (set (make-local-variable 'tex-trailer) "\\bye\n")) ;;;###autoload -(define-derived-mode latex-mode text-mode "LaTeX" +(define-derived-mode latex-mode tex-mode "LaTeX" "Major mode for editing files of input for LaTeX. Makes $ and } display the characters they match. Makes \" insert `` when it seems to be the beginning of a quotation, @@ -681,7 +939,7 @@ Use \\[tex-validate-buffer] to check buffer for paragraphs containing mismatched $'s or braces. Special commands: -\\{tex-mode-map} +\\{latex-mode-map} Mode variables: latex-run-command @@ -703,16 +961,16 @@ tex-show-queue-command Entering Latex mode runs the hook `text-mode-hook', then `tex-mode-hook', and finally `latex-mode-hook'. When the special subshell is initiated, `tex-shell-hook' is run." - (tex-common-initialization) - (setq tex-command latex-run-command) - (setq tex-start-of-header "\\\\documentstyle\\|\\\\documentclass") - (setq tex-end-of-header "\\\\begin{document}") - (setq tex-trailer "\\end{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{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]*$\\|[\f%]\\|[ \t]*\\$\\$\\|" + (concat "[ \t]*\\(\\$\\$\\|" "\\\\[][]\\|" "\\\\" (regexp-opt (append (mapcar 'car latex-section-alist) @@ -721,9 +979,9 @@ subshell is initiated, `tex-shell-hook' is run." "newpage" "footnote" "marginpar" "parbox" "caption")) t) "\\>\\|\\\\[a-z]*" (regexp-opt '("space" "skip" "page") t) - "\\>")) + "\\>\\)")) (setq paragraph-separate - (concat "[ \t]*$\\|[\f%]\\|[ \t]*\\$\\$[ \t]*$\\|" + (concat "[\f%]\\|[ \t]*\\($\\|" "\\\\[][]\\|" "\\\\" (regexp-opt (append (mapcar 'car latex-section-alist) @@ -731,17 +989,18 @@ subshell is initiated, `tex-shell-hook' is run." "\\>\\|\\\\\\(" (regexp-opt '("item" "bibitem" "newline" "noindent" "newpage" "footnote" "marginpar" "parbox" "caption")) - "\\|[a-z]*\\(space\\|skip\\|page[a-z]*\\)" - "\\)[ \t]*\\($\\|%\\)")) + "\\|\\$\\$\\|[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) - (set (make-local-variable 'fill-nobreak-predicate) - 'latex-fill-nobreak-predicate) + (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) - (run-hooks 'tex-mode-hook)) + (set (make-local-variable 'skeleton-end-hook) nil)) ;;;###autoload (define-derived-mode slitex-mode latex-mode "SliTeX" @@ -762,7 +1021,7 @@ Use \\[tex-validate-buffer] to check buffer for paragraphs containing mismatched $'s or braces. Special commands: -\\{tex-mode-map} +\\{slitex-mode-map} Mode variables: slitex-run-command @@ -789,29 +1048,6 @@ Entering SliTeX mode runs the hook `text-mode-hook', then the hook (setq tex-start-of-header "\\\\documentstyle{slides}\\|\\\\documentclass{slides}")) (defun tex-common-initialization () - (use-local-map tex-mode-map) - (setq local-abbrev-table text-mode-abbrev-table) - (if (null tex-mode-syntax-table) - (let ((char 0)) - (setq tex-mode-syntax-table (make-syntax-table)) - (set-syntax-table tex-mode-syntax-table) - (while (< char ? ) - (modify-syntax-entry char ".") - (setq char (1+ char))) - (modify-syntax-entry ?\C-@ "w") - (modify-syntax-entry ?\t " ") - (modify-syntax-entry ?\n ">") - (modify-syntax-entry ?\f ">") - (modify-syntax-entry ?$ "$$") - (modify-syntax-entry ?% "<") - (modify-syntax-entry ?\\ "/") - (modify-syntax-entry ?\" ".") - (modify-syntax-entry ?& ".") - (modify-syntax-entry ?_ ".") - (modify-syntax-entry ?@ "_") - (modify-syntax-entry ?~ " ") - (modify-syntax-entry ?' "w")) - (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. @@ -824,24 +1060,32 @@ Entering SliTeX mode runs the hook `text-mode-hook', then the hook (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))))) + (or (cdr (assq face tex-face-alist)) + (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 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-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) @@ -883,18 +1127,17 @@ 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 arg + (if (or arg (memq (char-syntax (preceding-char)) '(?/ ?\\)) + (eq (get-text-property (point) 'face) 'tex-verbatim) + (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 - (cond ((or (bobp) - (save-excursion - (forward-char -1) - (looking-at "\\s(\\|\\s \\|\\s>"))) - tex-open-quote) - ((= (preceding-char) ?\\) - ?\") - (t - tex-close-quote))))) + (insert (if (memq (char-syntax (preceding-char)) '(?\( ?> ?\s)) + tex-open-quote tex-close-quote)))) (defun tex-validate-buffer () "Check current buffer for paragraphs containing mismatched braces or $s. @@ -909,14 +1152,14 @@ on the line for the invalidity you want to see." (num-matches 0)) (with-output-to-temp-buffer "*Occur*" (princ "Mismatches:\n") - (save-excursion - (set-buffer standard-output) + (with-current-buffer standard-output (occur-mode) - (setq occur-buffer buffer) - (setq occur-nlines 0)) + ;; 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 (input-pending-p)) (not (bobp))) + (while (and (not (bobp))) (let ((end (point)) prev-end) ;; Scan the previous paragraph for invalidities. @@ -926,8 +1169,7 @@ on the line for the invalidity you want to see." (forward-char 2)) (goto-char (setq prev-end (point-min)))) (or (tex-validate-region (point) end) - (let* ((oend end) - (end (save-excursion (forward-line 1) (point))) + (let* ((end (line-beginning-position 2)) start tem) (beginning-of-line) (setq start (point)) @@ -952,19 +1194,22 @@ on the line for the invalidity you want to see." (forward-char (- start end)) (setq text-beg (point-marker)) (insert (format "%3d: " linenum)) - (put-text-property (marker-position text-beg) - (- (marker-position text-end) 1) - 'mouse-face 'highlight) - (put-text-property (marker-position text-beg) - (- (marker-position text-end) 1) - 'occur tem))))) + (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)))) - (save-excursion - (set-buffer standard-output) - (if (eq num-matches 0) - (insert "None!\n")) - (if (interactive-p) - (message "%d mismatches found" num-matches)))))) + (with-current-buffer standard-output + (let ((no-matches (zerop num-matches))) + (if no-matches + (insert "None!\n")) + (if (interactive-p) + (message (cond (no-matches "No mismatches found") + ((= num-matches 1) "1 mismatch found") + (t "%d mismatches found")) + num-matches))))))) (defun tex-validate-region (start end) "Check for mismatched braces or $'s in region. @@ -982,22 +1227,19 @@ area if a mismatch is found." (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 + (while (re-search-forward "\\s(" nil t) + (save-excursion + (let ((pos (match-beginning 0))) + (goto-char pos) (forward-sexp 1) - (or (= (preceding-char) match) - (error "Mismatched parentheses")))) - (forward-char 1))) + (or (eq (preceding-char) (cdr (syntax-after pos))) + (eq (char-after pos) (cdr (syntax-after (1- (point))))) + (error "Mismatched parentheses")))))) (error (skip-syntax-forward " .>") (setq failure-point (point))))) - (if failure-point - (progn - (goto-char failure-point) - nil) - t))) + (if failure-point (goto-char failure-point)) + (not failure-point))) (defun tex-terminate-paragraph (inhibit-validation) "Insert two newlines, breaking a paragraph for TeX. @@ -1014,66 +1256,113 @@ A prefix arg inhibits the checking." (message "Paragraph being closed appears to contain a mismatch")) (insert "\n\n")) -(defun tex-insert-braces () +(define-skeleton tex-insert-braces "Make a pair of braces and be poised to type inside of them." - (interactive "*") - (insert ?\{) - (save-excursion - (insert ?}))) + nil + ?\{ _ ?}) ;; This function is used as the value of fill-nobreak-predicate ;; in LaTeX mode. Its job is to prevent line-breaking inside ;; of a \verb construct. (defun latex-fill-nobreak-predicate () - (let ((opoint (point)) - inside) - (save-excursion - (save-restriction - (beginning-of-line) - (narrow-to-region (point) opoint) - (while (re-search-forward "\\\\verb\\(.\\)" nil t) - (unless (re-search-forward (regexp-quote (match-string 1)) nil 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") -;;; 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. +(defvar latex-block-args-alist + '(("array" nil ?\{ (skeleton-read "Format: ") ?\}) + ("tabular" nil ?\{ (skeleton-read "Format: ") ?\}) + ("minipage" nil ?\{ (skeleton-read "Size: ") ?\}) + ("picture" nil ?\( (skeleton-read "SizeX,SizeY: ") ?\)) + ;; FIXME: This is right for Prosper, but not for seminar. + ;; ("slide" nil ?\{ (skeleton-read "Title: ") ?\}) + ) + "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{" > (skeleton-read "Caption: ") "}" > \n + '(if (and (boundp 'reftex-mode) reftex-mode) (reftex-label "table")) + \n _) + ("figure" nil > _ \n "\\caption{" > (skeleton-read "Caption: ") "}" > \n + '(if (and (boundp 'reftex-mode) reftex-mode) (reftex-label "table")))) + "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) - (mapcar 'list - (append standard-latex-block-names - latex-block-names)) + (append 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) - "\\begin{" str ?\} - ?\[ (skeleton-read "[options]: ") & ?\] | -1 - \n _ \n - "\\end{" str ?\} >) + \n "\\begin{" str "}" + (cdr (assoc str latex-block-args-alist)) + > \n (or (cdr (assoc str latex-block-body-alist)) '(nil > _)) + (unless (bolp) '\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." - (while (and (re-search-backward "\\\\\\(begin\\|end\\)\\s *{") - (looking-at "\\\\end")) - (tex-last-unended-begin))) + (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 (re-search-forward "\\\\\\(begin\\|end\\)\\s *{[^}]+}") - (looking-at "\\\\begin")) + (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 () @@ -1082,9 +1371,7 @@ Mark is left at original location." (interactive) (let ((spot)) (save-excursion - (condition-case nil - (tex-last-unended-begin) - (error (error "Couldn't find unended \\begin"))) + (tex-last-unended-begin) (setq spot (point))) (push-mark) (goto-char spot))) @@ -1101,7 +1388,9 @@ Mark is left at original location." (when (eq (char-after) ?{) (let ((newpos (point))) (when (ignore-errors (backward-sexp 1) t) - (if (looking-at "\\\\end\\>") + (if (or (looking-at "\\\\end\\>") + ;; In case the \\ ends a verbatim section. + (and (looking-at "end\\>") (eq (char-before) ?\\))) (tex-last-unended-begin) (goto-char newpos)))))))) @@ -1140,48 +1429,116 @@ Mark is left at original location." (goto-char pos) (signal (car err) (cdr err)))))) -(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)))) +(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. +;; 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: -;;; The utility functions: +(define-derived-mode tex-shell shell-mode "TeX-Shell" + (set (make-local-variable 'compilation-parse-errors-function) + 'tex-compilation-parse-errors) + (compilation-shell-minor-mode t)) ;;;###autoload (defun tex-start-shell () (with-current-buffer (make-comint "tex-shell" - (or tex-shell-file-name (getenv "ESHELL") (getenv "SHELL") "/bin/sh") - nil) + (or tex-shell-file-name (getenv "ESHELL") shell-file-name) + nil + ;; Specify an interactive shell, to make sure it prompts. + "-i") (let ((proc (get-process "tex-shell"))) (set-process-sentinel proc 'tex-shell-sentinel) - (process-kill-without-query proc) - (setq comint-prompt-regexp shell-prompt-pattern) - (use-local-map tex-shell-map) - (compilation-shell-minor-mode t) - (add-hook 'comint-input-filter-functions 'shell-directory-tracker nil t) - (make-local-variable 'list-buffers-directory) - (make-local-variable 'shell-dirstack) - (make-local-variable 'shell-last-dir) - (make-local-variable 'shell-dirtrackp) - (run-hooks 'tex-shell-hook) + (set-process-query-on-exit-flag proc nil) + (tex-shell) (while (zerop (buffer-size)) (sleep-for 1))))) @@ -1190,13 +1547,13 @@ Mark is left at original location." 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 (process-buffer (get-process "tex-shell"))) + (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 (process-buffer (get-process "tex-shell"))) + (display-buffer (tex-shell-buf)) (tex-recenter-output-buffer nil)) (defun tex-shell-sentinel (proc msg) @@ -1219,6 +1576,14 @@ In the tex shell buffer this command behaves like `comint-send-input'." (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 *, @@ -1229,16 +1594,19 @@ evaluates to a command string. Return the process in which TeX is running." (save-excursion (let* ((cmd (eval command)) - (proc (or (get-process "tex-shell") (error "No TeX subprocess"))) + (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 (null file) + cmd + (if (file-name-absolute-p file) + (setq file (convert-standard-filename file))) + (if star (concat (substring cmd 0 star) + (shell-quote-argument file) + (substring cmd (1+ star))) + (concat cmd " " (shell-quote-argument file)))) (if background "&" "")))) ;; Switch to buffer before checking for subproc output in it. (set-buffer buf) @@ -1273,19 +1641,312 @@ If NOT-ALL is non-nil, save the `.dvi' file." (add-hook 'kill-emacs-hook 'tex-delete-last-temp-files) -(defvar tex-start-tex-marker nil - "Marker pointing after last TeX-running command in the TeX shell buffer.") +;; +;; 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 + :group 'tex) + +(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") + ("xpdf %r.pdf &" "%r.pdf") + ("gv %r.ps &" "%r.ps") + ("yap %r &" "%r.dvi") + ("advi %r &" "%r.dvi") + ("gv %r.pdf &" "%r.pdf") + ("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 -o %r.ps %r" "%r.dvi" "%r.ps") + ("ps2pdf %r.ps" "%r.ps" "%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 () - (let ((file (or tex-main-file - ;; Compatibility with AUCTeX - (and (boundp 'TeX-master) (stringp TeX-master) TeX-master) - (if (buffer-file-name) - (file-relative-name (buffer-file-name)) - (error "Buffer is not associated with any file"))))) - (if (string-match "\\.tex\\'" file) - (substring file 0 (match-beginning 0)) - file))) + "Return the relative name of the main file." + (let* ((file (or tex-main-file + ;; Compatibility with AUCTeX. + (with-no-warnings + (when (boundp 'TeX-master) + (cond ((stringp TeX-master) + (make-local-variable 'tex-main-file) + (setq tex-main-file TeX-master)) + ((and (eq TeX-master t) buffer-file-name) + (file-relative-name buffer-file-name))))) + ;; 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 (or (file-exists-p file) (string-match "\\.tex\\'" 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 + (concat + "(see the transcript file for additional information)" + "\\|^Output written on .*" + (regexp-quote (file-name-nondirectory file)) + " (.*)\\.") 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 (and (file-directory-p f) + ;; Avoid infinite loops. + (not (file-symlink-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)))) + ;; If no command seems to be applicable, arbitrarily pick the first one. + (setq cmds (if cmds (nreverse cmds) (list (car tex-compile-commands)))) + ;; 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 (nreverse 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 (nreverse 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 (shell-quote-argument root)) + (cons ?f (shell-quote-argument 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." @@ -1293,26 +1954,45 @@ If NOT-ALL is non-nil, save the `.dvi' file." (compile-command (if star (concat (substring command 0 star) - (comint-quote-filename file) + (shell-quote-argument file) (substring command (1+ star))) (concat command " " - (if (< 0 (length tex-start-options-string)) + tex-start-options + (if (< 0 (length tex-start-commands)) (concat - (shell-quote-argument tex-start-options-string) " ")) - (comint-quote-filename file))))) - (when dir - (let (shell-dirtrack-verbose) - (tex-send-command tex-shell-cd-command dir))) - (with-current-buffer (process-buffer (tex-send-command compile-command)) - (save-excursion - (forward-line -1) - (setq tex-start-tex-marker (point-marker))) - (make-local-variable 'compilation-parse-errors-function) - (setq compilation-parse-errors-function 'tex-compilation-parse-errors) - (compilation-forget-errors)) - (tex-display-shell) - (setq tex-last-buffer-texed (current-buffer)))) + (shell-quote-argument tex-start-commands) " ")) + (shell-quote-argument 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)) + (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. @@ -1323,23 +2003,11 @@ 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))) - (old-syntax-table (syntax-table)) - (tex-error-parse-syntax-table (copy-syntax-table (syntax-table))) found-desired (num-errors-found 0) last-filename last-linenum last-position begin-of-error end-of-error) - (modify-syntax-entry ?\{ "_" tex-error-parse-syntax-table) - (modify-syntax-entry ?\} "_" tex-error-parse-syntax-table) - (modify-syntax-entry ?\[ "_" tex-error-parse-syntax-table) - (modify-syntax-entry ?\] "_" tex-error-parse-syntax-table) - ;; Single quotations may appear in errors - (modify-syntax-entry ?\" "_" tex-error-parse-syntax-table) - ;; Don't parse previous compilations. - (set-marker compilation-parsing-end - (max compilation-parsing-end tex-start-tex-marker)) ;; Don't reparse messages already seen at last parse. (goto-char compilation-parsing-end) ;; Parse messages. @@ -1349,44 +2017,39 @@ for the error messages." 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))) + (let* ((this-error (copy-marker begin-of-error)) + (linenum (string-to-number (match-string 1))) (error-text (regexp-quote (match-string 3))) (filename (save-excursion - (unwind-protect - (progn - (set-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)) - (set-syntax-table old-syntax-table)))) + (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 - (goto-line linenum) - (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 - (marker-position last-position) t) - (goto-char starting-point))) - (point-marker)))) + (with-current-buffer + (if (equal filename (concat tex-zap-file ".tex")) + tex-last-buffer-texed + (find-file-noselect filename)) + (save-excursion + (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 @@ -1405,8 +2068,7 @@ for the error messages." 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")) + (setq compilation-error-list (nreverse compilation-error-list))) ;;; The commands: @@ -1508,10 +2170,10 @@ See \\[tex-file] for an alternative." 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 (expand-file-name (file-name-directory source-file)))) - (if tex-offer-save - (save-some-buffers)) + (file-dir (file-name-directory (expand-file-name source-file)))) (if (tex-shell-running) (tex-kill-job) (tex-start-shell)) @@ -1526,13 +2188,7 @@ This function is more useful than \\[tex-buffer] when you need the ;; don't work with file names that start with #. (format "_TZ_%d-%s" (process-id (get-buffer-process "*tex-shell*")) - (tex-strip-dots (system-name)))) - -(defun tex-strip-dots (s) - (setq s (copy-sequence s)) - (while (string-match "\\." s) - (aset s (match-beginning 0) ?-)) - s) + (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. @@ -1548,17 +2204,26 @@ This function is more useful than \\[tex-buffer] when you need the (nreverse elts) ":"))) (defun tex-shell-running () - (and (get-process "tex-shell") - (eq (process-status (get-process "tex-shell")) 'run))) + (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 + ;; `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. - (quit-process (get-process "tex-shell") t)) + (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. @@ -1566,7 +2231,6 @@ The last line of the buffer is displayed on line LINE of the window, or centered if LINE is nil." (interactive "P") (let ((tex-shell (get-buffer "*tex-shell*")) - (old-buffer (current-buffer)) (window)) (if (null tex-shell) (message "No TeX output buffer") @@ -1600,7 +2264,8 @@ is provided, use the alternative command, `tex-alt-dvi-print-command'." (tex-start-shell)) (tex-send-command (if alt tex-alt-dvi-print-command tex-dvi-print-command) - print-file-name-dvi t)))) + print-file-name-dvi + t)))) (defun tex-alt-print () "Print the .dvi file made by \\[tex-region], \\[tex-buffer] or \\[tex-file]. @@ -1617,7 +2282,10 @@ because there is no standard value that would generally work." (interactive) (or tex-dvi-view-command (error "You must set `tex-dvi-view-command'")) - (let ((tex-dvi-print-command tex-dvi-view-command)) + ;; Restart the TeX shell if necessary. + (or (tex-shell-running) + (tex-start-shell)) + (let ((tex-dvi-print-command (eval tex-dvi-view-command))) (tex-print))) (defun tex-append (file-name suffix) @@ -1670,9 +2338,193 @@ Runs the shell command defined by `tex-show-queue-command'." (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 latex-noindent-environments '("document")) + +(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)) + '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)) + ;; 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) + (save-excursion + ;; Make sure we're an argument to a macro and + ;; that the macro is at the beginning of a line. + (condition-case nil + (progn + (while (eq (char-syntax (char-after)) ?\() + (forward-sexp -1)) + (and (eq (char-syntax (char-after)) ?/) + (progn (skip-chars-backward " \t&") + (bolp)))) + (scan-error nil))) + (> 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) + (+ (if (and (looking-at "\\\\begin *{\\([^\n}]+\\)") + (member (match-string 1) + latex-noindent-environments)) + 0 tex-indent-basic) + indent (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 (or (not (eq (char-syntax (or (char-after pos) ?\s)) ?\()) + ;; Can't be an arg if there's an empty line inbetween. + (save-excursion (re-search-forward "^[ \t]*$" pos t))) + ;; 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) +;; arch-tag: c0a680b1-63aa-4547-84b9-4193c29c0080 ;;; tex-mode.el ends here