emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Shift selection using interactive spec


From: Thomas Lord
Subject: Re: Shift selection using interactive spec
Date: Thu, 13 Mar 2008 17:44:35 -0700
User-agent: Thunderbird 1.5.0.5 (X11/20060808)

A problem (in my view) with such an approach
is that transient mark mode has the wrong semantics
to implement shift-marking.

Transient mark mode is just about the appearance
of the display -- whether or not the current region
is highlighted.

Shift-marking and other kinds of popular-style marking
are about more than display -- it's about marks that go away
by default, unless you keep them.   You go into shift-mark
mode and that sets a "tentative mark".  Some commands keep
the tentative mark in place but, by default, most commands
automatically discard it.  It's gone without a trace.  Poof.

Sure, it's nice if the display reflects where tentative marks are
but the key thing is a mark that will be quickly discarded
unless the user takes (further) explicit action.

Many other applications feature *only* tentative marks --
they lack a "mark stack".  Emacs is fancier for having a
mark stack but note that the two ideas are complementary:
it makes sense to have an Emacs-style mark stack in which
the top entry can be toggled between "tentative" and
not tentative.

If you are familiar with HP-style calculators, like the HP-41...
tentative marks in emacs would work like result values stored
in register 0.   I.e., if you type "5 enter 7 enter +" then "12"
is left in register 0 but it could be described as a "tentative
12" because if you next type anything other than "enter" (or
similar) the "12" will be automatically discarded.  That's a
tentative stack value.

For example, suppose you are cutting and pasting between
two regions of a file, widely separated.  So, you have a mark
set and use C-x C-x to move back and forth between them (being
too lazy to set registers to those points ).   But, now you want
to mark some region in one part, C-xC-x to the other, mark
a region to replace, cut out the replaced stuff and drop in the
new stuff.   Good luck not getting confused about the state
of your mark stack if you aren't concentrating on it.

With tentative marks, though,  you go to one region, set a
tentative mark, copy out the stuff you want to paste elsewhere,
silently discard the tentative mark and C-xC-x to where you want
to paste, and there you go.   (This also points out that it might be
nice if yanking set a tentative rather than a full mark.  Ditto for
major motion commands like moving to the beginning of a buffer.)

Really, the exit stairs from my soapbox on this topic are around here
somewhere.  I'm sure I'll find them soon. :-)

-t



-t




Chong Yidong wrote:
Here is a prototype for implementing shift-selection using a new
interactive spec code, `^'.  It uses transient-mark-mode's `only'
setting as suggested by Miles and Johan.

I haven't changed all the movement commands, just enough of them to
play around with the shift selection.  (left, right, up, down, etc).

Currently, M-F doesn't DTRT, but this appears to be due to an existing
bug (feature?): if there is no mapping for M-F, Emacs 22 translates it
to M-f, while Emacs 23 translates it to f.  I haven't tracked down
where this change was made.

A few observations:

Firstly, if we adopt this approach, we have to work around the fact
that read_key_sequence automatically attempts translates unbound key
sequences by trying to shift them (e.g., changing S-right to right).
Here, I create a new global variable
this_command_keys_shift_translated, which is set to 1 by
read_key_sequence when it does this translation.  This tells
Fcall_interactively that the activating key sequence is indeed
shifted, regardless of what this_command_keys says.

The present code probably doesn't handle shifting/shifting properly
when Fread_key_sequence is called from Lisp, but that is easily
remedied.

Secondly, there are two C level functions, direct_output_forward_char
and direct_output_forward_char, which are called instead of
forward_char and backward_char if certain conditions are met.  This is
a redisplay optimization.  To get this to work, we must avoid using
these functions when appropriate.  In the present code, I don't call
them when this_command_keys_shift_translated is non-zero, but this may
need more analysis.

Thirdly, I'm not too happy about the variable separation in this code.
Here, callint.c pulls in this_command_keys_shift_translated and
this_single_command_key_start from keyboard.c, Qonly from frame.h,
Qidentity from casefiddle.c, and it needs to call intern ("shift") on
each invokation to get the symbol for `shift'.  If we end up going
with this approach, maybe someone could suggest a way to clean up
these variables.

Comments are welcome.


*** trunk/src/callint.c.~1.161.~        2008-02-21 11:10:47.000000000 -0500
--- trunk/src/callint.c 2008-03-13 18:48:05.000000000 -0400
***************
*** 121,128 ****
  If the string begins with `@', then Emacs searches the key sequence
   which invoked the command for its first mouse click (or any other
   event which specifies a window), and selects that window before
!  reading any arguments.  You may use both `@' and `*'; they are
!  processed in the order that they appear.
  usage: (interactive ARGS)  */)
       (args)
       Lisp_Object args;
