changeset 51005:aeb075459c1d

(tex-compile-history, tex-input-files-re) (tex-use-reftex, tex-compile-commands): New vars. (tex-summarize-command, tex-uptodate-p, tex-executable-exists-p) (tex-command-executable, tex-command-active-p, tex-compile-default) New functions. (tex-compile): New command. (tex-mode-map): Bind it to C-c C-c.
author Stefan Monnier <monnier@iro.umontreal.ca>
date Thu, 15 May 2003 01:29:53 +0000
parents fd6c7e5b080c
children ea8655d397d6
files lisp/textmodes/tex-mode.el
diffstat 1 files changed, 207 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/lisp/textmodes/tex-mode.el	Thu May 15 00:02:17 2003 +0000
+++ b/lisp/textmodes/tex-mode.el	Thu May 15 01:29:53 2003 +0000
@@ -693,6 +693,7 @@
     (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)
@@ -1524,6 +1525,53 @@
 
 (add-hook 'kill-emacs-hook 'tex-delete-last-temp-files)
 
+;;
+;; Machinery to guess the command that the user wants to execute.
+;;
+
+(defvar tex-compile-history nil)
+
+(defvar tex-input-files-re
+  (eval-when-compile
+    (concat "\\." (regexp-opt '("tex" "texi" "texinfo"
+				"bbl" "ind" "sty" "cls") t)
+	    ;; Include files with no dots (for directories).
+	    "\\'\\|\\`[^.]+\\'")))
+
+(defcustom tex-use-reftex t
+  "If non-nil, use RefTeX's list of files to determine what command to use."
+  :type 'boolean)
+
+(defvar tex-compile-commands
+  '(((concat "pdf" tex-command
+	     " " (shell-quote-argument tex-start-commands) " %f")
+     t "%r.pdf")
+    ((concat tex-command
+	     " " (shell-quote-argument tex-start-commands) " %f")
+     t "%r.dvi")
+    ("xdvi %r &" "%r.dvi")
+    ("advi %r &" "%r.dvi")
+    ("bibtex %r" "%r.aux" "%r.bbl")
+    ("makeindex %r" "%r.idx" "%r.ind")
+    ("texindex %r.??")
+    ("dvipdfm %r" "%r.dvi" "%r.pdf")
+    ("dvipdf %r" "%r.dvi" "%r.pdf")
+    ("dvips %r" "%r.dvi" "%r.ps")
+    ("gv %r.ps &" "%r.ps")
+    ("gv %r.pdf &" "%r.pdf")
+    ("xpdf %r.pdf &" "%r.pdf")
+    ("lpr %r.ps" "%r.ps"))
+  "List of commands for `tex-compile'.
+Each element should be of the form (FORMAT IN OUT) where
+FORMAT is an expression that evaluates to a string that can contain
+  - `%r' the main file name without extension.
+  - `%f' the main file name.
+IN can be either a string (with the same % escapes in it) indicating
+  the name of the input file, or t to indicate that the input is all
+  the TeX files of the document, or nil if we don't know.
+OUT describes the output file and is either a %-escaped string
+  or nil to indicate that there is no output file.")
+
 (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
@@ -1574,6 +1622,165 @@
 			    buffer-file-name)))))))
     (if (file-exists-p file) file (concat file ".tex"))))
 
