*** empty log message ***
[bpt/emacs.git] / lisp / textmodes / tex-mode.el
index 748680a..ffb0606 100644 (file)
@@ -1,7 +1,8 @@
 ;;; tex-mode.el --- TeX, LaTeX, and SliTeX mode commands -*- coding: utf-8 -*-
 
-;; Copyright (C) 1985, 1986, 1989, 1992, 1994, 1995, 1996, 1997, 1998, 1999,
-;;   2001, 2002, 2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
+;; Copyright (C) 1985, 1986, 1989, 1992, 1994, 1995, 1996, 1997, 1998
+;;   1999, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008
+;;   Free Software Foundation, Inc.
 
 ;; Maintainer: FSF
 ;; Keywords: tex
@@ -13,7 +14,7 @@
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
+;; the Free Software Foundation; either version 3, or (at your option)
 ;; any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
@@ -246,17 +247,20 @@ Normally set to either `plain-tex-mode' or `latex-mode'."
 (defcustom tex-fontify-script t
   "If non-nil, fontify subscript and superscript strings."
   :type 'boolean
-  :group 'tex)
+  :group 'tex
+  :version "23.1")
 (put 'tex-fontify-script 'safe-local-variable 'booleanp)
 
-(defcustom tex-font-script-display '(-0.2 . 0.2)
-  "Display specification for subscript and superscript content.
-The car is used for subscript, the cdr is used for superscripts."
+(defcustom tex-font-script-display '(-0.2 0.2)
+  "How much to lower and raise subscript and superscript content.
+This is a list of two floats.  The first is negative and
+specifies how much subscript is lowered, the second is positive
+and specifies how much superscript is raised.  Heights are
+measured relative to that of the normal text."
   :group 'tex
-  :type '(cons (choice (float :tag "Subscript")
-                      (const :tag "No lowering" nil))
-              (choice (float :tag "Superscript")
-                      (const :tag "No raising" nil))))
+  :type '(list (float :tag "Subscript")
+               (float :tag "Superscript"))
+  :version "23.1")
 
 (defvar tex-last-temp-file nil
   "Latest temporary file generated by \\[tex-region] and \\[tex-buffer].
@@ -609,7 +613,7 @@ An alternative value is \" . \", if you use a font with a narrow period."
                odd))
     (if (eq (char-after pos) ?_)
        `(face subscript display (raise ,(car tex-font-script-display)))
-      `(face superscript display (raise ,(cdr tex-font-script-display))))))
+      `(face superscript display (raise ,(cadr tex-font-script-display))))))
 
 (defun tex-font-lock-match-suscript (limit)
   "Match subscript and superscript patterns up to LIMIT."
@@ -638,7 +642,7 @@ An alternative value is \" . \", if you use a font with a narrow period."
 (defvar tex-verbatim-environments
   '("verbatim" "verbatim*"))
 (put 'tex-verbatim-environments 'safe-local-variable
-     (lambda (x) (require 'cl) (every 'stringp x)))
+     (lambda (x) (null (delq t (mapcar 'stringp x)))))
 
 (defvar tex-font-lock-syntactic-keywords
   '((eval . `(,(concat "^\\\\begin *{"
@@ -669,17 +673,47 @@ An alternative value is \" . \", if you use a font with a narrow period."
     (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))
+              (member (car-safe (cdr prop)) tex-font-script-display)
               (null (cddr prop)))
          (put-text-property beg next 'display nil))
       (setq beg next))))
 
