SimpleActionTutorial - robmcmullen/peppy GitHub Wiki
In the HelloWorldTutorial, you saw how to add a trivially simple action to the peppy menu system. In this tutorial, you'll see how to add a slightly more complicated action that could form the basis for a real action.
First, please read the HelloWorldTutorial because there are some explanations that won't be repeated here (unless I really want to belabor some point). Also, you could consult the MenuOverview if it was complete (which it isn't currently -- FIXME!) and you may as we go along want to refer to the API documents for:
The first question to ask is: what kind of major mode will this action operate with? For this example, we'll work with the FundamentalMode (and by default, all of its subclasses). That means we're operating with a `wx.StyledTextCtrl`, or typically in peppy, the `PeppySTC` (which is a subclass of `PeppyBaseSTC`)
As an example of some of the higher level functionality that peppy's action subclasses can provide, we're going to add an action that operates on the current selection. If there is no selection, the action will be disabled, and if there is a selection the action will be enabled in the menu system.
The action itself will be to bookend the selected text with the word "TESTING".
Like the HelloWorldTutorial, creating an entirely new plugin is beyond the scope of this tutorial, so let's work in the same file as before: `peppy/plugins/tutorial_plugin.py`
For a complicated algorithm, it's best to test it outside of the peppy GUI itself, just so you can make faster progress. The cycle of starting the GUI, testing, changing the code, and restarting does take a while.
In this case the algorithm itself is pretty simple. Using built-in python string methods, we can perform the operation like this:
newstring = "TESTING" + existingstring + "TESTING"
Instead of basing the action on `SelectAction`, we can use one of the convenience classes in peppy.actions.base module: RegionMutateAction. This base class already handles the boilerplate stuff of getting the text from the highlighted region and handling the `isEnabled` method: essentially all we have to do is plug in the algorithm.
Looking at the inheritance hierarchy of `RegionMutateAction`, you can see that it's made up of the classes TextModificationAction, BufferBusyActionMixin and `SelectAction`.
`TextModificationAction` provides the `worksWithMajorMode` method to assure that the action is only available when the major mode is FundamentalMode or one of its subclasses.
`BufferBusyActionMixin` provides the `isEnabled` method that assures the buffer isn't being modified by another thread (advanced: don't worry about this at the moment) and provides another hook called `isActionAvailable` to override in subclasses that want more specific control over the enabled state.
`RegionMutateAction` provides several things:
- an implementation of `isActionAvailable` that enables the action when a selected region is shown in the STC
- an implementation of `action` that takes care of some boilerplate stuff like gathering the text in the selection region
- the method `mutate` designed to be overridden by subclasses to perform the operation on the selected text
As with the HelloWorldTutorial, we'll use the `peppy/plugins/tutorial_plugin.py` file as the source file in which to add our new code. In the file, we're going to create a subclass of `RegionMutateAction` that will hold our new action.
The name of the new class should be unique within peppy, because the name of the class will be converted to a string and used as an identifier of the action. The uniqueness check is is not automatically performed in peppy yet (see ticket #281 for updates) so currently you'll have to manually scan the code or create names that you believe will be unlikely to be duplicated.
You'll also have to determine the location in the user interface where the new action will be placed. For now, let's only add a menu item. It could be added to an existing menu as listed in the MenuOverview or an entirely new menu; the menu system will automatically add new menus if it hasn't encountered them before.
To place it in the "Transform" menu, we need to add the class attribute `default_menu` with a tuple containing the name of the menu and its relative position. We should also add the class attribute `name` to specify the string that will be placed in the menu bar -- if this isn't specified, the name of the class will be used instead.
The first version of the action might be:
from peppy.actions.base import *
class TestingBookendAction(RegionMutateAction):
name = "Bookend Region"
default_menu = ("Transform", 955)
def mutate(self, txt):
txt = "TESTING" + txt + "TESTING"
return txt
where we've added the import of `peppy.actions.base` since it wasn't imported by `tutorial_plugin.py`. We also need to broadcast to peppy that this action is available, so we need to add this class to the `getActions` method of the `TutorialPlugin` class:
def getActions(self):
# You can return a list or yield the items individually
return [InsertHelloWorld, TestingBookendAction]
where we've added the class to the returned list.
Save the file, and we're ready to test.
Since this action needs a major mode based on `FundamentalMode`, you'll have to open some sort of text file when testing peppy. Try:
python peppy.py -t READMEand first examine the console to see if the tutorial plugin loaded successfully. If so, the action should appear in the Transforms menu. But because we've based the action on the `RegionMutateAction`, the action will be disabled in the menu bar until a region is highlighted. Once a region is highlighted, the action will be enabled and can be selected. See if it works!
Now, let's add a keybinding for this command. This is done by adding a class attribute `key_bindings` that specifies the strokes used to trigger the action. KeyboardProcessing has more details, but basically `key_bindings` is a dictionary that maps the platform name to the keystroke combination.
Keystroke customization for platforms is possible and covered in more detail in KeyboardProcessing, but we'll just use the same keystroke for all platforms. This is possible by specifying a 'default' platform in the `key_bindings` dictionary. Here's the full code for the second version of the action:
from peppy.actions.base import *
class TestingBookendAction(RegionMutateAction):
name = "Bookend Region"
default_menu = ("Transform", 955)
key_bindings = {'default': 'C-F8'}
def mutate(self, txt):
txt = "TESTING" + txt + "TESTING"
return txt
The only change from the first version is the addition of the `key_bindings` line that specifies the `default` platform, and they keybinding of the function key F8 plus the modifier Control (on unix and windows) or Command (on the Mac).
Running peppy again should show the keybinding in the menu. Note that the keybinding is subject to the same enabled criterion that the menu entry is: if there isn't a region highlighed, the keybinding won't function.
coming soon
There are several other convenience classes in peppy.actions.base that can be used to build actions quickly. They include WordOrRegionMutateAction, LineOrRegionMutateAction, and ParagraphOrRegionMutateAction.
`ParagraphOrRegionMutateAction` in particular is interesting, because if a region is not selected it uses the current major mode's idea of a paragraph to determine the text to operate on. This is an example of the power of peppy: `FundamentalMode` defines a method called `findParagraph` that can be overridden in subclasses. So, the same action can be used on different major modes, each of which can have their own idea of what delimits a paragraph.
For working examples of all of the available convenience classes, see the peppy.plugins.text_transforms.py module.