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: Sat, 15 Mar 2008 13:59:25 -0700
User-agent: Thunderbird 1.5.0.5 (X11/20060808)

Kim F. Storm wrote:
Now, before you settle on a solution, maybe you should also consider
how to handle the delete-selection-mode in a sensible way without
using the current pre-command-hook + command property approach.

Tentative marks simplify that too:

Suppose there is a global variable, tenative-mark,
which is always either nil or a mark.

The ordinary mark stack also exists.

The region between tenative-mark and point is
called the tentative-region.

save-excursion should save and restore the value
of tentative-mark, setting it to nil when running
its body.

Low level functions (in C) that insert text into
a buffer should unconditionally delete the tentative
region first, if there is one, and set tentative-mark
to nil.  Conceptually, this is a generalization of the
notion of a "point".  A point is an index naming
a theoretical "space between" two adjacent
characters in a buffer, or the space at the beginning
or end of buffer.   A "tentative region" is like
having a pseudo-point which indicates a conceptual
"space between" in a hypothetical buffer that results
from deleting the contents of the tentative region.


That is the essential functionality that
delete-selection-mode is aiming for but, by adding
the concept of a tentative-mark, that essential
functionality is simplified enough that it can
reasonably be built-in as unconditional behavior
of a few insertion commands.

There should be a a second and third variable:
maybe-preserved-tentative-mark and preserved-tentative-mark.
These are, again, either nil or a mark.   These are used
to control which commands preserve the tentative
mark vs. which set it back to nil (the default behavior):

When a command returns to a command loop, the
value of preserved-tentative-mark should be copied
to tentative-mark, unconditionally.  Both maybe-preserved-... and
preserved-... should be set to nil.    So, by default,
commands implicitly "clear" the tentative mark.

Before a command loop invokes a command, it
should first copy tenative-mark to maybe-preserved-...
That way, if a command wants to preserve the tentative
mark if it is already present (the default interpretation of
"shifted keysequences") then it can simply copy
maybe-preserved-...  to preserved-tentative-mark.
When the command returns, that saved mark will be
copied back to tentative-mark.

The function ensure-shifted-mode does this:

  if tentative-mark is nil,
    create a marker at the point
    set tentative-mark and maybe-preserved-... to that marker
  return

The function (call-interactively-preserving-transient 'X)
does:

   ensure-shifted-mode
   copy maybe-preserved-... to preserved-...
   call 'X interactively
   return

The command treat-as-shifted-sequence does this:

   unshift the invoking sequence
   push the new sequence back to input
   read a complete key sequence, selecting a command X
   call-interactively-preserving-transient X
   return

So, treat-as-shifted-sequence is a reasonable default binding
for shifted control and meta-keys or common sequences of
those, at least for users who want shift-select to happen.

The function returned by (treated-as-shifted-function 'X)
does this:

   call-interactively-preserving-transient 'X

and can be used to create bindings for key-sequences
that should enter "shift select mode" but that aren't
handled by default bindings to treat-as-shifted-sequence

I guess that the command yank would need to be modified.
I think that the modified version is simply

 copy maybe-preserved-... to preserved-...
 yank as before

Probably there are a small number of similar commands.

Another category of commands that will initially
behave oddly until modified are commands that
modify the buffer mainly elsewhere from the current
point.    replace-string is a decent example.   Those
can be fixed-on-demand to be:

   set transient-mark, maybe-preserved-..., and preserved-...
     all to nil
   behave as before

The display code can (or should unconditionally?)
highlight the transient region.

So, for example...

S-M-C-f, presumably invokes treat-as-shfited-sequence
and so selects the following word as part of the transient
region.   Repeating that extends the region.

Right-arrow, not invoking treat-as-shifted-.... implicitly
cancels the transient region.

On the other hand, typing 'x' ultimately calls a low-level
insert function with no save-excursion between the command
loop and that call.   That insertion automatically deletes
the transient region before inserting the x.

So, those basic things work right.

Similarly, instead of typing 'x', I can invoke the modified
yank.  If no transient-mark was set yank behaves as it
does today.   If a transient-mark is set, and so
maybe-... was set when the command was invoked,
then yank both (implicitly) deletes the transient region
*and* preserves the transient mark (so the pasted content
is the new content of the transient region).

What about something tricky like replace-string?   Using
only the above rules, with all of their unconditional properties,
they'll do something kind of dumb:  at their first "insert"
that happens to be outside of any save-excursion they'll
delete everything from the transient-mark (if present) to
the point.   A small core of those has to be fixed from the
start.   Obscure cases can be fixed on demand with a
one-line fix.  Either:

     (nilify-transient)  ; sets all three variables to nil
     (old-code)

or

(ignoring-transient . old-code) ; save-excursion and nilify transient for body

My *guess* is that the second choice is nicer most times but that
both are handy to have.


So:

three new variables
one new display feature (clone of an existing feature)
a couple unconditional variable copies in the command loop
a few new functions
unconditionally delete the transient region in some C code
modify some core functions like yank and replace-string
fix obscure commands that work similarly to yank or replace-string on-demand
lots and lots of code that doesn't have to be modified at all
a very simple "state machine" model of the behavior
a behavior that highly agrees with "all those other GUIs"


For extra special featurefulness, you could build on that
by adding a "transient region ring" -- a circular buffer that
remembers the last N transient regions whenever some
command nillifies the transient region at the top level.
A "remembered" transient region is a pair of marks:
the transient-mark and a marker at what was then the
point.

N should probably be small but the idea is that recent
transient regions are cached so that if the region is
accidentally cleared by some command then a user
can likely restore it, easily.

So, in terms of "pronouns" again -- "addressing modes"
for users -- the mark stack gives you a set of pronouns
for recursing while the transient region ring gives
you augmented short-term memory about "fat cursors".

I'm really surprised, as I've been led to think about
these features by the recent discussion, just exactly
how complementary "GUI-style shift-select" and
Emacs' user model and command interaction loop
turn out to be.   They fit together like hand and glove,
if only Emacs will actually implement it that way.

I'm one of those users who is both an Emacs power user
*and* make pretty heavy use of GUI-style apps.   I constantly
have the low-level frustration of how selection, cut, paste,
etc. work differently between these environments:  often typing
or mousing into some app as if it were Emacs or into Emacs as
if it were some other app.   So, I feel pretty viscerally the
pain that work on shift selection is trying to address.
I'm pretty convinced the little state machine model
I've been developing here is a really good model for the
desired functionality.   So, I'm skeptical that all the hair
of trying to use transient-mark-mode for this stuff is
a good idea.

-t





reply via email to

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