2

How do I run (async-)shell-command (but no other shell-mode buffers) in view-mode?

Mattias Bengtsson
  • 1,300
  • 1
  • 11
  • 18

2 Answers2

1

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.

Basil
  • 12,383
  • 43
  • 69
1

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.

Kaushal Modi
  • 25,651
  • 4
  • 80
  • 183
  • 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 Feb 16 '15 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 Feb 16 '15 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 Feb 17 '15 at 00: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 Feb 17 '15 at 00: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: https://www.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 Feb 17 '15 at 18:04