4

I currently use emacs for making and editing LaTeX documents. When compiling, I use an external program to compile into pdf. Right now, with the following code in my .emacs file, emacs will start compiling the document into a pdf whenever I save the file.

(defun auto-compile-latex ()
  (save-window-excursion
    (async-shell-command (format "cd %s; scons -u" default-directory))))
(add-hook 'LaTeX-mode-hook '(lambda ()
     (add-hook 'after-save-hook 'auto-compile-latex nil 'make-it-local)))

I prefer this over M-x compile, because I am more in the habit of saving, and it launches in the background, allowing me to continue working. However, I do not get a prominent notification when the compilation process finishes.

Ideally, I would want to run the following function whenever a compilation process finishes.

(defun latex-compilation-status (exit-code)
  (if (/= exit-code 0)
      (setq mode-name (propertize mode-name 'face 'font-lock-warning-face))
    (setq mode-name (propertize mode-name 'face 'mode-line-highlight))))

That way, I can have the color in the mode line automatically change depending on whether the compilation was successful or not. However, looking through the emacs documentation, I have not found any mention of a hook that gets run after async-shell-command completes. I know that there is the message in the minibuffer stating the exit status of the subprocess, but if I am typing at the time, it is often hard to notice.

Alternatively, I could wait for the shell command to complete, then change the color immediately. However, this then makes the entirety of emacs freeze while compiling, which is not desired.

How would I go about having this indication applied at the end of the compilation, without having emacs freeze during the process?

Eldritch Cheese
  • 1,177
  • 11
  • 21

4 Answers4

1

You should try the command async-start from https://github.com/jwiegley/emacs-async

Nicolas Dudebout
  • 9,172
  • 2
  • 34
  • 43
1

Thank you. I ended up getting it using a modified version of lawlist's code. This now has changes the color when starting to compile, then changes it again to indicate success or failure.

;Automatically compile any latex documents when saved.
(defun auto-compile-latex ()
  (setq mode-name (propertize mode-name 'face 'font-lock-string-face))
  (set-process-sentinel
   (start-process-shell-command "latex-compile" "latex-compile"
                                (format "cd %s; scons -u" default-directory))
   'latex-compile-sentinel))
;Change the color after compilation.  Still need to find the right hook to add it to.
(defun latex-compile-sentinel (process event)
  (if (string-equal event "finished\n")
      (setq mode-name (propertize mode-name 'face 'mode-line-highlight))
    (setq mode-name (propertize mode-name 'face 'font-lock-warning-face))))
;Hooks for latex-mode
(add-hook 'LaTeX-mode-hook '(lambda ()
   (add-hook 'after-save-hook 'auto-compile-latex nil 'make-it-local)))

As an aside, the emacs-async package did not work for this use. I assume that this is because emacs-async starts the secondary functions in a separate process, with some of the variables not propagated back to the parent process.

Eldritch Cheese
  • 1,177
  • 11
  • 21
  • 1
    The `if` part can be simplified as follows: `(setq mode-name (propertize mode-name 'face (if (equal event "finished\n") 'mode-line-highlight 'font-lock-warning-face))`. – Stefan Sep 16 '13 at 02:37
0

Here is another option using set-process-sentinel based upon a prior helpful answer by Francesco. Please do be careful, however, about using a let binding to define a start-process (which [in my lay opinion] is a definite "no no") because that would cause the process to begin immediately before its time. [FYI: Nicolas has gotten me out of quite a few jams in the past, so please be sure to take a good hard look at his solution also.]

Emacs: if latexmk finishes okay, then show pdf, else display errors

You would just add your function to the last part of the function dealing with success -- i.e., (when (= 0 (process-exit-status p)) . . .:

(defun latexmk ()
  ".latexmkrc contains the following entries (WITHOUT the four backslashes):
  $pdflatex = 'pdflatex -file-line-error -synctex=1 %O %S && (cp \"%D\" \"%R.pdf\")';
  $pdf_mode = 1;
  $out_dir = '/tmp';"
(interactive)
  (setq tex-file buffer-file-name)
  (setq pdf-file (concat "/tmp/" (car (split-string
    (file-name-nondirectory buffer-file-name) "\\.")) ".pdf"))
  (setq line (format "%d" (line-number-at-pos)))
  (setq skim "/Applications/Skim.app/Contents/SharedSupport/displayline")
  (setq tex-output (concat "*" (file-name-nondirectory buffer-file-name) "*") )
  (setq latexmk "/usr/local/texlive/2012/texmf-dist/scripts/latexmk/latexmk.pl")
  (setq latexmkrc "/Users/HOME/.0.data/.0.emacs/.latexmkrc")
  (if (buffer-modified-p)
    (save-buffer))
  (delete-other-windows)
  (set-window-buffer (split-window-horizontally) (get-buffer-create tex-output))
  (with-current-buffer tex-output (erase-buffer))
  (set-process-sentinel 
    (start-process "compile" tex-output latexmk "-r" latexmkrc tex-file)
    (lambda (p e) (when (= 0 (process-exit-status p))
      (start-process "displayline" nil skim "-b" line pdf-file tex-file)
      (switch-to-buffer (get-file-buffer tex-file))
      (if (get-buffer-process (get-buffer tex-output))
        (process-kill-without-query (get-buffer-process (get-buffer tex-output))))
      (kill-buffer tex-output)
      (delete-other-windows)))))
Community
  • 1
  • 1
lawlist
  • 13,099
  • 3
  • 49
  • 158
  • That's a lot of global variables. What about using one name-spaced variable to hold all the data, and extracting the values from that at the other end? – phils Sep 14 '13 at 05:12
  • I'd love to learn a better way of doing things, if you could please be so kind as to give an example or two. `let` bindings cannot be defined *inside* of the `set-process-sentinel`, because they are rejected when trying to run the function. Any `let` bindings defined *prior to* `set-process-sentinel` cannot be used inside the `lambda (p e)` function, since *prior* `let` bindings are not recognized within a `lambda` sub-function. `start-process` cannot be `let` bound, since it runs immediately as soon as it is bound. My goal was to have everything built into just one neat/clean function. – lawlist Sep 14 '13 at 05:57
  • `start process` and `concat` do not mix, even with a space separator -- e.g., `(concat arg-1 " " arg-2)` is rejected by `start-process`, so each argument needs to be separately spelled out. – lawlist Sep 14 '13 at 06:07
  • 1
    Regarding the variables, I mean instead of `setq`ing lots of global vars with names like `line`, you only need one global var, which could be an alist: `(defvar latexmk-data) (setq latexmk-data \`((tex-file . ,buffer-file-name) (line . ,(format "%d" (line-number-at-pos))))) (do-something-with (cdr (assq 'tex-file latexmk-data)))` or you could use destructuring-bind: `(defvar latexmk-data) (let ((tex-file buffer-file-name) (line (format "%d" (line-number-at-pos)))) (setq latexmk-data (list tex-file line))) (destructuring-bind (tex-file line) latexmk-data (do-something-with tex-file) ...)` – phils Sep 14 '13 at 09:10
  • Thank you very much for sharing two alternative methods of structuring the variables / bindings -- greatly appreciated ! :) – lawlist Sep 14 '13 at 15:15
0

You could also arrange for your auto-compile-latex to run compile.

Stefan
  • 27,908
  • 4
  • 53
  • 82