On Wed, May 12, 2010 at 10:37 AM, Edward K. Ream <[email protected]> wrote:
> At last we move on to real documentation. Let's begin by looking at
> leoEditCommands.py.
The following continues the documentation for leoEditCommands.py. The
third and final part will discuss classes one-by-one. This ensures
that I review every line of code, at least in so far as to discuss
things that I didn't initially remember but that seem important once
my memory is refreshed.
===== Text wrappers
Text wrappers allow Leo's core to be gui-independent. Instead of
containing Tk or Qt code, the code in leoEditCommands.py calls methods
of the baseTextWidget class in leoFrame.py.
Originally, the baseTextWidget class was a thin wrapper for Tk
methods. However, this is no longer so true. In particular, the
index arguments that can be passed to the wrapper methods must now be
Python ints rather than Tk index expressions (strings). As a useful
exception, however, the wrapper methods can take the string “end” to
mean len(s) -1, where s is the widget's text.
The question is, would Leo be better off without these wrappers? In
my mind, the answer is, “absolutely not.” There are several reasons
why I say this:
1. In practice, it is very easy to use the wrapper methods. They are
*relatively* easy to understand, and Leo's typing completion
understands that w is a member of baseTextWidget. Thus, with tying
completion on, I merely have to type “w.a” to see this list:
after:method
after_idle:method
appendText:method
2. The wrappers simplify the code. Consider this code in leoGui.py, the node:
Qt gui-->@thin qtGui.py--> << define text widget classes >>--> <<
define leoQTextEditWidget class >>-->Widget-specific overrides
(QTextEdit)-->insert (avoid call to setAllText)
It would be odious to put this code anywhere in Leo's core, even as a
helper method. This code is pure gui code.
Wrappers also simplify the code in another, more important way: they
allow a common interface to different kinds of widgets. In
particular, both headline and body text widgets have exactly the same
interface. In both the Tk and Qt widgets, the actual headline and
body text widgets are different *kinds* of widgets. It would be
horrid to write different code for commands depending on the whether
the command applied to headline or body text. Exactly this scenario
would arise if wrappers went away.
3. The wrappers are important performance optimizations. As the
headline “insert (avoid call to setAllText)” indicates, it would be
wrong to try to “simplify” the code in leoEditCommands.py by having
commands just compute the resulting text and then calling
w.setAllText. This would have detrimental affects on syntax
coloring. In other words, the lower-level calls to methods like
w.insert, w.delete, etc, are important performance optimizations.
To summarize, the wrapper classes provide the following important
benefits to Leo:
- They are easier to use than native widgets because the autocompleter
understands them.
- They simplify Leo's core by providing natural places for gui-specific code.
- They provide a single, consistent interface to different kinds of widgets.
- They provide lower-level interfaces to the gui which helps the
syntax colorer greatly.
- They naturally make Leo's core gui-independent, at *no* significant
execution cost.
However, wrappers do impose real costs: they are harder to understand.
In particular, the following subclasses always require work when I try
to modify them:
- The search classes, namely minibufferFind and searchCommandsClass.
- The spell classes, namely spellCommandsClass and spellTabHandler.
When changing the code in these classes, I have to enable traces to
see which code is being executed. It's tedious, but I see no
alternative. (Note, these classes are not text classes, but I might
well cover them here because their intellectual cost is similar to
that of the text wrappers, if not more so.)
Imo, it would be a *huge* mistake to remove these classes, no matter
how hard they are to understand. Indeed, these classes make it
possible to use common base classes in leoFind.py. Without these
adapter classes, one would be forced to duplicate way too much code.
The following gui classes also are hard to understand, but essential
nonetheless.
- The qui classes in qtGui.py, namely leoQtBaseTextWidgetClass and all
of its subclasses.
- The baseNativeTreeWidget class in baseNativeTreeWidget.py.
Another cost of wrapper classes is that they require unit tests to
ensure that subclasses implement all the methods of their base
classes. These classes reside in unitTest.leo in the tree whose root
is:
All unit tests-->General-->Check base classes & ivars
Several of these unit tests use lists of required members, namely
mustBeDefinedOnlyInBaseClass, mustBeDefinedInSubclasses,
mustBeDefinedInHighLevelSubclasses and mustBeDefined. All these
lists are defined in leoPy.leo, in the node:
Code-->Gui Base classes-->@thin leoFrame.py--><< define text classes
>>-->class baseTextWidget-->Birth & special methods (baseText)
You could call these lists a workaround for Python's not having proper
interface declarations.
That's about all one has to know/remember about text wrappers. The
short summary:
- leoFrame.py defines the base classes for all text wrappers,
including the “must be defined” lists.
- leoEditCommands.py defines subclasses that tailor the base classes
for particular commands.
- leoGui.leo defines subclasses that give native gui widgets a uniform
interface.
- unitTest.leo contains unit tests that ensure that subclasses
implement all the required methods of the base classes.
===== Old Tk indices
At present, several commands work only with Tk, or not at all. You
can find these by searching for ### in leoEditCommands.py. For
example, swapHelper contains the following code:
w.delete(find,'%s wordend' % find) ###
This works in Tk because an expression like “5.6 wordend” is a valid
Tk character index. I'll say more about character indices later in
the documentation for leoGlobals.py, but for now all you need to know
is that leoEditCommands.py contains some vestigial remnants of Tk
isms.
===== A complexity: interface with the minibuffer.
The minibufferFind class is complex primarily because it must be that
way, not because it is a wrapper class. It is *essential* that future
maintainers understand that this complexity is in no way a packaging
issue, nor is it any way due to stupidity or laziness on my part :-)
In fact, the present code is a *huge* improvement (for the user)
compared with LeoUser's original prototype code. This is *not* a
criticism of LeoUser or his code. His code pushed Leo forward in
precisely the best way: he created an initial proof of concept. I
then spent over a year polishing and rewriting the code.
Let us take a look at just one of a very large number of possible examples:
def searchWithPresentOptions (self,event):
k = self.k ; tag = 'search-with-present-options'
state = k.getState(tag)
if state == 0:
self.setupArgs(forward=None,regexp=None,word=None)
self.stateZeroHelper(
event,tag,'Search: ',self.searchWithPresentOptions,
escapes=[self.replaceStringShortcut])
elif k.getArgEscape:
# Switch to the replace command.
self.setupSearchPattern(k.arg) # 2010/01/10: update the
find text immediately.
k.setState('replace-string',1,self.replaceString)
self.replaceString(event=None)
else:
self.updateFindList(k.arg)
k.clearState()
k.resetLabel()
k.showStateAndMode()
self.generalSearchHelper(k.arg)
You need not study this in great detail. All you need to know is the following:
1. It is a state machine interacting with leoKeys.py. It must be a
state machine in order to deal with the sequence of characters that
the user types.
2. This state machine works *regardless* of the key bindings that the
user specifies. LeoUser's code contained nothing but hard (Tk)
bindings.
3. This state machine uses k.getState and (indirectly via
stateZeroHelper), k.getArg. I'll discuss these methods in greater
detail when discussing leoKeys.py and Leo's key handling. Here, all
you need to know is that k.getArg gets a *sequence* of characters
until the user hits return or some other character that ends the
argument.
This code is about as simple as can be imagined, given that it handles
dozens of fit-and-finish issues that most users never dream exist.
And that's just the point: the code hides all the complexity from the
user. The user just sees a minibuffer. Hey, how hard could that be
to do?
Each particular command has its own quirks, but each follows the same
general pattern, as you will see by comparing code. In particular,
each command uses k.getState and k.getArg, directly or indirectly.
Let me conclude with a plea: If you want to understand the code,
enable traces or set a breakpoint and execute the command in question.
Please, please, please do this before making any judgments about the
code. I put a ton of work into it. The code simplifies how the code
works for the user. It will be *much* easier to understand and adapt
the code than to rewrite it from scratch.
Edward
--
You received this message because you are subscribed to the Google Groups
"leo-editor" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to
[email protected].
For more options, visit this group at
http://groups.google.com/group/leo-editor?hl=en.