Recording a simple macro for the code editor

We would like to define a macro to insert a standard comment template (for separating sections of source code) at the current cursor location in the code editor. After the insertion, the cursor should be placed so that the user can immediately begin typing inside the template. Specifically, if we indicate the final cursor position with a “^”, the macro should produce the following:

######################################################################
# ^
######################################################################

The following steps would produce the desired macro:

  1. Start recording by selecting Record macro from the Tools menu.
  2. Enter a name for the macro, for example ‘Comment_template’. The name must be a valid python name (note no spaces or punctuation other than underscore.) Then click on the “OK” button.
  3. Type ‘#’ 70 times and press the Enter (Return) key to go to the next line.
  4. Type ‘# ‘ and press the Enter (Return) key to go to the next line.
  5. Type ‘#’ 70 times and press the up arrow key to move the cursor to the previous line.
  6. Finally, stop recording by selecting Stop Recording Macro from the Tools menu.

We now have a macro that can insert our template at the current cursor position. To insert this template at any desired location, move your cursor to that location, and select this macro from the Tools > Run Macro menu. The macro will be stored, so we can do this at any time in the future.

Macro Editor

Next, we can do more things such as adding a key-binding to run this macro, or edit the macro to perform (slightly) different actions. Select Tools > Edit Macros to open Canopy’s built-in Macro Editor.

The Macro Editor contains a macro browser, which lists all the macros that we have recorded. Selecting a macro name in the macro browser lets us edit the properties of the macro. We can set a key-binding by clicking on the ‘Keybinding’ box and pressing the desired key combination. We can specify whether the macro is available globally in Canopy, or only in the code editor. Returning to the code editor and pressing the chosen key combination should insert the comment template.

The macro editor also has toolbar buttons which let you run a macro, or start recording a new macro.

To view a recorded macro, double click on its name in the macro browser. An editor tab opens, showing the macro’s source code.

# -*- coding: utf-8 -*-

def run():
    code_task = get_active_task()
    code_editor = code_task.active_editor
    cursor = code_editor.cursor
    cursor.write(u'#######################################################################')
    code_editor.autoindent_newline()
    cursor.write(u'# ')
    code_editor.autoindent_newline()
    cursor.write(u'#######################################################################')
    cursor.previous_line()

The macro is simply a Python function, which is run when the macro is called. The function locates a code_editor object and its cursor attribute, then calls methods on these objects to perform the desired actions.

Editing macros

To make small changes to a macro without needing to re-record it, we can easily modify the macro source code. Continuing with the same example, if we want to make the long separator lines shorter, and also add another ‘Enter’ keypress after the second long separator line, we might edit the macro to:

# -*- coding: utf-8 -*-

def run():
    SEP_LENGTH = 50
    code_task = get_active_task()
    code_editor = code_task.active_editor
    cursor = code_editor.cursor
    cursor.write(u'#' * SEP_LENGTH)
    code_editor.autoindent_newline()
    cursor.write(u'# ')
    code_editor.autoindent_newline()
    cursor.write(u'#' * SEP_LENGTH)
    code_editor.autoindent_newline()
    cursor.previous_line(count=2)

Here we’ve created more maintainable separator strings, and we’ve introduced a ‘count’ parameter for the ‘cursor.previous_line’ method, to move the cursor up two lines.

Recording and editing macros for the code editor, like this, is a good way to get started. Next, you can move on to writing code editor macros from scratch. Later (not yet documented), you can write macros for other parts of Canopy, and for applications based on it.

Writing macros from scratch

Writing macros from scratch is straightforward if you know a little Python. In the Macro Editor’s File Menu, select New Macro. After you name your new macro, you will be shown a do-nothing macro, which you can edit to do what you want.

Many of the commands and objects which are useful in writing code editor macros are described at Code Editor Scripting Commands.

Commands for writing macros in the IPython pane are described at IPython Pane Scripting Commands.

Please note that the scripting API in this version of Canopy is likely to change in future releases. We plan for all recorded macros to be supported, but other current scripting commands may be changed.

Here is an example of a code editor macro written from scratch, which wraps the current paragraph of text (delimited by blank lines) to 80 characters, with no left indent except perhaps on the first line. After it has run you may want to press the Escape key to remove the residual highlighting of blank lines.

from textwrap import fill

def run():
    code_task = get_active_task()
    code_editor = code_task.active_editor
    cursor = code_editor.cursor

    def current_line_text():
        """ Returns text of the line under cursor.
        """
        text_lines = code_editor.text.splitlines()
        if len(text_lines) == cursor.line():
            return ''
        return text_lines[cursor.line()].strip()

    # Go to the empty line before the current paragraph.
    # Don't move if already at empty line.
    if current_line_text():
        code_editor.find(u'^\\s*$', case=True, regex=True, count=-1, wrap=False)

    # If no empty lines found, go to the first line.
    if current_line_text():
        code_editor.goto_line(1)
    else:
        cursor.next_line()
    code_editor.goto_column(0)

    start = code_editor.line # Save the starting line number

    # Look for the end of paragraph, by searching for empty line
    code_editor.find(u'^\\s*$', case=True, regex=True, wrap=False)
    last_line = current_line_text()
    # If no empty line found, go to last line of editor
    if last_line:
        code_editor.goto_line(-1)
        end = code_editor.line + 1
    else:
        end = code_editor.line

    num = end-start or 1
    code_editor.goto_line(start)
    code_editor.goto_column(0)

    cursor.next_line(count=num, select=True)
    text = code_editor.selection

    if text:
        text_filled = fill(text, width=80)
        if last_line:
            cursor.write(text_filled)
        else:
            cursor.writeln(text_filled)

    # stop highlighting all empty lines
    code_editor.disable_find_replace()