This is an engineering notebook post. It consists of notes to myself and other core developers, plus a brag in the post script. Feel free to ignore.
In the last four days, the vim-emulation code has consistently gotten simpler. This has kept my energy levels extremely high. It's been hard to stay asleep ;-)
When I awoke this morning, I saw how to make the vim-mode code dead simple. I'll revise the code today. The result will be much simpler than the real vim's main line code.
==== The problem
The present code works, and some programmers might be satisfied with it. However, it's tricky to know (and remember!) just how scanning works and just how the accumulating command (vc.command_list) and the previous dot (vc.dot_list) are updated.
In fact, it's not obvious that they *are* updated properly in all situations. The code feels fragile.
==== The solution
The solution is to apply the principle, "explicit is better than implicit" as follows:
1. The vc.handler var will contain the method used to handle the next incoming key. Initially, the handler will be the vc.do_normal_mode method.
vc.handler ivar collapses vc.do_key to something like this::
def do_key(vc,event):
'''Handle the next key in vim mode.'''
vc.init_scanner_vars(event)
vc.return_value = None
vc.handler()
return vc.return_value
This is, quite obviously, the simplest thing that could possibly work. As an internal check, I'll replace the return statement with::
if vc.return_value not in (True,False):
<< issue internal error message >>
return True
else:
return vc.return_value
2. Every handler must end with a (explicit!) call to one of the following **acceptance methods**:
vc.accept(handler,...)
# Add the key to the accumulating command.
vc.ignore(...)
# Ignore the key, issue a warning (by default) and retain all present state.
vc.done(handler,...)
# End the command and (by default) update the dot.
Every acceptance function sets vc.return_value to True or False, which is why the checking code at the end of vc.do_key will work.
By default, the acceptance functions set vc.return_value to True, indicating that vim mode has completely handled the key and that k.masterKeyHandler can just return.
Insert mode will call vc.accept(handler,return_val=False). As a result, k.masterKeyHandler will handle the key *exactly* as it does in non-vim mode.
That's it. The (explicit!) acceptance methods will collapse the complexity and increase the regularity of *all* the key-handling code in leoVim.py. It's a big step forward.
Edward
P.S. There are two kinds of handlers, **mode-handlers** and **command-handlers**.
vc.do_normal_mode will be a mode handler. It will look something like::
def do_normal_mode(vc):
'''Handle an outer normal mode command.'''
func = vc.normal_mode_dispatch_dict.get(vc.stroke)
if func:
func() # vc.do_key checks that func calls an acceptance method!
elif vc.is_plain_key(vc.stroke):
# ignore plain keys with a complaint.
vc.ignore()
else:
# Pass non-plain keys to k.masterKeyHandler
vc.accept(handler=vc.do_normal_mode,return_value=False)
The vim_d_... family of methods are examples of command handlers. In rough outline, they will become...
def vim_d(vc):
vc.accept(handler=vc.vim_d2)
# Accept the 'd' and let vc.vim_d2 handle the next character.
def vim_d2(vc):
if vc.stroke == 'd':
<< delete the line >>
vc.done()
else:
vc.handler=None
vc.begin_motion(after_motion_handler=vc.vim_d3)
# vc.vim_d3 will be called when the motion is completed.
assert vc.handler
# vc.begin_motion (or a helper!) must set vc.handler *now*.
def vim_d3(vc):
<< Complete the d command after the cursor has moved >>
vc.done()
# Properly updates the dot.
This is not completely trivial, but it makes explicit what must happen.
P.P.S. <brag>
I said in the post
https://groups.google.com/d/msg/leo-editor/5T71y-ePMSI/mOEXkVT4QpIJ"A big reason that Leo's code will be simpler than vim's own code
is the Python language. The goal is always simplicity, and the
resulting generality the simplicity makes possible. Well, it's almost
infinitely easier to see patterns in Python rather than C. That single perceptual advantage leads to better code."
Two comments:
1. I think it's fair to say that Leo also plays an important part in simplifying code :-) The ability to keep both the big picture and all the details constantly in view is crucial.
2. I am a mediocre programmer in most respects. But I have one great programming talent: the ability to keep revising code until it *obviously* is the simplest code possible. Later today, after the latest collapse in complexity, you will be able to see this talent to its full extent. Just compare Leo's vim-parsing code with the real vim's main line. You will be amazed. I certainly am :-)
</brag>
EKR