Previous: Python FAQ, Up: Scripting GPS

#### 16.8.8 Hooks

A hook is a named set of commands to be executed on particular occasions as a result of user actions in GPS.

GPS and its various modules define a number of standard hooks, which are called for instance when a new project is loaded, when a file is edited, and so on. You can define your own commands to be executed in such cases.

You can find out the list of hooks that GPS currently knows about by calling the Hook.list function, which takes no argument, and returns a list of hook names that you can use. More advanced description for each hook is available through the `Help->Python Extensions`.

         GPS> Hook.list
         project_changed
         open_file_action_hook
         preferences_changed
         [...]

         Python> GPS.Hook.list()

The description of each hooks includes a pointer to the type of the hook, that is what parameters the subprograms in this hook will receive. For instance:

The list of all known hook types can be found through the Hook.list_types command. This takes no argument and returns a list of all known types of hooks. As before, you can more information for each of these type through a call to Hook.describe_type.

##### 16.8.8.1 Adding commands to hooks

You can add your own command to existing hooks through a call to the Hook.add command. Whenever the hook is executed by GPS or another script, your command will also be executed, and will be given the parameters that were specified when the hook is run. The first parameter is always the name of the hook being executed.

This Hook.add applies to an instance of the hook class, and takes one parameter, the command to be executed. This is a subprogram parameter (see Subprogram parameters).

- GPS shell

  The command can be any GPS action (see Defining Actions). The arguments for the hook will be passed to the action, and are available as $1, $2, ...; In the following example, the message "Just executed the hook: project_changed" will be printed in the Shell console. Note that we are defining the action to be executed inline, but this could in fact be defined in a separate XML customization file for convenience.

                GPS> parse_xml """<action name="my_action"><shell>echo "Just executed the hook"</shell></action_name>"""
                GPS> Hook project_changed
                GPS> Hook.add %1 "my_action"

- Python

  The command must be a subprogram to execute. The arguments for the hook will be passed to this subprogram. In the following example, the message "The hook project_changed was executed by GPS" will be displayed in the Python console whenever the project changes.

                def my_callback (name):
                    print "The hook " + name + " was executed by GPS"
                GPS.Hook ("project_changed").add (my_callback)

The example above shows the simplest type of hook, which doesn't take any argument. However, most hooks receive several parameters. For instance, the hook "file_edited" receives the file name as a parameter.

- GPS shell

  The following code will print the name of the hook ("file_edited") and the name of the file in the shell console every time a file is open by GPS.

                GPS> parse_xml """<action name="my_action"><shell>echo name=$1 file=$2</shell></action>"""
                GPS> Hook "file_edited"
                GPS> Hook.add %1 "my_action"

- Python

  The following code prints the name of the file being edited by GPS in the python console whenever a new editor is opened. The second argument is of type GPS.File.

                def my_file_callback (name, file):
                    print "Editing " + file.name()
                GPS.Hook ("file_edited").add (my_file_callback)

##### 16.8.8.2 Action hooks

Some hooks have a special use in GPS. Their name always ends with "_action_hook".

As opposed to the standard hooks described in the previous section, the execution of the action hooks stops as soon as one of the subprograms returns a True value ("1" or "true"). The subprograms associated with that hook are executed one after the other. If any such subprogram knows how to act for that hook, it should do the appropriate action and return "1".

Other action hooks expect a string as a return value instead of a boolean. The execution will stop when a subprogram returns a non-empty string.

This mechanism is used extensively by GPS internally. For instance, whenever a file needs to be opened in an editor, GPS executes the "open_file_action_hook" hook to request its editing. Several modules are connected to that hook.

One of the first modules to be executed is the external editor module. If the user has chosen to use an external editor, this module will simply spawn Emacs or the external editor that the user has selected, and return 1. This immediately stops the execution of the "open_file_action_hook".

However, if the user doesn't want to use external editors, this module will return 0. This will keep executing the hook, and in particular will execute the source editor module, which will always act and open an editor internally in GPS.

This is a very flexible mechanism. In your own script, you could choose to have some special handling for files with a ".foo" extension for instance. If the user wants to open such a file, you would spawn for instance an external command (say "my_editor") on this file, instead of opening it in GPS.

