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.

diadochos
fonte
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)).

diadochos
fonte
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 é