#!/usr/bin/env python
'''
This file describes all hooks exported by GPS. It is used
to generate code, so any change to it requires a rebuilding
of GPS.
'''

class Mapping(object):
    '''
    Describe how to map an Ada type to a python type.
    If 'ada' is set to None, the parameter is ignored on the Ada side.
    If 'python' is set to None, the parameter is ignored in Python.
    %(ada)s is replaced by the name of the actual Ada parameter in 'topython'
    %(idx)d is replaced by the index of the parameter in 'toada'
      In 'toada', there is a local variable K for the kernel.
    'setreturn' is used to store the result of a function and pass it to
      python. The value is in a 'Tmp' variable.
    '''
    def __init__(self, ada, python,
                 topython='%(ada)s',
                 toada='Data.Nth_Arg(%(idx)d)',
                 setreturn='Data.Set_Return_Value (Tmp);',
                 withs=[]):
        self.ada = ada
        self.python = python
        self.toada = toada
        self.topython = topython
        self.withs = withs
        self.setreturn = setreturn if python is not None else 'null;'


class Hook_Type(object):
    '''Describe a type of hook'''
    def __init__(self, params=[], returns=None, return_default=None,
                 returns_run=-1, descr='', internal_run=False):
        """
        :param returns_run: the value returned, in Ada, when calling Run.
           By default, this is the same as returns, but could be set to
           None to ignore the returned value.
        :param internal_run: if True, the Run function is generated with a
           special name and should not be called directly by the code.
        """
        self.params = params
        self.descr = descr.strip()
        self.internal_run = internal_run
        self.returns = returns
        self.return_default = return_default
        self.returns_run = returns if returns_run == -1 else returns_run
        if self.descr:
            self.descr = '\n   --  ' + self.descr.replace('\n', '\n   --  ')


class Param(object):
    '''The description of one parameter for a hook type'''
    def __init__(self, name, type, default=None, descr='', inpython=True):
        self.name = name
        self.type = type
        self.descr = descr
        self.default = default
        self.inpython = inpython

    def show_in_ada(self):
        'Whether to show on the Ada side'
        return types[self.type].ada is not None

    def show_in_python(self):
        'Whether to show on the Python side'
        return self.inpython and types[self.type].python is not None


class Hook(object):
    '''The description of a specific hook'''
    def __init__(self, name, type, descr=''):
        self.name = name
        self.type = type
        self.raw_descr = descr
        self.descr = descr.strip()
        if self.descr:
            self.descr = '\n   --  ' + \
                self.descr.replace('\n', '\n   --  ') + '\n'


types = {
    '__hookname__': Mapping(None, 'str', topython='Self.Name.all'),
    'Task': Mapping(
        ada='Integer',
        python='GPS.Task',
        topython='Task_Manager.Shell.Get_Or_Create_Instance (Data, %(ada)s)',
        toada='Get_Data (Data.Nth_Arg (%(idx)d), ' +
            'K.Scripts.New_Class ("Task"))',
        withs=['Task_Manager.Shell']),

    'Context': Mapping(
        ada='GPS.Kernel.Selection_Context',
        python='GPS.Context',
        topython='Create_Context (Get_Script (Data), %(ada)s)',
        toada='Get_Data (Data, %(idx)d)'),

    'String': Mapping(ada='String', python='str'),
    'Integer': Mapping(ada='Integer', python='int'),
    'Boolean': Mapping(ada='Boolean', python='bool'),
    'Float': Mapping(ada='Float', python='float'),
    'Character': Mapping(
        ada='Glib.Gunichar',
        python=None),

    'File_Status': Mapping(
        ada='GPS.Kernel.File_Status',
        python='str',
        topython='File_Status\'Image (%(ada)s)',
        toada='File_Status\'Value (Data.Nth_Arg (%(idx)d))'),

    'Visible_Column': Mapping(
        ada='GNATCOLL.Xref.Visible_Column',
        python='int',
        topython='Integer (%(ada)s)',
        toada='GNATCOLL.Xref.Visible_Column'
            ' (Integer\'(Data.Nth_Arg(%(idx)d)))',
        withs=['GNATCOLL.Xref']),

    'Any_Type': Mapping(
        ada='GNATCOLL.Any_Types.Any_Type',
        python=None,
        withs=['GNATCOLL.Any_Types']),

    'Child_Group': Mapping(
        ada='Gtkada.MDI.Child_Group',
        python='String',
        toada='Gtkada.MDI.Child_Group\'Value (Data.Nth_Arg (%(idx)d))',
        topython='%(ada)s\'Img',
        withs=['Gtkada.MDI']),

    'Child_Position': Mapping(
        ada='Gtkada.MDI.Child_Position',
        python='String',
        toada='Gtkada.MDI.Child_Position\'Value (Data.Nth_Arg (%(idx)d))',
        topython='%(ada)s\'Img',
        withs=['Gtkada.MDI']),

    'Allowed_Areas': Mapping(
        ada='Gtkada.MDI.Allowed_Areas',
        python='String',
        toada='Gtkada.MDI.Allowed_Areas\'Value (Data.Nth_Arg (%(idx)d))',
        topython='%(ada)s\'Img',
        withs=['Gtkada.MDI']),

    'Preference': Mapping(
        ada='Default_Preferences.Preference',
        python=None,  # Not sent to python for backward compatibility
        toada='null',
        withs=['Default_Preferences']),

    'File': Mapping(
        ada='GNATCOLL.VFS.Virtual_File',
        python='GPS.File',
        topython='Create_File (Data.Get_Script, %(ada)s)',
        toada='GPS.Kernel.Scripts.Get_Data (Data.Nth_Arg (%(idx)d, '
            'Get_File_Class (K), Allow_Null => True))',
        withs=['GNATCOLL.VFS']),

    'Project': Mapping(
        ada='GNATCOLL.Projects.Project_Type',
        python='GPS.Project',
        toada='Get_Data (Data, %(idx)d)',
        topython='Create_Project (Data.Get_Script, %(ada)s)',
        withs=['GNATCOLL.Projects']),

    'Line_Info_Data': Mapping(
        ada='access GPS.Editors.Line_Information.Line_Information_Array',
        python=None,  # Not passed to python
        withs=['GPS.Editors.Line_Information']),

    'Message': Mapping(
        ada='GPS.Kernel.Messages.Message_Access',
        python='GPS.Message',
        topython='GPS.Kernel.Messages.Shell.Create_Message_Instance'
            ' (Get_Script (Data), %(ada)s)',
        toada='GPS.Kernel.Messages.Shell.Get_Data (Data.Nth_Arg (%(idx)d))',
        withs=['GPS.Kernel.Messages.Shell']),

    'Location': Mapping(
        ada='Location_Marker',
        python='GPS.Location',
        topython='To_String (%(ada)s)',
        toada='null'),

    'Distant_Server': Mapping(
        ada='Remote.Distant_Server_Type',
        python='str',
        toada='Remote.Distant_Server_Type\'Value (String\'(Data.Nth_Arg ' +
            '(%(idx)d)))',
        topython='Remote.Distant_Server_Type\'Image (%(ada)s)',
        withs=['Remote']),

    'Debugger': Mapping(
        ada='access GPS.Kernel.Base_Visual_Debugger\'Class',
        python='GPS.Debugger',
        topython='GPS.Kernel.Scripts.Get_Or_Create_Instance ' +
            '(Data.Get_Script, %(ada)s)',
        toada='Base_Visual_Debugger_Access (GNATCOLL.Scripts.Gtkada.Get_Data'
            ' (Data.Nth_Arg (%(idx)d, K.Scripts.New_Class ("Debugger"))))',
        withs=['Glib.Object', 'GNATCOLL.Scripts.Gtkada']),

    'Debugger_State': Mapping(
        ada='GPS.Kernel.Debugger_State',
        python='str',
        topython='GPS.Kernel.To_String (%(ada)s)',
        toada='GPS.Kernel.From_String (Data.Nth_Arg (%(idx)d))'),
}