This is done with a code similar to the following

         from os.path import *
         import os
         def my_foo_handler (name, file, line, column, \
                             column_end, enable_nav, new_file, reload):
             if splitext (file.name())[1] == ".foo":
                 os.spawnv \
                    (os.P_NOWAIT, "/usr/bin/emacs", ("emacs", file.name()))
                 return 1   ## Prevent further execution of the hook
             return 0  ## Let other subprograms in the hook do their job

         GPS.Hook ("open_file_action_hook").add (my_foo_handler)

##### 16.8.8.3 Running hooks

Any module in GPS is responsible for running the hooks when appropriate. Most of the time, the subprograms exported by GPS to the scripting languages will properly run the hook. But you might also need to run them in your own scripts.

As usual, this will result in the execution of all the functions bound to that hook, whether they are defined in Ada or in any of the scripting languages.

This is done through the Hook.run command. This applies to an instance of the Hook class, and a variable number of arguments These must be in the right order and of the right type for that specific type of hook.

If you are running an action hook, the execution will stop as usual as soon as one of the subprograms return a True value.

The following example shows how to run a simple hook with no parameter, and a more complex hook with several parameters. The latter will in fact request the opening of an editor for the file in GPS, and thus has an immediately visible effect on the interface. The file is opened at line 100. See the description of the hook for more information on the other parameters.

         GPS.Hook ("project_changed").run()
         GPS.Hook ("open_file_action_hook").run \
                       (GPS.File ("test.adb"), 100, 1, 0, 1, 1, 1)

##### 16.8.8.4 Creating new hooks

The list of hooks known to GPS is fully dynamic. GPS itself declares a number of hooks, mostly for its internal use although of course you can also connect to them.

But you can also create your own hooks to report events happening in your own modules and programs. This way, any other script or GPS module can react to these events.

Such hooks can either be of a type exported by GPS, which constraints the list of parameters for the callbacks, but make such hooks more portable and secure; or they can be of a general type, which allows basically any kind of parameters. In the latter case, checks are done at runtime to ensure that the subprogram that is called as a result of running the hook has the right number of parameters. If this isn't the case, GPS will complain and display error messages. Such general hooks will also not pass their parameters to other scripting languages.

Creating new hooks is done through a call to Hook.register. This function takes two arguments: the name of the hook you are creating, and the type of the hook.

The name of the hook is left to you. Any character is allowed in that name, although using only alphanumerical characters is recommended.

The type of the hook must be one of the following:

- "" (the empty string)

  This indicates that the hook doesn't take any argument. None should be given to Hook.run, and none should be expected by the various commands connected to that hook, apart from the hook name itself.

- one of the values returned by Hook.list_types

  This indicates that the hook is of one of the types exported by GPS itself. The advantage of using such explicit types as opposed to "general" is that GPS is able to make more tests for the validity of the parameters. Such hooks can also be connected to from other scripting languages.

- "general"

  This indicates that the hook is of the general type that allows any number of parameter, of any type. Other scripts will be able to connect to it, but will not be executed when the hook is run if they do not expect the same number of parameters that was given to Hook.run. Other scripts in other language will only receive the hook name in parameter, not the full list of parameters.

A small trick worth noting: if the command bound to a hook doesn't have the right number of parameters that this hook provide, the command will not be executed and GPS will report an error. You can make sure that your command will always be executed by either giving default values for its parameter, or by using python's syntax to indicate a variable number of arguments.

This is especially useful if you are connecting to a "general" hook, since you do not really know in advance how many parameters the call of Hook.run will provide.

         ## This callback can be connected to any type of hook
         def trace (name, *args):
            print "hook=" + name

         ## This callback can be connected to hooks with one or two parameters
         def trace2 (name, arg1, arg2=100):
            print "hook=" + str (arg1) + str (arg2)

         Hook.register ("my_custom_hook", "general")
         Hook ("my_custom_hook").add (trace2)
         Hook ("my_custom_hook").run (1, 2) ## Prints 1 2
         Hook ("my_custom_hook").run (1)    ## Prints 1 100