How do I run (async-)shell-command (but no other shell-mode buffers) in view-mode?
2 Answers
Do can define your another wrapper function that calls async-shell-command which enables the view-mode after calling that function.
(defun my/async-shell-command (command &optional output-buffer error-buffer)
(interactive
(list
(read-shell-command "Async shell command: " nil nil
(let ((filename
(cond
(buffer-file-name)
((eq major-mode 'dired-mode)
(dired-get-filename nil t)))))
(and filename (file-relative-name filename))))
current-prefix-arg
shell-command-default-error-buffer))
;; call the original function
(async-shell-command command output-buffer error-buffer)
;; switch to the shell command output buffer
(switch-to-buffer "*Async Shell Command*")
;; enable `view-mode'
(view-mode))
and bind that to the default async-shell-command binding M-&.
Note that the (interactive (list ..)) portion of the wrapper funciton is copied directly from the source code for async-shell-command. It's useful to retain the input arguments of the original function when writing a wrapper around it. The same applies if you were to use advice-add (emacs 24.4+) instead.
Similar thing can be done for shell-command too.
-
Awesome! I'll try this out tomorrow. From reading your code though I'm guessing that the last three lines would be enough, right? I don't quite understand the interactive part.Mattias Bengtsson– Mattias Bengtsson2015-02-16 23:31:35 +00:00Commented Feb 16, 2015 at 23:31
-
@MattiasBengtsson Nope, you will need the interactive part too. I have simply copied that portion from the source code. That portion is important as it creates the mechanism for entering the shell command in the minibuffer.Kaushal Modi– Kaushal Modi2015-02-16 23:40:55 +00:00Commented Feb 16, 2015 at 23:40
-
Did you test it? I'm wondering whether you won't get errors for trying to print on a readonly buffer.Malabarba– Malabarba2015-02-17 00:21:51 +00:00Commented Feb 17, 2015 at 0:21
-
@Malabarba I tested this to work as the OP wants. Calling this function, executes the async command as entered in the minibuffer and then makes the async buffer read-only (
view-mode). So when you try to type in that buffer, you won't be able to. Re-executing the same command, discards the old async buffer and shows the new content, again making that read-only.Kaushal Modi– Kaushal Modi2015-02-17 00:40:01 +00:00Commented Feb 17, 2015 at 0:40 -
Hm it doesn't quite work. The original async-shell-command will popup and then the switch-to-buffer call will add a new view of the buffer. The old view won't be in view-mode. See here: youtube.com/watch?v=DI4I-KOGA90 Hm, I wonder if it would be possible to construct an advice around (async-)shell-command? I'll try that out later this week.Mattias Bengtsson– Mattias Bengtsson2015-02-17 18:04:16 +00:00Commented Feb 17, 2015 at 18:04
Seeing as there are no shell command hooks, an alternative is to use advice. All asynchronous processes need to specify a sentinel function, which in the case of shell commands is shell-command-sentinel:
(define-advice shell-command-sentinel (:after (process _msg) my-view-output)
"Enable `view-mode' in `*Async Shell Command*' buffers."
(let ((buffer (process-buffer process)))
(and (memq (process-status process) '(exit signal))
(buffer-live-p buffer)
(with-current-buffer buffer
(view-mode)))))
Synchronous shell commands, on the other hand, all go through the command shell-command-on-region:
(define-advice shell-command-on-region (:after (&rest _) my-view-output)
"Enable `view-mode' in `*Shell Command Output*' buffer."
(let ((buffer (get-buffer "*Shell Command Output*")))
(when (buffer-live-p buffer)
(with-current-buffer buffer
(view-mode)))))
The latter advice is actually a bit more brittle than the former because it hard-codes the buffer name, but it should work for most cases. You should tweak it to suit your needs and use of shell commands.