|
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*." You are right that I don't know about that. I poked around in simple.elI 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") 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 |
[Prev in Thread] | Current Thread | [Next in Thread] |