2

I'd like to use shell-command-on-region on the region, in a way that keeps the selection, even in the case the length of the output changes, and works in regular emacs and in evil mode?


Note, I've already asked this related question which is similar, just not dealing with selection: How to use an external command to modify selected words and lines?

ideasman42
  • 8,375
  • 1
  • 28
  • 105
  • 2
    Does reactivating the mark do what you expect? E.g. hit C-x C-x – YoungFrog Aug 15 '17 at 09:26
  • @YoungFrog, no it failed for a few reasons, added answer that lists them. – ideasman42 Aug 15 '17 at 12:14
  • @ideasman42 Don't know what's your issue, but it works nicely from here with vanilla Emacs, though I don't know about evil mode. – xuchunyang Aug 15 '17 at 13:31
  • @xuchunyang - even with vanilla emacs, iirc adding more text has some issues. Though maybe under some conditions it works OK. Evil mode definitely needs special handling. – ideasman42 Aug 15 '17 at 14:27

1 Answers1

1

This can be done, I managed to get it working (with help from @wasamasa), notes:

  • The selection needs to be stored and restored.
  • It's important to run deactivate-mark is nil so Emacs can restore the selection.
  • Special handling is needed for evil-visual-line mode,
    (evil-visual-select ...) needs to be called.

Below is a wrapper for shell-command-on-region that handles re-selection.

;; Wrapper for 'shell-command-on-region', keeps the selection.
(defun my-shell-command-on-region-and-select
    (start
     end
     command
     &optional
     output-buffer
     replace
     error-buffer
     display-error-buffer
     region-noncontiguous-p)
  "Wrapper for 'shell-command-on-region', re-selecting the output.

Useful when called with a selection, so it can be modified in-place."
  (interactive)
  (let ((buffer-size-init (buffer-size)))
    (shell-command-on-region
     start
     end
     command
     output-buffer
     replace
     error-buffer
     display-error-buffer
     region-noncontiguous-p)
    (setq deactivate-mark nil)
    (setq end (+ end (- (buffer-size) buffer-size-init)))
    (set-mark start)
    (goto-char end)
    (activate-mark)
    ;; needed for evil line mode
    (when (and (boundp evil-state) (string= evil-state "visual"))
      (when (eq (evil-visual-type) evil-visual-line)
        (evil-visual-select start end 'line)))))

Example call:

(my-shell-command-on-region-keep-region-and-select
 (region-beginning) (region-end)
 "tr A-Za-z a-zA-Z && echo ' extra text'")
ideasman42
  • 8,375
  • 1
  • 28
  • 105
  • 2
    Note that `(let (deactivate-mark) ...)` is another idiom of that kind that protects the let-bound variable so that changes to it are limited to the body. – wasamasa Aug 15 '17 at 18:59