Запускать команды в Emacs асинхронно, но отображать вывод постепенно
У меня есть функция полезности:
(defun execute-in-buffer (command-with-args buffer)
"Execute a string COMMAND-WITH-ARGS representing a shell command with arguments,
inserting the results in BUFFER."
(switch-to-buffer buffer)
(insert (format ">>> %s\n" command-with-args))
(let* ((command-args-list (s-split " " command-with-args))
(command (car command-args-list))
(args (cdr command-args-list)))
(apply 'call-process command nil buffer t args)))
Это позволяет мне делать такие вещи, как (execute-in-buffer "ls /" (get-buffer-create "*my-output*")
, Тем не менее, он не очень подходит для медленных команд. Если я вызываю серию медленных команд, я не получаю никакого вывода до самого конца:
(let ((buf (get-buffer-create "*my-output")))
(execute-in-buffer "sleep 10" buf)
(execute-in-buffer "ls /" buf))
Я хочу иметь возможность вызывать синхронно, поэтому следующая команда запускается только после завершения предыдущей. Тем не менее, я хочу видеть вывод моих команд во время их выполнения. Как бы я это сделал?
(Пример кода только для галочки, я с радостью опущу его в пользу чего-то другого.)
2 ответа
Использование синхронных процессов
Если вы хотите придерживаться синхронных процессов (например, используя call-process
), вам нужно позвонить (redisplay)
после каждого звонка execute-in-buffer
чтобы дисплей был обновлен, а вывод был виден (см. также этот вопрос для более подробной информации). Однако выходные данные каждой команды не будут видны до тех пор, пока процесс не завершится, и emacs будет зависать во время работы внешних процессов.
Использование асинхронных процессов
Использование асинхронных процессов немного сложнее, но позволяет избежать зависания Emacs во время выполнения команд, что также решает проблему повторного отображения. Сложная часть здесь заключается в последовательной цепочке всех команд. Вот немного elisp, которое должно сработать:
(defun execute-commands (buffer &rest commands)
"Execute a list of shell commands sequentially"
(with-current-buffer buffer
(set (make-local-variable 'commands-list) commands)
(start-next-command)))
(defun start-next-command ()
"Run the first command in the list"
(if (null commands-list)
(insert "\nDone.")
(let ((command (car commands-list)))
(setq commands-list (cdr commands-list))
(insert (format ">>> %s\n" command))
(let ((process (start-process-shell-command command (current-buffer) command)))
(set-process-sentinel process 'sentinel)))))
(defun sentinel (p e)
"After a process exited, call `start-next-command' again"
(let ((buffer (process-buffer p)))
(when (not (null buffer))
(with-current-buffer buffer
;(insert (format "Command `%s' %s" p e) )
(start-next-command)))))
;; Example use
(with-current-buffer (get-buffer-create "*output*") (erase-buffer))
(execute-commands "*output*"
"echo 1"
"sleep 1"
"echo 2; sleep 1; echo 3"
"ls /")
Это работает для меня:
(async-shell-command "echo 1; sleep 10; echo 2; sleep 10; ls /" "*abcd*")
Это можно адаптировать, чтобы делать то, что вам нужно?