9

The normal behavior of Emacs when transient-mark-mode is active is that when you make a shift-selection, then if the next command is a non-shift movement, the mark is deactivated. For example, after the commands M-l (to mark the current line with the function below) and C-f, the mark is deactivated. How to emulate that behavior from elisp after (set-mark-command nil)?

For example:

(defun my-mark-current-line ()
  (interactive)
  (beginning-of-line)
  (set-mark-command nil)
  (end-of-line)
  (forward-char))

(global-set-key (kbd "M-l") 'my-mark-current-line)

Now do M-l C-f and the region will grow, but instead, I want the default behavior, i.e. the region to deactivate when C-f, and grow with C-S-f.

EDIT: should use a function different to set-mark-command that allows this? I couldn't find any.

Drew
  • 75,699
  • 9
  • 109
  • 225
mikl
  • 413
  • 3
  • 19
  • I believe that is not possible (and I might be wrong). As long as a region is active, navigation commands will change the selection. The shift-selection using `C-S-f` is analogous to `C-SPC` (activating a region) + `C-f` (navigation). You can probably get what you want by binding `C-f` to a wrapper function that first deactivates a region if active and then proceeds to do what `C-f` does (`forward-char`); and bind `C-S-f` directly to `forward-char`. Note that if you ever use emacs in terminal mode, `C-f` and `C-S-f` will both behave as `C-f` as the terminal cannot distinguish between the two. – Kaushal Modi May 09 '16 at 18:34
  • Also the wrapper and binding you did for `C-f` would apply to all other navigation commands you use too. – Kaushal Modi May 09 '16 at 18:36
  • btw `C-f` after `M-l` does not extend the region because there is no active region at the end of `M-l` (which is bound to `downcase-word` by default). – Kaushal Modi May 09 '16 at 18:37
  • 1
    @KaushalModi I think that the `M-l` referred to by the OP is not the default binding (`downcase-word`), but the custom binding of `my-mark-current-line` – nispio May 09 '16 at 19:03
  • indeed @nispio. – mikl May 10 '16 at 20:08

1 Answers1

10

Because shift translation and temporary activation of the mark is handled by the command loop, you will need to call the interactive versions of the movement functions in order to get the appropriate shift selection behavior from them:

;; (source: http://emacs.stackexchange.com/a/22166/93)
(defun my-mark-current-line ()
  (interactive)
  (beginning-of-line)
  (setq this-command-keys-shift-translated t)
  (call-interactively 'end-of-line)
  (call-interactively 'forward-char))

(global-set-key (kbd "M-l") 'my-mark-current-line)

Update:

Since writing the above answer, I took the time to learn a little more about how shift selection really works under the hood. It sets the symbol value of transient-mark-mode to be a cons cell of the form (only . OLDVAL), where OLDVAL is the value prior to shift selection.

The solution below avoids the use of call-interactively by activating the mark as needed, and setting the appropriate value of transient-mark-mode. Basically, I consider this solution to be less of a hack than the first one.

As a bonus, it now has an optional repeat count, and will extend the current selection in either direction if the mark is already active.

;; (source: http://emacs.stackexchange.com/a/22166/93)
(defun my-mark-current-line (&optional arg)
  "Uses shift selection to select the current line.
When there is an existing shift selection, extends the selection
in the appropriate direction to include current line."
  (interactive "p")
  (let ((oldval (or (cdr-safe transient-mark-mode) transient-mark-mode))
        (backwards (and mark-active (> (mark) (point))))
        (beg (and mark-active (mark-marker))))
    (unless beg
      (if backwards (end-of-line) (beginning-of-line))
      (setq beg (point-marker)))
    (if backwards (end-of-line (- 1 arg)) (beginning-of-line (+ 1 arg)))
    (unless mark-active
      (push-mark beg nil t))
    (setq transient-mark-mode (cons 'only oldval))))
nispio
  • 8,175
  • 2
  • 35
  • 73