# The following describe hook types (the various hook families with
# their list of parameters and return values). A reference to a 'type'
# is a reference to an entry in the 'types' dict above.

hook_types = {
    'simple_hooks': Hook_Type(
        [Param('name', '__hookname__')]),
    'string_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('str', 'String')]),
    'project_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('project', 'Project')]),
    'file_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('file', 'File')]),
    'file2_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('file', 'File'),
         Param('file2', 'File')]),
    'file_return_boolean_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('file', 'File')],
        returns='Boolean',
        return_default='False'),  # stop when one function returns True
    'task_hooks': Hook_Type(
        [Param('queue_id', 'Task')],
        descr='''
The hooks that report information about a running
background task or process'''),

    'file_location_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('file', 'File'),
         Param('line', 'Integer'),
         Param('column', 'Integer'),
         Param('project', 'Project', inpython=False,
               default='GNATCOLL.Projects.No_Project')]),

    'string_return_any_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('str', 'String')],
        returns='Any_Type',
        return_default='GNATCOLL.Any_Types.Empty_Any_Type'),

    'file_status_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('file', 'File'),
         Param('status', 'File_Status')]),

    'return_boolean_hooks': Hook_Type(
        [Param('name', '__hookname__')],
        returns='Boolean',
        return_default='True'),  # Stops when one returns False

    'diff_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('vcs_file', 'File'), # ??? From_Callback_Data_Diff used to
                                    # test Is_Cygin_Path and format it
         Param('orig_file', 'File'),
         Param('new_file', 'File'),
         Param('diff_file', 'File'),
         Param('title', 'String', default='""')],
        returns='Boolean',
        return_default='False'), # stops when one returns True

    'context_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('context', 'Context')],
        descr='Hooks that take a context as parameter'),

    'two_lines_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('context', 'Context', inpython=False, default='No_Context'),
         Param('line1', 'Integer'),
         Param('line2', 'Integer')]),

    'preferences_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('pref', 'Preference', default='null')]),

    'open_file_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('file', 'File'),
         Param('line', 'Integer', default=1, descr='''
If -1, all editors for this file will be closed instead'''),
         Param('column', 'Visible_Column', default=1),
         Param('column_end', 'Visible_Column', default=0),
         Param('enable_navigation', 'Boolean', default=True),
         Param('new_file', 'Boolean', default=True),
         Param('force_reload', 'Boolean', default=False),
         Param('focus', 'Boolean', default=True),
         Param('project', 'Project'),
         Param('group', 'Child_Group', default='Gtkada.MDI.Group_Default',
               inpython=False),
         Param('initial_position', 'Child_Position', inpython=False,
               default='Gtkada.MDI.Position_Automatic'),
         Param('Areas', 'Allowed_Areas', inpython=False,
               default='Gtkada.MDI.Central_Only'),
         Param('Title', 'String', default='""', inpython=False)
        ],
        returns='Boolean',
        return_default='False',  # Stops when one returns True
        returns_run=None),        # Ignore return value for Run

    # If file is set to No_File, this hook applies to all open_files.
    # ??? We used to emit the hook for all open_files, one by one, but it is
    # more efficient to run it once only
    'line_info_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('identifier', 'String'),
         Param('file', 'File'),
         Param('every_line', 'Boolean', default=True),
         Param('tooltip', 'String', default='""', inpython=False),
         Param('info', 'Line_Info_Data', inpython=False,
               default='null'),
         Param('icon_name', 'String', default='""', inpython=False)],
        descr='Info parameter must be Unchecked_Freed by the caller'),

    'rsync_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('synchronous', 'Boolean'),
         Param('force', 'Boolean'),
         Param('to_remote', 'Boolean'),
         Param('print_output', 'Boolean'),
         Param('print_command', 'Boolean'),
         Param('tool_name', 'String'),
         Param('host_name', 'String'),
         Param('queue_id', 'String'),
         Param('file', 'File')],
        returns='Boolean',
        return_default='False'),

    'compilation_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('category', 'String', descr='''
location or highlighting category that contains the compilation output'''),
         Param('quiet', 'Boolean', descr='''
If False, nothing should be reported to the user unless it is an error'''),
         Param('shadow', 'Boolean', descr='''
Whether the build launched was a Shadow builds, i.e. a secondary build
launched automatically by GPS after a real build. For instance, when
multiple toolchains mode is activated, the builds generating xref are
Shadow builds'''),
         Param('background', 'Boolean')],
        returns='Boolean',
        return_default='False'), # Stops when one returns True

    'compilation_finished_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('category', 'String', descr='''
location or highlighting category that contains the compilation output'''),
         Param('target', 'String'),
         Param('mode', 'String'),
         Param('shadow', 'Boolean', default='False', descr='''