--- 121,132 ----
  If the string begins with `@', then Emacs searches the key sequence
   which invoked the command for its first mouse click (or any other
   event which specifies a window), and selects that window before
!  reading any arguments.
! If the string begins with `^', and the key sequence which invoked the
!  command contains the shift modifier, then Emacs activates Transient
!  Mark Mode temporarily for this command.
! You may use `@', `*', and `^' together; they are processed in the
!  order that they appear.
  usage: (interactive ARGS)  */)
       (args)
       Lisp_Object args;
***************
*** 244,249 ****
--- 248,258 ----
      }
  }
+ extern int this_command_keys_shift_translated;
+ extern int this_single_command_key_start;
+ extern Lisp_Object Qonly;
+ extern Lisp_Object Qidentity;
+ DEFUN ("call-interactively", Fcall_interactively, Scall_interactively, 1, 3, 0,
         doc: /* Call FUNCTION, reading args according to its interactive 
calling specs.
  Return the value FUNCTION returns.
***************
*** 423,428 ****
--- 432,469 ----
        /* Ignore this for semi-compatibility with Lucid.  */
        else if (*string == '-')
        string++;
+       else if (*string == '^')
+       {
+         Lisp_Object *key = XVECTOR (this_command_keys)->contents
+           + this_single_command_key_start;
+         Lisp_Object *key_max = XVECTOR (this_command_keys)->contents
+           + this_command_key_count;
+         Lisp_Object shift = intern ("shift");
+         int shifted = this_command_keys_shift_translated;
+ + if (!shifted)
+           for (; key < key_max; ++key)
+             {
+               if (SYMBOLP (*key))
+                 shifted = !NILP (Fmemq (shift,
+                                         Fget (*key, Qevent_symbol_elements)));
+               if (!shifted)
+                 break;
+             }
+ + if (shifted)
+           {
+             Lisp_Object push_mark_call[4] = { intern ("push-mark"),
+                                               Qnil, Qnil, Qt };
+             if (!EQ (Vtransient_mark_mode, Qidentity))
+               Ffuncall (4, push_mark_call);
+             if (EQ (Vtransient_mark_mode, Qidentity)
+                 || NILP (Vtransient_mark_mode))
+               Vtransient_mark_mode = Qonly;
+           }
+ + string++;
+       }
        else if (*string == '@')
        {
          Lisp_Object event, tem;
*** trunk/src/keyboard.c.~1.947.~       2008-02-25 11:04:08.000000000 -0500
--- trunk/src/keyboard.c        2008-03-13 18:49:38.000000000 -0400
***************
*** 132,137 ****
--- 132,142 ----
  Lisp_Object raw_keybuf;
  int raw_keybuf_count;
+ /* This is non-zero if the present key sequence was obtained by
+    unshifting a key sequence (i.e. changing an upper-case letter to
+    lower-case or a shifted function key to an unshifted one).  */
+ int this_command_keys_shift_translated;
+ #define GROW_RAW_KEYBUF \
   if (raw_keybuf_count == XVECTOR (raw_keybuf)->size)                       \
     raw_keybuf = larger_vector (raw_keybuf, raw_keybuf_count * 2, Qnil)  \
***************
*** 1648,1653 ****
--- 1653,1659 ----
        Vthis_command = Qnil;
        real_this_command = Qnil;
        Vthis_original_command = Qnil;
+       this_command_keys_shift_translated = 0;
/* Read next key sequence; i gets its length. */
        i = read_key_sequence (keybuf, sizeof keybuf / sizeof keybuf[0],
***************
*** 1761,1767 ****
/* Recognize some common commands in common situations and
                 do them directly.  */
!             if (EQ (Vthis_command, Qforward_char) && PT < ZV)
                {
                    struct Lisp_Char_Table *dp
                    = window_display_table (XWINDOW (selected_window));
--- 1767,1774 ----
/* Recognize some common commands in common situations and
                 do them directly.  */
!             if (EQ (Vthis_command, Qforward_char) && PT < ZV
!                 && !this_command_keys_shift_translated)
                {
                    struct Lisp_Char_Table *dp
                    = window_display_table (XWINDOW (selected_window));
***************
*** 1801,1807 ****
                    direct_output_forward_char (1);
                  goto directly_done;
                }
!             else if (EQ (Vthis_command, Qbackward_char) && PT > BEGV)
                {
                    struct Lisp_Char_Table *dp
                    = window_display_table (XWINDOW (selected_window));
--- 1808,1815 ----
                    direct_output_forward_char (1);
                  goto directly_done;
                }
!             else if (EQ (Vthis_command, Qbackward_char) && PT > BEGV
!                      && !this_command_keys_shift_translated)
                {
                    struct Lisp_Char_Table *dp
                    = window_display_table (XWINDOW (selected_window));
***************
*** 9194,9199 ****
--- 9202,9212 ----
    /* Likewise, for key_translation_map and input-decode-map.  */
    volatile keyremap keytran, indec;
+ /* If we are trying to map a key by unshifting it (i.e. changing an
+      upper-case letter to lower-case or a shifted function key to an
+      unshifted one), then we set this to != 0.  */
+   volatile int shift_translated = 0;
+ /* If we receive a `switch-frame' or `select-window' event in the middle of
       a key sequence, we put it off for later.
       While we're reading, we keep the event here.  */
***************
*** 10113,10118 ****
--- 10126,10133 ----
          keybuf[t - 1] = new_key;
          mock_input = max (t, mock_input);
+ shift_translated = 1; + goto replay_sequence;
        }
        /* If KEY is not defined in any of the keymaps,
***************
*** 10154,10159 ****
--- 10169,10176 ----
              fkey.start = fkey.end = 0;
              keytran.start = keytran.end = 0;
+ shift_translated = 1; + goto replay_sequence;
            }
        }
***************
*** 10191,10196 ****
--- 10208,10215 ----
      }
+ if (shift_translated && read_key_sequence_cmd)
+     this_command_keys_shift_translated = 1;
UNGCPRO;
    return t;
*** trunk/src/cmds.c.~1.102.~   2008-02-01 13:47:24.000000000 -0500
--- trunk/src/cmds.c    2008-03-13 13:36:57.000000000 -0400
***************
*** 56,62 ****
    return make_number (PT + XINT (n));
  }
! DEFUN ("forward-char", Fforward_char, Sforward_char, 0, 1, "p",
         doc: /* Move point right N characters (left if N is negative).
  On reaching end of buffer, stop and signal error.  */)
       (n)
--- 56,62 ----
    return make_number (PT + XINT (n));
  }
! DEFUN ("forward-char", Fforward_char, Sforward_char, 0, 1, "^p",
         doc: /* Move point right N characters (left if N is negative).
  On reaching end of buffer, stop and signal error.  */)
       (n)
***************
*** 92,98 ****
    return Qnil;
  }
! DEFUN ("backward-char", Fbackward_char, Sbackward_char, 0, 1, "p",
         doc: /* Move point left N characters (right if N is negative).
  On attempt to pass beginning or end of buffer, stop and signal error.  */)
       (n)
--- 92,98 ----
    return Qnil;
  }
