Vimpulse and vim-mode

Vegard Øye vegard_oye at hotmail.com
Tue Mar 1 14:59:11 CET 2011


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



More information about the implementations-list mailing list