Wykonanie asynchroniczne w org babel

14

Czy istnieje dobra ogólna personalizacja org-babel, aby działała asynchronicznie? Ostatnio planuję używać MATLAB-a przez org-babel, ale chciałbym to w sposób asynchroniczny, ponieważ niektóre obliczenia wymagają czasu.

Nie chcę dostosowywać tylko ob-matlab. Jest tak, ponieważ uważam, że należy to zrobić na poziomie frameworka zamiast aplikacji. Innymi słowy, ta sama modyfikacja powinna włączyć funkcję asynchronizacji dla innych rozszerzeń języka, np. Języka R.

Czy ktoś ma dobre rozwiązanie? Do tej pory próbowałem, async.ela także deferred.elmodyfikować, org-babel-execute-safely-maybektóre można obecnie znaleźć ob-core.el.

diadochos
źródło
Inną wskazówką jest przekazanie bloku babel do screena lub tmuxa.
stardiviner 30.04.16
Nigdy tego nie wdrożyłem, ale wydaje się to możliwe. Dzięki.
diadochos
Wydaje mi się, że akceptuję własną odpowiedź, ponieważ w ciągu ostatniego miesiąca nie opublikowano żadnego innego rozwiązania.
diadochos

Odpowiedzi:

6

Do tej pory odkryłem, że odrodzenie nowego procesu Emacsa jest rozwiązaniem.

Oto co zrobiłem.

1. Dodaj funkcję, aby rozpocząć proces zewnętrznego emacsa.

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. Dodaj plik konfiguracyjny, aby załadować w nowym procesie emacs.

Powyższa funkcja uruchamia emacsa w --batchtrybie. W ten sposób normalny plik init.el nie zostanie załadowany.

Zamiast tego chcemy utworzyć krótszy plik konfiguracyjny (w celu załadowania ścieżek itp.).

Ścieżka do naszego nowego pliku konfiguracyjnego jest zapisana we async-emacs-repl-org-babel-init-filefragmencie powyżej.

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

Tutaj my ...

  1. Dodaj ścieżki pakietów.
  2. Powiedz trybowi org, aby nie pytał, czy wykonać blok kodu.
  3. Powiedz org-babel, które języki są konieczne.

Przypis 1: Bez tego ustawienia ocena zakończy się niepowodzeniem "No org-babel-execute function for $lang!"

Przypis 2: Oczywiście, jeśli chcesz, możesz załadować normalny plik init.el zamiast tworzyć nowy plik konfiguracyjny. Zrób to, dodając (setq org-babel-async-init-file "~/.emacs.d/init")do swojego init.el. Myślę jednak, że utworzenie pliku konfiguracyjnego dla tego zadania jest prostsze.

3. Dodatkowo ...

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

Dodaj do 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")

Dodaj do org-babel-async-init.el (możesz ich nie potrzebować. Są to dla 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"))

Dodaj do org-babel-async-init.el (możesz ich nie potrzebować. Są to dla Julii, R i innych języków używających 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. Wykorzystanie

(Po powyższej konfiguracji.)

  1. Przesuń kursor do fragmentu kodu, który chcesz wykonać.
  2. Uruchom M-x my/async-emacs-repl-org-babel-execute(zamiast robić C-c C-c). W razie potrzeby uruchomi to zewnętrzny proces Emacsa jako serwer REPL, a następnie uruchom blok źródłowy, w którym się znajdujesz.

Podziękowanie

Z tego postu dowiedziałem się, jak rozpocząć proces emacsa do oceny org-babel . Chciałbym podziękować autorowi.

Komentarze do dostosowania

Pomysł tutaj jest prosty. Zacznij nowy emacs przetwarzać jako REPL dla Elisp, należy find-filedo tego samego pliku .org jesteśmy edytujesz, goto-linedo tego samego punktu kursora, prowadzony org-babel-execute-src-block, save-buffer. Przestań wychodzić, dopóki użytkownik nie zatrzyma procesu (w przeciwnym razie wykresy znikną natychmiast po ich wyświetleniu). Można oczywiście pomyśleć o rozszerzeniu tego przez:

  • Używanie trybów org C-c C-czamiast uruchamiania funkcji ręcznie / ustawianie nowego skrótu klawiszowego (co można uzyskać za pomocą porad).
  • Warunkowe przełączanie nazwy procesu według: zmiennej sesji i języka
  • Warunkowe przełączanie plików init w zależności od języka.

Wydaje mi się, że sukces tego podejścia pokazuje ogólny sposób rozwijania funkcji asynchronicznych w Emacsie. Tworzenie warstwy „poleceń”, dodawanie skryptów do wykonywania zadań oraz tworzenie ram do uruchamiania i ponownego wykorzystywania procesów emacs. Podobnie jak framework Symfony PHP (PHP nie ma wątków) ma funkcje poleceń.

Edytuj historię

Kod refaktoryzowany (02.02.2016). Rozwiązanie wykorzystuje teraz proces Emacsa (2016-04-02). Rozwiązanie jest teraz uproszczone i ma tylko jedno interactivepolecenie do uruchomienia (2016-04-02. Dodano konfigurację (2016-04-12).

diadochos
źródło
Widziałeś async.el?
PythonNut
Tak, mam. Zasadniczo uruchamia nowy proces Emacsa i uruchamia lambdaprzypisaną mu funkcję. Nie użyłem go do tego rozwiązania, ponieważ nie mogłem znaleźć sposobu na przesłanie danych do nowego procesu. Komunikowanie procesu jest konieczne, jeśli chcesz skorzystać z funkcji: session org-babel.
diadochos
Dziękujemy za pracę nad tym rozwiązaniem. Próbowałem, ale TERM=vt200 emacs --batch -nw --eval '(load "~/.emacs.d/org-babel-async-init")' --eval '(while t (print (eval (read))))': exited abnormally with code 255.pojawia się następujący komunikat o błędzie: Niestety, powinien to być komentarz, a nie odpowiedź, ale po prostu nie mam wystarczającej liczby punktów.
mhartm
Czy po wykonaniu tego widzisz bufor o nazwie „ org-babel-async ”? Jeśli możesz go znaleźć, ten bufor prawdopodobnie zawiera więcej informacji o błędzie. „zakończono nienormalnie z kodem 255” zazwyczaj występuje, gdy program, który chciałeś uruchomić w spawnowanym procesie emacs, nie powiódł się. Możliwe wyjścia: 1) Sprawdź, czy masz plik określony w moim / async-emacs-repl-org-babel-init-file. Jeśli nie, utwórz taki, jak opisano powyżej. 2) Sprawdź, czy podałeś język, w którym chcesz używać org-babel-do-load-languages. 3) #+SRC_BEGINWykonywany blok zawiera błąd.
diadochos
Ok, więc problem był taki, że trzeba zapisać mój plik org przed uruchomieniem M-x my/async-emacs-repl-org-babel-execute, inaczej bufor „org-babel-asynchroniczny” będzie narzekać: ...t/Dropbox/org/work.org locked by maarhart@htkl... (pid 68694): (s, q, p, ?)? Please type q, s, or p; or ? for help. Gdyby można to rozwiązać, byłoby fantastycznie. W każdym razie dzięki za to jest niesamowite! Nawiasem mówiąc, czy można go powiązać, C-c C-cczy będzie kolidować z trybem org?
mhartm