! DEFUN ("backward-char", Fbackward_char, Sbackward_char, 0, 1, "^p",
         doc: /* Move point left N characters (right if N is negative).
  On attempt to pass beginning or end of buffer, stop and signal error.  */)
       (n)
*** trunk/lisp/simple.el.~1.905.~       2008-03-10 21:57:09.000000000 -0400
--- trunk/lisp/simple.el        2008-03-13 13:39:17.000000000 -0400
***************
*** 3651,3657 ****
  If you are thinking of using this in a Lisp program, consider
  using `forward-line' instead.  It is usually easier to use
  and more reliable (no dependence on goal column, etc.)."
!   (interactive "p\np")
    (or arg (setq arg 1))
    (if (and next-line-add-newlines (= arg 1))
        (if (save-excursion (end-of-line) (eobp))
--- 3651,3657 ----
  If you are thinking of using this in a Lisp program, consider
  using `forward-line' instead.  It is usually easier to use
  and more reliable (no dependence on goal column, etc.)."
!   (interactive "^p\np")
    (or arg (setq arg 1))
    (if (and next-line-add-newlines (= arg 1))
        (if (save-excursion (end-of-line) (eobp))
***************
*** 3684,3690 ****
  If you are thinking of using this in a Lisp program, consider using
  `forward-line' with a negative argument instead.  It is usually easier
  to use and more reliable (no dependence on goal column, etc.)."
!   (interactive "p\np")
    (or arg (setq arg 1))
    (if (interactive-p)
        (condition-case nil
--- 3684,3690 ----
  If you are thinking of using this in a Lisp program, consider using
  `forward-line' with a negative argument instead.  It is usually easier
  to use and more reliable (no dependence on goal column, etc.)."
!   (interactive "^p\np")
    (or arg (setq arg 1))
    (if (interactive-p)
        (condition-case nil
***************
*** 4307,4313 ****
  (defun backward-word (&optional arg)
    "Move backward until encountering the beginning of a word.
  With argument, do this that many times."
!   (interactive "p")
    (forward-word (- (or arg 1))))
(defun mark-word (&optional arg allow-extend)
--- 4307,4313 ----
  (defun backward-word (&optional arg)
    "Move backward until encountering the beginning of a word.
  With argument, do this that many times."
!   (interactive "^p")
    (forward-word (- (or arg 1))))
(defun mark-word (&optional arg allow-extend)








reply via email to

[Prev in Thread] Current Thread [Next in Thread]