Whether the build launched was a Shadow builds, i.e. a secondary build
launched automatically by GPS after a real build. For instance, when
multiple toolchains mode is activated, the builds generating xref are
Shadow builds''', inpython=False),
         Param('background', 'Boolean', default='False', inpython=False),
         Param('status', 'Integer')]),

    'character_hooks': Hook_Type(
        [Param('name',      '__hookname__'),
         Param('file',      'File'),
         Param('char',      'Character', default=0, inpython=False),
         Param('interactive', 'Boolean', default='True', inpython=False)]),

    'marker_hooks': Hook_Type(
        [Param('name',      '__hookname__'),
         Param('location',  'Location')]),

    'html_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('url_or_file', 'String'),
         Param('enable_navigation', 'Boolean', default=True),
         Param('anchor', 'String', default='""')],
        returns='Boolean',
        return_default='False', # Stop when one function returns True
        returns_run=None),      # Ignore return value for Run

    'message_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('message', 'Message')]),

    'server_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('server', 'Distant_Server'),
         Param('nickname', 'String')]),

    'debugger_hooks': Hook_Type(
        [Param('name', '__hookname__'),
         Param('debugger', 'Debugger')]),

    'debugger_states_hooks': Hook_Type(
        [Param('name',      '__hookname__'),
         Param('debugger',  'Debugger'),
         Param('new_state', 'Debugger_State')]),

    'debugger_string_hooks': Hook_Type(
        [Param('name',      '__hookname__'),
         Param('debugger',  'Debugger'),
         Param('str',       'String')],
        returns='String',
        return_default='""'),  # Stop when one returns non-empty string
}

# The following describe all specific hooks. They all belong to one
# of the families described above

hooks = [
    Hook('activity_checked_hook', 'simple_hooks',
         descr='''
Emitted when an activity status has been checked, the last step done
after the activity has been committed. It is at this point that the
activity closed status is updated.'''),

    Hook('after_character_added', 'character_hooks', descr='''
Emitted when a character has been added in the editor. This hook is also
called for the backspace key.\n
.. seealso:: :func:`GPS.Predefined_Hooks.character_added`\n
.. seealso:: :func:`GPS.Predefined_Hooks.word_added`'''),

    Hook('annotation_parsed_hook', 'simple_hooks',
         descr='Emitted when the last annotation has been parsed'),

    Hook('before_exit_action_hook', 'return_boolean_hooks', descr='''
Emitted when GPS is about to exit. If the function returns False,
the exit is aborted, and you should display a dialog to explain why'''),

    Hook('before_file_saved', 'file_hooks', descr='''
Emitted immediately before a file is saved'''),

    Hook('bookmark_added', 'string_hooks', descr='''
Emitted when a new bookmark has been created by the user. The
parameter is the name of the bookmark'''),

    Hook('bookmark_removed', 'string_hooks', descr='''
Emitted when a bookmark has been removed by the user. The parameter
is the name of the bookmark'''),

    Hook('buffer_edited', 'file_hooks', descr='''
Emitted after the user has stopped modifying the contents of an editor'''),

    Hook('build_mode_changed', 'string_hooks'),

    Hook('build_server_connected_hook', 'simple_hooks', descr='''
Emitted when GPS connects to the build server in remote mode'''),

    Hook('character_added', 'character_hooks', descr='''
Emitted when a character is going to be added in the editor. It is also
called when a character is going to be removed, in which case the last
parameter is 8 (control-h).\n
.. seealso:: :func:`GPS.Predefined_Hooks.after_character_added`\n
.. seealso:: :func:`GPS.Predefined_Hooks.word_added`'''),

    Hook('clipboard_changed', 'simple_hooks', descr='''
Emitted when the contents of the clipboard has changed, either
because the user added a new entry to it (Copy or Cut) or because
the index of the last paste operation has changed (Paste Previous)'''),

    Hook('commit_done_hook', 'simple_hooks',
         descr='Emitted when a commit has been done'),

    Hook('compilation_starting', 'compilation_hooks', descr='''
Emitted when a compilation operation is about to start.\n
Among the various tasks that GPS connects to this hook are: check
whether unsaved editors should be saved (asking the user), and stop the
background task that parses all xref info. If ``quiet`` is True, no
visible modification should be done in the MDI, such as raising consoles
or clearing their content, since the compilation should happen in
background mode.\n
Funtions connected to this hook should return False if the compilation
should not occur for some reason, True if it is OK to start the
compilation. Typically, the reason to reject a compilation would be
because the user has explicitly cancelled it through a graphical dialog,
or because running a background compilation is not suitable at this
time.\n
.. code-block:: python\n
   # The following code adds a confirmation dialog to all
   # compilation commands.
   def on_compilation_started(hook, category, quiet, shadow):
      if not quiet:
         return MDI.yes_no_dialog("Confirm compilation ?")
      else:
         return True
   Hook("compilation_starting").add(on_compilation_started)

.. code-block:: python\n
   # If you create a script to execute your own build script, you
   # should always do the following as part of your script. This
   # ensures a better integration in GPS (saving unsaved editors,
   # reloading xref information automatically in the end, raising
   # the GPS console, parsing error messages for automatically
   # fixable errors,...)

   if notHook ("compilation_starting").run_until_failure(
        "Builder results", False, False):
      return

   # ... spawn your command
   Hook("compilation_finished").run("Builder results")'''),

    Hook('compilation_finished', 'compilation_finished_hooks', descr='''
Emitted when a compile operation has finished.\n
Among the various tasks that GPS connects to this hook are the
automatic reparsing of all xref information, and the activation of
the automatic-error fixes. See also the hook "xref_updated"'''),

    Hook('compute_build_targets', 'string_return_any_hooks', descr='''
Emitted whenever GPS needs to compute a list of subtargets for a given
build target. The handler should check whether name is a known build
target, and if so, return a list of tuples, where each tuple corresponds
to one target and contains a display name (used in the menus, for
instance), the name of the target and the full path for the project.\n
If `str` is not known, it should return an empty list.\n
The `str` parameter is the name of the target, for instance 'main', 'exec'
or 'make'.\n
.. code-block:: python\n
   def compute_targets(hook, name):
      if name == "my_target":
        return [(display_name_1, target_1, ''),
                (display_name_2, target_2, '')]
      return ""
   GPS.Hook("compute_build_targets").add(compute_targets) '''),

    Hook('context_changed', 'context_hooks', descr='''
Emitted when the current context changes in GPS, such as when a new
file or entity is selected, or a window is created'''),

    Hook('contextual_menu_close', 'simple_hooks', descr='''
