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).

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.

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:

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