21

RStudio provides a one-button way for produce PDF file from LaTeX+R source with Knitr. It looks great for doing reproducible research. And I am trying to configure my Emacs for:

  • at left buffer LaTeX+R code in Knitr way;
  • at right buffer PDF output preview;
  • one key combination for compiling.

If it is possible: how should I setup this, please?

(ESS works fine, but I don't know how to set up Knitr-way and one-button-for-compiling.)

drobnbobn
  • 575
  • 4
  • 15
  • I am pretty sure there's a better way to do that, but I have a short Elisp function with a key binding running `rmarkdown::render` (via `shell-command`) on the the current `buffer-file-name`, which will update the pdf in the other window. – daroczig Oct 01 '15 at 20:45
  • 1
    @daroczig will that work for LaTeX, or just markdown? – Tyler Oct 01 '15 at 21:23
  • @daroczig: in my answer I possibly tried to do just this for Rnw files (LaTeX+R). As regard Rmd files (Rmd+R), which is simpler, please start a separate post. – antonio Oct 01 '16 at 18:55
  • 1
    I couldn' get a proper setup. I have to knit first with polymode-weave. Then select exporter and everything to create the .tex-file. From there I have to run latex. I tried to adapt the Script above, but my elisp is just not there yet. What I want is to knit the .Rnw-file, create the pdf (pdflatex) and view it. I set my system up, that if I type "C-c C-a" (Tex-Command-run-all) in the latex file, the pdf is built and viewed, but I cannot call that function on the tex-file from my_knitr(). Help would be appreciated. (defun my_knitr () "Run Knitr in R-Poly mode and create and view pdf" (interacti – Krisselack Jan 31 '19 at 13:10
  • @Krisselack it looks like the features I describe in my answer below have been removed from ESS. I'll have to update it to reflect the new approach they use – Tyler Jan 31 '19 at 18:59
  • @Krisselack please try the updated answer below and see if that works for you. – Tyler Mar 12 '19 at 17:43

2 Answers2

15

UPDATE

As of ESS 19.04, the ess-noweb and ess-swv libraries are obsolete:

Consequently, my original answer (below) no longer applies. The features these libraries used to provide are now provided by polymode, and the configuration is simpler. To get minimal support, all you need is to install the ess, polymode and poly-R packages (from MELPA, or from source if that's how you roll).

That's it! Now if you open a Rnw file, you should see the PM-Rnw flag in the modeline, and there will be a Polymode menu at the top. You can weave your file into a .tex file via M-n w (or the polymode menu), and export it to .pdf via M-n e (or the menu). You'll be prompted for an exporter the first time you do this; I just picked knitr.

NOTE: exporting (M-n e) automatically runs your code, generates the pdf and displays it, all in one go. I wasn't able to get that "one-click" behaviour with the old version described below.

The generated files will have the word -woven and -exported appended. If you don't like this, you can customize the options polymode-weaver-output-file-format and polymode-exporter-output-file-format.

The process is similar for RMarkdown files (.Rmd).

Full details are provided in the polymode manual

Original Answer (obsolete after ESS 19.04)

Three variables need to be set:

  1. ess-swv-pdflatex-commands, in customization group ess-sweave needs to have "pdflatex" as the first command. i.e., it should look something like: ("pdflatex" "texi2pdf" "make")
  2. ess-swv-processor, in customization group ess-R, should be the value "knitr"
  3. ess-pdf-viewer-pref in the customization group ess to "emacsclient". This assumes you are running the emacs server, or emacs is running in --daemon mode. You should also be using pdf-tools if at all possible, as it is far preferable to the built-in Emacs pdf viewer.

I use a hook to add two keybindings for calling BibTeX and texi2pdf:

(add-hook 'ess-noweb-mode-hook 'my-noweb-hook)

(defun my-noweb-hook ()
  (define-key ess-noweb-mode-prefix-map "b"
    #'(lambda () (interactive) (TeX-command "BibTeX" 'TeX-master-file)))
  (define-key ess-noweb-mode-prefix-map "P"
    #'(lambda () (interactive)
        (ess-swv-PDF "texi2pdf"))))

Once this is done, M-n s will knit your document, M-n b will bibtex it, and M-n P will process it with pdflatex.

Note that there's no simple way for Emacs to know when knitting is completed, so you can't set this up to knit & latex in one step; you have to manually trigger pdflatex after you've seen the knitting has finished.

Given the multiple steps here - Rnw -> Latex -> PDF, I don't think you can get Synctex to keep your pdf and Rnw files to scroll together, but I'd be thrilled to be proved wrong.

As for window arrangement, I can never get them to stay where I want them. So to compensate I have become quite adept at shuffling windows and buffers as I need them ;)

Yihui posted a short video on the knitr site demonstrating some of this.

Tyler
  • 21,719
  • 1
  • 52
  • 92
  • I've done my best to extract all the necessary config, and only the necessary config, from my .emacs. I may have missed something, it's kind of hairy in there. – Tyler Oct 01 '15 at 21:22
  • it made possible for me to compile knitr documents. But still try to find one-key combo (for Rnw -> PDF). Hope it is possible, since Rstudio have this. – drobnbobn Nov 07 '15 at 21:53
  • @Tyler: Thank you, your solution works like a charm ootb (even without editing the config)! Packages: ess, polymode, poly-r – Krisselack Mar 13 '19 at 11:18
5

This is an all-in-one solution. It will create and display a PDF from an Rnw.
Specifically it will:

  1. Save the Rnw buffer and knit it,
  2. Apply a given LaTeX engine to the resulting TeX file,
  3. Identify the BibTeX engine executable (e.g. biber, bibtex8),
  4. Run the BibTeX engine on the TeX file if the bib file is newer than TeX file,
  5. Run LaTeX again, 6 Open resulting PDF in the designated viewer.

The procedure tries to exit with informative messages if one of the above steps above fails.
An R instance will be opened, if necessary, or the current one is used to show knitting process.
LaTeX output is sent to "TeX-output" buffer, which also is popped in case of compilation error.

Usage

Meta-x knit-me to create and view the PDF.
Meta-x knit-me-clear to remove intermediate LaTeX files and knit-me.

The bibliography requires the "biblatex" package, i.e.:

\usepackage[
    options...      
    backend=ENGINE,
]{biblatex}
\addbibresource{foo.bib}

The name of the bib-engine (e.g. bibtex, biber) is obtained parsing the backend keyword.
\addbibresource command is parsed to obtain the bibliography file: if foo.bib is newer than the TeX file the bib engine is run. In this regard, only the first \addbibresource command is taken into account if there are many.

Customise

To actually view the PDF, set the viewer executable path with:

(setq pdf-viewer "path/to/pdf-viewer")

Possibly use a viewer like SumatraPDF, which automatically updates the PDF when recompiled and does not block the opened file preventing new compilations.

The default LaTeX engine is pdflatex (assumed in the current path). Customise with:

(setq latex-engine "newengine"
      latex-args   "--option-1 --option-2")

Of course you might want to bind knit-me and knit-me-clear to some convenient keys.

Notes

Tested in Windows MiKTeX, with biber and bibtex8 backends and GNU Emacs 25.1.1.

Elisp code

;; (require 'ess-site) ; assumed in your init file

(defun knit-me-clear () 
  "Delete intermediate LaTeX files and run `knkt-me'.
These are based on extensions .aux, .blg, .out, .run.xml, .bbl, .log, -blx.bib"

  (interactive)
  (check-Rnw)
  (let
      ((file)
       (stem (file-name-sans-extension (buffer-file-name))))
    (dolist (elt
         (list ".aux" ".blg" ".out" ".run.xml" ".bbl" ".log" "-blx.bib"))
      (setq file (concat stem elt))
      (if (file-exists-p file) (delete-file file))))  
  (knit-me))


(defun knit-me () 
  "Knit->LaTeX-engine->bibtex-engine->LaTeX-engine->View.
Default LaTeX engine is \"pdflatex\" and can be customised with `latex-engine';
default LaTeX arguments are set to nil and can be customised with `latex-args';
default PDF viewer is set to nil and can be customised with `pdf-viewer'.
Bibliography must be set via \"biblatex\" LaTeX package.
Bibliography engine is obtained from \"backend\" option in \"biblatex\" package.
A reference  LaTeX bib file is obtained from the first LaTeX command \"\addbibresource{foo.bib}\".
The biblatex-engine is run if the bib file is newer of the TeX file. 
If there are multiple \"\addbibresource\" only the first will be used to decide whether to run the biblatex-engine."

  (interactive)

  ;; Default values
  (defvar pdf-viewer nil)
  (defvar latex-engine "pdflatex")
  (defvar latex-args nil)

  (check-Rnw)

  ;;If 1 R-proc found, associate it with buffer;
  ;;if many found, ask to choose one; if none found, launch and associate
  (ess-force-buffer-current "Process to use: ")

  ;;Save Rnw buffer
  (save-buffer)


  (let*
      (;;Set file paths
       (pathstem (file-name-sans-extension (buffer-file-name)))
       (namestem (file-name-nondirectory pathstem))
       (cur-dir     (file-name-directory pathstem))
       (rnw-name    (concat namestem ".Rnw"))
       (tex-name    (concat namestem ".tex"))

       ;;Create LaTeX commmand
       (latex-args (concat latex-args " " namestem))
       (latex-cmd (concat latex-engine " " latex-args))

       ;;Create knit commmand
       (knit-cmd (format "require(knitr); setwd('%s'); knit('%s')"  cur-dir rnw-name))

       ;;Get R buffer proc
       (r-proc (ess-get-process))
       (r-buf (ess-get-process-buffer))

       ;;TeX executable process and bibtex engine/file
       (tex-proc)
       (tex-buf)
       (bibfile (bib-getfile))
       (bibengine (bib-getengine))
       (bibfile-updated
    (file-newer-than-file-p
     (concat cur-dir (file-name-nondirectory bibfile) ".bib") (concat pathstem ".tex")))


       ;;Command success
       (success nil)
       (error-msg "")
       )


    (setq default-directory cur-dir)

    ;; Exit on error
    (catch 'exit-func

      ;;Check bibtex file and engine
      (when (not bibfile)
    (setq error-msg (bib-getfile t))
    (throw 'exit-func nil))     
      (when (not bibengine)
    (setq error-msg (bib-getengine t))      
    (throw 'exit-func nil))

      ;; Biber wants .bib
      (let ((fail (and (string= bibengine "biber")
              (string= (file-name-nondirectory bibfile) (file-name-base bibfile)))))
    (setq success (not fail)))
      (when (not success)
    (setq error-msg
          (format "biber wants \\addbibresource{%s%s}" (file-name-base bibfile) ".bib"))
    (throw 'exit-func nil))


      ;; Knitting
      (switch-to-buffer-other-window r-buf)
      (message knit-cmd)
      (ess-eval-linewise knit-cmd nil t nil t) 
      ;; Foll. 3 lines are an alternative to ess-eval
      ;; (inferior-ess-mark-as-busy r-proc)  ; knit immediately after this line       
      ;; (process-send-string r-proc (format "cat(\"%s\");%s\n" knit-cmd knit-cmd)) ; real 
      ;; (ess-wait-for-process r-proc nil)

      ;; Check for knitting results
      (with-current-buffer r-buf
    ;; Parse last 3 lines
    (let ((beg) (end) (out))
      (goto-char (point-max))
      (setq end (point))
      (forward-line -3) 
      (setq beg (point))
      (setq out (buffer-substring-no-properties beg end))

      ;; Knitting successful?
      (setq success "output file: %s\n\n[1] \"%s\"\n> ")
      (setq success (string= (format success tex-name tex-name) out))))

      (when (not success)
    (setq error-msg (concat "Unable to knit " rnw-name))
    (throw 'exit-func nil))

      ;; First LaTeXing
      (setq tex-buf (get-buffer-create "TeX-output")) ; Create output buffer or use existing
      (with-current-buffer tex-buf                   
    (buffer-disable-undo)
    (erase-buffer))
      (message "1st latex ...")
      (send-r-mess (format "Starting LaTeX (see \"%s\")" (buffer-name tex-buf)))      
      (send-r-mess latex-cmd)
      (setq success (= 0 (call-process latex-engine nil tex-buf t latex-args)))
      (goto-char (point-max))

      ;; Check 1st LaTeX results
      (when (not success)
    (setq error-msg (concat "Unable to LaTeX " namestem))
    (switch-to-buffer-other-window tex-buf) 
    (other-window 1)
    (throw 'exit-func nil))

      ;; Run bibtex engine
      (when bibfile-updated  
    (message "biblatex ...")
    (send-r-mess (concat bibengine " "  namestem))
    (setq success (= 0 (call-process bibengine nil tex-buf t namestem)))
    (goto-char (point-max))

    ;; Check bibtex results
    (when (not success)
      (setq error-msg (concat "Unable to " bibengine " " namestem))
      (switch-to-buffer-other-window tex-buf) 
      (other-window 1)
      (throw 'exit-func nil)))

      ;; Second LaTeXing
      (message "2nd latex ...")
      (send-r-mess latex-cmd)
      (setq success (= 0 (call-process latex-engine nil tex-buf t latex-args)))
      (goto-char (point-max))

      ;; Check 2nd LaTeX results
      (when (not success)
    (setq error-msg (concat "Unable to LaTeX " pathstem))
    (switch-to-buffer-other-window tex-buf) 
    (other-window 1)
    (throw 'exit-func nil))

      ;; View   
      (if (not pdf-viewer) (throw 'exit-func nil))
      (send-r-mess  "...and now the viewer")
      (goto-char (point-max))
      (setq success (file-exists-p pdf-viewer))
      (when (not success)
    (setq error-msg (concat "Can\\'t find executable " pdf-viewer))
    (throw 'exit-func nil))

      ;; If you need viewer console output, use "(start-process "pdf-viewer" tex-buf ...";
      ;; but you block tex-buf buffer till the viewer is open
      (start-process "pdf-viewer" nil pdf-viewer (concat namestem ".pdf")))

    (if success
    (if bibfile-updated (message (concat "Updated to "  (file-name-nondirectory bibfile))))
      (message error-msg)
      (send-r-mess error-msg))))

(defun bib-getfile(&optional show-messages)
  "Check if 'addbibresource' command and related file exist. 
If found, return .bib file full path, else:
if SHOW-MESSAGES is nil return nil, if SHOW-MESSAGES is non-nil return related error."
  (save-excursion
    (goto-char (point-min))
    (re-search-forward "\\\\addbibresource{\\(.+\\)}" nil t))
  (let ((fmatch (match-string-no-properties 1)) 
    (success nil)
    mess)    
    (cond 
     ((not fmatch) (setq mess "Missing \\addbibresource command."))
     ((not (file-exists-p (concat (file-name-sans-extension fmatch) ".bib")))
      (setq mess (concat "Missing file: " fmatch ".bib")))
     ;; if no problem, sucess=message=bib-file-path
     (t (setq mess (concat (file-name-directory (buffer-file-name)) fmatch)
          success mess)))

    (if show-messages mess success)))

(defun bib-getengine(&optional show-messages)
  "Find biblatex engine.
If found,  engine name, else:
if SHOW-MESSAGES is nil return nil, if SHOW-MESSAGES is non-nil return related error."
  (save-excursion
    (goto-char (point-min))
    (let ((pack (re-search-forward "\\\\usepackage *\\(\\[[^]]*\\)\\] *{ *biblatex *}" nil t))
      (bend nil)
      mess)

      (when pack (setq pack (match-string-no-properties 1)))
      (when (and pack
         (string-match "[^[:alpha:]]+backend *= *\\([^, \n]+\\)" pack))
    (setq bend (match-string 1 pack)))
      (cond 
       ((not pack) (setq mess "Missing biblatex package command."))
       ((not bend) (setq mess "Missing biblatex backend."))
       ;; if no problem, sucess=message=bib-file-path
       (t (setq mess bend)))
      (if show-messages mess bend))))


(defun send-r-mess (mess)
  "Just send MESS at the end of R console buffer"
  (process-send-string (ess-get-process)
             (format "cat('%s\\n')\n" mess)))

(defun check-Rnw ()
  "Give error if `ess-dialect' is not \"R\""
  (if (not (string= "R" ess-dialect))
      (error "Not an Rnw buffer")))
antonio
  • 1,762
  • 12
  • 24