Ejecución asíncrona en org babel

14

¿Existe una buena personalización general de org-babel para que se ejecute de forma asincrónica? Recientemente planeo usar MATLAB a través de org-babel, pero me gustaría hacerlo de manera asíncrona, ya que algunos cálculos llevan tiempo.

No deseo personalizar solo ob-matlab. Esto se debe a que creo que debería hacerse en el nivel del marco en lugar de una aplicación. En otras palabras, una misma modificación debería habilitar la función asíncrona para otras extensiones de idioma, por ejemplo, el lenguaje R.

¿Alguien tiene una buena solución? Hasta ahora he intentado async.el, así como deferred.elmodificar, org-babel-execute-safely-maybeque se puede encontrar en ob-core.eleste momento.

diadochos
fuente
Otra pista es que puede pasar el bloque de babel a la pantalla o tmux.
stardiviner
Nunca lo he implementado, pero parece posible. Gracias.
diadochos
Supongo que estoy aceptando mi propia respuesta ya que no se ha publicado ninguna otra solución durante el último mes.
diadochos

Respuestas:

6

Hasta ahora he descubierto que generar un nuevo proceso de Emacs es una solución.

Aquí está lo que he hecho.

1. Agregue una función para iniciar un proceso externo de emacs.

init.el

(defvar my/async-emacs-repl-org-babel-init-file "~/.emacs.d/org-babel-async-init" "File to load on executing async babel evaluation.")

(defun my/async-emacs-repl--start (process-name init-file)
  "Start a new Emacs process as a REPL server."
  (async-shell-command (concat
                        "TERM=vt200 emacs --batch -nw"
                        " --eval '(load \"" init-file "\")'"
                        " --eval '(while t (print (eval (read))))'"
                        )
                       process-name))

(defun my/async-emacs-repl--org-babel--start-server ()
  "Starts an Emacs process for async org-babel execution."
  (my/async-emacs-repl--start "*org-babel-async*" my/async-emacs-repl-org-babel-init-file))

(defun my/async-emacs-repl--org-babel--start-if-not-exists ()
  "Starts an Emacs process if the process does not exist."
  (if (not (get-buffer-process "*org-babel-async*")) (my/async-emacs-repl--org-babel--start-server)))

(defun my/async-emacs-repl--org-babel--execute--build-command (file-name line-number)
  "Build the command for executing `org-babel-execute-src-block'."
  (concat
   "(progn"
   " (find-file \"" file-name "\")"
   " (revert-buffer t t)"
   " (goto-line " (number-to-string line-number) ")"
   " (org-babel-execute-src-block t)"
   " (save-buffer)"
   ")"
   "\n"))

(defun my/async-emacs-repl--org-babel--execute (process-name file-name line-number)
  "Sends the command to the server to run the code-block the cursor is at."
  (process-send-string
   process-name
   (my/async-emacs-repl--org-babel--execute--build-command file-name line-number)))

(defun my/async-emacs-repl-org-babel-do-execute ()
  "Run org babel execution at point."
  (my/async-emacs-repl--org-babel--execute "*org-babel-async*" (buffer-file-name) (line-number-at-pos)))

(defun my/async-emacs-repl-org-babel-execute ()
  "Run by the user. Executes command. Starts buffer if not exists."
  (interactive)
  (save-buffer)
  (my/async-emacs-repl--org-babel--start-if-not-exists)
  (my/async-emacs-repl-org-babel-do-execute))

2. Agregue un archivo de configuración para cargar en el nuevo proceso de emacs.

La función anterior inicia emacs en el --batchmodo. Por lo tanto, el init.el normal no se cargará.

En su lugar, queremos crear un archivo de configuración más corto (para cargar rutas, etc.).

La ruta a nuestro nuevo archivo de configuración se almacena async-emacs-repl-org-babel-init-fileen el fragmento de arriba.

org-babel-async-init.el

;; 1
(package-initialize)

;; 2
(setq org-confirm-babel-evaluate nil)

;; 3
(let ((my/org-babel-evaluated-languages
       '(emacs-lisp
         ditaa
         python
         ruby
         C
         matlab
         clojure
         sh
         dot
         plantuml)))
  (org-babel-do-load-languages
   'org-babel-load-languages
   (mapcar (lambda (lang)
             (cons lang t))
           my/org-babel-evaluated-languages)))

Aquí nosotros ...

  1. Agregar rutas de paquetes.
  2. Indique a org-mode que no pregunte si debe ejecutar el bloque de código.
  3. Dile a org-babel qué idiomas son necesarios.

Nota 1: sin esta configuración, la evaluación fallará con "No org-babel-execute function for $lang!"

Nota 2: Por supuesto, si lo desea, puede cargar el archivo init.el normal en lugar de crear un nuevo archivo de configuración. Haz eso agregando (setq org-babel-async-init-file "~/.emacs.d/init")a tu init.el. Pero creo que crear un archivo de configuración para esta tarea es más sencillo.

3. Además ...

Añadir a init.el

