- Undocumented Matlab - https://undocumentedmatlab.com -

EditorMacro v2 – setting Command Window key-bindings

Posted By Yair Altman On August 20, 2009 | 4 Comments

Some weeks ago, I introduced my EditorMacro utility [1] as a means of assigning user-defined text strings and runnable macros (callbacks) to keyboard shortcuts in the Matlab Editor. I later showed how EditorMacro can be used to set non-textual editor macros [2].
Since then, I was contacted by several users with questions, enhancement requests and improvement suggestions. Of these, the most notable was from Perttu Ranta-aho who suggested (and provided sample code for) using the editor’s built-in actions. The next logical step was to extend this to support the editor menus, and then extend again to support Command Window key-bindings. We bounced several increasingly-powerful versions of the utility between us, until arriving at the version I uploaded yesterday, which has the following significant improvements over the first version:

  • several fixes: bugs, edge cases, stability (EDT etc.) etc.
  • support for built-in (native) Editor actions
  • support for built-in (native) Command-Window actions

Built-in native actions

It turns out that the Editor and Command-Window both have some ~200 built-in (native) available actions, about half of them common. Actions are identified by name, which is a lowercase dash-separated description like ‘selection-up’ (this format is familiar to Emacs users). Altogether, there are over 300 unique built-in actions which can be used, but only ~100 of them are assigned a default key-bindings. A few dozen actions have multiple assigned key-bindings. For example, the ‘selection-up’ action is assigned to both ‘shift pressed UP’ (=<shift>-<up>) and ‘shift pressed KP_UP’ (=<shift>-<Keypad-up>):

>> [bindings, actions] = EditorMacro
actions =
...[snip]
 'selection-page-down'     'shift pressed PAGE_DOWN'     'editor native action'
 'selection-page-up'       'shift pressed PAGE_UP'       'editor native action'
 'selection-previous-word'                   {2x1 cell}  'editor native action'
 'selection-up'                              {2x1 cell}  'editor native action'
 'set-read-only'                                     []  'editor native action'
 'set-writable'                                      []  'editor native action'
 'shift-insert-break'      'shift pressed ENTER'         'editor native action'
 'shift-line-left'         'ctrl pressed OPEN_BRACKET'   'editor native action'
 'shift-line-right'        'ctrl pressed CLOSE_BRACKET'  'editor native action'
 'shift-tab-pressed'       'shift pressed TAB'           'editor native action'
...[snip]...
 'toggle-typing-mode'      'pressed INSERT'              'editor native action'
 'uncomment'               'ctrl pressed T'              'editor native action'
 'undo'                    'ctrl pressed Z'              'editor native action'
 'unselect'                'pressed ESCAPE'              'editor native action'
 'adjust-window-bottom'                              []  'cmdwin native action'
 'adjust-window-top'                                 []  'cmdwin native action'
 'beep'                    'ctrl pressed G'              'cmdwin native action'
 'break-interrupt'         'ctrl pressed CANCEL'         'cmdwin native action'
...[snip]...
 'toggle-typing-mode'      'pressed INSERT'              'cmdwin native action'
 'unselect'                'ctrl pressed BACK_SLASH'     'cmdwin native action'
 'new-mfile'               'ctrl pressed N'              'editor menu action'
 'Figure'                                            []  'editor menu action'
...[snip]...

Even more interesting, apparently some 200 actions do not have any pre-assigned default key-bindings, such as ‘set-read-only’ and ‘set-writable’ in the snippet above. Let’s take the ‘match-brace’ action for example. This sounded promising so I assigned it an unused key-binding and indeed found that it can be very useful: if your cursor is placed on a beginning or end of some code, clicking the assigned key-binding will jump the cursor to the other end, and then back again. This works nicely for (..), [..], for..end, try..end, if..end, etc.

>> % Ensure that -M is unassigned
>> bindings = EditorMacro('alt m')
bindings =
   Empty cell array: 0-by-4
>> % Assign the key-binding and verify
>> EditorMacro('alt m','match-brace','run');
>> bindings = EditorMacro('alt m')
b =
  'alt pressed M'  'match-brace'  'run'  'editor native action'

Implementation details

Interested readers of this post are encouraged to look within EditorMacro’s source code [3] and see how the native actions and keybindings were retrieved and modified for the editor, Command-Window and menus. In a nutshell, the native action names and key-bindings are stored in a Java Map object. Here’s a code snippet:

%% Get all available actions even those without any key-binding
function actionNames = getNativeActions(hEditorPane)
  try
    actionNames = {};
    actionKeys = hEditorPane.getActionMap.allKeys;
    actionNames = cellfun(@char,cell(actionKeys),'Uniform',0);
    actionNames = sort(actionNames);
  catch
    % never mind...
  end
%% Get all active native shortcuts (key-bindings)
function accelerators = getAccelerators(hEditorPane)
  try
    accelerators = cell(0,2);
    inputMap = hEditorPane.getInputMap;
    inputKeys = inputMap.allKeys;
    accelerators = cell(numel(inputKeys),2);
    for ii = 1 : numel(inputKeys)
      thisKey = inputKeys(ii);
      thisAction = inputMap.get(thisKey);
      accelerators(ii,:) = {char(thisKey), char(thisAction)};
    end
    accelerators = sortrows(accelerators,1);
  catch
    % never mind...
  end

