EditorMacro v2 – setting Command Window key-bindings

Some weeks ago, I introduced my EditorMacro utility 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.

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 <Alt>-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 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 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

Tags: , , ,

Bookmark and SharePrint Print

4 Responses to EditorMacro v2 – setting Command Window key-bindings

  1. jo says:

    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?

    • Yair Altman says:

      @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

  2. mb says:

    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 http://blogs.mathworks.com/desktop/2011/05/09/r2011a-matlab-editor-api/ .
    I hope it will save someone’s time.

    Regards,
    mb

    • 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()

Leave a Reply


Your email address will not be published. Required fields are marked *