On 2011-02-28 21:57 +0100, Frank Fischer wrote:
On 2011-02-28 21:45 +0100, Frank Fischer wrote:
On Mon, Feb 28, 2011 at 04:23:21PM +0100, Vegard Øye wrote:
If it is possible to reliably distinguish linear changes from non-linear ones in `after-change-functions', we can choose the appropriate representation accordingly. Any invocation of, e.g., `self-insert-command' will be represented as a linear change, while anything that doesn't fit the linear mold is represented as keystrokes.
Well, but must "linear changes" are very easily covered by the keystrokes version (e.g., insertion of "abc" is just "abc"). Others are problematic (note that vim-mode's repeat system is keystroke based and linear changes have never been problems).
I am replying to both of your last two posts. My point is that auto-completion /is/ a linear change. In fact, disregard the word "linear" (probably the most abused word in the mathematical dictionary), and try to think of a series of completion commands as a kind of "bumbling typist": "Now, what is that word I want to type, 'abacus'? So, a-b-a-c-u-s [6 letters]. No, that's not right, although the word does start with 'ab': Backspace, Backspace, Backspace, Backspace [4 times]. How about, hm, 'abbreviation'? b-r-e-v-i-a-t-i-o-n. No, that's not exactly right either ..." It's not as intimidating now, right? Since we only look at the actual /changes/, there are no nasty time delays or special keys to reckon with. It's just typing. This "bumbling typist" only presses letter keys and Backspace; he can either insert text from the left to the right, or delete text from the right to the left. Thus, he doesn't know about arrow keys (he cannot move the cursor directly). Nor does he press Delete (he can only delete text in one direction). If all vi users were "bumbling typists", we wouldn't need a keystrokes-based system; recording the inserted text would be sufficient. To compare the two approaches, Vim doesn't repeat paired delimiters ("(" and ")" around point), but it handles completion well. The keystrokes-based approach does repeat paired delimiters, but it goes "too far" with completion. So, could we combine their strengths and make the system fall back on old-fashioned insertion when the user calls a completion command? It seems we can, by monitoring changes from `after-change-functions'. If the current buffer changes /could/ have been performed by a "bumbling typist", they are recorded as insertion. If not, the keystrokes are recorded instead. Things the "bumbling typist" can do: * Insert text directly before point * Delete text directly before point * Insert text, delete some of it, and insert more (completion) Things the "bumbling typist" /cannot/ do: * Move point * Change the buffer * Save the buffer * Delete text after point * Insert text after point * Delete text not directly before point * Insert text not directly before point If the command does something from the first list and /not/ anything from the second list, then (and only then) it's recorded as insertion. Otherwise, it is recorded as keystrokes. So, we should automatically get completion in the first category, and paired delimiters in the second.
the first three arguments should be (BEG END &optional TYPE), and maybe a fourth one for the register. The define macros should let one specify further arguments, though.
Perhaps the order of arguments depends on the order of the interactive specification.
Yeah, and the `define-operator' macro should let one add code to that specification (not replace it, just augment it). Actually, `vimpulse-define-operator' allowed for this, just not in a very obvious way. If the body contained an `interactive' form, the return value of that form (which must be a list) would be concatenated with the calculated values for BEG and END. E.g., (vimpulse-define-operator vimpulse-insert (beg end &optional arg) "Insert before point." (interactive (list current-prefix-arg)) ...) This would roughly expand to the following function definition: (defun vimpulse-insert (beg end &optional arg) "Insert before point." (interactive (append (vimpulse-calculate-beg-and-end) (list current-prefix-arg))) ...) There might be a better syntax for this, but the functionality is there.
Btw, what about commands like , and ;. The do not have a specific type. Their type may be either explicit or implicit depending on the previous search command.
Good point. "}" is another motion which, depending on the circumstances, may use the `line' type instead of `exclusive'. I ended up with a global variable for the current type, which is initially set when the motion is executed, but may also be changed by the motion itself. The final value is used for the type when the buffer positions are normalized.
There is one case where I cannot see any other solution than a blacklist, though: normalizing Emacs' region to the Visual selection. We have to do this /before/ the next command is called (so we can't inspect its behavior), and we don't want to do it if the next command is a motion.
I do not understand. You can inspect the command in a pre-command hook and normalize the region accordingly or what do you mean? Of course, this only works if the commands use the interactive statement nicely.
You can inspect the command, but you can't really know what it is going to do. You could check out `interactive-form' to get a clue (which I tried), but the problem is that the form turns into mush if the function is compiled. Also, the command's behavior may be contingent on the activation of the region (e.g., skeletons). Consequently, there is no reliable way to identify region commands, at least not before the command is executed. It /is/ possible to identify all motions defined with the `define-motion' macro, though. :)
Perhaps we should just give define-motion not only the ability to define new motions but also the extend old motions with the necessary meta-data?
Yes. Very much yes. One should, for example, be able to specify that Emacs' own `next-line' and `previous-line' commands have a type of `line' (which I have, since I use them for "j" and "k" in my .emacs).
Well, I never cared about cl dependencies, I often used macros like when, unless, lexical-let and defun*. Imo cl is not really a bad dependency at least at compile time.
`when' and `unless' are provided by vanilla Elisp (in subr.el), cl.el just redefines them. `defun*' is for keyword arguments, which we'll probably not need. :) `lexical-let' provides lexical scope, which I've never used in Vimpulse. (I've only had one use for it: in my test framework, I needed to evaluate forms defined outside the evaluating function; regular local bindings could overwrite the form. What do you use `lexical-let' for? Closures?)
When you press "dw", "d" calls `delete', `delete' reads "w", "w" is bound to `forward-word', `forward-word' is executed to get buffer positions, and the positions are passed to the operator's arguments. Then the operator's body is executed.
It's just the difference to how vim-mode combines motions and operators. In vim-mode operator-pending is just a state as any other state, insert, normal or visual. When an operator is executed, the command's body is not executed immediately. Instead it is remembered for later use, then vim-mode switches to operator-pending mode and gives the control completely back to Emacs. When a motion is executed in operator-pending mode the stored operator is finally executed along with the motion. Similar things happen when a register is selected. In this case the register is stored and then vim-mode returns to normal mode and proceeds as usual.
If I understand vim-mode correctly, the motion is read and executed in another iteration of the command loop. In Vimpulse, everything happens immediately: "w" is read from the keyboard, the corresponding command is found with (key-binding "w"), and then it is executed. I ended up building a little "keypress parser" for handling counts as well. This is able to handle things that Emacs' command loop actually can't, such as "da2w". See below (search for `vimpulse-keypress-parser'): http://gitorious.org/vimpulse/vimpulse/blobs/master/vimpulse-operator.el Vimpulse does have a brief Operator-Pending state, which is entered before `key-binding' is called. When the motion is finished executing, the state promptly switches back to Normal. Thus, everything is done in a single iteration of the command loop.
Little question: do you really use Emacs's default forward-word? I ask because it behaves differently than Vim's.
One thing that annoyed me with Emacs' default command set is that there is no command for moving to the /beginning/ of the next word. With the exception of line movement, I don't use Emacs' commands. However, we may reuse Emacs' /concept/ of sentences and words. The `sentence-end-double-space' variable, for example, influences how sentences are understood. Should we honor such things, or should we invent our own system from scratch? -- Vegard