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: Sun, 16 Mar 2008 14:00:03 -0700
User-agent: Thunderbird 1.5.0.5 (X11/20060808)

Stefan Monnier wrote:
I do not follow you, Tom: how would the right arrow magically forget
your tentative mark? 


There are three, per-buffer variables:

    tentative-mark:
        the "other side" (besides the point) of the current,
        shift-selected region, as far as primitive operations
        on buffers are concerned.    The tenative-mark, if not
        nil, combines with the point to create a "fat cursor" --
        e.g., primitive insertion operations delete the contents
        of the fat cursor before inserting.

    maybe-preserved-tentative-mark:
        The value of tentative-mark as last observed by the
        user.  That is, before every interactive command
        invocation, the value of tentative-mark is unconditionally
        copied to become the value of maybe-preserved-....

    preserved-tentative-mark:
         The value that, in the opinion of the currently running
         command, should become the value of transient-mark
         after the command completes.   By default, this is nil.
         Command loops, when interactively invoked commands
         return, unconditionally copy the value of perserved-tentative-mark
         to tentative-mark.

So, suppose I type S-right-arrow, invoking shift-select mode.
That invokes a generic function -- let's dub it treat-as-shifted-sequence.
That function creates a marker at the point and sets preserved-tentative-mark
to that new marker.   Then it looks up what the unshifted sequence right-arrow
is bound to and runs that command.   That command (forward-char) is
ignorant of the three variables so it doesn't change them at all.   When
control returns to the command loop, the value of preserved-tentative-mark
is copied to tentative-mark -- so now there is a tentative-mark (start of a
shift-mark region) set.

Typing S-right-arrow again has almost the same effect.   Instead of
creating a new marker, the generic function treat-as-shifted-sequence
notices that, this time, maybe-preserved-tentative-mark is non-nil.
Rather than create a new mark, it just copies that value to preserved-....
So, the tentative mark (start of shift region) is preserved.

But then you type just right-arrow, with no shift.   When invoked,
tentative-mark and maybe-preserved-.... are still both set to that
original mark, but preserved-tentative-mark has its default value of
nil.   The right arrow binding (forward char) is still ignorant of those
three variables.   It changes nothing.   When control returns to the
command loop, preserved-... (which is bound to nil) is copied to
tentative-mark






 In what way is that different from setting
mark-active to nil?  

There are two aspects to the answer:  cleanliness and
semantics.

Cleanliness: the three variable proposal needs a *tiny bit*
of new code in the C part of Emacs.   In exchange, it
doesn't need transitive-mark or delete-mark... elisp code
at all (because the default behaviors are better).

Cleanliness again:  In the three variable system, most
commands DTRT by default, even if they remain ignorant
of the three variables.

Semantics:  the three variable model is sensitive to
"cycle phases" in user interaction.   maybe-perserved-...
is a reliable source of the value of the tentative mark as
of the time the user last saw it.    preserved-.... is
how function control what the value of the tentative
mark will be when the user next sees it.   tentative-mark
itself is the value honored by primitives.    Making
those distinctions has a side effect: the default behaviors
comes out correctly, for free, and the non-default
behaviors are trivial to implement in generic ways.
In contrast, just having a binary distinction between
an active and inactive mark means having to make those
other distinctions harder to implement -- lots of functions
have to be modified because that's the only way left
to make those distinctions.   It's *why* you're ending up
thinking about distinguishing "motion commands" and
the like.   Maybe the simple form is just: "I dunno. 
The three-variable implementation is just cleaner.
It happens to hit a sweet spot that way.   It just *is*."



I have the strong impression that you do not know
what is the "temporary" form of transient-mark-mode" (this is
a transient-mark-mode that's activated until the region is "consumed")
  
You are right that I don't know about that.  I poked around in simple.el
a little to see if I could find what you meant but I didn't.  So, please point
me to the code.

That aside, first you had to invent "motion" commands, and now there
is another category of commands that are "consuming" commands.  Ugh.
This proliferation of categories is the price for using a binary "active"
flag rather than the three-variable system.    How are those categories
supposed to compose, anyway?   That is, if I call a "consuming" command
as a function, does that make my command also "consuming"?    This seems
fragile and hard to control.