Called just before a contextual menu is destroyed. At this time, the
value returned by :func:`GPS.contextual_context` is still the one used
in the hook contextual_menu_open and you can still reference the data
you stored in the context. This hook is called even if no action was
selected by the user. However, it is always called before the action is
executed, since the menu itself is closed first.\n
 .. seealso:: :func:`GPS.Predefined_Hooks.contextual_menu_open`'''),

    Hook('contextual_menu_open', 'simple_hooks', descr='''
Called just before a contextual menu is created. It is called before any
of the filters is evaluated, and can be used to precomputed data shared
by multiple filters to speed up the computation. Use
:func:`GPS.contextual_context` to get the context of the contextual menu
and store precomputed data in it.\n
.. seealso:: :func:`GPS.Predefined_Hooks.contextual_menu_close`'''),

    Hook('debugger_breakpoints_changed', 'debugger_hooks', descr='''
The list of breakpoints set in the debugger was reloaded. It might
not have changed since the last time.'''),

    Hook('debugger_command_action_hook', 'debugger_string_hooks',
         descr='''
Called when the user types a command in the debugger console, or emits
the console through the GPS.Debugger API. It gives you a chance to
override the behavior for the command, or even define your own
commands. Note that you must ensure that any debugger command you
execute this way does finish with a prompt. The function should return
the output of your custom command\n
.. code-block:: python\n
    ## The following example implements a new gdb command, "hello". When
    ## the user types this command in the console, we end up executing
    ## "print A" instead. This can be used for instance to implement
    ## convenient macros
    def debugger_commands(hook, debugger, command):
       if command == "hello":
          return 'A=' + debugger.send("print A", False)
       else:
          return ""
    GPS.Hook("debugger_command_action_hook").add(debugger_commands)'''),

    Hook('debugger_context_changed', 'debugger_hooks', descr='''
Emitted when the context of the debuggee has changed, for instance
after thread switching, frame selection,...'''),

    Hook('debugger_executable_changed', 'debugger_hooks', descr='''
Emitted when the executable associated with the debugger has changed,
for instance via /Debug/Debug/Open File. This is also called initially
when the executable is given on the command line.'''),

    Hook('debugger_process_stopped', 'debugger_hooks', descr='''
Called when the debugger has ran and has stopped, for example when
hitting a breakpoint, or after a next command. If you need to know when
the debugger just started processing a command, you can connect to the
debugger_state_changed hook instead. Conceptually, you could connect to
debugger_state_changed at all times instead of debugger_process_stopped
and check when the state is now "idle".\n
.. seealso:: :func:`GPS.Predefined_Hooks.debugger_stated_changed`'''),

    Hook('debugger_process_terminated', 'debugger_hooks', descr=''''
Emitted when the debugged process has finished'''),

    Hook('debugger_question_action_hook', 'debugger_string_hooks', descr='''
Emitted just before displaying an interactive dialog, when
the underlying debugger is asking a question to the user. This hook
can be used to disable the dialog (and send the reply directly to the
debugger instead). It should return a non-empty string to pass to the
debugger if the dialog should not be displayed.
You cannot send any command to the debugger in this hook.
The string parameter contains the debugger question.\n
.. code-block:: python\n
    def gps_question(hook, debugger, str):
       return "1"   ## Always choose choice 1

    GPS.Hook("debugger_question_action_hook").add(gps_question)

    debug=GPS.Debugger.get()
    debug.send("print &foo")'''),

    Hook('debugger_started', 'debugger_hooks', descr='''
Emitted after the debugger has been spawned, and when it is possible
to send commands to it. Better to use debugger_state_changed\n
.. seealso:: :func:`GPS.Predefined_Hooks.debugger_stated_changed`'''),

    Hook('debugger_state_changed', 'debugger_states_hooks',
         descr='''
Indicates a change in the status of the debugger: ``new_state`` can be
one of "none" (the debugger is now terminated), "idle" (the debugger is
now waiting for user input) or "busy" (the debugger is now processing a
command, and the process is running). As opposed to
debugger_process_stopped, this hook is called when the command is just
starting its executing (hence the debugger is busy while this hook is
called, unless the process immediately stopped).\n
This hook is also called when internal commands are sent to the
debugger, and thus much more often than if it was just reacting to user
input. It is therefore recommended that the callback does the minimal
amount of work, possibly doing the rest of the work in an idle callback
to be executed when GPS is no longer busy.\n
If the new state is "busy", you cannot send additional commands to the
debugger.\n
When the state is either "busy" or "idle", GPS.Debugger.command will
return the command that is about to be executed or the command that was
just executed and just completed.'''),

    Hook('debugger_terminated', 'debugger_hooks', descr='''
Emitted just before the connection to the debugger is closed. It
is still possible to send commands. Better to use debugger_state_changed'''),

    Hook('desktop_loaded', 'simple_hooks'),

    Hook('diff_action_hook', 'diff_hooks', descr='''
Emitted to request the display of the comparison window'''),

    Hook('file_changed_detected', 'file_return_boolean_hooks', descr='''
Emitted whenever GPS detects that an opened file changed on the
disk. You can connect to this hook if you want to change the default
behavior, which is asking if the user wants to reload the file. Your
function should return 1 if the action is handled by the function, and
return 0 if the default behavior is desired.\n
.. code-block:: python\n
  import GPS

  def on_file_changed(hook, file):
      # automatically reload the file without prompting the user
      ed = GPS.EditorBuffer.get(file, force = 1)
      return 1

  # install a handler on "file_changed_detected" hook
  GPS.Hook("file_changed_detected").add(on_file_changed)'''),

    Hook('file_changed_on_disk', 'file_hooks', descr='''
Emitted when some external action has changed the contents of a file on
the disk, such as a VCS operation. The parameter might be a directory
instead of a file, indicating that any file in that directory might have
changed.'''),

    Hook('file_closed', 'file_hooks', descr='''
Emitted just before the last editor for a file is closed. You can still
use :func:`EditorBuffer.get` and :func:`current_view` to access the last
editor for `file`.'''),

    Hook('file_deleted', 'file_hooks', descr='''
Emitted whenever GPS detects that a file was deleted on the disk. The
parameter might be a directory instead of a file, indicating that any
file within that directory has been deleted.'''),

    Hook('file_edited', 'file_hooks', descr='''
Emitted when a file editor has been opened for a file that was not already
opened before. Do not confuse with the hook open_file_action, which is
used to request the opening of a file.\n
.. seealso:: :func:`GPS.Predefined_Hooks.open_file_action_hook`'''),

    Hook('file_line_action_hook', 'line_info_hooks', descr='''
