如何在Emacs中设置Knitr工作流程?


18

RStudio提供了一种一键式方法,可以使用Knitr从LaTeX + R源生成PDF文件。看起来很适合进行可重复的研究。我正在尝试将我的Emacs配置为:

  • 以Knitr方式在左侧缓冲区LaTeX + R代码;
  • 在右侧缓冲区PDF输出预览中;
  • 一种组合键进行编译。

如果可能的话:请问我该如何设置?

(ESS可以正常工作,但是我不知道如何设置Knitr-way和一键式编译。)


我很确定有一个更好的方法,但是我有一个简短的Elisp函数,在current上运行了rmarkdown::render(通过shell-command)键绑定buffer-file-name,这将在另一个窗口中更新pdf。
daroczig 2015年

1
@daroczig可以用于LaTeX,或仅用于markdown吗?
泰勒(Tyler)

@daroczig:在我的回答中,我可能试图对Rnw文件(LaTeX + R)做到这一点。至于Rmd文件(Rmd + R),它更简单,请另发一篇文章。
antonio

1
我无法进行正确的设置。我必须先用多模编织进行编织。然后选择导出器,然后选择所有内容以创建.tex文件。从那里我必须运行乳胶。我试图改写上面的脚本,但我的elisp尚不存在。我想要的是编织.Rnw文件,创建pdf(pdflatex)并查看它。我设置了系统,如果我在乳胶文件中键入“ Cc Ca”(Tex-Command-run-all),则会生成并查看pdf,但是我无法通过my_knitr()在tex文件上调用该函数。 。帮助将不胜感激。(defun my_knitr()“在R-Poly模式下运行Knitr并创建和查看pdf”(interacti
Krisselack

@Krisselack似乎我在下面的答案中描述的功能已从ESS中删除。我将不得不对其进行更新以反映他们使用的新方法
泰勒(Tyler)

Answers:


12

更新

从ESS 19.04开始,ess-nowebess-swv库已过时:

因此,我的原始答案(如下)不再适用。这些库提供的功能现在由多模式提供,并且配置更简单。为了获得最少的支持,您所需要做的就是安装和软件包(从MELPA或从源代码开始安装ess,如果您这样做)。polymodepoly-R

而已!现在,如果您打开一个Rnw文件,则应该PM-Rnw在modeline中看到该标志,并且Polymode顶部将有一个菜单。你可以编织你的文件转换成一个.tex通过文件M-n w(或polymode菜单),并将其导出到.pdf通过M-n e(或菜单)。首次执行此操作时,系统会提示您选择导出器。我刚刚选了knitr

注意:导出(M-n e)会自动运行您的代码,生成pdf并将其显示出来。我无法使用下面描述的旧版本来获得“一键式”行为。

生成的文件将带有单词-woven-exported。如果您不喜欢这样,可以自定义选项polymode-weaver-output-file-formatpolymode-exporter-output-file-format

RMarkdown文件(.Rmd)的过程与此类似。

多模式手册中提供了完整的详细信息

原始答案(在ESS 19.04之后过时)

需要设置三个变量:

  1. ess-swv-pdflatex-commands,自定义组ess-sweave中的第一个命令需要使用“ pdflatex”。即,它应该类似于:("pdflatex" "texi2pdf" "make")
  2. ess-swv-processor定制组中的ess-R,应为值"knitr"
  3. ess-pdf-viewer-pref在自定义组ess"emacsclient"。假设您正在运行emacs服务器,或者emacs以--daemon模式运行。您也应该尽可能使用pdf工具,因为它比内置的Emacs pdf查看器更可取。

我使用钩子来添加两个绑定来调用BibTeX和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"))))

完成此操作后,M-n s将编织您的文档,M-n b将其bibtex,并M-n P使用pdflatex处理它。

请注意,没有简单的方法让Emacs知道何时完成编织,因此您不能一步一步将其设置为编织和乳胶。您必须在完成编织手动触发pdflatex 。

考虑到此处的多个步骤-Rnw-> Latex-> PDF,我认为您无法让Synctex来保持pdf和Rnw文件一起滚动,但是我很高兴被证明是错误的。

至于窗户的布置,我永远无法让他们呆在我想要的地方。因此,为了弥补这一点,我变得非常熟练,可以根据需要改组窗口和缓冲区;)

Yihui 在针织网站上发布了一个简短的视频,演示了其中的一些内容。


我已尽力从.emacs中提取所有必要的配置,并且仅提取必要的配置。我可能错过了一些东西,那里有些毛茸茸。
泰勒(Tyler)

这使我能够编辑针织文件。但是仍然尝试找到一键组合(对于Rnw-> PDF)。希望有可能,因为Rstudio拥有此功能。
drobnbobn 2015年

@Tyler:谢谢您,您的解决方案就像一个超级插件(即使不编辑配置)也一样!包裹:ess,polymode,poly-r
Krisselack

5

这是一个多合一的解决方案。它将从Rnw创建并显示PDF
具体来说,它将:

  1. 保存Rnw缓冲区并进行编织,
  2. 将给定的LaTeX引擎应用于生成的TeX文件,
  3. 识别BibTeX引擎可执行文件(例如biber,bibtex8),
  4. 如果bib文件比TeX文件新,请在TeX文件上运行BibTeX引擎,
  5. 再次运行LaTeX,6在指定的查看器中打开生成的PDF。

如果上述步骤之一失败,该过程将尝试退出并提供提示性消息。
如有必要,将打开一个R实例,或者使用当前实例显示编织过程。
LaTeX输出将发送到“ TeX-output”缓冲区,如果发生编译错误,该缓冲区也会弹出。

用法

Meta- x knit-me创建和查看PDF。
Meta- x knit-me-clear删除中间的LaTeX文件和knit-me

参考书目需要“ biblatex”软件包,即:

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

通过解析关键字bibtexbiber获得了bib引擎的名称(例如)backend
\addbibresource解析命令以获取书目文件:如果foo.bib比TeX文件新,则运行bib引擎。在这方面,\addbibresource如果有很多命令,则仅考虑第一个命令。

定制

要实际查看PDF,请使用以下命令设置查看器的可执行路径:

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

可能使用诸如SumatraPDF的查看器,该查看器在重新编译时会自动更新PDF,并且不会阻止打开的文件阻止新的编译。

默认的LaTeX引擎为pdflatex(假定在当前路径中)。自定义:

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

当然,您可能需要绑定knit-me并绑定knit-me-clear一些方便的键。

笔记

在Windows MiKTeX(带有biberbibtex8后端)以及GNU Emacs 25.1.1中进行了测试。

Elisp代码

;; (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")))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.