and neither do you know about the "only" form of transient-mark-mode
(that is a form of transient-mark-mode which is deactivated after the
very next command).

  

You keep misunderstanding me.   I understand (enough) about how transient
mark mode models "activation".   What I'm saying in response to that design,
not in ignorance of it, is that "activation" is the wrong model and that the
three-variable model is the model users are thinking of.   And the three variable
model seems to come out cleanly in code.   And the three variable model extends
and complements the traditional Emacs mark stack.

There's a funny bit in the Emacs documentation:

     Novice Emacs Lisp programmers often try to use the mark for the wrong
     purposes.  The mark saves a location for the user's convenience.
     Most editing commands should not alter the mark.

I'm not, by a long shot, calling you a novice.   Really.   But the
admonishment of that bit in the documentation applies to this
problem in a subtle way.

The marker (not mark but marker) which is set when a user is
shift-selecting:  it does not, in GUIs, act like a "location [saved] for
the user's convenience".    Yes, it's a saved location.  Yes, it's a convenience
feature.   But commands don't actually *use* that marker in the same
way that they use traditional Emacs *marks*.

The boolean "active" flag is an attempt to turn the top of the
traditional mark stack into a discriminated union.   When that flag is
set, the marker on the top of the mark stack is supposed to behave
"like the beginning of a shift-selected-region".   But that's ugly and
complicated because it changes the model that commands are "used to"
about what the mark stack means.  The mark stack is a saved location
for the user to pop back to -- except when it isn't because it's a saved
location for the very different behavior of a shift-selected region.

The three-variable model -- the concept of a "fat cursor" that is
sensitive to the cycle phases of interaction -- is exactly what all those
other GUI programs do and it is quite consonant with traditional Emacs.
It's a much simpler approach.



  
What users expect, in the traditional GUI paradigm, is a kind of
mark that doesn't toggle between "activated" and "deactivated" but,
rather, is always active when present but which commands tend to
cause to go away entirely.   Let's dub that "the mistake".  The
mistake is adding an "active" flag instead of a separate tentative
mark.
    

In what way is it different, really?  The problem is how/when to
activate/set it and how/when to deactivate/unset it.  Whether it's
a flag or a mark makes no difference.

  

It makes a difference in terms of the amount of existing code that
needs to be modified.   In the three variable system, most existing
code can remain ignorant of the three variables -- TRT happens by
default.




  
It's *because* of the mistake that, for example, Stefan is now
positing the existence of a formal category of "motion commands"
that have to be modified to call a function that dynamically
asserts that they are motion commands.
    

??? The notion of "motion commands" is needed because the behavior we
want is "when a motion command is bound to a non-shifted key but is
activated via the shifted of that key, we want the motion to select
a region".
  

Better (more consistent, simpler) is:  "when a command is bound to a non-shifted
key but is invoked via a shifted sequence, we want the shift-select region to
remain active during that command".   There's no need to talk about "motion".



Try it in your typical GUI editor: S-arrow will select a region, but S-a
will just insert an upper-case A.
  

That's different.   S-A (at least conceptually) has a non-default binding.  It doesn't,
by default, "insert 'a' and be in shift-select mode".  

Not many modes have "hyper" bindings.   A good test for a design might be how
easy it is for eccentric users to say "Ok, I want 'shift-select' functionality but, in Emacs,
I'd prefer it via the hyper key, not the shift key".   So, H-S-A would, very reasonably,
insert an upper case 'A' and be in shift-select mode.

(I was thinking, personally, that I might use a foot pedal for shift-select mode.)
I.e. this need is 100% unrelated to the way the selected region is
implemented and when/how it gets de-selected.

  

It's true that a traditional mark stack plus a binary variable can be used
to emulate the three-variable solution.   However, that emulation requires
far broader and more conceptually dissonant changes than the three variable
solution.

-t






        Stefan



  


reply via email to

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