The menu retrieval was more difficult: while it is possible to directly access the menubar reference (jMainPane.getRootPane.getMenuBar), the menu items themselves are not visible until their main menu item is clicked (displayed). The only way I know to access menu actions/keybindings is to read them from the individual menu items (if anyone knows a better way please tell me – perhaps some central key-listener repository?). Therefore, a simulation of the menu-click events is done and the menu hierarchy is traveled recuresively to collect all its actions and key-bindings.
A final note relates to the use of EDT. EDT really deserves a separate post, but in a nutshell it means that any action that affects the GUI needs to be invoked asynchronously (via the EDT) rather than synchronously (on the main Matlab thread). This is no real problem in the editor, but it is indeed an issue in the Command Window: If we do not use EDT there, we get ugly red stack-trace exceptions thrown on the Command Window whenever we run our EditorMacro-assigned macro. Here’s the code snippet that solves this:

try
   % Matlab 7
   %jEditorPane.insert(caretPosition, macro);  % better to use replaceSelection() than insert()
   try
      % Try to dispatch on EDT
      awtinvoke(jEditorPane.java, 'replaceSelection', macro);
   catch
      % no good - try direct invocation
      jEditorPane.replaceSelection(macro);
   end
catch
   % Matlab 6
   %jEditorPane.insert(macro, caretPosition);  % note the reverse order of input args vs. Matlab 7...
   try
      % Try to dispatch on EDT
      awtinvoke(jEditorPane.java, 'replaceRange', macro, jEditorPane.getSelStart, jEditorPane.getSelEnd);
   catch
      % no good - try direct invocation
      jEditorPane.replaceRange(macro, jEditorPane.getSelStart, jEditorPane.getSelEnd);
   end
end

Known limitations

Some limitations remain in EditorMacro – here are the major ones:

  • Multi-key bindings are still not reported properly, nor can they be assigned. For example, the editor menu action ‘to-lower-case’ has a pre-assigned default key-binding of <Alt>-<U>-<L>, but this is reported as unassigned. Of course, you can always add another (single-key) assignment for this action, for example: <Alt>-<Ctrl>-<L>
  • Menus are dynamically recreated whenever the Editor is docked/undocked, or a separate type of file is edited (e.g., switching from an m-file to a c-file). Similarly, whenever the active desktop window changes from the Command Window to another desktop-docked window (e.g., Command History). In all these cases, the dynamically recreated menus override any conflicting key-binding previously done with EditorMacro.
  • Unfortunately, my Matlab 6-installed computer crashed, so while the first version of EditorMacro works on ML6, the new version might well not. If you have ML6 available, please email me so I can post a working version with your help. This also means I won’t be able to post much about undocumented ML6 stuff in the future. Perhaps this is Fate’s way of telling me to progress with the times…
  • Key bindings are sometimes lost when switching between a one-document editor and a two-document one (i.e., adding/closing the second doc)
  • Key bindings are not saved between editor sessions
  • In split-pane mode, when inserting a text macro on the secondary (right/bottom) editor pane, then both panes (and the actual document) are updated but the secondary pane does not display the inserted macro (the primary pane looks ok).

The first couple of limitations have a non-perfect workaround that Perttu came up with. He implemented this in his KeyBindings utility [4] on the Matlab File Exchange. Perhaps one day Perttu or me will find the time to merge these utilities into one.
Have you designed some crafty user-defined macro? or found some important unassigned built-in action? Please share your experience using EditorMacro in the comments section below.

Categories: Desktop, High risk of breaking in future versions, Java, Listeners


4 Comments (Open | Close)

4 Comments To "EditorMacro v2 – setting Command Window key-bindings"

#1 Comment By jo On August 21, 2009 @ 13:43

Thanks!
EditorMacro has been added to the startup.m file! 🙂
So far I’ve created few macros that remarkably speed up my work.
One of them is a macro that executes code between tags embedded in a comment of an m-file – excellent alternative for Edit Run Configuration… + F5 combination during debugging an m-file, but… I cannot solve one problem: automatic saving file before run (eg. to activate fresh breakpoints).
Your post about internal commands gave me new hope. I’ve made some trials to save m-file programmatically but without effect.

Do you know if such an action is possible from inside the macro?

#2 Comment By Yair Altman On August 22, 2009 @ 12:47

@Jo – Here’s a snippet of a sample macro (callback) function:

function myCallback(jDocument, jEventData, varargin)
   filename = jDocument.getFilename;
   % do some user-defined stuff

   % Now save the file:
   com.mathworks.mlservices.MLEditorServices.saveDocument(filename);
end  % myCallback

Also look at Perttu’s KeyBindings utility for other usage examples of MLEditorServices.

Yair

#3 Comment By mb On January 13, 2012 @ 05:38