+(defcustom tex-suscript-height-ratio 0.8
+  "Ratio of subscript/superscript height to that of the preceding text.
+In nested subscript/superscript, this factor is applied repeatedly,
+subject to the limit set by `tex-suscript-height-minimum'."
+  :type 'float
+  :group 'tex
+  :version "23.1")
+
+(defcustom tex-suscript-height-minimum 0.0
+  "Integer or float limiting the minimum size of subscript/superscript text.
+An integer is an absolute height in units of 1/10 point, a float
+is a height relative to that of the default font.  Zero means no minimum."
+  :type '(choice (integer :tag "Integer height in 1/10 point units")
+                (float :tag "Fraction of default font height"))
+  :group 'tex
+  :version "23.1")
+
+(defun tex-suscript-height (height)
+  "Return the integer height of subscript/superscript font in 1/10 points.
+Not smaller than the value set by `tex-suscript-height-minimum'."
+  (ceiling (max (if (integerp tex-suscript-height-minimum)
+                   tex-suscript-height-minimum
+                 ;; For bootstrapping.
+                 (condition-case nil
+                     (* tex-suscript-height-minimum
+                        (face-attribute 'default :height))
+                   (error 0)))
+               ;; NB assumes height is integer.
+               (* height tex-suscript-height-ratio))))
+
 (defface superscript
-  '((t :height 0.8)) ;; :raise 0.2
+  '((t :height tex-suscript-height)) ;; :raise 0.2
   "Face used for superscripts."
   :group 'tex)
 (defface subscript
-  '((t :height 0.8)) ;; :raise -0.2
+  '((t :height tex-suscript-height)) ;; :raise -0.2
   "Face used for subscripts."
   :group 'tex)
 
@@ -1186,24 +1220,27 @@ on the line for the invalidity you want to see."
        (setq occur-revert-arguments (list nil 0 (list buffer))))
       (save-excursion
        (goto-char (point-max))
-       (while (and (not (bobp)))
-         (let ((end (point))
-               prev-end)
+       ;; Do a little shimmy to place point at the end of the last
+       ;; "real" paragraph. Need to avoid validating across an \end,
+       ;; because that blows up latex-forward-sexp.
+       (backward-paragraph)
+       (forward-paragraph)
+       (while (not (bobp))
            ;; Scan the previous paragraph for invalidities.
-           (if (search-backward "\n\n" nil t)
-               (progn
-                 (setq prev-end (point))
-                 (forward-char 2))
-             (goto-char (setq prev-end (point-min))))
-           (or (tex-validate-region (point) end)
-               (let* ((end (line-beginning-position 2))
+         (backward-paragraph)
+         (save-excursion
+           (or (tex-validate-region (point) (save-excursion
+                                              (forward-paragraph)
+                                              (point)))
+               (let ((end (line-beginning-position 2))
                       start tem)
                  (beginning-of-line)
                  (setq start (point))
                  ;; Keep track of line number as we scan,
                  ;; in a cumulative fashion.
                  (if linenum
-                     (setq linenum (- linenum (count-lines prevpos (point))))
+                     (setq linenum (- linenum
+                                      (count-lines prevpos (point))))
                    (setq linenum (1+ (count-lines 1 start))))
                  (setq prevpos (point))
                  ;; Mention this mismatch in *Occur*.
@@ -1224,10 +1261,10 @@ on the line for the invalidity you want to see."
                      (add-text-properties
                       text-beg (- text-end 1)
                       '(mouse-face highlight
-                        help-echo "mouse-2: go to this invalidity"))
+                                   help-echo
+                                   "mouse-2: go to this invalidity"))
                      (put-text-property text-beg (- text-end 1)
-                                        'occur-target tem)))))
-           (goto-char prev-end))))
+                                        'occur-target tem))))))))
       (with-current-buffer standard-output
        (let ((no-matches (zerop num-matches)))
          (if no-matches
@@ -1250,7 +1287,9 @@ area if a mismatch is found."
            (narrow-to-region start end)
            ;; First check that the open and close parens balance in numbers.
            (goto-char start)
-           (while (<= 0 (setq max-possible-sexps (1- max-possible-sexps)))
+           (while (and (not (eobp))
+                       (<= 0 (setq max-possible-sexps
+                                   (1- max-possible-sexps))))
              (forward-sexp 1))
            ;; Now check that like matches like.
            (goto-char start)
@@ -1258,6 +1297,7 @@ area if a mismatch is found."
              (save-excursion
                (let ((pos (match-beginning 0)))
                  (goto-char pos)
+                 (skip-chars-backward "\\\\") ; escaped parens
                  (forward-sexp 1)
                  (or (eq (preceding-char) (cdr (syntax-after pos)))
                      (eq (char-after pos) (cdr (syntax-after (1- (point)))))
@@ -1275,9 +1315,13 @@ A prefix arg inhibits the checking."
   (interactive "*P")
   (or inhibit-validation
       (save-excursion
+       ;; For the purposes of this, a "paragraph" is a block of text
+       ;; wherein all the brackets etc are expected to be balanced.  It
+       ;; may start after a blank line (ie a "proper" paragraph), or
+       ;; a begin{} or end{} block, etc.
        (tex-validate-region
         (save-excursion
-          (search-backward "\n\n" nil 'move)
+          (backward-paragraph)
           (point))
         (point)))
       (message "Paragraph being closed appears to contain a mismatch"))
@@ -1385,13 +1429,41 @@ Return the value returned by the last execution of BODY."
     (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."
+  "Leave point at the end of the next `\\end' that is unmatched."
   (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-next-unmatched-eparen (otype)
+  "Leave point after the next unmatched escaped closing parenthesis.
+The string OTYPE is an opening parenthesis type: `(', `{', or `['."
+  (condition-case nil
+      (let ((ctype (char-to-string (cdr (aref (syntax-table)
+                                             (string-to-char otype))))))
+       (while (and (tex-search-noncomment
+                    (re-search-forward (format "\\\\[%s%s]" ctype otype)))
+                   (save-excursion
+                     (goto-char (match-beginning 0))
+                     (looking-at (format "\\\\%s" (regexp-quote otype)))))
+         (tex-next-unmatched-eparen otype)))
+    (wrong-type-argument (error "Unknown opening parenthesis type: %s" otype))
+    (search-failed (error "Couldn't find closing escaped paren"))))
+
+(defun tex-last-unended-eparen (ctype)
+  "Leave point at the start of the last unended escaped opening parenthesis.
+The string CTYPE is a closing parenthesis type:  `)', `}', or `]'."
+  (condition-case nil
+      (let ((otype (char-to-string (cdr (aref (syntax-table)
+                                             (string-to-char ctype))))))
+       (while (and (tex-search-noncomment
+                    (re-search-backward (format "\\\\[%s%s]" ctype otype)))
+                   (looking-at (format "\\\\%s" (regexp-quote ctype))))
+         (tex-last-unended-eparen ctype)))
+    (wrong-type-argument (error "Unknown opening parenthesis type: %s" ctype))
+    (search-failed (error "Couldn't find unended escaped paren"))))
+
 (defun tex-goto-last-unclosed-latex-block ()
   "Move point to the last unclosed \\begin{...}.
 Mark is left at original location."
@@ -1403,26 +1475,34 @@ Mark is left at original location."
     (push-mark)
     (goto-char spot)))
 
+;; Don't think this one actually _needs_ (for the purposes of
+;; tex-mode) to handle escaped parens.
 (defun latex-backward-sexp-1 ()
-  "Like (backward-sexp 1) but aware of multi-char elements."
+  "Like (backward-sexp 1) but aware of multi-char elements and escaped parens."
   (let ((pos (point))
        (forward-sexp-function))
     (backward-sexp 1)
-    (if (looking-at "\\\\begin\\>")
-       (signal 'scan-error
-               (list "Containing expression ends prematurely"
-                     (point) (prog1 (point) (goto-char pos))))
-      (when (eq (char-after) ?{)
-       (let ((newpos (point)))
-         (when (ignore-errors (backward-sexp 1) t)
-           (if (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))))))))
-
+    (cond ((looking-at "\\\\\\(begin\\>\\|[[({]\\)")
+          (signal 'scan-error
+                  (list "Containing expression ends prematurely"
+                        (point) (prog1 (point) (goto-char pos)))))
+         ((looking-at "\\\\\\([])}]\\)")
+          (tex-last-unended-eparen (match-string 1)))
+         ((eq (char-after) ?{)
+          (let ((newpos (point)))
+            (when (ignore-errors (backward-sexp 1) t)
+              (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))))))))
+
+;; Note this does not handle things like mismatched brackets inside
+;; begin/end blocks.
+;; Needs to handle escaped parens for tex-validate-*.
+;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2007-09/msg00038.html
 (defun latex-forward-sexp-1 ()
-  "Like (forward-sexp 1) but aware of multi-char elements."
+  "Like (forward-sexp 1) but aware of multi-char elements and escaped parens."
   (let ((pos (point))
        (forward-sexp-function))
     (forward-sexp 1)
@@ -1439,10 +1519,19 @@ Mark is left at original location."
        ((looking-at "\\\\begin\\>")
        (goto-char (match-end 0))
        (tex-next-unmatched-end))
+       ;; A better way to handle this, \( .. \) etc, is probably to
+       ;; temporarily change the syntax of the \ in \( to punctuation.
+       ((looking-back "\\\\[])}]")
+       (signal 'scan-error
+               (list "Containing expression ends prematurely"
+                     (- (point) 2) (prog1 (point)
+                                     (goto-char pos)))))
+       ((looking-back "\\\\\\([({[]\\)")
+       (tex-next-unmatched-eparen (match-string 1)))
        (t (goto-char newpos))))))
 
 (defun latex-forward-sexp (&optional arg)
-  "Like `forward-sexp' but aware of multi-char elements."
+  "Like `forward-sexp' but aware of multi-char elements and escaped parens."
   (interactive "P")
   (unless arg (setq arg 1))
   (let ((pos (point)))
@@ -1843,7 +1932,8 @@ FILE is typically the output DVI or PDF file."
                  (not (file-symlink-p f)))
             (unless (string-match ignored-dirs-re f)
               (setq files (nconc
-                           (directory-files f t tex-input-files-re)
+                            (ignore-errors ;Not readable or something.
+                              (directory-files f t tex-input-files-re))
                            files)))
           (when (file-newer-than-file-p f file)
             (setq uptodate nil)))))
@@ -2034,29 +2124,37 @@ for the error messages."
          (file-name-directory (buffer-file-name tex-last-buffer-texed)))
        found-desired (num-errors-found 0)
        last-filename last-linenum last-position
-       begin-of-error end-of-error)
+       begin-of-error end-of-error errfilename)
     ;; Don't reparse messages already seen at last parse.
     (goto-char compilation-parsing-end)
     ;; Parse messages.
     (while (and (not (or found-desired (eobp)))
-               (prog1 (re-search-forward "^! " nil 'move)
+               ;; First alternative handles the newer --file-line-error style:
+               ;; ./test2.tex:14: Too many }'s.
+               ;; Second handles the old-style:
+               ;; ! Too many }'s.
+               (prog1 (re-search-forward
+                       "^\\(?:\\([^:\n]+\\):[[:digit:]]+:\\|!\\) " nil 'move)
                  (setq begin-of-error (match-beginning 0)
-                       end-of-error (match-end 0)))
+                       end-of-error (match-end 0)
+                       errfilename (match-string 1)))
                (re-search-forward
                 "^l\\.\\([0-9]+\\) \\(\\.\\.\\.\\)?\\(.*\\)$" nil 'move))
       (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
-               (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))))
+             ;; Prefer --file-liner-error filename if we have it.
+             (or errfilename
+                 (save-excursion
+                   (with-syntax-table tex-error-parse-syntax-table
+                     (backward-up-list 1)
+                     (skip-syntax-forward "(_")
+                     (while (not (file-readable-p (thing-at-point 'filename)))
+                       (skip-syntax-backward "(_")
+                       (backward-up-list 1)
+                       (skip-syntax-forward "(_"))
+                     (thing-at-point 'filename)))))
             (new-file
              (or (null last-filename)
                  (not (string-equal last-filename filename))))
@@ -2126,57 +2224,31 @@ The value of `tex-command' specifies the command to use to run TeX."
   (let* ((zap-directory
           (file-name-as-directory (expand-file-name tex-directory)))
          (tex-out-file (expand-file-name (concat tex-zap-file ".tex")
-                                        zap-directory)))
+                                        zap-directory))
+        (main-file (expand-file-name (tex-main-file)))
+        (ismain (string-equal main-file (buffer-file-name)))
+        already-output)
     ;; Don't delete temp files if we do the same buffer twice in a row.
     (or (eq (current-buffer) tex-last-buffer-texed)
        (tex-delete-last-temp-files t))
-    ;; Write the new temp file.
-    (save-excursion
-      (save-restriction
-       (widen)
-       (goto-char (point-min))
-       (forward-line 100)
-       (let ((search-end (point))
-             (default-directory zap-directory)
-             (already-output 0))
-         (goto-char (point-min))
-
-          ;; Maybe copy first line, such as `\input texinfo', to temp file.
-         (and tex-first-line-header-regexp
-              (looking-at tex-first-line-header-regexp)
-              (write-region (point)
-                            (progn (forward-line 1)
-                                   (setq already-output (point)))
-                            tex-out-file nil nil))
-
-         ;; Write out the header, if there is one,
-         ;; and any of the specified region which extends before it.
-         ;; But don't repeat anything already written.
-         (if (re-search-forward tex-start-of-header search-end t)
-             (let (hbeg)
-               (beginning-of-line)
-               (setq hbeg (point))     ;mark beginning of header
-               (if (re-search-forward tex-end-of-header nil t)
-                   (let (hend)
-                     (forward-line 1)
-                     (setq hend (point)) ;mark end of header
-                     (write-region (max (min hbeg beg) already-output)
-                                   hend
-                                   tex-out-file
-                                   (not (zerop already-output)) nil)
-                     (setq already-output hend)))))
-
-         ;; Write out the specified region
-         ;; (but don't repeat anything already written).
-         (write-region (max beg already-output) end
-                       tex-out-file
-                       (not (zerop already-output)) nil))
-       ;; Write the trailer, if any.
-       ;; Precede it with a newline to make sure it
-       ;; is not hidden in a comment.
-       (if tex-trailer
-           (write-region (concat "\n" tex-trailer) nil
-                         tex-out-file t nil))))
+    (let ((default-directory zap-directory)) ; why?
+      ;; We assume the header is fully contained in tex-main-file.
+      ;; We use f-f-ns so we get prompted about any changes on disk.
+      (with-current-buffer (find-file-noselect main-file)
+       (setq already-output (tex-region-header tex-out-file
+                                               (and ismain beg))))
+      ;; Write out the specified region (but don't repeat anything
+      ;; already written in the header).
+      (write-region (if ismain
+                       (max beg already-output)
+                     beg)
+                   end tex-out-file (not (zerop already-output)))
+      ;; Write the trailer, if any.
+      ;; Precede it with a newline to make sure it
+      ;; is not hidden in a comment.
+      (if tex-trailer
+         (write-region (concat "\n" tex-trailer) nil
+                       tex-out-file t)))
     ;; Record the file name to be deleted afterward.
     (setq tex-last-temp-file tex-out-file)
     ;; Use a relative file name here because (1) the proper dir
@@ -2185,6 +2257,52 @@ The value of `tex-command' specifies the command to use to run TeX."
     (tex-start-tex tex-command (concat tex-zap-file ".tex") zap-directory)
     (setq tex-print-file tex-out-file)))
 
+(defun tex-region-header (file &optional beg)
+  "If there is a TeX header in the current buffer, write it to FILE.
+Return point at the end of the region so written, or zero.  If
+the optional buffer position BEG is specified, then the region
+written out starts at BEG, if this lies before the start of the header.
+
+If the first line matches `tex-first-line-header-regexp', it is
+also written out.  The variables `tex-start-of-header' and
+`tex-end-of-header' are used to locate the header.  Note that the
+start of the header is required to be within the first 100 lines."
+  (save-excursion
+    (save-restriction
+      (widen)
+      (goto-char (point-min))
+      (let ((search-end (save-excursion
+                         (forward-line 100)
+                         (point)))
+           (already-output 0)
+           hbeg hend)
+       ;; Maybe copy first line, such as `\input texinfo', to temp file.
+       (and tex-first-line-header-regexp
+            (looking-at tex-first-line-header-regexp)
+            (write-region (point)
+                          (progn (forward-line 1)
+                                 (setq already-output (point)))
+                          file))
+       ;; Write out the header, if there is one, and any of the
+       ;; specified region which extends before it.  But don't repeat
+       ;; anything already written.
+       (and tex-start-of-header
+            (re-search-forward tex-start-of-header search-end t)
+            (progn
+              (beginning-of-line)
+              (setq hbeg (point))      ; mark beginning of header
+              (when (re-search-forward tex-end-of-header nil t)
+                (forward-line 1)
+                (setq hend (point))    ; mark end of header
+                (write-region
+                 (max (if beg
+                          (min hbeg beg)
+                        hbeg)
+                      already-output)
+                 hend file (not (zerop already-output)))
+                (setq already-output hend))))
+       already-output))))
+
 (defun tex-buffer ()
   "Run TeX on current buffer.  See \\[tex-region] for more information.
 Does not save the buffer, so it's useful for trying experimental versions.