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 3 '15 at 0: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 3 '15 at 0: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 3 '15 at 0: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 3 '15 at 5:05
  • Do you mean M-right instead of S-right? – Andreas Röhler Jun 3 '15 at 5:42
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.

  • 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 3 '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 4 '15 at 1: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)
  • 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 3 '15 at 7:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.