6

Under shift-select-mode, cursor keys behave like many other text editing environments: Shift+motion starts selecting (sets and activates the mark, in Emacs terms), and motion without Shift stops selecting (deactivates the mark, in Emacs terms).

So far so good, but Emacs isn't your basic editor. It has so many sophisticated motions that it isn't practical to manage a Shift variant for all of them.

This doesn't mesh well with commands that mark a region. For example, I type

foo

and move the cursor to the beginning, then press C-M-SPC. This results in the mark at the end of the sexp, i.e. after foo, and the point before the f. I would expect that pressing S-right will retain the mark and move the point. But it doesn't: since the previous command wasn't a shift-selectable command (a command with ^ in its interactive specification), pressing S-right at that point sets the mark before the f.

How can I configure Emacs so that when the mark is already set and active, Shift+motion doesn't move the mark? I always have transient-mark-mode on, that's not a concern.

  • Perhaps looking at the function `handle-shift-selection` will give you some ideas -- it is `elisp`, but tied directly in to the C-source code in `callint.c`. You can change the `elisp` function to do whatever you need. – lawlist Jun 03 '15 at 00:20
  • Note that `set-mark-command` and family also have circumstances where tests are performed and the mark is activated/deactivated. You will need to think about those circumstances when deciding to revise/rewrite `handle-shift-selection`. A portion of the mark activation/deactivation occurs at the C-source code level depending upon whether `transient-mark-mode` is active -- also tied into C-source -- see `transient_mark_mode` within the following files `buffer.c`; `callint.c`; `editfns.c`; `globals.h`; `insdel.c`; `keyboard.c` – lawlist Jun 03 '15 at 00:27
  • One last comment -- `transient-mark-mode` is designed to be both global, and buffer-local. This just about exhausts my one-and-one-half cents worth of contributing to this thread :) – lawlist Jun 03 '15 at 00:33
  • 1
    I'm slightly confused. If the mark is already set, won't just a plain `right` do? I only use shift if I want to activate the mark _and_ move. If the mark is already active, I just move. – PythonNut Jun 03 '15 at 05:05
  • Do you mean M-right instead of S-right? – Andreas Röhler Jun 03 '15 at 05:42
  • @PythonNut No, I use `S-right` when I want to set/keep the mark and move, and `right` when I want to remove/leave-unset the mark and move. That's how Emacs 24 works out of the box. – Gilles 'SO- stop being evil' Jun 03 '15 at 07:56
  • @Gilles but that's not how it works out of the box, right? Otherwise, this question wouldn't exist. – PythonNut Jun 03 '15 at 14:37
  • @PythonNut Launch Emacs 24.x with `emacs -Q` and switch to `*scratch*`. `shift-select-mode` is enabled by default. Type `ab`, so the point is now after `b`. Press `S-left`: this sets the mark after `b` and point before `a` and activates the mark. Press `left`: this deactivates the mark and moves point before `a`. My question is about a different scenario where I use a command which doesn't have `^` in its interactive spec. – Gilles 'SO- stop being evil' Jun 03 '15 at 16:58

3 Answers3

2

This is the code I've ended up using on Emacs ≥23. The end result is: if the mark isn't active on a shifted motion command, set it at the cursor's original location and activate it; if the mark is active on a non-shifted motion command, deactivate it; commands without interactive "^" don't affect the mark unless it's part of their normal job.

(defadvice handle-shift-selection
  (around handle-shift-selection-better-integration activate)
  (cond
   ((not shift-select-mode)) ;; do nothing if shift-select-mode is off
   ((eq (car-safe transient-mark-mode) 'only)
    ;; If the original mechanism is in progress, use it
    ad-do-it)
   ((and mark-active
         (not this-command-keys-shift-translated)
         (or (symbolp last-command-event)
             (eq (car-safe transient-mark-mode) 'only)))
    ;; On a non-shifted event, deactivate the mark either if it had been set
    ;; by a shift-selection command or if the event was a cursor key or
    ;; other function key.
    (deactivate-mark))
   ((and (not mark-active)
         this-command-keys-shift-translated)
    ;; On a shifted event, set the mark to the current location and activate
    ;; it..
    (push-mark nil t t))
   ;; Note that we never set the mark to the current location if it was
   ;; already active..
   ))
(setq shift-select-mode t)
1

This solution appears to work:

(defadvice handle-shift-selection
  (around leave-mark-intact activate preactivate compile)
  (cond ((and shift-select-mode this-command-keys-shift-translated)
          (unless mark-active
            (setq transient-mark-mode
              (cons 'only
                (unless (eq transient-mark-mode 'lambda)
                  transient-mark-mode)))
            (push-mark nil nil t)))
    ((eq (car-safe transient-mark-mode) 'only)
      (setq transient-mark-mode (cdr transient-mark-mode))
      (deactivate-mark))))

The changes are simple. It simply does nothing if the mark is already set.

PythonNut
  • 10,243
  • 2
  • 29
  • 75
  • No, that's not what I want: if I type `C-M-SPC` then `left`, the mark isn't deactivated. – Gilles 'SO- stop being evil' Jun 03 '15 at 16:59
  • I like your solution, @PythonNut , thanks! If I press `C-M-SPC left` mark isn't deactivated and I expect it, that's good. But could it be enhanced in next way: if I press `C-M-SPC left` then `S-right` then `left`, it should be deactivated. – Netsu Nov 04 '15 at 01:43
1

The following mostly works, though C-SPC (set-mark-command) no longer activates the region. Probably you won't care if you like shift-selecting because set-mark-command doesn't really mesh conceptually with shift-selection anyway. Otherwise you can add a check for it with (eq this-command 'set-mark-command).

(defun simulate-shift-selection ()
  (unless (and mark-active
               (eq (car-safe transient-mark-mode) 'only))
    (setq transient-mark-mode
          (cons 'only
                (unless (eq transient-mark-mode 'lambda)
                  transient-mark-mode)))
    ;;(push-mark nil nil t)
    ))

(add-hook 'activate-mark-hook #'simulate-shift-selection)
npostavs
  • 9,033
  • 1
  • 21
  • 53
  • This works but has undesirable side effects, such as C-SPC not activating the region which you noticed. I've adopted a different solution which doesn't affect the mark active status except for shiftable keychords. – Gilles 'SO- stop being evil' Aug 03 '15 at 07:54