org-babel – Ejecución asincrónica en org babel

Pregunta:

¿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 asincrónica, 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 idioma R.

¿Alguien tiene una buena solución? Hasta ahora he probado async.el y deferred.el para modificar org-babel-execute-safely-maybe que se pueda encontrar en ob-core.el en este momento.

Respuesta:

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

Esto es lo que hice.

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

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 emacs.

La función anterior inicia emacs en el modo --batch . 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 en async-emacs-repl-org-babel-init-file en 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. Agregue rutas de paquetes.
  2. Dile a org-mode que no pregunte si ejecutar el bloque de código.
  3. Dígale a org-babel qué idiomas son necesarios.

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

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

3. Además …

Agregar 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)

Agregar 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 (es posible que no los necesite. 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 lenguajes 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. Ejecute Mx my/async-emacs-repl-org-babel-execute (en lugar de hacer Cc Cc ). 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 iniciar un proceso 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. Inicie un nuevo proceso emacs como un REPL para Elisp, find-file en el mismo archivo .org que estamos editando, goto-line al mismo punto del cursor, ejecute 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:

  • Usar Cc Cc org-mode en lugar de ejecutar funciones a mano / configurar una nueva combinación de teclas (que se puede lograr mediante consejos).
  • Cambiar condicionalmente el nombre del proceso según: la variable de sesión y el idioma
  • Cambiar condicionalmente los archivos de inicio según el idioma.

De hecho, me parece que el éxito de este enfoque está 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 emacs. Al igual que el marco Symfony de PHP (PHP no tiene subprocesos) tiene funciones de comando.

Editar historial

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

Leave a Comment

Your email address will not be published. Required fields are marked *

web tasarım