Over the past years there have been quite a few requests to enable keyboard macros and keybinding modifications in the Matlab editor. Some posters have even noted this lack as their main reason to use an external editor. Coincidentally, some weeks ago I was approached by a reader (Grant Roch) to help with assigning some textual editor macros, and with joint work we were able to figure out a basic working mechanism. The latest user comment on this issue was posted on the official Matlab Desktop blog yesterday. All this prompted me to finally post a solution to this need: My EditorMacro utility is now available for download on the Mathworks File Exchange. In this post I will detail some of its inner workings, which rely on undocumented Matlab features.
In a nutshell, EditorMacro sets the KeyPressedCallback property (explained in a previous post) for each of the editor’s document panes, to an internal function. This internal function then checks each keystroke against a list of registered keybindings. The list itself is persisted in the editor object’s hidden ApplicationData property (accessible via the getappdata/setappdata built-in functions). If a match is found, then the associated macro is invoked. Depending on the macro type, some text can be inserted at the current editor caret position, or replacing the selected editor text, or a non-text Matlab function/command can be invoked.
This enables EditorMacro to be used for quickly inserting code templates (header comments, try-catch blocks etc.) or for automating Matlab unit testing.
Here’s a typical usage example: start by defining a simple function that returns a dynamic header comment:
function comment = createHeaderComment(hDocument, eventData)
timestamp = datestr(now);
username = getenv('username');
%computer = getenv('computername'); % unused
lineStr = repmat('%',1,35);
comment = sprintf(...
['%s\n' ...
'%% \n' ...
'%% Name: functionName\n' ...
'%% \n' ...
'%% Desc: enter description here\n' ...
'%% \n' ...
'%% Inputs: enter inputs here\n' ...
'%% \n' ...
'%% Outputs: enter outputs here\n' ...
'%% \n' ...
'%% Created: %s\n' ...
'%% \n' ...
'%% Author: %s\n' ...
'%% \n' ...
'%s\n'], ...
lineStr, timestamp, username, lineStr);
end % createHeaderComment
Now define a macro to use this function, and another simple try-catch template macro:
>> EditorMacro('Alt-Control-h', @createHeaderComment);
>> macroStr = 'try\n % Main code here\ncatch\n % Exception handling here\nend';
>> b = EditorMacro('Ctrl alt T', macroStr)
b =
'ctrl alt pressed H' @createHeaderComment 'text'
'ctrl alt pressed T' [1x60 char] 'text'
Now start with a blank document and click <Ctrl>-<Alt>-H and <Ctrl>-<Alt>-T. This is the end result:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Name: functionName
%
% Desc: enter description here
%
% Inputs: enter inputs here
%
% Outputs: enter outputs here
%
% Created: 01-Jul-2009 23:31:46
%
% Author: Yair Altman
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
try
% Main code here
catch
% Exception handling here
end
Keybindings are normalized using Java’s built-in javax.swing.KeyStroke.getKeyStroke() method, to enable the user a very wide range of keystroke naming formats (e.g., ‘Alt-Control-T’ or ‘ctrl alt t’).
I have taken great pains to make EditorMacro compatible with all Matlab versions since 6.0 (R12). This was no easy feat: Matlab 7 made some significant changes to the editor layout. Luckily, once I found out how to get a handle to the Matlab 6 editor object (this took some hours of trial-and-error), listing its display hierarchy was simple and the modifications were generally straight-forward, although non-trivial (different quirks due to missing default type-castings, missing eventData in invoked callbacks etc…). Please feel free to look at EditorMacro’s source code for the clearly-marked Matlab 6 segments.
For the record, here is a snippet showing how to get the editor object in Matlab 6 and 7. Note that the desktop handle is only useful to get the editor handle in Matlab 6 – we need a totally different way in Matlab 6:
try
% Matlab 7
jDesktop = com.mathworks.mde.desk.MLDesktop.getInstance;
jEditor = jDesktop.getGroupContainer('Editor');
catch
% Matlab 6
try
%desktop = com.mathworks.ide.desktop.MLDesktop.getMLDesktop;
% no use: can't get to the editor from the desktop handle in Matlab 6...
openDocs = com.mathworks.ide.editor.EditorApplication.getOpenDocuments;
firstDoc = openDocs.elementAt(0);
jEditor = firstDoc.getParent.getParent.getParent;
catch
error('Cannot find Matlab editor handle: possibly no open documents');
end
end
Another complication arose due to the different layout used for floating/maximized/tiled document layout in the editor. Yet another was due to the different behavior (at least on Matlab 6) between a one-document and a multi-document editor view.
Due to the way keyboard events are processed by the Matlab editor, KeyPressedCallback needs to be set separately for all the open document panes and split-panes. Since we also wish newly-opened documents to recognize the macro bindings, we need to set the common container ancestor’s ComponentAddedCallback to an internal function that will handle the KeyPressedCallback instrumentation for each newly-opened document. Again, this needs to be done somewhat differently for Matlab 6/7.
Note that EditorMacro relies on the editor’s internal display layout, which is very sensitive to modification between Matlab releases (as it has between Matlab 6 and 7, for example).
All-in-all, I believe EditorMacro provides a useful and long-awaited service, which should in fact be part of the built-in editor.