Barry Kelly
Barry Kelly

Reputation: 42152

How to make region transient in elisp when in transient-mark-mode

I wrote an elisp macro that preserves the region when in transient-mark-mode:

(defmacro keep-region (command)
  "Wrap command in code that saves and restores the region"
  (letrec ((command-name (symbol-name command))
           (advice-name (concat command-name "-keep-region")))
    `(progn
       (defadvice ,command (around ,(intern advice-name))
         (let (deactivate-mark)
           (save-excursion
             ad-do-it)))
       (ad-activate (quote ,command)))))

(keep-region replace-string)
(keep-region replace-regexp)

This preserves the region for commands that are advised using the keep-region macro; very helpful when you want to make multiple replacements in a selected block.

The problem is that after running a command that has been advised using this macro, the region loses its transient nature; subsequent movement commands extend the region, rather than deselecting it.

How can I programmatically re-enable the transience of the marked region?

Upvotes: 3

Views: 467

Answers (3)

npostavs
npostavs

Reputation: 5027

The problem is that after running a command that has been advised using this macro, the region loses its transient nature; subsequent movement commands extend the region, rather than deselecting it.

You should rather talk about "shift-selected nature": movement commands extending the region is what happens when the mark is activated in the "normal" way.

The shift-select status is stored inside the transient-mark-mode variable, and is modified by someone (handle-shift-selection?) who doesn't care about the value of deactivate-mark. We can get around this by saving the value of transient-mark-mode:

(defmacro keep-region (command)
  "Wrap command in code that saves and restores the region"
  (letrec ((command-name (symbol-name command))
           (advice-name (concat command-name "-keep-region")))
    `(progn
       (defadvice ,command (around ,(intern advice-name))
         (let ((deactivate-mark nil)
               (transient-mark-mode transient-mark-mode))
           (save-excursion
             ad-do-it)))
       (ad-activate (quote ,command)))))

transient-mark-mode is a variable defined in `buffer.c'.

...

Lisp programs may give this variable certain special values:

...

  • A value of (only . OLDVAL) enables Transient Mark mode temporarily. After any subsequent point motion command that is not shift-translated, or any other action that would normally deactivate the mark (e.g. buffer modification), the value of
    `transient-mark-mode' is set to OLDVAL.

Upvotes: 1

Stefan
Stefan

Reputation: 28541

Just remove your calls to exchange-point-and-mark. The preservation is done by the deactive-mark let-binding anyway.

Upvotes: 1

user355252
user355252

Reputation:

From C-h f transient-mark-mode:

Transient Mark mode is a global minor mode. When enabled, the region is highlighted whenever the mark is active. The mark is "deactivated" by changing the buffer, and after certain other operations that set the mark but whose main purpose is something else--for example, incremental search, <, and >.

Hence, activate-mark after exchange-point-and-mark should restore the transient nature of the mark.

I am not sure, though, why you are using exchange-point-and-mark here, and why you are calling it twice. In my opinion, just saving (point) and (mark) in a let-binding and restoring them after ad-do-it would be easier. push-mark and pop-mark might help as well, since the latter automatically reactivates the mark anyway.

Upvotes: 2

Related Questions