<type>documentation</type>
<domain>scripting</domain>
<id>overview</id>
<title>Overview of the Scripting Framework</title>
<date>$Date$</date>
<version>$Revision$</version>
<product>per+</product>

Scripts are Python modules or packages containing one or more Python functions which
implement the script's functionality. Any top-level function with a name that starts with
a character other than underscore ``_`` is added to Wing's command set, so it becomes
accessible from menus, key bindings, and the toolbar.  Scripts can also use the scripting
API to hook into IDE functionality in other ways, for example to perform an action
every time an editor is saved to disk.

When Wing starts up, it will search for scripts in all directories in the path configured
with the `IDE Extension Scripting > Search Path
<script:show_preferences_gui#prefname="main.script-path">`__ preference. By default this
path contains a directory named ``scripts`` within the `Settings Directory
<ref:install/user-settings-dir>`__. Scripts can also be placed in ``scripts`` inside the
``Install Directory`` shown in Wing's `About box <script:about-application>`__, but this
is not recommended since it is harder to manage across updates of Wing.

Scripts can be modules named ``*.py`` and packages, which are directories that contain a
file named ``__init__.py`` file and any number of other ``*.py`` files or sub-packages.
For packages, Wing loads only the modules that are imported in the ``__init__.py`` file.

Script files within each directory are scanned in alphabetical order. When multiple
script-defined commands with the same name are found, the command that is loaded last
overrides any loaded earlier under the same name. However, scripts cannot replace
internally defined commands, as detailed below.

**Naming Commands**

Commands added by scripts can be referred to either by their short name or their fully
qualified name (FQN).

The short name of a command is the same as the function name, optionally with underscores
replaced by dashes (``cmdname.replace('_', '-')``).

The FQN of a command always starts with ``.user.``, followed by the module name, followed
by the short name. 

For example, if a function named ``do_it`` is defined inside a module named ``xpext.py``,
then the short name of the command created will be ``do-it`` and the FQN will be
``.user.xpext.do-it``.

**Overriding Internal Commands**

Wing will not allow a script to override any of the commands documented in the `Command
Reference <ref:commands/index>`__. If a script is named the same as a command in Wing, it
can only be invoked using its fully qualified name. This is a safeguard against breaking
the IDE by adding a script.

One implication of this behavior is that a script may be broken if a future version of
Wing ever adds a command with the same name. This can generally be avoided by using
appropriately descriptive and unique names and/or by referencing the command from key
bindings and menus using only its fully qualified name.

**Execution Context**

Scripts are run in the same process space as the IDE, using Wing's private Python 2.7
interpreter. Because they are in the same process space, scripts have the potential for
breaking the IDE. For example, a script entering into an infinite loop will lock up Wing.

To avoid this, script-provided functionality must be written within the framework for
cooperative asynchronous multi-tasking that Wing uses internally. In this approach,
lengthy computations are split into small units that are interleaved with the main event
loop. This is supported in the scripting API by ``InstallTimeout`` in ``CAPIApplication``.
This calls a given function periodically until it is removed with ``RemoveTimeout``,
until it returns a value where ``bool(value)`` is ``False``, or until the script that
installed it is reloaded.

This example implements a command that counts down from 10 in the status area at
the bottom of the screen:

.. python::

  import wingapi
  def start_counting():
    counter = [10]
    def count():
      counter[0] -= 1
      wingapi.gApplication.SetStatusMessage("Time left: {}".format(counter[0]), timeout=1)
      return counter[0]
    wingapi.gApplication.InstallTimeout(1000, count)

To interact asynchronously with a sub-process, use this approach in combination with
``AsyncExecuteCommandLine*``.  Here is an example that runs ``ping`` for ten seconds
and shows status messages at the bottom of the IDE window:

.. python::

  import wingapi
  import sys
  import time
  def process_example():
    cmdline = ['ping', '-t', '9', 'wingware.com']
    handler = wingapi.gApplication.AsyncExecuteCommandLine(cmdline[0], None, *cmdline[1:])
    timeout = time.time() + 10
    def poll(timeout=timeout):
      kill = time.time() > timeout
      if kill or handler.Iterate():
        stdout, stderr, err, status = handler.Terminate(kill)
        if kill:
          msg = "Time out"
        elif err is not None:
          msg = "Process failed to start;  exit_status={}, errno={}".format(status, err)
        else:
          msg = "Process exited; stdout len={}; stderr len={}".format(len(stdout), len(stderr))
        wingapi.gApplication.SetStatusMessage(msg)
        return False
      else:
        if handler.stdout:
          msg = "Last output line: {}".format(handler.stdout[-1].splitlines()[-1])
        else:
          msg = "No output yet"
        wingapi.gApplication.SetStatusMessage(msg)
        return True
  
    wingapi.gApplication.InstallTimeout(100, poll)

For additional examples, see the ``scripts`` folder inside the ``Install Directory``
listed in Wing's `About box <script:about-application>`__.

**Signals**

Another important concept in writing extension scripts is the use of signals to control
when script-provided functionality is implemented. Each of the classes in the scripting
API provides signals that notify of different events in the user interface. Signals may be
connected to handlers that are called when the signal is emitted with the defined set of
parameters for that signal.

For example, ``CAPIApplication`` emits ``project-open(filename)`` when a new project has
been opened. The signal can be connected to a handler function as follows:

.. python::

  import wingapi
  def _proj_open(filename):
    wingapi.gApplication.SetStatusMessage("Project opened: {}".format(filename))
  gSignalID = wingapi.gApplication.Connect('project-open', _proj_open)

Once this script is loaded into Wing, ``_proj_open`` will be called every time a new
project is opened. This example displays message in the status area at the bottom of the
IDE window. The message includes the filename, which is the single parameter sent with
this particular signal.

Disconnecting from the signal later would be accomplished as follows for the above
example:

.. python::

  wingapi.gApplication.Disconnect(gSignalID)
  
Signals for each class are documented in ``wingapi.py``.  For additional examples,
see the ``scripts`` folder inside the ``Install Directory`` listed in Wing's
`About box <script:about-application>`__.

**Reloading Scripts**

Wing watches script files and automatically reloads them when they are edited inside Wing
and saved to disk.  The only exception to this occurs when a new script is added.  In this
case, Wing will not load the new script until ``Reload All Scripts`` in the ``Edit``
menu is executed or the IDE is restarted.

Reloading will not work for any file that sets ``_ignore_scripts`` at the top level, or
for modules outside of the script path. For details on how reloading works, see `Advanced
Scripting <ref:scripting/advanced>`__.

