Wing Tips: Extending Wing with Python (Part 3 of 4)

Jun 28, 2019


In this issue of Wing Tips we continue to look at how to extend Wing's functionality, by taking a look at extension scripts that collect arguments from the user.

This article assumes you already know how to add and try out extension scripts in Wing. If you haven't read the previous installments of this series, you may want to take a look at Part 1 where we introduced Wing's scripting framework and set up auto-completion for the scripting API, and Part 2 where we used Wing to debug itself for easier extension script development.

Script Arguments

Extension scripts may be defined with arguments, which Wing will try to collect from the user if not specified. For example:

import wingapi

def test_argument(text):
  wingapi.gApplication.ShowMessageDialog("Result", "You typed: {}".format(text))

After the script is loaded, you can use Command by Name from the Edit menu to execute it by typing test-argument as the command name. Because the type of argument text is undefined, Wing collects it as a string in an entry area shown at the bottom of the IDE window:

/images/blog/scripting-3/arg-collection.png

In key bindings and added toolbar items, it is often useful to provide the value of an argument as part of the invocation like this: test-argument(text="hello")

This displays the message dialog without first prompting for the value of text:

/images/blog/scripting-3/arg-collection-result.png

Script arguments must always be passed using the keyword arg=value form. If a script has multiple arguments and only some are specified in its invocation, Wing collects only the unspecifed arguments from the user.

Default values may be specified in the script definition, in order to avoid collecting the argument from the user if it is not given in the invocation:

import wingapi

def test_argument(text="default"):
  wingapi.gApplication.ShowMessageDialog("Result", "You typed: {}".format(text))

This simplest form, without specifying argument type or interface, is sufficient for many scripting tasks that require argument collection.

Argument Type and Interface

Extension scripts may also specify the type and interface to use for arguments by by setting the arginfo function attribute on the script. This is a map from argument name to a specification that includes documentation, data type, argument collection interface, and a label.

This example collects two arguments, a filename and a choice from a popup menu:

import wingapi
from wingutils import datatype
from guiutils import formbuilder

def test_arg_entry(filename, word):
  wingapi.gApplication.ShowMessageDialog('Choice {}'.format(word), "You chose: {}".format(filename))

_choices = [
  ("Default", None),
  None,
  ("One", 1),
  ("Two", 2),
  ("Three", 3)
]

test_arg_entry.arginfo = {
  'filename': wingapi.CArgInfo(
    "The filename to enter",               # The tooltip shown to use over this field
    datatype.CType(''),                    # The data type is string
    formbuilder.CFileSelectorGui(),        # Use a file selection field to collect the value
    "Filename:"                            # The field label
    ),
  'word': wingapi.CArgInfo(
    "The word to enter",
    datatype.CType(''),
    formbuilder.CPopupChoiceGui(_choices), # Use a popup menu to collect this value
    "Word:"
  )
}

# This causes the script to be listed in a new menu Scripts in the menu bar
test_arg_entry.contexts = [wingapi.kContextNewMenu('Scripts')]

Notice that this imports some additional modules not used in our previous examples in this series: datatype is used to specify the type of an argument, and formbuilder is used to specify how the value is collected from the user. These are documented in Argument Collection in the users manual.

Once you load the script, you can try it with Test arg entry in the Scripts menu that should appear in the menu bar. The value collection area will look like this:

/images/blog/scripting-3/argentry.png

Pressing Tab moves between the fields and Enter executes the command, which displays this dialog:

/images/blog/scripting-3/argentry-result.png

Using a Dialog for Argument Entry

Arguments may also be collected in a dialog, rather than at the bottom of the IDE window. For the above example, this would be done by appending the following code:

test_arg_entry.flags = {'force_dialog_argentry': True}

After this change is saved, executing Test arg entry from the Scripts menu displays the argument entry fields in a dialog instead:

/images/blog/scripting-3/dialog-argentry.png

Providing History and Completion

Argument entry can also implement history (accessible by pressing the up and down arrows) and completion of matching choices with Tab or Enter. The following example does both for its argument text:

import wingapi
from wingutils import datatype
from guiutils import formbuilder

# The command does nothing other than adding the argument to history
# (first=most recent)
_history = []
def test_arg_entry(text):
  if text in _history:
    _history.remove(text)
  _history.insert(0, text)

# For completions, we just use the matching words in this file
def _completions(fragment):
  fn = __file__
  if fn.endswith(('.pyc', '.pyo')):
    fn = fn[:-1]
  with open(fn) as f:
    txt = f.read()
  return sorted([t for t in txt.split() if t.startswith(fragment)])

test_arg_entry.arginfo = {
  'text': wingapi.CArgInfo(
    "Test with completion and history",
    datatype.CType(''),
    formbuilder.CSmallTextGui(history=_history,
                              choices=_completions)
  ),
}
test_arg_entry.contexts = [wingapi.kContextNewMenu('Scripts')]
test_arg_entry.flags = {'force_dialog_argentry': True}

After replacing the script created earlier with the above code you should see an auto-completer as you type:

/images/blog/scripting-3/auto-completion.png

After executing the command one or more times, the up and down arrows traverse the stored history:

/images/blog/scripting-3/history.gif

Note that in this implementation the history is lost each time the script is reloaded. One way to save history or any other value across script reload or sessions is to store it using SetAttribute() on the current project obtained from wingapi.gApplication.GetProject().

The above covers most argument collection needed by extension scripts for Wing. A few other data entry methods are also supported, as documented in Argument Collection in the users manual.



That's it for now! In the next Wing Tip we'll take a look at the API in more detail, in order to write a more complex scripting example.



Share this article: