On Sunday, August 3, 2014 1:29:19 PM UTC-5, Edward K. Ream wrote:
>> ...keep revising code until it *obviously* is the simplest code possible.
>To make this scheme work, all key handlers must end in an acceptance
method: vc.accept, vc.delegate or vc.ignore.
> As a result, [the new] key handlers [will] know nothing about state
handlers and vice versa!
As discussed in another thread, rev ebacdc1... puts this scheme into
practice.
Here, I'll discuss these changes in detail, including complications that I
discovered yesterday and further simplifications that I'll make today.
1. In general, ending each command handler with an acceptance method has
been a great success. Acceptance methods clearly indicate what should
happen when a command handler ends.
Acceptance handlers hide the existence of the vc.next_func and
vc.return_value ivars from all command handlers. This makes each command
handler much cleaner in appearance. Furthermore, it is now possible to
tweak acceptance methods without changing command handlers in any way.
In other words, even though the acceptance methods follow the principle,
"explicit is better than implicit", they *also* hide implementation details
in a most useful way. It's the best of both "implicit" and "explicit".
2. Besides the **direct** acceptance methods, vc.accept, vc.delegate or
vc.ignore, vc.done & vc.quit, command handlers can also end with
**indirect** acceptance methods: vc.begin_insert_mode, vc.begin_motion,
vc.end_insert_mode, and vc.vim_digits. Indirect acceptance methods
(eventually!) call direct acceptance methods.
**Important**: command handlers can use different acceptance methods
(direct or indirect) in different branches of their code, provided that all
branches end in a call to exactly one acceptance method. In practice,
checking this requirement is easy. Furthermore, the checking code in
vc.do_state will warn if this requirement has not been met.
3. I did get one surprise: vc.do_inner_motion can't just call
vc.do_state(motion_dispatch_d,'motion') because vc.do_inner_motion must
call the after-motion callback, vc.motion_func. No big deal, except...
4. At present, vc.done calls vc.reinit_ivars, and that caused problems for
vc.do_inner_motion because vc.reinit_ivars wiped out the ivars needed when
calling the motion callback, including vc.motion_func itself!
The temporary hack was simply to save/restore the needed ivars in
vc.do_inner_motion. Happily, this morning I saw a cleaner, more explicit
way to init ivars if and when needed. This will simplify the init code, and
more importantly, clarify exactly what is happening and why.
To recap: the problem is that vc.reinit_ivars clears too many ivars.
Furthermore, the dot ivars are handled as special cases. The solution is
to define the following methods:
- vc.init_dot_vars()
- vc.init_motion_vars()
- vc.init_state_vars()
- vc.init_persistent_vars()
Doh! We have now clearly grouped *all* ivars into *disjoint* classes. We
can now define the following:
def init_all_ivars(vc):
'''Init all vim-mode ivars. Called only from the ctor.'''
vc.init_dot_vars()
vc.init_motion_vars()
vc.init_state_vars()
vc.init_persistent_vars()
def init_ivars_after_quit(vc):
'''Init vim-mode ivars after the keyboard-quit command.'''
vc.init_motion_vars()
vc.init_state_vars()
def init_ivars_after_done(vc):
'''Init vim-mode ivars when a command completes.'''
if not vc.in_motion:
vc.init_motion_ivars()
vc.init_state_ivars()
**Important**: it would be very bad design to call vc.init_ivars_after_quit
inside vc.init_ivars_after_done just because (for now!) the effect would be
the same. Glen Meyers calls reusing common code "code-level" binding. It
can cause all kinds of problems.
Instead, we want what Meyers calls "functional" binding. The (proper) code
shown above makes vc.init_after_quit and vc.init_after_done independent of
each other, regardless of what happens in future.
So this is good. We have now created a higher-level grouping of ivars that
makes clear what is intended. This scheme is simple and flexible: new
(disjoint!) groups of ivars can be created at any time. The new scheme is
yet another example of using abstraction as a design and coding tool.
===== Conclusions
Leo's vim code is now spectacularly different, both visually and
functionally, from the real vim's code. Wherever possible, Leo uses methods
to hide the blah, blah, blah of implementation details. Imo, the result is
*far* better design and code than vim's.
Imo, if vim were recreated today from scratch, it would be reasonable to
use Leo's vim-mode code as a starting point.
Edward
P.S. To emphasize what I said before in another post: Python encourages
simplifications that are, in practice, denied to C programmers. Leo's
dispatch dicts for vim mode are an example. Creating such dicts is
difficult in plain C, and non-trivial in C++. So C programmers don't
create those dicts and miss all the simplifying possibilities afforded by
them. There are many other ways that Python aids simplification while C
inhibits it.
EKR
--
You received this message because you are subscribed to the Google Groups
"leo-editor" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
Visit this group at http://groups.google.com/group/leo-editor.
For more options, visit https://groups.google.com/d/optout.