+(defun tex-summarize-command (cmd)
+  (if (not (stringp cmd)) ""
+    (mapconcat 'identity
+	       (mapcar (lambda (s) (car (split-string s)))
+		       (split-string cmd "\\s-*\\(?:;\\|&&\\)\\s-*"))
+	       "&")))
+
+(defun tex-uptodate-p (file)
+  "Return non-nil if FILE is not uptodate w.r.t the document source files.
+FILE is typically the output DVI or PDF file."
+  ;; We should check all the files included !!!
+  (and
+   ;; Clearly, the target must exist.
+   (file-exists-p file)
+   ;; And the last run must not have asked for a rerun.
+   ;; FIXME: this should check that the last run was done on the same file.
+   (let ((buf (condition-case nil (tex-shell-buf) (error nil))))
+     (when buf
+       (with-current-buffer buf
+	 (save-excursion
+	   (goto-char (point-max))
+	   (and (re-search-backward
+		 "(see the transcript file for additional information)" nil t)
+		(> (save-excursion
+		     (or (re-search-backward "\\[[0-9]+\\]" nil t)
+			 (point-min)))
+		   (save-excursion
+		     (or (re-search-backward "Rerun" nil t)
+			 (point-min)))))))))
+   ;; And the input files must not have been changed in the meantime.
+   (let ((files (if (and tex-use-reftex
+			 (fboundp 'reftex-scanning-info-available-p)
+			 (reftex-scanning-info-available-p))
+		    (reftex-all-document-files)
+		  (list (file-name-directory (expand-file-name file)))))
+	 (ignored-dirs-re
+	  (concat
+	   (regexp-opt
+	    (delq nil (mapcar (lambda (s) (if (eq (aref s (1- (length s))) ?/)
+					 (substring s 0 (1- (length s)))))
+			      completion-ignored-extensions))
+	    t) "\\'"))
+	 (uptodate t))
+     (while (and files uptodate)
+       (let ((f (pop files)))
+	 (if (file-directory-p f)
+	     (unless (string-match ignored-dirs-re f)
+	       (setq files (nconc
+			    (directory-files f t tex-input-files-re)
+			    files)))
+	   (when (file-newer-than-file-p f file)
+	     (setq uptodate nil)))))
+     uptodate)))
+    
+
+(autoload 'format-spec "format-spec")
+
+(defvar tex-executable-cache nil)
+(defun tex-executable-exists-p (name)
+  "Like `executable-find' but with a cache."
+  (let ((cache (assoc name tex-executable-cache)))
+    (if cache (cdr cache)
+      (let ((executable (executable-find name)))
+	(push (cons name executable) tex-executable-cache)
+	executable))))
+
+(defun tex-command-executable (cmd)
+  (let ((s (if (stringp cmd) cmd (eval (car cmd)))))
+    (substring s 0 (string-match "[ \t]\\|\\'" s))))
+
+(defun tex-command-active-p (cmd fspec)
+  "Return non-nil if the CMD spec might need to be run."
+  (let ((in (nth 1 cmd))
+	(out (nth 2 cmd)))
+    (if (stringp in)
+	(let ((file (format-spec in fspec)))
+	  (when (file-exists-p file)
+	    (or (not out)
+		(file-newer-than-file-p
+		 file (format-spec out fspec)))))
+      (when (and (eq in t) (stringp out))
+	(not (tex-uptodate-p (format-spec out fspec)))))))
+
+(defun tex-compile-default (dir fspec)
+  "Guess a default command in DIR given the format-spec FSPEC."
+  ;; TODO: Learn to do latex+dvips!
+  (let ((cmds nil)
+	(unchanged-in nil))
+    ;; Only consider active commands.
+    (dolist (cmd tex-compile-commands)
+      (when (tex-executable-exists-p (tex-command-executable cmd))
+	(if (tex-command-active-p cmd fspec)
+	    (push cmd cmds)
+	  (push (nth 1 cmd) unchanged-in))))
+    ;; Remove those commands whose input was considered stable for
+    ;; some other command (typically if (t . "%.pdf") is inactive
+    ;; then we're using pdflatex and the fact that the dvi file
+    ;; is inexistent doesn't matter).
+    (let ((tmp nil))
+      (dolist (cmd cmds)
+	(unless (member (nth 1 cmd) unchanged-in)
+	  (push cmd tmp)))
+      (if tmp (setq cmds tmp)))
+    ;; remove commands whose input is not uptodate either.
+    (let ((outs (delq nil (mapcar (lambda (x) (nth 2 x)) cmds))))
+      (dolist (cmd (prog1 cmds (setq cmds nil)))
+	(unless (member (nth 1 cmd) outs)
+	  (push cmd cmds))))
+    ;; 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)
+      (while hist
+	(setq re (concat "\\`"
+			 (regexp-quote (tex-command-executable (pop hist)))
+			 "\\([ \t]\\|\\'\\)"))
+	(dolist (cmd cmds)
+	  (if (string-match re (car cmd))
+	      (setq hist nil cmds (list cmd))))))
+    ;; Substitute and return.
+    (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))
+	  (root (file-name-sans-extension file))
+	  (dir (file-name-directory (expand-file-name file)))
+	  (fspec (list (cons ?r (comint-quote-filename root))
+		       (cons ?f (comint-quote-filename file))))
+	  (default (tex-compile-default dir fspec)))
+     (list dir
+	   (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."