Emitted to request the display of new information on the side of the
editors. You usually will not connect to this hook, but you might want to
run it yourself to ask GPS to display some information on the side of
its editors.
If Info is null or empty, existing line information is removed.
If the first index in Info is 0, then space is reserved on the side for a
new column, but no information is added yet. The first item should provide
info to compute the maximum width of the column (text + icon).
If the first index is -1, then extra information is added in the status
bar (not on the side), using the provided Icon_Nam and Tooltip.
Otherwise, information is added for all the lines with a corresponding
entry in Info.'''),

    Hook('file_renamed', 'file2_hooks', descr='''
Emitted whenever a GPS action renamed a file on the disk. `file`
indicates the initial location of the file, while ``renamed`` indicates
the new location. The parameters might be directories instead of files,
indicating that the directory has been renamed, and thus any file within
that directory have their path changed.'''),

    Hook('file_saved', 'file_hooks', descr='''
Emitted whenever a file has been saved'''),

    Hook('file_status_changed', 'file_status_hooks', descr='''
Emitted when a file status has changed. The value for the status could
be one of "UNMODIFIED", "MODIFIED" or "SAVED".'''),

    Hook('gps_started', 'simple_hooks', descr='''
Emitted when GPS is fully loaded and its window is visible to the user.
You should not do any direct graphical action before this hook has been
called, so it is recommended that in most cases your start scripts
connect to this hook.'''),

    Hook('html_action_hook', 'html_hooks', descr='''
Emitted to request the display of HTML files. It is generally useful
if you want to open an HTML file and let GPS handle it in the usual
manner.'''),

    Hook('location_changed', 'file_location_hooks', descr='''
Emitted when the location in the current editor has changed, and the
cursor has stopped moving.'''),

    Hook('log_parsed_hook', 'simple_hooks',
         descr='Emitted when the last log has been parsed'),

    Hook('marker_added_to_history', 'marker_hooks', descr='''
Emitted when a new marker is added to the history list of previous
locations, where the user can navigate backwards and forwards.'''),

    Hook('message_selected', 'message_hooks'),

    Hook('open_file_action_hook', 'open_file_hooks', descr='''
Emitted when GPS needs to open a file. You can connect to this hook if
you want to have your own editor open, instead of GPS's internal
editor. Your function should return 1 if it did open the file or 0 if
the next function connected to this hook should be called.\n
The file should be opened directly at `line` and `column`. If
`column_end` is not 0, the given range should be highlighted if
possible.  `enable_navigation` is set to True if the new location
should be added to the history list, so that the user can navigate
forward and backward across previous locations. `new_file` is set to
True if a new file should be created when file is not found. If set to
False, nothing should be done. `force_reload` is set to true if the
file should be reloaded from the disk, discarding any change the user
might have done. focus is set to true if the open editor should be given
the keyboard focus.\n
.. seealso:: :func:`GPS.Predefined_Hooks.file_edited`\n
.. code-block:: python\n
    GPS.Hook('open_file_action_hook').run(
              GPS.File("gps-kernel.ads"),
              322, # line
              5,   # column
              9,   # column_end
              1,   # enable_navigation
              1,   # new_file
              0)   # force_reload'''),

    Hook('preferences_changed',
        # ??? Should not be emitted if Kernel.Preferences.Is_Frozen
        'preferences_hooks', descr='''
Emitted when the value of some of the preferences changes. Modules should
refresh themselves dynamically.'''),

    Hook('project_changed', 'simple_hooks', descr='''
Emitted when the project has changed. A new project has been loaded, and
all previous settings and caches are now obsolete. In the callbacks for
this hook, the attribute values have not been computed from the project
yet, and will only return the default values. Connect to the
project_view_changed hook instead to query the actual values.\n
.. seealso:: :func:`GPS.Predefined_Hooks.project_view_changed`'''),

    Hook('project_changing', 'file_hooks', descr='''
Emitted just before a new project is loaded'''),

    Hook('project_editor', 'simple_hooks', descr='''
Emitted before the :guilabel:`Project Editor` is opened. This allows a
custom module to perform specific actions before the actual creation of
this dialog.'''),

    Hook('project_saved', 'project_hooks', descr='''
Emitted when a project is saved to disk. It is called for each
project in the hierarchy.'''),

    Hook('project_view_changed', 'simple_hooks', descr='''
Emitted when the project view has been changed, for instance because one
of the environment variables has changed. This means that the list of
directories, files or switches might now be different. In the callbacks
for this hook, you can safely query the new attribute values.'''),

    Hook('revision_parsed_hook', 'simple_hooks',
         descr='Emitted when the last revision has been parsed'),

    Hook('rsync_action_hook', 'rsync_hooks', descr='internal use only'),

    Hook('rsync_finished', 'simple_hooks'),

    Hook('search_functions_changed', 'simple_hooks', descr='''
Emitted when the list of registered search functions changes.'''),

    Hook('search_regexps_changed', 'simple_hooks', descr='''
Emitted when a new regexp has been added to the list of predefined search
patterns.'''),

    Hook('search_reset', 'simple_hooks', descr='''
Emitted when the current search pattern is reset or changed by the
user or when the current search is no longer possible because the setup
of GPS has changed.'''),

    Hook('server_config_hook', 'server_hooks', descr='''
Emitted when a server is assigned to a server operations category.\n
The `server_type` parameter is the server operations category. It can
take the values "BUILD_SERVER", "EXECUTION_SERVER" or "DEBUG_SERVER".'''),

    Hook('server_list_hook', 'simple_hooks', descr='''
Emitted when the list of configured servers has changed.'''),

    Hook('status_parsed_hook', 'simple_hooks',
         descr='Emitted when the last status has been parsed'),

    Hook('stop_macro_action_hook', 'simple_hooks', descr='''
You should run this hook to request that the macro currently being
replayed be stopped. No more events should be processed as part of this
macro.'''),

    Hook('source_lines_folded', 'two_lines_hooks'),

    Hook('source_lines_revealed', 'context_hooks', descr='''
Emitted when a range of lines becomes visible on the screen.'''),

    Hook('source_lines_unfolded', 'two_lines_hooks'),

    Hook('task_changed', 'task_hooks', descr='''
Emitted when a background task's status is changed (such as making
progress or paused). For efficiency, GPS will not necessarily emit
this hook for each progress made by the task when this progress is
frequent.'''),

    Hook('task_started', 'task_hooks', descr='''
Emitted when a new background task is started'''),

    Hook('task_terminated', 'task_hooks', descr='''
Emitted when a background task is terminated'''),

    Hook('variable_changed', 'simple_hooks', descr='''
Emitted when one of the scenario variables has been renamed, removed or
when one of its possible values has changed.'''),

    Hook('word_added', 'file_hooks', descr='''
Emitted when a word has been added in an editor.\n
.. seealso:: :func:`GPS.Predefined_Hooks.character_added`'''),

    Hook('xref_updated', 'simple_hooks', descr='''
Emitted when the cross-reference information has been updated.''')
]