Hi,
In Matlab 2011 com.mathworks.mlservices.MLEditorServices has no such method as saveDocument.
Instead of it one can use matlab editor api to save the document:

filehandle = matlab.desktop.editor.getActive 
filehandle.save() ;

More about it can be found here [11] .
I hope it will save someone’s time.

Regards,
mb

#4 Comment By Yair Altman On January 13, 2012 @ 05:59

This is indeed a very good example of the risks in using internal Matlab components – their API can change dramatically from one release to another, without prior notice.

Here is the alternative in the latest Matlab release (12a):

% Get the reference to the editor application
jEditorApp = com.mathworks.mde.editor.MatlabEditorApplication.getInstance;
jEditorApp = com.mathworks.mlservices.MLEditorServices.getEditorApplication;  % an alternative

% Save the current file
jEditorApp.getActiveEditor.save();
jEditorApp.getActiveEditor.saveAs(filename);

Other useful methods of the jEditorApp.getActiveEditor reference are:

addEventListener(com.mathworks.matlab.api.editor.EditorEventListener)
addPropertyChangeListener(java.beans.PropertyChangeListener)
addPropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
appendText(java.lang.String)
bringToFront()
close()
closeNoPrompt()
dispose()
fireEditorEvent(com.mathworks.matlab.api.editor.EditorEvent)
firePropertyChange(java.lang.String, java.lang.Object, java.lang.Object)
getBreakpointMargin() : com.mathworks.matlab.api.debug.BreakpointMargin
getCaretPosition() : int
getComponent() : java.awt.Component
getDocument() : javax.swing.text.Document
getExecutionArrowMargin() : com.mathworks.matlab.api.debug.ExecutionArrowMargin
getLanguage() : com.mathworks.matlab.api.editor.EditorLanguage
getLength() : int
getLineNumber() : int
getLongName() : java.lang.String
getProperty(java.lang.Object) : java.lang.Object
getSelection() : java.lang.String
getShortName() : java.lang.String
getStorageLocation() : com.mathworks.matlab.api.datamodel.StorageLocation
getText() : java.lang.String
getTextComponent() : javax.swing.text.JTextComponent
getTextWithSystemLineEndings() : java.lang.String
goToFunction(java.lang.String, java.lang.String)
goToLine(int, boolean)
goToLine(int, int)
goToPositionAndHighlight(int, int)
insertAndFormatTextAtCaret(java.lang.String)
insertTextAtCaret(java.lang.String)
isBuffer() : boolean
isDirty() : boolean
isEditable() : boolean
isMCode() : boolean
isOpen() : boolean
lineAndColumnToPosition(int, int) : int
lockIfOpen() : boolean
negotiateSave() : boolean
positionToLineAndColumn(int) : int[]
putProperty(java.lang.String, java.lang.Object)
refreshMenus()
reload()
reloadAndReturnError() : java.lang.String
removeEventListener(com.mathworks.matlab.api.editor.EditorEventListener)
removePropertyChangeListener(java.beans.PropertyChangeListener)
removePropertyChangeListener(java.lang.String, java.beans.PropertyChangeListener)
replaceText(java.lang.String, int, int)
save()
saveAndReturnError() : java.lang.String
saveAs(java.lang.String)
saveAsAndReturnError(java.lang.String) : java.lang.String
setCaretPosition(int)
setClean()
setDirtyUntilSave()
setEditable(boolean)
setEditorStatusBarText(java.lang.String)
setSelection(int, int)
setStatusText(java.lang.String)
smartIndentContents()
unlock()

Article printed from Undocumented Matlab: https://undocumentedmatlab.com

URL to article: https://undocumentedmatlab.com/articles/editormacro-v2-setting-command-window-key-bindings

URLs in this post:

[1] EditorMacro utility: http://undocumentedmatlab.com/blog/editormacro-assign-a-keyboard-macro-in-the-matlab-editor/

[2] set non-textual editor macros: http://undocumentedmatlab.com/blog/non-textual-editor-actions/

[3] EditorMacro’s source code: http://www.mathworks.com/matlabcentral/fileexchange/24615

[4] KeyBindings utility: http://www.mathworks.com/matlabcentral/fileexchange/25089

[5] EditorMacro – assign a keyboard macro in the Matlab editor : https://undocumentedmatlab.com/articles/editormacro-assign-a-keyboard-macro-in-the-matlab-editor

[6] R2009b keyboard bindings : https://undocumentedmatlab.com/articles/r2009b-keyboard-bindings

[7] Changing Matlab's Command Window colors : https://undocumentedmatlab.com/articles/changing-matlab-command-window-colors

[8] Bold color text in the Command Window : https://undocumentedmatlab.com/articles/bold-color-text-in-the-command-window

[9] Another Command Window text color hack : https://undocumentedmatlab.com/articles/another-command-window-text-color-hack

[10] cprintf – display formatted color text in the Command Window : https://undocumentedmatlab.com/articles/cprintf-display-formatted-color-text-in-command-window

[11] : http://blogs.mathworks.com/desktop/2011/05/09/r2011a-matlab-editor-api/

Copyright © Yair Altman - Undocumented Matlab. All rights reserved.