;; This will stop the new process buffer from getting focus.
(setq display-buffer-alist (append display-buffer-alist '(("*org-babel-async*" display-buffer-no-window))))

;; This will automatically show the result section.
(global-auto-revert-mode 1)

Añadir a org-babel-async-init.el

;; This will skip the "Save anyway?" confirmation of automatically saving the file when you also edited the buffer from Emacs while an asynchronous process is running.
(defun advice:verify-visited-file-modtime (orig-func &rest args) t)
(advice-add 'verify-visited-file-modtime :around 'advice:verify-visited-file-modtime)

;; This will skip the "Select coding system" prompt that appears when the result is inserted. This may vary among environments.
(setq coding-system-for-write 'utf-8)

;; This will skip the "changed on disk; really edit the buffer?" checking.
(defun ask-user-about-supersession-threat (fn) "blatantly ignore files that changed on disk")

Agregue a org-babel-async-init.el (puede que no necesite estos. Estos son para MATLAB)

;; This will set MATLAB cli path.
(setq-default matlab-shell-command "/Applications/MATLAB_R2016a.app/bin/matlab")
;; The MATLAB cli path can be obtained by running `fullfile(matlabroot, 'bin')` in your MATLAB.

;; This will stop MATLAB from showing the splash (the MATLAB logo) at the beginning.
(setq-default matlab-shell-command-switches '("-nodesktop" "-nosplash"))

Agregue a org-babel-async-init.el (es posible que no los necesite. Estos son para Julia, R y otros idiomas que usan ESS).

;; This will enable :session header in Julia and other languages that use ESS (Emacs speaks statistics).
(load "/path/to/ess-site")
;; This will suppress ESS from prompting for session directory.
(setq ess-ask-for-ess-directory nil)

4. Uso

(Después de la configuración anterior).

  1. Mueva el cursor al fragmento de código que desea ejecutar.
  2. Corre M-x my/async-emacs-repl-org-babel-execute(en lugar de hacerlo C-c C-c). Esto iniciará un proceso externo de Emacs como un servidor REPL si es necesario, y luego ejecutará el bloque fuente en el que se encuentra.

Expresiones de gratitud

Aprendí la idea de comenzar un proceso de emacs para la evaluación de org-babel de esta publicación . Me gustaría agradecer al autor.

Comentarios para personalización

La idea aquí es simple. Iniciar una nueva emacs procesan como REPL para Elisp, hacerlo find-fileen el mismo archivo .org estamos editando, goto-lineal mismo punto del cursor, ejecutar org-babel-execute-src-block, save-buffer. Deje de salir hasta que el usuario detenga el proceso (de lo contrario, los gráficos desaparecerían inmediatamente después de mostrarse). Naturalmente, uno puede pensar en extender esto al:

  • Usando org-mode's en C-c C-clugar de ejecutar funciones a mano / configurando una nueva combinación de teclas (que se puede lograr mediante consejos).
  • Cambio condicional del nombre del proceso según: variable de sesión y el idioma
  • Condicionalmente cambiar archivos init basados ​​en el idioma.

De hecho, el éxito de este enfoque me parece estar mostrando una forma general de desarrollar características asíncronas en Emacs. Crear una capa de "comandos", agregar scripts para realizar tareas y tener un marco para iniciar y reutilizar procesos de emacs. Al igual que el framework Symfony de PHP (PHP no tiene hilos) tiene características de Comando.

Editar historial

Código refactorizado (02/04/2016). La solución ahora reutiliza un proceso de Emacs (2016-04-02). La solución ahora está simplificada y solo tiene un interactivecomando para ejecutar (02/04/2016. Configuración agregada (12/04/2016).

diadochos
fuente
¿Has visto async.el?
PythonNut
Sí tengo. Básicamente, inicia un nuevo proceso de Emacs y ejecuta la lambdafunción que se le asigna. No lo usé para esta solución porque no pude encontrar una manera de enviar datos al nuevo proceso. La comunicación del proceso es necesaria si desea utilizar la función: session de org-babel.
diadochos
Gracias por trabajar en esta solución. Lo intenté pero recibo este mensaje de error: TERM=vt200 emacs --batch -nw --eval '(load "~/.emacs.d/org-babel-async-init")' --eval '(while t (print (eval (read))))': exited abnormally with code 255.Lo siento, esto debería ser un comentario y no una respuesta, pero simplemente no tengo suficientes puntos.
mhartm
Después de ejecutar eso, ¿ve un búfer llamado " org-babel-async "? Si puede encontrar uno, ese búfer probablemente contiene más información sobre el error. "Salió anormalmente con el código 255" generalmente ocurre cuando el programa que quería ejecutar en el proceso generado de emacs falló. Posibles salidas: 1) Verifique si tiene el archivo especificado en mi / async-emacs-repl-org-babel-init-file. Si no lo hace, cree uno como se describe anteriormente. 2) Verifique si ha enumerado el idioma en el que desea usar org-babel-do-load-languages. 3) El #+SRC_BEGINbloque que está ejecutando contiene un error.
diadochos
De acuerdo, por lo que el problema era que necesito para salvar mi archivo org antes de ejecutar M-x my/async-emacs-repl-org-babel-execute, de lo contrario el buffer "org-babel-asíncrono" se quejará: ...t/Dropbox/org/work.org locked by maarhart@htkl... (pid 68694): (s, q, p, ?)? Please type q, s, or p; or ? for help. Entonces, si esto se puede resolver, sería fantástico. Gracias de todos modos por esto, ¡es increíble! Por cierto, ¿es posible vincularlo C-c C-co entrará en conflicto con el modo org?
mhartm