It been over 3 months – maybe you thought I had given up – but no. I had some holidays and other distractions, but I’m back and will be working fairly consistently for a while.
The topic for today is “Commands”. Commands are a standard interface for code in one module of the editor to call code in some other module. Various objects in the editor have commands attached to perform various tasks specific to that object. To some extent you can think of commands like object methods in an object-orient system, but that is only part of the story.
In many cases, commands are associated with names in a key-map. The key maps were hinted at in my previous note about managing input, but they are broader than that and details have changed since last I wrote. So maybe that is a good place to start.
A key map maps arbitrary strings to commands. They are currently implemented as a sorted array allowing easy binary searching.
All input keystrokes and mouse actions are translated to strings, such as “Chr-g” for the character “g” or “S-Backspace” for the backspace key being pressed while Shift is down, or Click-1 for a click on mouse-button-1. The commands associated with some of these keys perform the action directly, others resubmit the input as an action, so “C-Chr-F” (control-F) might cause “Move-Char” to be be submitted. A different module will pick this up and handle it.
Key maps are associated with panes, so when an event happens, each pane from the leaf to the root is tried in order until one has a map which supports the event. Documents can also have key maps. Some panes are directly connected to a document and when that pane is being processed, the keymap for the document will also be tried.
This last possibility, keymaps on documents, is a fairly recent invention so I have not explored all the implication of it yet. I might end up finding lots of uses for this so that most actions that a document can perform are performed through commands attached to a key map.
Commands more generally
Currently there are a few places where commands are attached directly to objects rather than through a keymap, though this might change.
Specifically each pane has a separate command which is called for specific actions on that pane. It is called when the pane is “Damaged” and needs to be redrawn. It is called when the pane is being destroyed. And it is called if the pane needs to be cloned (to create a new pane viewing the same point in the same document). Having one command for all of this is quite possible, but a little ugly. So this might be combined into the per-pane keymap. Or maybe a pane will end up with a list of keymaps.
Each document has a list of “views”. These are often panes but can be anything else. The word-count module creates a view on any document it wants to count.
When any change happens in the document, each view is told about the change. A pane can note that the pane is damaged so a refresh will happen. The word-count module can clear its cached counts so they are recalculated on need. This telling about change also happens via a command. It is less obvious that a keymap should be provided here rather than a single command, but further experience may suggest otherwise.
Ins and Outs of a Command
So I’ve talked about how a command is used, but what is it? It is a function pointer with a specific interface. It is passed a “struct cmd_info” and returns an integer. The integer can mean slightly different things in different cases. For event handling commands, if the integer is zero, then the command didn’t handle the event and something else should be tried. For “refresh” commands, non-zero means that children need to be refreshed even if they weren’t already damaged.
The “struct cmd_info” contains various details which can have slightly different meanings in different contexts, though much of it is fairly fixed.
- key : the name of the event or action. This allows a single command to handle multiple actions.
- two panes: home and focus. The “focus” pane is where the event should happen. For a keystroke, it is the leaf-most pane that contains the cursor. For a “Clone” command, it is the parent to attach a clone to.
The “home” pane is the pane where the command as found. Each pane contain module-specific data and the commands associated with a pane may want to access that data. It may not be in the “focus”, but it will be in the “home” pane.
- two numbers: “numeric” and “extra”. “numeric” is a count that suggests how many times the command should be repeated. “extra” is arbitrary extra information that can be used as needed. The “refresh” commands use it to communicate what sort of damage happened. Some event commands use it to connect consecutive “change” commands that can then be grouped into a single “undo”.
- two co-ordinates: x and y. These are used for mouse events and any event that needs a position on the screen. They are relative to the “focus” pane.
- An arbitrary string “str”. This is a bit like “extra”, but for text. The “Replace” function expects a string value to be stored into the document.
- Finally a mark and a point. The point is usually the location of the cursor in the event window. The “mark” is somewhere else, if it is used. It must be in the same document as “point”. Together they can identify a range for a document for some action to happen. Once I work out how select and copy/paste works, the mark might get a more defined meaning.
Some of these fields may be used as outputs as well as input. When a “refresh” function reports that children should be treated as damaged, “extra” reports the specific damage bits. I suspect that a “choose other pane” function (use for C-x-4-?? Emacs functions) will probably return the chosen pane in “focus”.
As you can see, this is a fairly restricted set of value, but has some flexibility. I really hope that I won’t find the need to extent it too much.
One way it can be extended is to attach attributes to the mark or point. This is how the word-count module works. It doesn’t exactly work as a command yet, but that is the plan. It can be passed a “mark”, and it determines word and line counts and stores them in attributes attached to the mark. They can then be examined by the caller.
I suspect there will turn out to be more opportunities to use commands. One obvious place is to handle user-typed commands, with “M-x” in emacs or “:” in vi. The arguments to the command could be passed via the “str”.
One place where I will need some sort of call-out functionality is for customizing renderers. I want to encourage purpose-built renders for specific needs, but there is a place for have a fairly generic renderer that is easily customizable for simple customizations. I imagine that if a location in a document has a “render” attribute attached, then that might lead to a command which takes some roles in part of the rendering. So far this is just a vague idea, no details.
It seems likely that there will be real changes to the nature of a command before I am done, because it is still early days. But I’m quite sure that the idea of a “command” as a ubiquitous tool for passing control between modules is a a good one.
I’ve decided that the code has advanced enough that I have published it – partly to ensure an off-site backup. git://neil.brown.name/edlib holds the code and http://git.neil.brown.name/?p=edlib.git lets you browse it.