Run commands in Emacs asynchronously, but display output incrementally - emacs

I have a utility function:
(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)))
This allows me to do things like (execute-in-buffer "ls /" (get-buffer-create "*my-output*"). However, it's not well suited for slow commands. If I call a series of slow commands, I don't get any output until the very end:
(let ((buf (get-buffer-create "*my-output")))
(execute-in-buffer "sleep 10" buf)
(execute-in-buffer "ls /" buf))
I want to be able to call synchronously, so the next command only runs after the previous one finishes. However, I want to see the output from my commands as they run. How would I do this?
(Example code is just for show, I'm happy to drop it in favour of something else.)

Using synchronous processes
If you want to stick with synchronous processes (e.g. using call-process), you need to call (redisplay) after each call to execute-in-buffer for the display to be updated and the output to be visible (also see this question for more details). However, the output of each command will not be visible until the process terminates, and emacs will hang while the external processes are running.
Using asynchronous processes
Using asynchronous processes is a bit more complicated, but avoids hanging Emacs while the commands are running, which also solves the redisplay issue. The tricky part here is to sequentially chain all commands. Here is a bit of elisp which should do the trick:
(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 /")

This works for me:
(async-shell-command "echo 1; sleep 10; echo 2; sleep 10; ls /" "*abcd*")
This can be adapted to do what you need?

Related

emacs: How to Capture stdout from dired-do-shell-command?

In Emacs, is there a way to capture the stdout of dired-do-shell-command, say to the kill-ring?
Without knowing how, I end up going to the Messages buffer and getting the stdout manually from there.
Help for that command says that output goes to a buffer named *Shell Command Output*, assuming command has not &. If this is the case, this piece of code will do what you want:
(defun do-shell-and-copy-to-kill-ring (command &optional arg file-list)
(interactive
(let ((files (dired-get-marked-files t current-prefix-arg)))
(list
(dired-read-shell-command "! on %s: " current-prefix-arg files)
current-prefix-arg
files)))
(dired-do-shell-command command arg file-list)
(with-current-buffer "*Shell Command Output*"
(copy-region-as-kill (point-min) (point-max))))
For async commands, you need to wait for them and look in *Async Shell Command* buffer.

Launching shell command in Eshell without prompt interfence

I'm trying to customize my eshell to intercept python to do two things:
Open a new window and run python if no arguments are given (e.g., $ python)
Run the command "per usual" if arguments are given (e.g., $ python foobar.py)
So far I have something like this
(defun eshell/python (&rest cmd-args)
(if (not cmd-args)
(progn (run-python "python")
(select-window (split-window-below))
(switch-to-buffer "*Python*")
(balance-windows)
nil)
(message "Use '*python' to call python directly")))
I've tried replacing (message ...) with a few different things:
Based on the ouput of eshell-parse-command "python test.py" I tried
(progn (eshell-trap-errors
(eshell-named-command "python" cmd-args)))
but it hits a recursion limit.
Since *python test.py does what I want, I then tried
(progn (eshell-trap-errors
(eshell-named-command (concat "*" "python") cmd-args)))
but that puts the python process in the background and interrupts stdout with the output of my eshell-prompt-function.
Finally, I've fiddled with shell-command but I can't get it to write to the eshell buffer. In particular,
(progn (eshell-trap-errors
(shell-command (mapconcat (lambda(x) x) cmd-args " ")
(get-buffer "*eshell*") (get-buffer "*eshell*"))))
gives me a Text is read only message and moves point to start of the eshell buffer.
Is what I'm looking for possible?
Edit 1
Running without eshell/python defined, I've instead tried to avoid alias problems:
(defun eshell/gvr (&rest cmd-args)
(if (not cmd-args)
(progn (run-python "python")
(select-window (split-window-below))
(switch-to-buffer "*Python*")
(balance-windows)
nil)
(progn (eshell-trap-errors
(eshell-named-command "python" cmd-args)))))
If test.py is
print "Hello World"
x = raw_input("What should I repeat? ")
print x
running gvr test.py in eshell fails when I reply to the prompt because eshell tries to execute the input instead of handing it to python, but running python test.py goes off without a hitch.
How can I get run my own subprocesses in eshell the same way that they happen by default?

Run elisp program without Emacs?

elisp is a good language, I find it can handle all kind of jobs, but can I use it like a shell script?
i.e. execute some *.el files from the console, without launching Emacs. Or launch Emacs, but don't enter interactive mode.
You can most definitely run elisp scripts in Emacs without starting the editor interface.
Here are the notes I've made/copied from a few extremely useful Q&As on the subject here at S.O. (and the following two in particular).
Emacs shell scripts - how to put initial options into the script?
Idiomatic batch processing of text in Emacs?
Much of this information, and more besides, is also covered in the following excellent overview, which is recommended reading:
https://swsnr.de/blog/2014/08/12/emacs-script-pitfalls/
;;;; Elisp executable scripts
;; --batch vs --script
;; M-: (info "(emacs) Initial Options") RET
;; M-: (info "(elisp) Batch Mode") RET
;; Processing command-line arguments (boiler-plate)
;; http://stackoverflow.com/questions/6238331/#6259330 (and others)
;;
;; For robustness, it's important to both pass '--' as an argument
;; (to prevent Emacs from trying to process option arguments intended
;; for the script), and also to exit explicitly with `kill-emacs' at
;; the end of the script (to prevent Emacs from carrying on with other
;; processing, and/or visiting non-option arguments as files).
;;
;; #!/bin/sh
;; ":"; exec emacs -Q --script "$0" -- "$#" # -*-emacs-lisp-*-
;; (pop argv) ; Remove the "--" argument
;; ;; (setq debug-on-error t) ; if a backtrace is wanted
;; (defun stdout (msg &optional args) (princ (format msg args)))
;; (defun stderr (msg &optional args) (princ (format msg args)
;; #'external-debugging-output))
;; ;; [script body here]
;; Always exit explicitly. This returns the desired exit
;; status, and also avoids the need to (setq argv nil).
;; (kill-emacs 0)
;; Processing with STDIN and STDOUT via --script:
;; https://stackoverflow.com/questions/2879746/#2906967
;;
;; #!/bin/sh
;; ":"; exec emacs -Q --script "$0" -- "$#" # -*-emacs-lisp-*-
;; (pop argv) ; Remove the "--" argument
;; (setq debug-on-error t) ; if a backtrace is wanted
;; (defun stdout (msg &optional args) (princ (format msg args)))
;; (defun stderr (msg &optional args) (princ (format msg args)
;; #'external-debugging-output))
;; (defun process (string)
;; "Reverse STRING."
;; (concat (nreverse (string-to-list string))))
;;
;; (condition-case nil
;; (let (line)
;; (while (setq line (read-from-minibuffer ""))
;; (stdout "%s\n" (process line))))
;; (error nil))
;;
;; ;; Always exit explicitly. This returns the desired exit
;; ;; status, and also avoids the need to (setq argv nil).
;; (kill-emacs 0)
Emacs aside, the only other elisp interpreter/compiler I'm aware of is Guile. If you're keen on general coding in elisp, that should be worth a look (especially if performance is a concern).

Using Emacs, is it possible to pin the compilation command to a specific buffer/directory?

Right now I am using the following to compile, when I'm in for example main.cpp
C-x b Makefile RET M-x compile RET RET
I actually have M-x compile as a keyboard shortcut, but the problem is I would really like not having to go through all that trouble to simply run my Makefile.
I need to visit Makefile to make sure the compile command is executed using the same directory. Is there any way to pin the directory so I can simply go M-x compile RET RET?
Best regards
Use recompile instead. C-u M-x recompile will let you edit the compile command first. Either way the compile will work out of the directory the last compile was done in.
See my answer here
Directory local variables provide an easy way to trigger the compile from a parent directory of any source file in a subdirectory.
I run emacs primarily on windows.
When I have a makefile that is in a parent directory of a C module, I use this as the compile command:
cd .. && nmake <arguments here>
for example:
cd .. && nmake CONFIG=Debug PLATFORM=x64 target
Beyond that, I find that specifying the make command line that I want to run for various modules is sort of a pain. I wanted a way to attach the default compile command to the buffer being edited. So I wrote a little elisp to handle that job. I figured to insert into the header comments of each buffer a line that would stipulate my preferred compile command, like this:
compile: cd .. && nmake CONFIG=Debug PLATFORM=x64 target
And then have a piece of elisp run, before I invoke M-x compile that grabs the line and proposes it as the compile command I would like to run.
This defun pulls a line out of the header comments:
(defun cheeso-c-get-value-from-comments (marker-string line-limit)
"gets a string from the header comments in the current buffer.
This is used to extract the compile command from the comments. It
could be used for other purposes too.
It looks for \"marker-string:\" and returns the string that
follows it, or returns nil if that string is not found.
eg, when marker-string is \"compile\", and the following
string is found at the top of the buffer:
compile: cl.exe /I uthash
...then this command will return the string
\"cl.exe /I uthash\"
It's ok to have whitespace between the marker and the following
colon.
"
(let (start search-limit found)
;; determine what lines to look in
(save-excursion
(save-restriction
(widen)
(cond ((> line-limit 0)
(goto-char (setq start (point-min)))
(forward-line line-limit)
(setq search-limit (point)))
((< line-limit 0)
(goto-char (setq search-limit (point-max)))
(forward-line line-limit)
(setq start (point)))
(t ;0 => no limit (use with care!)
(setq start (point-min))
(setq search-limit (point-max))))))
;; look in those lines
(save-excursion
(save-restriction
(widen)
(let ((re-string
(concat "\\b" marker-string "[ \t]*:[ \t]*\\(.+\\)$")))
(if (and start
(< (goto-char start) search-limit)
(re-search-forward re-string search-limit 'move))
(buffer-substring-no-properties
(match-beginning 1)
(match-end 1))))))))
Ok, now I need something to invoke that before I invoke compile.
(defun cheeso-invoke-compile-interactively ()
"fn to wrap the `compile' function. This simply
checks to see if `compile-command' has been previously set, and
if not, invokes `cheeso-guess-compile-command' to set the value.
Then it invokes the `compile' function, interactively."
(interactive)
(cond
((not (boundp 'cheeso-local-compile-command-has-been-set))
(cheeso-guess-compile-command)
(set (make-local-variable 'cheeso-local-compile-command-has-been-set) t)))
;; local compile command has now been set
(call-interactively 'compile))
Then of course, the defun that guesses the compile command:
(defun cheeso-guess-compile-command ()
"set `compile-command' intelligently depending on the
current buffer, or the contents of the current directory."
(interactive)
(set (make-local-variable 'compile-command)
(cond
(buffer-file-name
(let ((filename (file-name-nondirectory buffer-file-name)))
(cond
;; editing a C-language source file - check for an
;; explicitly-specified command
((string-equal (substring buffer-file-name -2) ".c")
(let ((explicit-compile-command
(cheeso-c-get-value-from-comments "compile" 34)))
(or explicit-compile-command
(concat "nmake " ;; assume a makefile exists
(file-name-sans-extension filename)
".exe"))))
;; editing a makefile - just run nmake
((string-equal (substring buffer-file-name -8) "makefile")
"nmake ")
;; something else - do a typical .exe build
(t
(concat "nmake "
(file-name-sans-extension filename)
".exe")))))
(t
;; punt
"nmake "))))
The final bit is to bind C-x C-e , normally bound to compile, to the wrapper defun:
(global-set-key "\C-x\C-e" 'cheeso-invoke-compile-interactively)
Now, when I do C-x C-e in the buffer, it searches for the compile command, and proposes to me the command that it finds. I can edit the proposed compile command, then press ENTER and run it.

Why is my term-mode-hook not selecting line mode?

I wrote this elisp function:
(defun run (command)
"Open a terminal running a command."
(interactive "sCommand: ")
(if (buffer-exists (concat "*" command "*" )) (kill-buffer (concat "*" command "*")))
(let ((term-mode-hook (cons (lambda () (term-line-mode)) term-mode-hook)))
(ansi-term (cons "sh" (cons "-i" (list "-c" command))) command)))
This works nicely except that the new ansi-term buffers remains in char mode (which is the default), so as far as I can tell the term-line-mode call is not doing anything. If I replace (term-line-mode) with (message "foo") I do see the message in the messages buffer.
The definition of term-line-mode in lisp/term.el is:
(defun term-line-mode ()
"Switch to line (\"cooked\") sub-mode of term mode.
This means that Emacs editing commands work as normally, until
you type \\[term-send-input] which sends the current line to the inferior."
(interactive)
(when (term-in-char-mode)
(use-local-map term-old-mode-map)
(term-update-mode-line)))
What am I doing wrong?
I wasn't able to get "term-line-mode" to work as you want in any of the term hooks; however, it does work if you advise the "ansi-term" function:
(defadvice ansi-term (after advice-term-line-mode activate)
(term-line-mode))