#########################################################################
# Code below
#########################################################################


def generate():
    """
    Generate Ada code for the hooks
    """

    withs = {'with %s;' % n
             for t in hook_types.values()
             for p in t.params
             for n in types[p.type].withs}
    withs.update({'with %s;' % n
                  for t in hook_types.values()  if t.returns is not None
                  for n in types[t.returns].withs})
    withs = '\n'.join(sorted(withs))

    f = open('../generated/gps-kernel-hooks.ads', 'wb')
    f.write('''--  Automatically generated from hooks.py
pragma Style_Checks (Off);
pragma Warnings (Off, "unit * is not referenced in spec");
with GNATCOLL.Scripts;   use GNATCOLL.Scripts;
%(withs)s
package GPS.Kernel.Hooks is

   procedure Register_Hooks
      (Kernel : not null access Kernel_Handle_Record'Class);
   --  Make all hooks available to python

   function Name (Self : not null access Hook_Function'Class) return String
      with Inline => True;
   function Name (Self : Hook_Types'Class) return String with Inline => True;
   --  Return the name for Self. When the hook has not been registered,
   --  it has no Name so we display the type.

   ---------------------------
   -- File_Line_Action_Hook --
   ---------------------------
   --  Wrappers around "file_line_action_hook"

   procedure Create_Line_Information_Column
     (Kernel     : not null access GPS.Kernel.Kernel_Handle_Record'Class;
      File       : GNATCOLL.VFS.Virtual_File;
      Identifier : String;
      Info       : GPS.Editors.Line_Information.Line_Information_Record :=
         GPS.Editors.Line_Information.Empty_Line_Information;
      Every_Line : Boolean := True);
   --  Request the creation of a column on the side of some editors.
   --  Info is used to determine the size of the column: it can contain either
   --  an image or some markup. If Empty_Line_Information is given, the column
   --  will have the default size needed to contain one icon.

   procedure Remove_Line_Information_Column
     (Kernel     : not null access GPS.Kernel.Kernel_Handle_Record'Class;
      File       : GNATCOLL.VFS.Virtual_File;
      Identifier : String);
   --  Remove the column identified by Identifier for the editors of File.
   --  If File is empty, then the column will be removed for all open files.

   procedure Add_Line_Information
     (Kernel     : not null access GPS.Kernel.Kernel_Handle_Record'Class;
      File       : GNATCOLL.VFS.Virtual_File;
      Identifier : String;
      Info       : access GPS.Editors.Line_Information.Line_Information_Array;
      Tooltip    : String := "";
      Icon_Name  : String := "");
   --  Add line information to File.
   --  The range of Info must correspond to the range of line numbers
   --  that are to be modified. If the range is -1 .. -1, the info is added to
   --  the status line of the editors (and tooltip is then used when hovering
   --  that label).
   --  Info will be freed by the editor.
   --  Icon_Name is the name of a stock icon to display

   -----------
   -- Hooks --
   -----------

''' % {'withs': withs})

    b = open('../generated/gps-kernel-hooks.adb', 'wb')
    b.write('''--  Automatically generated from hooks.py
pragma Style_Checks (Off);
pragma Warnings (Off, "comparison with True is redundant");
pragma Warnings (Off, "constant * is not referenced");
with Ada.Tags;                  use Ada.Tags;
with GPS.Kernel.Scripts;        use GPS.Kernel.Scripts;
with GPS.Kernel.Scripts.Hooks;  use GPS.Kernel.Scripts.Hooks;
with GNATCOLL.Traces;           use GNATCOLL.Traces;
package body GPS.Kernel.Hooks is
   Me : constant Trace_Handle := Create ("HOOKS", GNATCOLL.Traces.Off);
   use type GNATCOLL.Any_Types.Any_Type;

   function Name (Self : Hook_Types'Class) return String is
   begin
      if Self.Name /= null then
         return Self.Name.all;
      else
         return "unregistered " & Self.Type_Name;
      end if;
   end Name;

   function Name (Self : not null access Hook_Function'Class) return String is
   begin
      if Self.all in Python_Hook_Function'Class then
         return Get_Name (Python_Hook_Function (Self.all).Func);
      else
         return External_Tag (Self'Tag);
      end if;
   end Name;

   ------------------------------------
   -- Create_Line_Information_Column --
   ------------------------------------

   procedure Create_Line_Information_Column
     (Kernel     : not null access GPS.Kernel.Kernel_Handle_Record'Class;
      File       : GNATCOLL.VFS.Virtual_File;
      Identifier : String;
      Info       : GPS.Editors.Line_Information.Line_Information_Record :=
         GPS.Editors.Line_Information.Empty_Line_Information;
      Every_Line : Boolean := True)
   is
      Params : aliased GPS.Editors.Line_Information.Line_Information_Array :=
         (0 .. 0 => Info);
   begin
      if File /= GNATCOLL.VFS.No_File then
         File_Line_Action_Hook.Run
            (Kernel       => Kernel,
             File         => File,
             Identifier   => Identifier,
             Info         => Params'Access,
             Every_Line   => Every_Line);
      else
         for F of Kernel.Open_Files loop
            File_Line_Action_Hook.Run
               (Kernel       => Kernel,
                File         => File,
                Identifier   => Identifier,
                Info         => Params'Access,
                Every_Line   => Every_Line);
         end loop;
      end if;
   end Create_Line_Information_Column;

   ------------------------------------
   -- Remove_Line_Information_Column --
   ------------------------------------

   procedure Remove_Line_Information_Column
     (Kernel     : not null access GPS.Kernel.Kernel_Handle_Record'Class;
      File       : GNATCOLL.VFS.Virtual_File;
      Identifier : String)
   is
   begin
      if Kernel.Is_In_Destruction then
         null;
      elsif File /= GNATCOLL.VFS.No_File then
         File_Line_Action_Hook.Run
            (Kernel       => Kernel,
             File         => File,
             Identifier   => Identifier,
             Info         => null);
      else
         for F of Kernel.Open_Files loop
            File_Line_Action_Hook.Run
               (Kernel       => Kernel,
                File         => File,
                Identifier   => Identifier,
                Info         => null);
         end loop;
      end if;
   end Remove_Line_Information_Column;

   --------------------------
   -- Add_Line_Information --
   --------------------------

   procedure Add_Line_Information
     (Kernel     : not null access GPS.Kernel.Kernel_Handle_Record'Class;
      File       : GNATCOLL.VFS.Virtual_File;
      Identifier : String;
      Info       : access GPS.Editors.Line_Information.Line_Information_Array;
      Tooltip    : String := "";
      Icon_Name  : String := "") is
   begin
      if File /= GNATCOLL.VFS.No_File then
         File_Line_Action_Hook.Run
            (Kernel       => Kernel,
             File         => File,
             Identifier   => Identifier,
             Info         => Info,
             Tooltip      => Tooltip,
             Icon_Name    => Icon_Name);
      else
         for F of Kernel.Open_Files loop
            File_Line_Action_Hook.Run
               (Kernel       => Kernel,
                File         => File,
                Identifier   => Identifier,
                Info         => Info,
                Tooltip      => Tooltip,
                Icon_Name    => Icon_Name);
         end loop;
      end if;
   end Add_Line_Information;
''')

    for type_name in sorted(hook_types.keys()):
        t = hook_types[type_name]
        params = [';\n       %s : %s%s' % (
                        p.name.title(),
                        types[p.type].ada,
                        ' := %s' % p.default if p.default is not None else '')
                  for p in t.params if p.show_in_ada()]

        plist = [', %s' % p.name.title()
                 for p in t.params if p.show_in_ada()]

        python_params = [p for p in t.params if p.show_in_python()]
        pset = ['''
                  Data.Set_Nth_Arg
                     (%d, %s);''' % (
                      index + 1,
                      types[p.type].topython % {
                          'ada': p.name.title()})
                for (index, p) in enumerate(python_params)]

        python_and_ada_params = [
            p for p in t.params if p.show_in_ada() and p.show_in_python()]
        p_in_run = [',\n             %s => %s' % (
            p.name.title(),
            types[p.type].toada % {'idx': idx + 2})  # '+1 to ignore Self'
            for (idx, p) in enumerate(python_and_ada_params)]

        param_descr = '\n'.join(
            '   --  %s : %s' % (p.name.title(),
                                p.descr.strip().replace('\n', '\n   --    '))
            for p in t.params if p.descr)
        if param_descr:
            param_descr = '\n' + param_descr

        subst = {
            'name': type_name.title(),
            'basename': type_name,
            'descr': t.descr,
            'param_descr': param_descr,
            'pcount': len([p for p in t.params if p.show_in_python()]),
            'plist': ''.join(plist),
            'pset': ''.join(pset),
            'p_in_run': ''.join(p_in_run),
            'returndefault': t.return_default,
            'run_name': 'Run' if not t.internal_run else 'Run_Internal',
            'params': ''.join(params)}

        # Settings for the callback function

        if t.returns is None:
            subst['func_proc_or_func'] = 'procedure'
            subst['func_returns'] = ''
            subst['ada_call'] = '''
               %(name)s_Function'Class (F.all).Execute
                  (Kernel%(plist)s);''' % subst
        else:
            subst['func_proc_or_func'] = 'function'
            subst['func_returns'] = ' return %s' % types[t.returns].ada
            subst['func_return_type'] = types[t.returns].ada
            if t.returns_run:
                subst['tmp_return'] = ' Tmp'
            else:
                subst['tmp_return'] = ''
            subst['ada_call'] = '''
               declare
                  Tmp : constant %(func_return_type)s := %(name)s_Function'Class
                     (F.all).Execute (Kernel%(plist)s);
               begin
                  if Active (Me) then
                     Decrease_Indent (Me);
                  end if;
                  if Tmp /= %(returndefault)s then
                     return%(tmp_return)s;
                  end if;
               end;''' % subst

        # Settings for the Run function

        if t.returns_run:
            subst['returntype'] = types[t.returns_run].ada
            subst['run_proc_or_func'] = 'function'
            subst['run_exit'] = ''
            subst['returns_run'] = ' return %s' % types[t.returns_run].ada
            subst['returns_run_type'] = types[t.returns_run].ada
            subst['run_body'] = '''
                  declare
                     Tmp : constant %(returns_run_type)s := F2.Execute (Data);
                  begin
                     Free (Data);
                     if Tmp /= %(returndefault)s then
                        if Active (Me) then
                           Decrease_Indent (Me);
                        end if;
                        return Tmp;
                     end if;
                  end;''' % subst
            subst['returns_body'] = '\n      return %s;' % t.return_default
            subst['run_from_python_body'] = types[t.returns_run].setreturn
            subst['run_from_python_var'] = '''
         Tmp : constant %(returntype)s := Self.Run
            (Kernel => K%(p_in_run)s);''' % subst

        # Settings for the Run procedure

        else:
            subst['run_proc_or_func'] = 'procedure'
            subst['returns_run'] = ''
            subst['run_exit'] = '''
      if Active (Me) then
         Decrease_Indent (Me);
      end if;'''

            if t.returns is None:
                subst['returns_body'] = ''
                subst['run_body'] = '''
                         declare
                            Tmp : constant Boolean := F2.Execute (Data);
                            pragma Unreferenced (Tmp);
                         begin
                            Free (Data);
                         end;'''
            else:
                subst['returns_body'] = '''
      if Active (Me) then
         Trace (Me, "Default handling for " & Name (Self));
      end if;''' % subst
                subst['run_body'] = '''
                         declare
                            Tmp : constant Boolean := F2.Execute (Data);
                         begin
                            Free (Data);
                            if Tmp /= %(returndefault)s then
                               if Active (Me) then
                                  Decrease_Indent (Me);
                               end if;
                               return;
                            end if;
                         end;''' % subst

            subst['run_from_python_var'] = ''
            subst['run_from_python_body'] = '''Self.Run   --  not dispatching
         (Kernel   => K%(p_in_run)s);''' % subst

        # Output specs

        hooks.sort(key=lambda h: h.name)

        f.write('''
   --------------
   -- %(name)s --
   --------------%(descr)s

   type %(name)s_Function is abstract new Hook_Function
      with null record;
   %(func_proc_or_func)s Execute
      (Self   : %(name)s_Function;
       Kernel : not null access Kernel_Handle_Record'Class%(params)s)%(func_returns)s is abstract;

   type %(name)s is new Hook_Types with null record;
   overriding function Type_Name (Self : %(name)s) return String
      is ("%(basename)s");
   procedure Add
      (Self  : in out %(name)s;
       Obj   : not null access %(name)s_Function'Class;
       Last  : Boolean := True;
       Watch : access Glib.Object.GObject_Record'Class := null);
   %(run_proc_or_func)s %(run_name)s
      (Self   : in out %(name)s;
       Kernel : not null access Kernel_Handle_Record'Class%(params)s)%(returns_run)s;
   overriding procedure Run_From_Python
      (Self : in out %(name)s; Data : in out Callback_Data'Class);%(param_descr)s
''' % subst)

        # Output body

        b.write('''
   ---------
   -- Add --
   ---------

   procedure Add
      (Self  : in out %(name)s;
       Obj   : not null access %(name)s_Function'Class;
       Last  : Boolean := True;
       Watch : access Glib.Object.GObject_Record'Class := null) is
   begin
      Self.Add_Hook_Func (Obj, Last, Watch);
   end Add;

   ---------
   -- %(run_name)s --
   ---------

   %(run_proc_or_func)s %(run_name)s
      (Self   : in out %(name)s;
       Kernel : not null access Kernel_Handle_Record'Class%(params)s)%(returns_run)s
   is
      use Hook_Func_Lists;

      List : array (1 .. Natural (Self.Funcs.Length)) of
        access Hook_Function'Class;
      Last : Natural := 0;
   begin
      if Active (Me) then
         Increase_Indent (Me, "Run " & Name (Self));
      end if;

      --  One of the issues here is that running a hook could add or remove
      --  a function from Self.Funcs. We use an explicit copy to compensate

      for J of Self.Funcs loop
         Last := Last + 1;
         List (Last) := J.Func;
         List (Last).Refcount := List (Last).Refcount + 1;
      end loop;

      for F of List loop
         begin
            if F.Refcount = 1 then
                --  Skip already deleted hooks
                null;
            elsif F.all in Python_Hook_Function'Class then
               declare
                  F2 : constant Subprogram_Type :=
                     Python_Hook_Function (F.all).Func;
                  Data : Callback_Data'Class :=
                     F2.Get_Script.Create (Arguments_Count => %(pcount)s);
               begin%(pset)s%(run_body)s
               end;
            else%(ada_call)s
            end if;
         exception
            when E : others =>
               Trace (Me, E, "While running "
                  & Name (Self) & ":" & Name (F));
         end;
      end loop;

      for J of List loop
         if J.Refcount = 1 then
            Remove_Hook_Func (Self, J);
         end if;
      end loop;%(run_exit)s%(returns_body)s
   end %(run_name)s;

   ---------------------
   -- Run_From_Python --
   ---------------------

   overriding procedure Run_From_Python
      (Self : in out %(name)s; Data : in out Callback_Data'Class)
   is
      K : constant Kernel_Handle := Get_Kernel (Data);
   begin
      declare%(run_from_python_var)s
      begin
         %(run_from_python_body)s
      end;
   exception
      when E : others =>
         Trace (Me, E, " while running " & Name (Self));
   end Run_From_Python;
''' % subst)

        # Output list of hooks

        f.write('\n')
        for hook in hooks:
            if hook.type == type_name:
                f.write('''   N_%(name)s : aliased constant String := "%(base)s";
   %(varname)s : aliased %(type)s;%(descr)s
''' % {'name': hook.name.title(),
       'varname': '%s_Hook' % hook.name.title()
           if not hook.name.endswith('_hook')
           else hook.name.title(),
       'base': hook.name,
       'type': type_name.title(),
       'descr': hook.descr})

    f.write('end GPS.Kernel.Hooks;\n')
    f.close()

    b.write('''
   --------------------
   -- Register_Hooks --
   --------------------

   procedure Register_Hooks
      (Kernel : not null access Kernel_Handle_Record'Class)
   is
   begin
      Trace (Me, "Register predefined hooks");
''')
    for h in hooks:
        varname = ('%s_Hook' % h.name.title()
           if not h.name.endswith('_hook')
           else h.name.title())
        b.write('      %s.Name := N_%s\'Access;\n' % (varname, h.name.title()))
        b.write('      %s.Register (Kernel);\n' % varname)
    b.write('''   end Register_Hooks;
end GPS.Kernel.Hooks;
''')
    b.close()


