Execução assíncrona em org babel


14

Existe uma boa personalização geral do org-babel para executar de forma assíncrona? Recentemente, planejo usar o MATLAB via org-babel, mas gostaria de uma forma assíncrona, pois alguns cálculos levam tempo.

Não desejo personalizar apenas o ob-matlab. Isso ocorre porque acho que isso deve ser feito no nível da estrutura, e não em um aplicativo. Em outras palavras, uma mesma modificação deve ativar o recurso assíncrono para outras extensões de idioma, por exemplo, idioma R.

Alguém tem uma boa solução? Até agora eu tentei async.el, bem como deferred.elpara modificar org-babel-execute-safely-maybeque pode ser encontrada em ob-core.elno momento.


Outra dica é passar o bloco babel para a tela ou tmux.
— Stardiviner

Eu nunca implementei isso, mas parece possível. Obrigado.
— Diadochos

Acho que estou aceitando minha própria resposta, pois não houve nenhuma outra solução publicada nos últimos um mês.
— Diadochos

Respostas:


6

Até agora, descobri que gerar um novo processo Emacs é uma solução.

Aqui está o que eu fiz.

1. Adicione uma função para iniciar um processo externo do 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. Adicione um arquivo de configuração para carregar no novo processo emacs.

A função acima inicia o emacs no --batchmodo. Portanto, o init.el normal não será carregado.

Em vez disso, queremos criar um arquivo de configuração mais curto (para carregar caminhos e assim por diante).

O caminho para o nosso novo arquivo de configuração é armazenado no async-emacs-repl-org-babel-init-filesnippet acima.

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

Aqui nós ...

  1. Adicione caminhos de pacote.
  2. Diga ao modo organizacional para não perguntar se deseja executar o bloco de código.
  3. Diga à org-babel quais idiomas são necessários.

Nota de rodapé 1: sem essa configuração, a avaliação falhará com "No org-babel-execute function for $lang!"

Nota de rodapé 2: É claro que você pode carregar o init.el normal em vez de criar um novo arquivo de configuração, se desejar. Faça isso adicionando (setq org-babel-async-init-file "~/.emacs.d/init")ao seu init.el. Mas acho que criar um arquivo de configuração para esta tarefa é mais direto.

3. Além disso ...

Adicionar ao 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)

Adicionar ao 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")

Adicione a org-babel-async-init.el (pode não ser necessário. Estes são para o 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"))

Adicione ao org-babel-async-init.el (talvez você não precise deles. Estes são para Julia, R e outros idiomas que usam o 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

(Após a configuração acima.)

  1. Mova o cursor para o trecho de código que você deseja executar.
  2. Execute M-x my/async-emacs-repl-org-babel-execute(em vez de fazer C-c C-c). Isso iniciará um processo externo do Emacs como um servidor REPL, se necessário, e executará o bloco de origem em que você está.

Agradecimentos

Eu aprendi a idéia de iniciar um processo emacs para avaliação de org-babel a partir deste post . Eu gostaria de agradecer ao autor.

Comentários para personalização

A ideia aqui é simples. Iniciar um novo emacs processo como um REPL para Elisp, fazer find-filepara o mesmo arquivo .org estamos editando, goto-linepara o mesmo ponto do cursor, executar org-babel-execute-src-block, save-buffer. Pare de sair até que o usuário pare o processo (caso contrário, os gráficos desapareceriam imediatamente após serem exibidos). Pode-se naturalmente pensar em estender isso:

  • Usando o modo organizacional em C-c C-cvez de executar funções manualmente / definindo um novo atalho de teclado (que pode ser obtido com conselhos).
  • Comutação condicional do nome do processo de acordo com: variável de sessão e idioma
  • Troca condicional de arquivos init com base no idioma.

De fato, o sucesso dessa abordagem me parece mostrar uma maneira geral de desenvolver recursos assíncronos no Emacs. Criando uma camada de "comandos", adicione scripts para executar tarefas e tenha uma estrutura para iniciar e reutilizar os processos do emacs. Assim como o framework Symfony do PHP (o PHP não possui threads) possui recursos de comando.

Editar histórico

Código refatorado (02/04/2016). A solução agora reutiliza um processo Emacs (02/04/2016). Solução agora simplificada e possui apenas um interactivecomando para executar (02/04/2016. Configuração adicionada (12/04/2016)).


Você viu async.el?
— PythonNut

Sim, eu tenho. Essencialmente, inicia um novo processo do Emacs e executa a lambdafunção atribuída a ele. Não o usei para esta solução porque não consegui encontrar uma maneira de enviar dados para o novo processo. É necessário comunicar o processo se você quiser usar o recurso: session do org-babel.
— Diadochos 13/04

Obrigado por trabalhar nesta solução. Eu tentei, mas recebo esta mensagem de erro: TERM=vt200 emacs --batch -nw --eval '(load "~/.emacs.d/org-babel-async-init")' --eval '(while t (print (eval (read))))': exited abnormally with code 255.Desculpe, isso deve ser um comentário e não uma resposta, mas eu simplesmente não tenho pontos suficientes.
— precisa saber é

Depois de executar isso, você vê um buffer chamado " org-babel-async "? Se você puder encontrar um, esse buffer provavelmente contém mais informações sobre o erro. "saiu anormalmente com o código 255" geralmente ocorre quando o programa que você deseja executar no processo emacs gerado falhou. Possíveis soluções: 1) Verifique se você possui o arquivo especificado em meu / async-emacs-repl-org-babel-init-file. Caso contrário, crie um como descrito acima. 2) Verifique se você listou o idioma em que deseja usar org-babel-do-load-languages. 3) O #+SRC_BEGINbloco que você está executando contém um erro.
— Diadochos

Ok, então o problema era que eu preciso para salvar meu arquivo org antes de correr M-x my/async-emacs-repl-org-babel-execute, caso contrário o buffer "org-babel-async" vai reclamar: ...t/Dropbox/org/work.org locked by maarhart@htkl... (pid 68694): (s, q, p, ?)? Please type q, s, or p; or ? for help. Portanto, se isso puder ser resolvido, seria fantástico. Obrigado mesmo assim por isso, é incrível! A propósito, é possível vinculá-lo C-c C-cou entrará em conflito com o modo organizacional?
— precisa saber é

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.