def generate_doc():
    """
    Generate the documentation for hooks.
    """
    f = open('../../docs/users_guide/GPS/generated_hooks.py', 'wb')
    f.write('''
import GPS
class Predefined_Hooks:
    """
    This class is not available in GPS itself. It is included in this
    documentation as a way to describe all the predefined hooks that GPS
    exports.

    Each function below describes the name of the hook (as should be used
    as parameter to GPS.Hook constructor), as well as the list of parameters
    that are passed by GPS.

    """
''')

    for h in hooks:
        type = hook_types[h.type]

        def p_param(p):
            descr = p.descr.strip()
            if descr:
                descr = '\n         ' + descr.replace('\n', '\n         ')
            return descr

        params = ['\n      :param %(python)s %(name)s:%(default)s%(doc)s' % {
                     'python': types[p.type].python,
                     'doc': p_param(p),
                     'default': ' (default: %s)' % p.default
                                if p.default else '',
                     'name': p.name}
                  for p in type.params if p.show_in_python()]

        plist = ','.join([p.name for p in type.params])

        descr = h.raw_descr.strip() 
        if descr:
            descr = '\n      ' + descr.replace('\n', '\n      ') + '\n'

        if type.returns is not None and types[type.returns].python is not None:
            returns = '\n      :return: %s\n' % types[type.returns].python
        else:
            returns = ''

        subst = {
            'base': h.name,
            'descr': descr,
            'plist': plist,
            'return': returns,
            'params': ''.join(params)
        }

        f.write('''
    # %(base)s = '%(base)s'
    def %(base)s(%(plist)s):
        """%(descr)s%(params)s%(return)s
        """
''' % subst)


    f.write('GPS.Predefined_Hooks = Predefined_Hooks\n')
    f.close()

generate()
generate_doc()
