Logo Search packages:      
Sourcecode: gretl version File versions

gtksourcebuffer.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- 
 *  gtksourcebuffer.c
 *
 *  Copyright (C) 1999,2000,2001,2002 by:
 *          Mikael Hermansson <tyan@linux.se>
 *          Chris Phelps <chicane@reninet.com>
 *          Jeroen Zwartepoorte <jeroen@xs4all.nl>
 *          
 *  Copyright (C) 2003 - Paolo Maggi <paolo.maggi@polito.it>
 *          Gustavo Giráldez <gustavo.giraldez@gmx.net>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>
#include <gtk/gtk.h>

#include "gtksourceview-i18n.h"
#include "gtksourcebuffer.h"
#include "gtksourcetag.h"
#include "gtksourcetag-private.h"

#include "gtksourceundomanager.h"
#include "gtksourceview-marshal.h"
#include "gtktextregion.h"

#include "gtksourceiter.h"

/*
#define ENABLE_DEBUG
#define ENABLE_PROFILE
*/
#undef ENABLE_DEBUG
#undef ENABLE_PROFILE

#ifdef ENABLE_DEBUG
#define DEBUG(x) (x)
#else
#define DEBUG(x)
#endif

#ifdef ENABLE_PROFILE
#define PROFILE(x) (x)
#else
#define PROFILE(x)
#endif

/* define this to always highlight in an idle handler, and not
 * possibly in the expose method of the view */
#undef LAZIEST_MODE

/* in milliseconds */
#define WORKER_TIME_SLICE                   30
#define INITIAL_WORKER_BATCH                40960
#define MINIMUM_WORKER_BATCH                1024

#define MAX_CHARS_BEFORE_FINDING_A_MATCH    2000

typedef struct _SyntaxDelimiter      SyntaxDelimiter;
typedef struct _PatternMatch         PatternMatch;

/* Signals */
enum {
      CAN_UNDO = 0,
      CAN_REDO,
      HIGHLIGHT_UPDATED,
      MARKER_UPDATED,
      LAST_SIGNAL
};

/* Properties */
enum {
      PROP_0,
      PROP_ESCAPE_CHAR,
      PROP_CHECK_BRACKETS,
      PROP_HIGHLIGHT,
      PROP_MAX_UNDO_LEVELS,
      PROP_LANGUAGE
};

struct _SyntaxDelimiter 
{
      gint                offset;
      gint                depth;
      GtkSyntaxTag       *tag;
};

struct _PatternMatch
{
      GtkPatternTag        *tag;
      GtkSourceBufferMatch  match;
};

struct _GtkSourceBufferPrivate 
{
      gint                   highlight:1;
      gint                   check_brackets:1;

      GtkTextTag            *bracket_match_tag;
      GtkTextMark           *bracket_mark;
      guint                  bracket_found:1;
      
      GArray                *markers;

      GList                 *syntax_items;
      GList                 *pattern_items;
      GtkSourceRegex        *reg_syntax_all;
      gunichar               escape_char;

      /* Region covering the unhighlighted text */
      GtkTextRegion         *refresh_region;

      /* Syntax regions data */
      GArray                *syntax_regions;
      GArray                *old_syntax_regions;
      gint                   worker_last_offset;
      gint                   worker_batch_size;
      guint                  worker_handler;

      /* views highlight requests */
      GtkTextRegion         *highlight_requests;

      GtkSourceLanguage     *language;

      GtkSourceUndoManager  *undo_manager;
};



static GtkTextBufferClass *parent_class = NULL;
static guint       buffer_signals[LAST_SIGNAL] = { 0 };

static void        gtk_source_buffer_class_init       (GtkSourceBufferClass    *klass);
static void        gtk_source_buffer_init             (GtkSourceBuffer         *klass);
static GObject  *gtk_source_buffer_constructor          (GType                    type,
                                           guint                    n_construct_properties,
                                           GObjectConstructParam   *construct_param);
static void        gtk_source_buffer_finalize         (GObject                 *object);
static void      gtk_source_buffer_set_property         (GObject                 *object,
                                           guint                    prop_id,
                                           const GValue            *value,
                                           GParamSpec              *pspec);
static void      gtk_source_buffer_get_property         (GObject                 *object,
                                           guint                    prop_id,
                                           GValue                  *value,
                                           GParamSpec              *pspec);

static void        gtk_source_buffer_can_undo_handler       (GtkSourceUndoManager    *um,
                                           gboolean                 can_undo,
                                           GtkSourceBuffer         *buffer);
static void        gtk_source_buffer_can_redo_handler (GtkSourceUndoManager    *um,
                                           gboolean                 can_redo,
                                           GtkSourceBuffer         *buffer);

static void        gtk_source_buffer_move_cursor            (GtkTextBuffer           *buffer,
                                           GtkTextIter             *iter,
                                           GtkTextMark             *mark, 
                                           gpointer                 data);

static void        gtk_source_buffer_real_insert_text       (GtkTextBuffer           *buffer,
                                           GtkTextIter             *iter,
                                           const gchar             *text,
                                           gint                     len);
static void        gtk_source_buffer_real_delete_range      (GtkTextBuffer           *buffer,
                                           GtkTextIter             *iter,
                                           GtkTextIter             *end);

static const GtkSyntaxTag *iter_has_syntax_tag        (const GtkTextIter       *iter);

static void        get_tags_func                      (GtkTextTag              *tag, 
                                                     gpointer                 data);

static void  highlight_region                   (GtkSourceBuffer         *source_buffer,
                                           GtkTextIter             *start, 
                                           GtkTextIter             *end);

static GList      *gtk_source_buffer_get_syntax_entries     (const GtkSourceBuffer   *buffer);
static GList      *gtk_source_buffer_get_pattern_entries    (const GtkSourceBuffer   *buffer);

static void  sync_syntax_regex                  (GtkSourceBuffer         *buffer);

static void      build_syntax_regions_table             (GtkSourceBuffer         *buffer,
                                           const GtkTextIter       *needed_end);
static void      update_syntax_regions                  (GtkSourceBuffer         *source_buffer,
                                           gint                     start,
                                           gint                     delta);

static void      invalidate_syntax_regions              (GtkSourceBuffer         *source_buffer,
                                           GtkTextIter             *from,
                                           gint                     delta);
static void      refresh_range                          (GtkSourceBuffer         *buffer,
                                           GtkTextIter             *start,
                                           GtkTextIter             *end);
static void      ensure_highlighted                     (GtkSourceBuffer         *source_buffer,
                                           const GtkTextIter       *start,
                                           const GtkTextIter       *end);

static gboolean    gtk_source_buffer_find_bracket_match_real (GtkTextIter          *orig, 
                                              gint                  max_chars);

static void  gtk_source_buffer_remove_all_source_tags (GtkSourceBuffer   *buffer,
                                          const GtkTextIter *start,
                                          const GtkTextIter *end);

static void sync_with_tag_table                 (GtkSourceBuffer *buffer);


GType
gtk_source_buffer_get_type (void)
{
      static GType our_type = 0;

      if (!our_type) {
            static const GTypeInfo our_info = {
                  sizeof (GtkSourceBufferClass),
                  NULL, /* base_init */
                  NULL, /* base_finalize */
                  (GClassInitFunc) gtk_source_buffer_class_init,
                  NULL, /* class_finalize */
                  NULL, /* class_data */
                  sizeof (GtkSourceBuffer),
                  0,    /* n_preallocs */
                  (GInstanceInitFunc) gtk_source_buffer_init
            };

            our_type = g_type_register_static (GTK_TYPE_TEXT_BUFFER,
                                       "GtkSourceBuffer",
                                       &our_info, 
                                       0);
      }
      
      return our_type;
}
      
static void
gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
{
      GObjectClass        *object_class;
      GtkTextBufferClass  *tb_class;

      object_class      = G_OBJECT_CLASS (klass);
      parent_class      = g_type_class_peek_parent (klass);
      tb_class    = GTK_TEXT_BUFFER_CLASS (klass);
            
      object_class->constructor  = gtk_source_buffer_constructor;
      object_class->finalize     = gtk_source_buffer_finalize;
      object_class->get_property = gtk_source_buffer_get_property;
      object_class->set_property = gtk_source_buffer_set_property;
      
      klass->can_undo    = NULL;
      klass->can_redo    = NULL;
      klass->highlight_updated = NULL;
      klass->marker_updated    = NULL;
      
      /* Do not set these signals handlers directly on the parent_class since
       * that will cause problems (a loop). */
      tb_class->insert_text   = gtk_source_buffer_real_insert_text;
      tb_class->delete_range  = gtk_source_buffer_real_delete_range;

      g_object_class_install_property (object_class,
                               PROP_ESCAPE_CHAR,
                               g_param_spec_unichar ("escape_char",
                                                 _("Escape Character"),
                                                 _("Escaping character "
                                                 "for syntax patterns"),
                                                 0,
                                                 G_PARAM_READWRITE));

      g_object_class_install_property (object_class,
                               PROP_CHECK_BRACKETS,
                               g_param_spec_boolean ("check_brackets",
                                                 _("Check Brackets"),
                                                 _("Whether to check and "
                                                 "highlight matching brackets"),
                                                 TRUE,
                                                 G_PARAM_READWRITE));
      
      g_object_class_install_property (object_class,
                               PROP_HIGHLIGHT,
                               g_param_spec_boolean ("highlight",
                                                 _("Highlight"),
                                                 _("Whether to highlight syntax "
                                                 "in the buffer"),
                                                 FALSE,
                                                 G_PARAM_READWRITE));
      
      g_object_class_install_property (object_class,
                               PROP_MAX_UNDO_LEVELS,
                               g_param_spec_int ("max_undo_levels",
                                             _("Maximum Undo Levels"),
                                             _("Number of undo levels for "
                                               "the buffer"),
                                             0,
                                             200,
                                             25,
                                             G_PARAM_READWRITE));
      
      g_object_class_install_property (object_class,
                               PROP_LANGUAGE,
                               g_param_spec_object ("language",
                                                _("Language"),
                                                _("Language object to get "
                                                "highlighting patterns from"),
                                                GTK_TYPE_SOURCE_LANGUAGE,
                                                G_PARAM_READWRITE));
      
      buffer_signals[CAN_UNDO] =
          g_signal_new ("can_undo",
                    G_OBJECT_CLASS_TYPE (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GtkSourceBufferClass, can_undo),
                    NULL, NULL,
                    gtksourceview_marshal_VOID__BOOLEAN,
                    G_TYPE_NONE, 
                    1, 
                    G_TYPE_BOOLEAN);

      buffer_signals[CAN_REDO] =
          g_signal_new ("can_redo",
                    G_OBJECT_CLASS_TYPE (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GtkSourceBufferClass, can_redo),
                    NULL, NULL,
                    gtksourceview_marshal_VOID__BOOLEAN,
                    G_TYPE_NONE, 
                    1, 
                    G_TYPE_BOOLEAN);

      buffer_signals[HIGHLIGHT_UPDATED] =
          g_signal_new ("highlight_updated",
                    G_OBJECT_CLASS_TYPE (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GtkSourceBufferClass, highlight_updated),
                    NULL, NULL,
                    gtksourceview_marshal_VOID__BOXED_BOXED,
                    G_TYPE_NONE, 
                    2, 
                    GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE,
                    GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);

      buffer_signals[MARKER_UPDATED] =
          g_signal_new ("marker_updated",
                    G_OBJECT_CLASS_TYPE (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GtkSourceBufferClass, marker_updated),
                    NULL, NULL,
                    gtksourceview_marshal_VOID__BOXED,
                    G_TYPE_NONE, 
                    1, 
                    GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
}

static void
gtk_source_buffer_init (GtkSourceBuffer *buffer)
{
      GtkSourceBufferPrivate *priv;

      priv = g_new0 (GtkSourceBufferPrivate, 1);

      buffer->priv = priv;

      priv->undo_manager = gtk_source_undo_manager_new (GTK_TEXT_BUFFER (buffer));

      priv->check_brackets = TRUE;
      priv->bracket_mark = NULL;
      priv->bracket_found = FALSE;
      
      priv->markers = g_array_new (FALSE, FALSE, sizeof (GtkSourceMarker *));

      /* highlight data */
      priv->refresh_region =  gtk_text_region_new (GTK_TEXT_BUFFER (buffer));
      priv->syntax_regions =  g_array_new (FALSE, FALSE,
                                   sizeof (SyntaxDelimiter));
      priv->highlight_requests = gtk_text_region_new (GTK_TEXT_BUFFER (buffer));
      priv->worker_handler = 0;

      /* initially the buffer is empty so it's entirely analyzed */
      priv->worker_last_offset = -1;
      priv->worker_batch_size = INITIAL_WORKER_BATCH;
      
      g_signal_connect (G_OBJECT (buffer),
                    "mark_set",
                    G_CALLBACK (gtk_source_buffer_move_cursor),
                    NULL);

      g_signal_connect (G_OBJECT (priv->undo_manager),
                    "can_undo",
                    G_CALLBACK (gtk_source_buffer_can_undo_handler),
                    buffer);

      g_signal_connect (G_OBJECT (priv->undo_manager),
                    "can_redo",
                    G_CALLBACK (gtk_source_buffer_can_redo_handler),
                    buffer);

}

static void 
tag_added_or_removed_cb (GtkTextTagTable *table, GtkTextTag *tag, GtkSourceBuffer *buffer)
{
      sync_with_tag_table (buffer);
      
}


static void 
tag_table_changed_cb (GtkSourceTagTable *table, GtkSourceBuffer *buffer)
{
      sync_with_tag_table (buffer);
}

static GObject *
gtk_source_buffer_constructor (GType                  type,
                         guint                  n_construct_properties,
                         GObjectConstructParam *construct_param)
{
      GObject *g_object;
      gint i;

      /* Check the construction parameters to see if the user
       * specified a tag-table, and create and set an empty
       * GtkSourceTagTable if he didn't */
      for (i = 0; i < n_construct_properties; i++)
      {
            if (!strcmp ("tag-table", construct_param [i].pspec->name) &&
                g_value_get_object (construct_param [i].value) == NULL)
            {
#if (GLIB_MINOR_VERSION <= 2)
                  g_value_set_object_take_ownership (construct_param [i].value,
                                             gtk_source_tag_table_new ());
#else
                  g_value_take_object (construct_param [i].value,
                                   gtk_source_tag_table_new ());
#endif

                  break;
            }
      }
      
      g_object = G_OBJECT_CLASS (parent_class)->constructor (type, 
                                                 n_construct_properties,
                                                 construct_param);
      
      if (g_object) 
      {
            GtkSourceTagStyle *tag_style;
            
            GtkSourceBuffer *source_buffer = GTK_SOURCE_BUFFER (g_object);

            tag_style = gtk_source_tag_style_new ();
            
            gdk_color_parse ("white", &tag_style->foreground);
            gdk_color_parse ("gray", &tag_style->background);
            tag_style->mask |= (GTK_SOURCE_TAG_STYLE_USE_BACKGROUND |
                            GTK_SOURCE_TAG_STYLE_USE_FOREGROUND);

            tag_style->italic = FALSE;
            tag_style->bold = TRUE;
            tag_style->underline = FALSE;
            tag_style->strikethrough = FALSE;

            /* Set default bracket match style */
            gtk_source_buffer_set_bracket_match_style (source_buffer, tag_style);

            gtk_source_tag_style_free (tag_style);

            if (GTK_IS_SOURCE_TAG_TABLE (GTK_TEXT_BUFFER (source_buffer)->tag_table))
            {
                  g_signal_connect (GTK_TEXT_BUFFER (source_buffer)->tag_table ,
                                "changed",
                                G_CALLBACK (tag_table_changed_cb),
                                source_buffer);
            }
            else
            {
                  g_assert (GTK_IS_TEXT_TAG_TABLE (GTK_TEXT_BUFFER (source_buffer)->tag_table));

                  g_warning ("Please use GtkSourceTagTable with GtkSourceBuffer.");

                  g_signal_connect (GTK_TEXT_BUFFER (source_buffer)->tag_table,
                                "tag_added",
                                G_CALLBACK (tag_added_or_removed_cb),
                                source_buffer);

                  g_signal_connect (GTK_TEXT_BUFFER (source_buffer)->tag_table,
                                "tag_removed",
                                G_CALLBACK (tag_added_or_removed_cb),
                                source_buffer);                   
            }
      }
      
      return g_object;
}

static void
gtk_source_buffer_finalize (GObject *object)
{
      GtkSourceBuffer *buffer;

      g_return_if_fail (object != NULL);
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (object));

      buffer = GTK_SOURCE_BUFFER (object);
      g_return_if_fail (buffer->priv != NULL);
      
      if (buffer->priv->markers)
            g_array_free (buffer->priv->markers, TRUE);

      if (buffer->priv->worker_handler) {
            g_source_remove (buffer->priv->worker_handler);
      }

      /* we can't delete marks if we're finalizing the buffer */
      gtk_text_region_destroy (buffer->priv->refresh_region, FALSE);
      gtk_text_region_destroy (buffer->priv->highlight_requests, FALSE);

      g_object_unref (buffer->priv->undo_manager);

      g_array_free (buffer->priv->syntax_regions, TRUE);
      if (buffer->priv->old_syntax_regions)
            g_array_free (buffer->priv->old_syntax_regions, TRUE);
      
      if (buffer->priv->reg_syntax_all) {
            gtk_source_regex_destroy (buffer->priv->reg_syntax_all);
            buffer->priv->reg_syntax_all = NULL;
      }
      
      g_list_free (buffer->priv->syntax_items);
      g_list_free (buffer->priv->pattern_items);

      if (buffer->priv->language != NULL)
            g_object_unref (buffer->priv->language);

      g_free (buffer->priv);
      buffer->priv = NULL;
      
      /* TODO: free syntax_items, patterns, etc. - Paolo */
      
      G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void 
gtk_source_buffer_set_property (GObject      *object,
                        guint         prop_id,
                        const GValue *value,
                        GParamSpec   *pspec)
{
      GtkSourceBuffer *source_buffer;
      
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (object));

      source_buffer = GTK_SOURCE_BUFFER (object);
    
      switch (prop_id)
      {
            case PROP_ESCAPE_CHAR:
                  gtk_source_buffer_set_escape_char (source_buffer,
                                             g_value_get_uint (value));
                  break;
                  
            case PROP_CHECK_BRACKETS:
                  gtk_source_buffer_set_check_brackets (source_buffer,
                                                g_value_get_boolean (value));
                  break;
                  
            case PROP_HIGHLIGHT:
                  gtk_source_buffer_set_highlight (source_buffer,
                                           g_value_get_boolean (value));
                  break;
                  
            case PROP_MAX_UNDO_LEVELS:
                  gtk_source_buffer_set_max_undo_levels (source_buffer,
                                                 g_value_get_int (value));
                  break;
                  
            case PROP_LANGUAGE:
                  gtk_source_buffer_set_language (source_buffer,
                                          g_value_get_object (value));
                  break;
                  
            default:
                  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                  break;
      }
}

static void 
gtk_source_buffer_get_property (GObject    *object,
                        guint       prop_id,
                        GValue     *value,
                        GParamSpec *pspec)
{
      GtkSourceBuffer *source_buffer;
      
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (object));

      source_buffer = GTK_SOURCE_BUFFER (object);
    
      switch (prop_id)
      {
            case PROP_ESCAPE_CHAR:
                  g_value_set_uint (value, source_buffer->priv->escape_char);
                  break;
                  
            case PROP_CHECK_BRACKETS:
                  g_value_set_boolean (value, source_buffer->priv->check_brackets);
                  break;
                  
            case PROP_HIGHLIGHT:
                  g_value_set_boolean (value, source_buffer->priv->highlight);
                  break;
                  
            case PROP_MAX_UNDO_LEVELS:
                  g_value_set_int (value,
                               gtk_source_buffer_get_max_undo_levels (source_buffer));
                  break;
                  
            case PROP_LANGUAGE:
                  g_value_set_object (value, source_buffer->priv->language);
                  break;
                  
            default:
                  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
                  break;
      }
}

/**
 * gtk_source_buffer_new:
 * @table: a #GtkSourceTagTable, or NULL to create a new one.
 * 
 * Creates a new source buffer.
 * 
 * Return value: a new source buffer.
 **/
GtkSourceBuffer *
gtk_source_buffer_new (GtkSourceTagTable *table)
{
      GtkSourceBuffer *buffer;

      buffer = GTK_SOURCE_BUFFER (g_object_new (GTK_TYPE_SOURCE_BUFFER, 
                                      "tag-table", table, 
                                      NULL));
      
      return buffer;
}

/**
 * gtk_source_buffer_new_with_language:
 * @language: a #GtkSourceLanguage.
 * 
 * Creates a new source buffer using the highlighting patterns in
 * @language.  This is equivalent to creating a new source buffer with
 * the default tag table and then calling
 * gtk_source_buffer_set_language().
 * 
 * Return value: a new source buffer which will highlight text
 * according to @language.
 **/
GtkSourceBuffer *
gtk_source_buffer_new_with_language (GtkSourceLanguage *language)
{
      GtkSourceBuffer *buffer;

      g_return_val_if_fail (GTK_IS_SOURCE_LANGUAGE (language), NULL);

      buffer = gtk_source_buffer_new (NULL);

      gtk_source_buffer_set_language (buffer, language);

      return buffer;
}

static void
gtk_source_buffer_can_undo_handler (GtkSourceUndoManager    *um,
                            gboolean                   can_undo,
                            GtkSourceBuffer           *buffer)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      g_signal_emit (G_OBJECT (buffer),
                   buffer_signals[CAN_UNDO], 
                   0, 
                   can_undo);
}

static void
gtk_source_buffer_can_redo_handler (GtkSourceUndoManager    *um,
                            gboolean                   can_redo,
                            GtkSourceBuffer           *buffer)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      g_signal_emit (G_OBJECT (buffer),
                   buffer_signals[CAN_REDO], 
                   0, 
                   can_redo);
}

static void
get_tags_func (GtkTextTag *tag, gpointer data)
{
        GSList **list = NULL;

      g_return_if_fail (data != NULL);

      list = (GSList **) data;

      if (GTK_IS_SOURCE_TAG (tag))
      {
            *list = g_slist_prepend (*list, tag);
      }
}

static void
gtk_source_buffer_move_cursor (GtkTextBuffer *buffer,
                         GtkTextIter   *iter, 
                         GtkTextMark   *mark, 
                         gpointer       data)
{
      GtkTextIter iter1, iter2;

      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (iter != NULL);
      g_return_if_fail (mark != NULL);
      g_return_if_fail (gtk_text_iter_get_buffer (iter) == buffer);

      if (mark != gtk_text_buffer_get_insert (buffer))
            return;

      if (GTK_SOURCE_BUFFER (buffer)->priv->bracket_found) 
      {
            gtk_text_buffer_get_iter_at_mark (buffer,
                                      &iter1,
                                      GTK_SOURCE_BUFFER (buffer)->priv->bracket_mark);
            iter2 = iter1;
            gtk_text_iter_forward_char (&iter2);
            gtk_text_buffer_remove_tag (buffer,
                                  GTK_SOURCE_BUFFER (buffer)->priv->bracket_match_tag,
                                  &iter1, 
                                  &iter2);
      }

      if (!GTK_SOURCE_BUFFER (buffer)->priv->check_brackets)
            return;

      iter1 = *iter;
      if (gtk_source_buffer_find_bracket_match_real (&iter1, MAX_CHARS_BEFORE_FINDING_A_MATCH)) 
      {
            if (!GTK_SOURCE_BUFFER (buffer)->priv->bracket_mark)
                  GTK_SOURCE_BUFFER (buffer)->priv->bracket_mark =
                        gtk_text_buffer_create_mark (buffer, 
                                               NULL,
                                               &iter1, 
                                               FALSE);
            else
                  gtk_text_buffer_move_mark (buffer,
                                       GTK_SOURCE_BUFFER (buffer)->priv->bracket_mark,
                                       &iter1);

            iter2 = iter1;
            gtk_text_iter_forward_char (&iter2);
            gtk_text_buffer_apply_tag (buffer,
                                 GTK_SOURCE_BUFFER (buffer)->priv->bracket_match_tag,
                                 &iter1, 
                                 &iter2);
            GTK_SOURCE_BUFFER (buffer)->priv->bracket_found = TRUE;
      }
      else
      {
            GTK_SOURCE_BUFFER (buffer)->priv->bracket_found = FALSE;
      }
}

static void
gtk_source_buffer_real_insert_text (GtkTextBuffer *buffer,
                            GtkTextIter   *iter,
                            const gchar   *text, 
                            gint           len)
{
      gint start_offset;

      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (iter != NULL);
      g_return_if_fail (text != NULL);
      g_return_if_fail (gtk_text_iter_get_buffer (iter) == buffer);

      start_offset = gtk_text_iter_get_offset (iter);

      /*
       * iter is invalidated when
       * insertion occurs (because the buffer contents change), but the
       * default signal handler revalidates it to point to the end of the
       * inserted text 
       */
      parent_class->insert_text (buffer, iter, text, len);

      gtk_source_buffer_move_cursor (buffer, 
                               iter, 
                               gtk_text_buffer_get_insert (buffer),
                               NULL);

      if (!GTK_SOURCE_BUFFER (buffer)->priv->highlight)
            return;

      update_syntax_regions (GTK_SOURCE_BUFFER (buffer), 
                         start_offset,
                         g_utf8_strlen (text, len));

}

static void
gtk_source_buffer_real_delete_range (GtkTextBuffer *buffer,
                             GtkTextIter   *start,
                             GtkTextIter   *end)
{
      gint delta;
      GtkTextMark *mark;
      GtkTextIter iter;
      GSList *markers;
            
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (start != NULL);
      g_return_if_fail (end != NULL);
      g_return_if_fail (gtk_text_iter_get_buffer (start) == buffer);
      g_return_if_fail (gtk_text_iter_get_buffer (end) == buffer);

      gtk_text_iter_order (start, end);
      delta = gtk_text_iter_get_offset (start) - 
                  gtk_text_iter_get_offset (end);

      /* remove the markers in the deleted region if deleting more than one character */
      if (ABS (delta) > 1)
      {
            markers = gtk_source_buffer_get_markers_in_region (GTK_SOURCE_BUFFER (buffer),
                                                   start, end);
            while (markers)
            {
                  gtk_source_buffer_delete_marker (GTK_SOURCE_BUFFER (buffer), markers->data);
                  markers = g_slist_delete_link (markers, markers);
            }
      }
            
      parent_class->delete_range (buffer, start, end);

      mark = gtk_text_buffer_get_insert (buffer);
      gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
            
      gtk_source_buffer_move_cursor (buffer,
                               &iter,
                               mark,
                               NULL);

      /* move any markers which moved to this line because of the
       * deletion to the beginning of the line */
      iter = *start;
      if (!gtk_text_iter_ends_line (&iter))
            gtk_text_iter_forward_to_line_end (&iter);
      markers = gtk_source_buffer_get_markers_in_region (GTK_SOURCE_BUFFER (buffer),
                                             start, &iter);
      if (markers)
      {
            GSList *m;
            
            gtk_text_iter_set_line_offset (&iter, 0);
            for (m = markers; m; m = g_slist_next (m))
                  gtk_source_buffer_move_marker (GTK_SOURCE_BUFFER (buffer),
                                           GTK_SOURCE_MARKER (m->data),
                                           &iter);
            g_slist_free (markers);
      }

      if (!GTK_SOURCE_BUFFER (buffer)->priv->highlight) 
            return;

      update_syntax_regions (GTK_SOURCE_BUFFER (buffer),
                         gtk_text_iter_get_offset (start),
                         delta);
}

static GSList *
gtk_source_buffer_get_source_tags (const GtkSourceBuffer *buffer)
{
      GSList *list = NULL;
      GtkTextTagTable *table;

      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);

      table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
      gtk_text_tag_table_foreach (table, get_tags_func, &list);
      list = g_slist_reverse (list);      
      
      return list;
}

static void
sync_with_tag_table (GtkSourceBuffer *buffer)
{
      GtkTextTagTable *tag_table;
      GSList *entries;
      GSList *list;

      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      if (buffer->priv->syntax_items) {
            g_list_free (buffer->priv->syntax_items);
            buffer->priv->syntax_items = NULL;
      }

      if (buffer->priv->pattern_items) {
            g_list_free (buffer->priv->pattern_items);
            buffer->priv->pattern_items = NULL;
      }

      tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
      g_return_if_fail (tag_table != NULL);

      list = entries = gtk_source_buffer_get_source_tags (buffer);
      
      while (entries != NULL) 
      {     
            if (GTK_IS_SYNTAX_TAG (entries->data)) 
            {
                  buffer->priv->syntax_items =
                      g_list_prepend (buffer->priv->syntax_items, entries->data);
                  
            } 
            else if (GTK_IS_PATTERN_TAG (entries->data)) 
            {
                  buffer->priv->pattern_items =
                      g_list_prepend (buffer->priv->pattern_items, entries->data);
                  
            }

            entries = g_slist_next (entries);
      }

      g_slist_free (list);

      buffer->priv->syntax_items = g_list_reverse (buffer->priv->syntax_items);
      buffer->priv->pattern_items = g_list_reverse (buffer->priv->pattern_items);
      
      if (buffer->priv->syntax_items != NULL)
      {
            sync_syntax_regex (buffer);
      }
      else
      {
            if (buffer->priv->reg_syntax_all) {
                  gtk_source_regex_destroy (buffer->priv->reg_syntax_all);
                  buffer->priv->reg_syntax_all = NULL;
            }
      }

      if (buffer->priv->highlight)
            invalidate_syntax_regions (buffer, NULL, 0);
}

static void
sync_syntax_regex (GtkSourceBuffer *buffer)
{
      GString *str;
      GList *cur;
      GtkSyntaxTag *tag;

      str = g_string_new ("");
      cur = buffer->priv->syntax_items;

      while (cur != NULL) {
            g_return_if_fail (GTK_IS_SYNTAX_TAG (cur->data));

            tag = GTK_SYNTAX_TAG (cur->data);
            g_string_append (str, tag->start);
            
            cur = g_list_next (cur);
            
            if (cur != NULL)
                  g_string_append (str, "|");
      }

      if (buffer->priv->reg_syntax_all)
            gtk_source_regex_destroy (buffer->priv->reg_syntax_all);
      
      buffer->priv->reg_syntax_all = gtk_source_regex_compile (str->str);

      g_string_free (str, TRUE);
}

static const GtkSyntaxTag *
iter_has_syntax_tag (const GtkTextIter *iter)
{
      const GtkSyntaxTag *tag;
      GSList *list;

      g_return_val_if_fail (iter != NULL, NULL);

      list = gtk_text_iter_get_tags (iter);
      tag = NULL;

      while ((list != NULL) && (tag == NULL)) {
            if (GTK_IS_SYNTAX_TAG (list->data))
                  tag = GTK_SYNTAX_TAG (list->data);
            list = g_slist_next (list);
      }

      g_slist_free (list);

      return tag;
}


static GList *
gtk_source_buffer_get_syntax_entries (const GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);

      return buffer->priv->syntax_items;
}

static GList *
gtk_source_buffer_get_pattern_entries (const GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);

      return buffer->priv->pattern_items;
}

static gboolean
gtk_source_buffer_find_bracket_match_real (GtkTextIter *orig, gint max_chars)
{
      GtkTextIter iter;
      
      gunichar base_char;
      gunichar search_char;
      gunichar cur_char;
      gint addition;
      gint char_cont;
      gint counter;
      
      gboolean found;

      const GtkSyntaxTag *base_tag;

      iter = *orig;

      if (!gtk_text_iter_backward_char (&iter))
            return FALSE;
      
      cur_char = gtk_text_iter_get_char (&iter);

      base_char = search_char = cur_char;
      base_tag = iter_has_syntax_tag (&iter);
      
      switch ((int) base_char) {
            case '{':
                  addition = 1;
                  search_char = '}';
                  break;
            case '(':
                  addition = 1;
                  search_char = ')';
                  break;
            case '[':
                  addition = 1;
                  search_char = ']';
                  break;
            case '<':
                  addition = 1;
                  search_char = '>';
                  break;
            case '}':
                  addition = -1;
                  search_char = '{';
                  break;
            case ')':
                  addition = -1;
                  search_char = '(';
                  break;
            case ']':
                  addition = -1;
                  search_char = '[';
                  break;
            case '>':
                  addition = -1;
                  search_char = '<';
                  break;
            default:
                  addition = 0;
                  break;
      }

      if (addition == 0)
            return FALSE;

      counter = 0;
      found = FALSE;
      char_cont = 0;
      
      do {
            gtk_text_iter_forward_chars (&iter, addition);
            cur_char = gtk_text_iter_get_char (&iter);
            ++char_cont;
            
            if ((cur_char == search_char || cur_char == base_char) &&
                base_tag == iter_has_syntax_tag (&iter))
            {
                  if ((cur_char == search_char) && counter == 0) {
                        found = TRUE;
                        break;
                  }
                  if (cur_char == base_char)
                        counter++;
                  else 
                        counter--;
            }
      } 
      while (!gtk_text_iter_is_end (&iter) && !gtk_text_iter_is_start (&iter) && 
            ((char_cont < max_chars) || (max_chars < 0)));

      if (found)
            *orig = iter;

      return found;
}

/**
 * gtk_source_iter_find_matching_bracket:
 * @iter: a #GtkTextIter.
 * 
 * Tries to match the bracket character currently at @iter with its
 * opening/closing counterpart, and if found moves @iter to the position
 * where it was found.
 *
 * @iter must be a #GtkTextIter belonging to a #GtkSourceBuffer.
 * 
 * Return value: TRUE if the matching bracket was found and the @iter
 * iter moved.
 **/
gboolean
gtk_source_iter_find_matching_bracket (GtkTextIter *iter)
{
      g_return_val_if_fail (iter != NULL, FALSE);

      return gtk_source_buffer_find_bracket_match_real (iter, -1);
}

/**
 * gtk_source_buffer_can_undo:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Determines whether a source buffer can undo the last action.
 * 
 * Return value: TRUE if it's possible to undo the last action.
 **/
gboolean
gtk_source_buffer_can_undo (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), FALSE);

      return gtk_source_undo_manager_can_undo (buffer->priv->undo_manager);
}

/**
 * gtk_source_buffer_can_redo:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Determines whether a source buffer can redo the last action
 * (i.e. if the last operation was an undo).
 * 
 * Return value: TRUE if a redo is possible.
 **/
gboolean
gtk_source_buffer_can_redo (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), FALSE);

      return gtk_source_undo_manager_can_redo (buffer->priv->undo_manager);
}

/**
 * gtk_source_buffer_undo:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Undoes the last user action which modified the buffer.  Use
 * gtk_source_buffer_can_undo() to check whether a call to this
 * function will have any effect.
 *
 * Actions are defined as groups of operations between a call to
 * gtk_text_buffer_begin_user_action() and
 * gtk_text_buffer_end_user_action(), or sequences of similar edits
 * (inserts or deletes) on the same line.
 **/
void
gtk_source_buffer_undo (GtkSourceBuffer *buffer)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (gtk_source_undo_manager_can_undo (buffer->priv->undo_manager));

      gtk_source_undo_manager_undo (buffer->priv->undo_manager);
}

/**
 * gtk_source_buffer_redo:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Redoes the last undo operation.  Use gtk_source_buffer_can_redo()
 * to check whether a call to this function will have any effect.
 **/
void
gtk_source_buffer_redo (GtkSourceBuffer *buffer)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (gtk_source_undo_manager_can_redo (buffer->priv->undo_manager));

      gtk_source_undo_manager_redo (buffer->priv->undo_manager);
}

/**
 * gtk_source_buffer_get_max_undo_levels:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Determines the number of undo levels the buffer will track for
 * buffer edits.
 * 
 * Return value: the maximum number of possible undo levels.
 **/
gint
gtk_source_buffer_get_max_undo_levels (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), 0);

      return gtk_source_undo_manager_get_max_undo_levels (buffer->priv->undo_manager);
}

/**
 * gtk_source_buffer_set_max_undo_levels:
 * @buffer: a #GtkSourceBuffer.
 * @max_undo_levels: the desired maximum number of undo levels.
 * 
 * Sets the number of undo levels for user actions the buffer will
 * track.  If the number of user actions exceeds the limit set by this
 * function, older actions will be discarded.
 *
 * A new action is started whenever the function
 * gtk_text_buffer_begin_user_action() is called.  In general, this
 * happens whenever the user presses any key which modifies the
 * buffer, but the undo manager will try to merge similar consecutive
 * actions, such as multiple character insertions into one action.
 * But, inserting a newline does start a new action.
 **/
void
gtk_source_buffer_set_max_undo_levels (GtkSourceBuffer *buffer,
                               gint             max_undo_levels)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      if (gtk_source_undo_manager_get_max_undo_levels (
                buffer->priv->undo_manager) != max_undo_levels)
      {
            gtk_source_undo_manager_set_max_undo_levels (buffer->priv->undo_manager,
                                               max_undo_levels);
            g_object_notify (G_OBJECT (buffer), "max_undo_levels");
      }
}

/**
 * gtk_source_buffer_begin_not_undoable_action:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Marks the beginning of a not undoable action on the buffer,
 * disabling the undo manager.  Typically you would call this function
 * before initially setting the contents of the buffer (e.g. when
 * loading a file in a text editor).
 *
 * You may nest gtk_source_buffer_begin_not_undoable_action() /
 * gtk_source_buffer_end_not_undoable_action() blocks.
 **/
void
gtk_source_buffer_begin_not_undoable_action (GtkSourceBuffer *buffer)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      gtk_source_undo_manager_begin_not_undoable_action (buffer->priv->undo_manager);
}

/**
 * gtk_source_buffer_end_not_undoable_action:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Marks the end of a not undoable action on the buffer.  When the
 * last not undoable block is closed through the call to this
 * function, the list of undo actions is cleared and the undo manager
 * is re-enabled.
 **/
void
gtk_source_buffer_end_not_undoable_action (GtkSourceBuffer *buffer)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      gtk_source_undo_manager_end_not_undoable_action (buffer->priv->undo_manager);
}

/**
 * gtk_source_buffer_get_check_brackets:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Determines whether bracket match highlighting is activated for the
 * source buffer.
 * 
 * Return value: TRUE if the source buffer will highlight matching
 * brackets.
 **/
gboolean
gtk_source_buffer_get_check_brackets (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), FALSE);

      return buffer->priv->check_brackets;
}

/**
 * gtk_source_buffer_set_check_brackets:
 * @buffer: a #GtkSourceBuffer.
 * @check_brackets: TRUE if you want matching brackets highlighted.
 * 
 * Controls the bracket match highlighting function in the buffer.  If
 * activated, when you position your cursor over a bracket character
 * (a parenthesis, a square bracket, etc.) the matching opening or
 * closing bracket character will be highlighted.  You can specify the
 * style with the gtk_source_buffer_set_bracket_match_style()
 * function.
 **/
void
gtk_source_buffer_set_check_brackets (GtkSourceBuffer *buffer,
                              gboolean         check_brackets)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      check_brackets = (check_brackets != FALSE);

      if (check_brackets != buffer->priv->check_brackets)
      {
            buffer->priv->check_brackets = check_brackets;
            g_object_notify (G_OBJECT (buffer), "check_brackets");
      }
}

/**
 * gtk_source_buffer_set_bracket_match_style:
 * @source_buffer: a #GtkSourceBuffer.
 * @style: the #GtkSourceTagStyle specifying colors and text
 * attributes.
 * 
 * Sets the style used for highlighting matching brackets.
 **/
void 
gtk_source_buffer_set_bracket_match_style (GtkSourceBuffer         *source_buffer,
                                 const GtkSourceTagStyle *style)
{
      GtkTextTag *tag;
      GValue foreground = { 0, };
      GValue background = { 0, };

      g_return_if_fail (GTK_IS_SOURCE_BUFFER (source_buffer));
      g_return_if_fail (style != NULL);

      /* create the tag if not already done so */
      if (source_buffer->priv->bracket_match_tag == NULL)
      {
            source_buffer->priv->bracket_match_tag = gtk_text_tag_new (NULL);
            gtk_text_tag_table_add (gtk_text_buffer_get_tag_table (
                                    GTK_TEXT_BUFFER (source_buffer)),
                              source_buffer->priv->bracket_match_tag);
            g_object_unref (source_buffer->priv->bracket_match_tag);
      }
      
      g_return_if_fail (source_buffer->priv->bracket_match_tag != NULL);
      tag = source_buffer->priv->bracket_match_tag;

      /* Foreground color */
      g_value_init (&foreground, GDK_TYPE_COLOR);
      
      if ((style->mask & GTK_SOURCE_TAG_STYLE_USE_FOREGROUND) != 0)
            g_value_set_boxed (&foreground, &style->foreground);
      else
            g_value_set_boxed (&foreground, NULL);
      
      g_object_set_property (G_OBJECT (tag), "foreground_gdk", &foreground);

      /* Background color */
      g_value_init (&background, GDK_TYPE_COLOR);

      if ((style->mask & GTK_SOURCE_TAG_STYLE_USE_BACKGROUND) != 0)
            g_value_set_boxed (&background, &style->background);
      else
            g_value_set_boxed (&background, NULL);
      
      g_object_set_property (G_OBJECT (tag), "background_gdk", &background);
      
      g_object_set (G_OBJECT (tag), 
                  "style", style->italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL,
                  "weight", style->bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL,
                  "strikethrough", style->strikethrough,
                  "underline", style->underline ? PANGO_UNDERLINE_SINGLE : PANGO_UNDERLINE_NONE,
                  NULL);      
}

/**
 * gtk_source_buffer_get_highlight:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Determines whether text highlighting is activated in the source
 * buffer.
 * 
 * Return value: TRUE if highlighting is enabled.
 **/
gboolean
gtk_source_buffer_get_highlight (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), FALSE);

      return buffer->priv->highlight;
}

/**
 * gtk_source_buffer_set_highlight:
 * @buffer: a #GtkSourceBuffer.
 * @highlight: TRUE if you want to activate highlighting.
 * 
 * Controls whether text is highlighted in the buffer.  If @highlight
 * is TRUE, the text will be highlighted according to the patterns
 * installed in the buffer (either set with
 * gtk_source_buffer_set_language() or by adding individual
 * #GtkSourceTag tags to the buffer's tag table).  Otherwise, any
 * current highlighted text will be restored to the default buffer
 * style.
 *
 * Tags not of #GtkSourceTag type will not be removed by this option,
 * and normal #GtkTextTag priority settings apply when highlighting is
 * enabled.
 *
 * If not using a #GtkSourceLanguage for setting the highlighting
 * patterns in the buffer, it is recommended for performance reasons
 * that you add all the #GtkSourceTag tags with highlighting disabled
 * and enable it when finished.
 **/
void
gtk_source_buffer_set_highlight (GtkSourceBuffer *buffer,
                         gboolean         highlight)
{
      GtkTextIter iter1;
      GtkTextIter iter2;

      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      highlight = (highlight != FALSE);

      if (buffer->priv->highlight == highlight)
            return;

      buffer->priv->highlight = highlight;

      if (highlight) {
            invalidate_syntax_regions (buffer, NULL, 0);

      } else {
            if (buffer->priv->worker_handler) {
                  g_source_remove (buffer->priv->worker_handler);
                  buffer->priv->worker_handler = 0;
            }
            gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer),
                                  &iter1, 
                                  &iter2);
            gtk_source_buffer_remove_all_source_tags (buffer,
                                            &iter1,
                                            &iter2);
      }
      g_object_notify (G_OBJECT (buffer), "highlight");
}

/* Idle worker code ------------ */

static gboolean
idle_worker (GtkSourceBuffer *source_buffer)
{
      GtkTextIter start_iter, end_iter, last_end_iter;
      gint i;
      
      if (source_buffer->priv->worker_last_offset >= 0) {
            /* the syntax regions table is incomplete */
            build_syntax_regions_table (source_buffer, NULL);
      }

      /* Now we highlight subregions requested by our views */
      gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer), &last_end_iter, 0);
      for (i = 0; i < gtk_text_region_subregions (
                 source_buffer->priv->highlight_requests); i++) {
            gtk_text_region_nth_subregion (source_buffer->priv->highlight_requests,
                                     i, &start_iter, &end_iter);

            if (source_buffer->priv->worker_last_offset < 0 ||
                source_buffer->priv->worker_last_offset >=
                gtk_text_iter_get_offset (&end_iter)) {
                  ensure_highlighted (source_buffer, 
                                  &start_iter, 
                                  &end_iter);
                  last_end_iter = end_iter;
            } else {
                  /* since the subregions are ordered, we are
                   * guaranteed here that all subsequent
                   * subregions will be beyond the already
                   * analyzed text */
                  break;
            }
      }
      gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer), &start_iter, 0);

      if (!gtk_text_iter_equal (&start_iter, &last_end_iter)) {
            /* remove already highlighted subregions from requests */
            gtk_text_region_substract (source_buffer->priv->highlight_requests,
                                 &start_iter, &last_end_iter);
            gtk_text_region_clear_zero_length_subregions (
                  source_buffer->priv->highlight_requests);
      }
      
      if (source_buffer->priv->worker_last_offset < 0) {
            /* idle handler will be removed */
            source_buffer->priv->worker_handler = 0;
            return FALSE;
      }
      
      return TRUE;
}

static void
install_idle_worker (GtkSourceBuffer *source_buffer)
{
      if (source_buffer->priv->worker_handler == 0) {
            /* use the text view validation priority to get
             * highlighted text even before complete validation of
             * the buffer */
            source_buffer->priv->worker_handler =
                  g_idle_add_full (GTK_TEXT_VIEW_PRIORITY_VALIDATE,
                               (GSourceFunc) idle_worker,
                               source_buffer, 
                               NULL);
      }
}

/* Syntax analysis code -------------- */

static gboolean
is_escaped (GtkSourceBuffer *source_buffer, const gchar *text, gint index)
{
      gchar *tmp = (gchar *) text + index;
      gboolean retval = FALSE;

      if (source_buffer->priv->escape_char == 0)
            return FALSE;
      
      tmp = g_utf8_find_prev_char (text, tmp);
      while (tmp && g_utf8_get_char (tmp) == source_buffer->priv->escape_char) 
      {
            retval = !retval;
            tmp = g_utf8_find_prev_char (text, tmp);
      }
      return retval;
}

static const GtkSyntaxTag * 
get_syntax_start (GtkSourceBuffer      *source_buffer,
              const gchar          *text,
              gint                  length,
              GtkSourceBufferMatch *match)
{
      GList *list;
      GtkSyntaxTag *tag;
      gint pos;
      
      /*
      g_return_val_if_fail (text != NULL, NULL);
      g_return_val_if_fail (length >= 0, NULL);
      g_return_val_if_fail (match != NULL, NULL);
      */

      if (length == 0)
            return NULL;
      
      list = gtk_source_buffer_get_syntax_entries (source_buffer);

      if (list == NULL)
            return NULL;

      pos = 0;
      do {
            /* check for any of the syntax highlights */
            pos = gtk_source_regex_search (
                  source_buffer->priv->reg_syntax_all,
                  text,
                  pos,
                  length,
                  match);
            if (pos < 0 || !is_escaped (source_buffer, text, match->startindex))
                  break;
            pos = match->startpos + 1;
      } while (pos >= 0);

      if (pos < 0)
            return NULL;

      while (list != NULL) {
            tag = list->data;
            
            if (gtk_source_regex_match (tag->reg_start, text,
                                  pos, match->endindex))
                  return tag;

            list = g_list_next (list);
      }

      return NULL;
}

static gboolean 
get_syntax_end (GtkSourceBuffer      *source_buffer,
            const gchar          *text,
            gint                  length,
            GtkSyntaxTag         *tag,
            GtkSourceBufferMatch *match)
{
      GtkSourceBufferMatch tmp;
      gint pos;

      g_return_val_if_fail (text != NULL, FALSE);
      g_return_val_if_fail (length >= 0, FALSE);
      g_return_val_if_fail (tag != NULL, FALSE);

      if (!match)
            match = &tmp;
      
      pos = 0;
      do {
            pos = gtk_source_regex_search (tag->reg_end, text, pos,
                                     length, match);
            if (pos < 0 || !is_escaped (source_buffer, text, match->startindex))
                  break;
            pos = match->startpos + 1;
      } while (pos >= 0);

      return (pos >= 0);
}

/* Syntax regions code ------------- */

static gint
bsearch_offset (GArray *array, gint offset)
{
      gint i, j, k;
      gint off_tmp;
      
      if (!array || array->len == 0)
            return 0;
      
      i = 0;
      /* border conditions */
      if (g_array_index (array, SyntaxDelimiter, i).offset > offset)
            return 0;
      j = array->len - 1;
      if (g_array_index (array, SyntaxDelimiter, j).offset <= offset)
            return array->len;
      
      while (j - i > 1) {
            k = (i + j) / 2;
            off_tmp = g_array_index (array, SyntaxDelimiter, k).offset;
            if (off_tmp == offset)
                  return k + 1;
            else if (off_tmp > offset)
                  j = k;
            else
                  i = k;
      }
      return j;
}

static void
adjust_table_offsets (GArray *table, gint start, gint delta)
{
      if (!table)
            return;

      while (start < table->len) {
            g_array_index (table, SyntaxDelimiter, start).offset += delta;
            start++;
      }
}
      
static void 
invalidate_syntax_regions (GtkSourceBuffer *source_buffer,
                     GtkTextIter     *from,
                     gint             delta)
{
      GArray *table, *old_table;
      gint region, saved_region;
      gint offset;
      SyntaxDelimiter *delim;
      
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (source_buffer));
      
      table = source_buffer->priv->syntax_regions;
      g_assert (table != NULL);
      
      if (from) {
            offset = gtk_text_iter_get_offset (from);
      } else {
            offset = 0;
      }

      DEBUG (g_message ("invalidating from %d", offset));
      
      if (!gtk_source_buffer_get_syntax_entries (source_buffer))
      {
            /* Shortcut case: we don't have syntax entries, so we
             * won't build the table.  OTOH, we do need to refresh
             * the highilighting in case there are pattern
             * entries. */
            GtkTextIter start, end;
            
            g_array_set_size (table, 0);
            source_buffer->priv->worker_last_offset = -1;

            gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (source_buffer), &start, &end);
            if (from)
                  start = *from;
            refresh_range (source_buffer, &start, &end);

            return;
      }
      
      /* check if the offset has been analyzed already */
      if ((source_buffer->priv->worker_last_offset >= 0) &&
          (offset > source_buffer->priv->worker_last_offset))
            /* not yet */
            return;

      region = bsearch_offset (table, offset);
      if (region > 0) {
            delim = &g_array_index (table,
                              SyntaxDelimiter,
                              region - 1);
            if (delim->tag &&
                delim->offset == offset) {
                  /* take previous region if we are just at the
                     start of a syntax region (i.e. we're
                     invalidating because somebody deleted a
                     opening syntax pattern) */
                  region--;
            }
      }
      
      /* if delta is negative, some text was deleted and surely some
       * syntax delimiters have gone, so we don't need those in the
       * saved table */
      if (delta < 0) {
            saved_region = bsearch_offset (table, offset - delta);
      } else {
            saved_region = region;
      }

      /* free saved old table */
      if (source_buffer->priv->old_syntax_regions) {
            g_array_free (source_buffer->priv->old_syntax_regions, TRUE);
            source_buffer->priv->old_syntax_regions = NULL;
      }

      /* we don't want to save information if delta is zero,
       * i.e. the invalidation is not because the user edited the
       * buffer */
      if (table->len - saved_region > 0 && delta != 0) {
            gint old_table_size;

            DEBUG (g_message ("saving table information"));
                        
            /* save table to try to recover still valid information */
            old_table_size = table->len - saved_region;
            old_table = g_array_new (FALSE, FALSE, sizeof (SyntaxDelimiter));
            g_array_set_size (old_table, old_table_size);
            source_buffer->priv->old_syntax_regions = old_table;

            /* now copy from r through the end of the table */
            memcpy (&g_array_index (old_table, SyntaxDelimiter, 0),
                  &g_array_index (table, SyntaxDelimiter, saved_region),
                  sizeof (SyntaxDelimiter) * old_table_size);

            /* adjust saved table offsets */
            adjust_table_offsets (old_table, 0, delta);
      }
      
      /* chop table */
      g_array_set_size (table, region);

      /* update worker_last_offset from the new conditions in the table */
      if (region > 0) {
            source_buffer->priv->worker_last_offset =
                  g_array_index (table, SyntaxDelimiter, region - 1).offset;
      } else {
            source_buffer->priv->worker_last_offset = 0;
      }
      
      install_idle_worker (source_buffer);
}

static gboolean
delimiter_is_equal (SyntaxDelimiter *d1, SyntaxDelimiter *d2)
{
      return (d1->offset == d2->offset &&
            d1->depth == d2->depth &&
            d1->tag == d2->tag);
}

/**
 * next_syntax_region:
 * @source_buffer: the GtkSourceBuffer to work on
 * @state: the current SyntaxDelimiter
 * @head: text to analyze
 * @head_length: length in bytes of @head
 * @head_offset: offset in the buffer where @head starts
 * @match: GtkSourceBufferMatch object to get the results
 * 
 * This function can be seen as a single iteration in the analyzing
 * process.  It takes the current @state, searches for the next syntax
 * pattern in head (starting from byte index 0) and if found, updates
 * @state to reflect the new state.  @match is also filled with the
 * matching bounds.
 * 
 * Return value: TRUE if a syntax pattern was found in @head.
 **/
static gboolean 
next_syntax_region (GtkSourceBuffer      *source_buffer,
                SyntaxDelimiter      *state,
                const gchar          *head,
                gint                  head_length,
                gint                  head_offset,
                GtkSourceBufferMatch *match)
{
      GtkSyntaxTag *tag;
      gboolean found;
      
      if (!state->tag) {
            /* we come from a non-syntax colored region, so seek
             * for an opening pattern */
            tag = (GtkSyntaxTag *) get_syntax_start (
                  source_buffer, head, head_length, match);

            if (!tag)
                  return FALSE;
            
            state->tag = tag;
            state->offset = match->startpos + head_offset;
            state->depth = 1;

      } else {
            /* seek the closing pattern for the current syntax
             * region */
            found = get_syntax_end (source_buffer,
                              head, head_length,
                              state->tag, match);
            
            if (!found)
                  return FALSE;
            
            state->offset = match->endpos + head_offset;
            state->tag = NULL;
            state->depth = 0;
            
      }
      return TRUE;
}

static void 
build_syntax_regions_table (GtkSourceBuffer   *source_buffer,
                      const GtkTextIter *needed_end)
{
      GArray *table;
      GtkTextIter start, end;
      GArray *old_table;
      gint old_region;
      gboolean use_old_data;
      gchar *slice, *head;
      gint offset, head_length;
      GtkSourceBufferMatch match;
      SyntaxDelimiter delim;
      GTimer *timer;

      g_return_if_fail (GTK_IS_SOURCE_BUFFER (source_buffer));
      
      /* we shouldn't have been called if the buffer has no syntax entries */
      g_assert (gtk_source_buffer_get_syntax_entries (source_buffer) != NULL);
      
      /* check if we still have text to analyze */
      if (source_buffer->priv->worker_last_offset < 0)
            return;
      
      /* compute starting iter of the batch */
      offset = source_buffer->priv->worker_last_offset;
      gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer),
                                  &start, offset);
      
      DEBUG (g_message ("restarting syntax regions from %d", offset));
      
      /* compute ending iter of the batch */
      gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer),
                                  &end, offset + source_buffer->priv->
                                  worker_batch_size);
      
      /* extend the range to include needed_end if necessary */
      if (needed_end && gtk_text_iter_compare (&end, needed_end) < 0)
            end = *needed_end;
      
      /* always stop processing at end of lines: this minimizes the
       * chance of not getting a syntax pattern because it was split
       * in between batches */
      if (!gtk_text_iter_ends_line (&end))
            gtk_text_iter_forward_to_line_end (&end);

      table = source_buffer->priv->syntax_regions;
      g_assert (table != NULL);
      
      /* get old table information */
      use_old_data = FALSE;
      old_table = source_buffer->priv->old_syntax_regions;
      old_region = old_table ? bsearch_offset (old_table, offset) : 0;
      
      /* setup analyzer */
      if (table->len == 0) {
            delim.offset = offset;
            delim.tag = NULL;
            delim.depth = 0;

      } else {
            delim = g_array_index (table, SyntaxDelimiter, table->len - 1);
            g_assert (delim.offset <= offset);
      }

      /* get slice of text to work on */
      slice = gtk_text_iter_get_slice (&start, &end);
      head = slice;
      head_length = strlen (head);

      timer = g_timer_new ();

      /* MAIN LOOP: build the table */
      while (head_length > 0) {
            if (!next_syntax_region (source_buffer,
                               &delim,
                               head,
                               head_length,
                               offset,
                               &match)) {
                  /* no further data */
                  break;
            }

            /* check if we can use the saved table */
            if (old_table && old_region < old_table->len) {
                  /* don't fall behind the current match */
                  while (old_region < old_table->len &&
                         g_array_index (old_table,
                                    SyntaxDelimiter,
                                    old_region).offset < delim.offset) {
                        old_region++;
                  }
                  if (old_region < old_table->len &&
                      delimiter_is_equal (&delim,
                                    &g_array_index (old_table,
                                                SyntaxDelimiter,
                                                old_region))) {
                        /* we have an exact match; we can use
                         * the saved data */
                        use_old_data = TRUE;
                        break;
                  }
            }

            /* add the delimiter to the table */
            g_array_append_val (table, delim);
                  
            /* move pointers */
            head += match.endindex;
            head_length -= match.endindex;
            offset += match.endpos;
      }
    
      g_free (slice);
      g_timer_stop (timer);

      if (use_old_data) {
            /* now we copy the saved information from old_table to
             * the end of table */
            gint region = table->len;
            gint count = old_table->len - old_region;
            
            DEBUG (g_message ("copying %d delimiters from saved table information", count));

            g_array_set_size (table, table->len + count);
            memcpy (&g_array_index (table, SyntaxDelimiter, region),
                  &g_array_index (old_table, SyntaxDelimiter, old_region),
                  sizeof (SyntaxDelimiter) * count);
            
            /* set worker_last_offset from the last copied
             * element, so we can continue to analyze the text in
             * case the saved table was incomplete */
            region = table->len;
            offset = g_array_index (table, SyntaxDelimiter, region - 1).offset;
            source_buffer->priv->worker_last_offset = offset;
            gtk_text_iter_set_offset (&end, offset);
            
      } else {
            /* update worker information */
            source_buffer->priv->worker_last_offset =
                  gtk_text_iter_is_end (&end) ? -1 : gtk_text_iter_get_offset (&end);
            
            head_length = gtk_text_iter_get_offset (&end) - gtk_text_iter_get_offset (&start);
            
            if (head_length > 0) {
                  /* update profile information only if we didn't use the saved data */
                  source_buffer->priv->worker_batch_size =
                        MAX (head_length * WORKER_TIME_SLICE
                             / (g_timer_elapsed (timer, NULL) * 1000),
                             MINIMUM_WORKER_BATCH);
            }
      }
            
      /* make sure the analyzed region gets highlighted */
      refresh_range (source_buffer, &start, &end);
      
      /* forget saved table if we have already "consumed" at least
       * two of its delimiters, since that probably means it
       * contains invalid, useless data */
      if (old_table && (use_old_data ||
                    source_buffer->priv->worker_last_offset < 0 ||
                    old_region > 1)) {
            g_array_free (old_table, TRUE);
            source_buffer->priv->old_syntax_regions = NULL;
      }
      
      PROFILE (g_message ("ended worker batch, %g ms elapsed",
                      g_timer_elapsed (timer, NULL) * 1000));
      DEBUG (g_message ("table has %u entries", table->len));

      g_timer_destroy (timer);
}

static void 
update_syntax_regions (GtkSourceBuffer *source_buffer,
                   gint             start_offset,
                   gint             delta)
{
      GArray *table;
      gint first_region, region;
      gint table_index, expected_end_index;
      gchar *slice, *head;
      gint head_length, head_offset;
      GtkTextIter start_iter, end_iter;
      GtkSourceBufferMatch match;
      SyntaxDelimiter delim;
      gboolean mismatch;
      
      table = source_buffer->priv->syntax_regions;
      g_assert (table != NULL);

      if (!source_buffer->priv->highlight)
            return;
      
      if (!gtk_source_buffer_get_syntax_entries (source_buffer))
      {
            /* Shortcut case: we don't have syntax entries, so we
             * just refresh_range() the edited area */
            gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer),
                                        &start_iter, start_offset);
            end_iter = start_iter;
            if (delta > 0)
                  gtk_text_iter_forward_chars (&end_iter, delta);
      
            gtk_text_iter_set_line_offset (&start_iter, 0);
            gtk_text_iter_forward_to_line_end (&end_iter);

            refresh_range (source_buffer, &start_iter, &end_iter);

            return;
      }
      
      /* check if the offset is at an unanalyzed region */
      if (source_buffer->priv->worker_last_offset >= 0 &&
          start_offset >= source_buffer->priv->worker_last_offset) {
            /* update saved table offsets which potentially
             * contain the offset */
            region = bsearch_offset (source_buffer->priv->old_syntax_regions, start_offset);
            adjust_table_offsets (source_buffer->priv->old_syntax_regions, region, delta);
            return;
      }
      
      /* we shall start analyzing from the beginning of the line */
      gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer),
                                  &start_iter, start_offset);
      gtk_text_iter_set_line_offset (&start_iter, 0);
      head_offset = gtk_text_iter_get_offset (&start_iter);
      first_region = bsearch_offset (table, head_offset);

      /* initialize analyzing context */
      delim.tag = NULL;
      delim.offset = 0;
      delim.depth = 0;
      /* first expected match */
      table_index = first_region;
      
      /* calculate starting context: delim, head_offset, start_iter and table_index */
      if (first_region > 0) {
            head_offset = g_array_index (table, SyntaxDelimiter, first_region - 1).offset;
            gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer),
                                        &start_iter,
                                        head_offset);
            if (g_array_index (table, SyntaxDelimiter, first_region - 1).tag) {
                  /* we are inside a syntax colored region, so
                   * we expect to see the opening delimiter
                   * first */
                  table_index = first_region - 1;
            }

            if (table_index > 0) {
                  /* set the initial analyzing context to the
                   * delimiter right before the next expected
                   * delimiter */
                  delim = g_array_index (table, SyntaxDelimiter, table_index - 1);
            }
            
      } else {
            /* no previous delimiter, so start analyzing at the
             * start of the buffer */
            head_offset = 0;
            gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (source_buffer),
                                    &start_iter);
      }

      /* lookup the edited region */
      region = bsearch_offset (table, start_offset);
      
      /* calculate ending context: expected_end_index and end_iter */
      if (region < table->len) {
            gint end_offset;
            
            /* *corrected* end_offset */
            end_offset = g_array_index (table, SyntaxDelimiter, region).offset + delta;

            /* FIRST INVALIDATION CASE:
             * the ending delimiter was deleted */
            if (end_offset < start_offset) {
                  /* ending delimiter was deleted, so invalidate
                     from the starting delimiter onwards */
                  DEBUG (g_message ("deleted ending delimiter"));
                  invalidate_syntax_regions (source_buffer, &start_iter, delta);
                  
                  return;
            }

            /* set ending iter */
            gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer),
                                        &end_iter,
                                        end_offset);

            /* calculate expected_end_index */
            if (g_array_index (table, SyntaxDelimiter, region).tag)
                  expected_end_index = region;
            else
                  expected_end_index = MIN (region + 1, table->len);
            
      } else {
            /* set the ending iter to the end of the buffer */
            gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (source_buffer),
                                    &end_iter);
            expected_end_index = table->len;
      }

      /* get us the chunk of text to analyze */
      head = slice = gtk_text_iter_get_slice (&start_iter, &end_iter);
      head_length = strlen (head);

      /* We will start analyzing the slice of text and see if it
       * matches the information from the table.  When we hit a
       * mismatch, it means we need to invalidate. */
      mismatch = FALSE;
      while (next_syntax_region (source_buffer,
                           &delim,
                           head,
                           head_length,
                           head_offset,
                           &match)) {
            /* correct offset, since the table has the old offsets */
            if (delim.offset > start_offset + delta)
                  delim.offset -= delta;

            if (table_index + 1 > table->len ||
                !delimiter_is_equal (&delim,
                               &g_array_index (table,
                                           SyntaxDelimiter,
                                           table_index))) {
                  /* SECOND INVALIDATION CASE: a mismatch
                     against the saved information or a new
                     delimiter not present in the table */
                  mismatch = TRUE;
                  break;
            }
            
            /* move pointers */
            head += match.endindex;
            head_length -= match.endindex;
            head_offset += match.endpos;
            table_index++;
      }

      g_free (slice);

      if (mismatch || table_index < expected_end_index) {
            /* we invalidate if there was a mismatch or we didn't
             * advance the table_index enough (which means some
             * delimiter was deleted) */
            DEBUG (g_message ("changed delimiter at %d", delim.offset));
            
            invalidate_syntax_regions (source_buffer, &start_iter, delta);
            
            return;
      }

      /* no syntax regions changed */
      
      /* update trailing offsets with delta ... */
      adjust_table_offsets (table, region, delta);

      /* ... worker data ... */
      if (source_buffer->priv->worker_last_offset >= start_offset + delta)
            source_buffer->priv->worker_last_offset += delta;
      
      /* ... and saved table offsets */
      adjust_table_offsets (source_buffer->priv->old_syntax_regions, 0, delta);

      /* the syntax regions have not changed, so set the refreshing bounds */
      gtk_text_buffer_get_iter_at_offset (GTK_TEXT_BUFFER (source_buffer),
                                  &start_iter, start_offset);
      end_iter = start_iter;
      if (delta > 0)
            gtk_text_iter_forward_chars (&end_iter, delta);
      
      /* adjust bounds to line bounds to correctly highlight
         non-syntax patterns */
      gtk_text_iter_set_line_offset (&start_iter, 0);
      gtk_text_iter_forward_to_line_end (&end_iter);
      
      refresh_range (source_buffer, &start_iter, &end_iter);
}

/* Beginning of highlighting code ------------ */

/**
 * search_patterns:
 * @matches: the starting list of matches to work from (can be NULL)
 * @text: the text which will be searched for
 * @length: the length (in bytes) of @text
 * @offset: the offset the beginning of @text is at
 * @index: an index to add the match indexes (usually: @text - base_text)
 * @patterns: additional patterns (can be NULL)
 * 
 * This function will fill and return a list of PatternMatch
 * structures ordered by match position in @text.  The initial list to
 * work on is @matches and it will be modified in-place.  Additional
 * new pattern tags might be specified in @patterns.
 *
 * From the patterns already in @matches only those whose starting
 * position is before @offset will be processed, and will be removed
 * if they don't match again.  New patterns will only be added if they
 * match.  The returned list is ordered.
 * 
 * Return value: the new list of matches
 **/
static GList * 
search_patterns (GList       *matches,
             const gchar *text,
             gint         length,
             gint         offset,
             gint         index,
             GList       *patterns)
{
      GtkSourceBufferMatch match;
      PatternMatch *pmatch;
      GList *new_pattern;
      
      new_pattern = patterns;
      while (new_pattern || matches) {
            GtkPatternTag *tag;
            gint i;
            
            if (new_pattern) {
                  /* process new patterns first */
                  tag = new_pattern->data;
                  new_pattern = new_pattern->next;
                  pmatch = NULL;
            } else {
                  /* process patterns already in @matches */
                  pmatch = matches->data;
                  tag = pmatch->tag;
                  if (pmatch->match.startpos >= offset) {
                        /* pattern is ahead of offset, so our
                         * work is done */
                        break;
                  }
                  /* temporarily remove the PatternMatch from
                   * the list */
                  matches = g_list_delete_link (matches, matches);
            }
            
            /* do the regex search on @text */
            i = gtk_source_regex_search (tag->reg_pattern,
                                   text,
                                   0,
                                   length,
                                   &match);
            
            if (i >= 0 && match.endpos != i) {
                  GList *p;
                  
                  /* create the match structure */
                  if (!pmatch) {
                        pmatch = g_new0 (PatternMatch, 1);
                        pmatch->tag = tag;
                  }
                  /* adjust offsets (indexes remain relative to
                   * the current pointer in the text) */
                  pmatch->match.startpos = match.startpos + offset;
                  pmatch->match.endpos = match.endpos + offset;
                  pmatch->match.startindex = match.startindex + index;
                  pmatch->match.endindex = match.endindex + index;
                  
                  /* insert the match in order (prioritize longest match) */
                  for (p = matches; p; p = p->next) {
                        PatternMatch *tmp = p->data;
                        if (tmp->match.startpos > pmatch->match.startpos ||
                            (tmp->match.startpos == pmatch->match.startpos &&
                             tmp->match.endpos < pmatch->match.endpos)) {
                              break;
                        }
                  }
                  matches = g_list_insert_before (matches, p, pmatch);

            } else if (pmatch) {
                  /* either no match was found or the match has
                   * zero length (which probably indicates a
                   * buggy syntax pattern), so free the
                   * PatternMatch structure if we were analyzing
                   * a pattern from @matches */
                  if (i >= 0 && i == match.endpos) {
                        gchar *name;
                        g_object_get (G_OBJECT (tag), "name", &name, NULL);
                        g_warning ("The regex for pattern tag `%s' matched "
                                 "a zero length string.  That's probably "
                                 "due to a buggy regular expression.", name);
                        g_free (name);
                  }
                  g_free (pmatch);
            }
      }

      return matches;
}

static void 
check_pattern (GtkSourceBuffer *source_buffer,
             GtkTextIter     *start,
             const gchar     *text,
             gint             length)
{
      GList *matches;
      gint offset, index;
      GtkTextIter start_iter, end_iter;
      const gchar *ptr;

#ifdef ENABLE_PROFILE
      static GTimer *timer = NULL;
      static gdouble seconds = 0.0;
      static gint acc_length = 0;
#endif
      
      if (length == 0 || !gtk_source_buffer_get_pattern_entries (source_buffer))
            return;
      
      PROFILE ({
            if (timer == NULL)
                  timer = g_timer_new ();
            acc_length += length;
            g_timer_start (timer);
      });
      
      /* setup environment */
      index = 0;
      offset = gtk_text_iter_get_offset (start);
      start_iter = end_iter = *start;
      ptr = text;
      
      /* get the initial list of matches */
      matches = search_patterns (NULL,
                           ptr, length,
                           offset, index,
                           gtk_source_buffer_get_pattern_entries (source_buffer));
      
      while (matches && length > 0) {
            /* pick the first (nearest) match... */
            PatternMatch *pmatch = matches->data;
            
            gtk_text_iter_set_offset (&start_iter,
                                pmatch->match.startpos);
            gtk_text_iter_set_offset (&end_iter,
                                pmatch->match.endpos);

            /* ... and apply it */
            gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (source_buffer),
                                 GTK_TEXT_TAG (pmatch->tag),
                                 &start_iter,
                                 &end_iter);

            /* now skip it completely */
            offset = pmatch->match.endpos;
            index = pmatch->match.endindex;
            length -= (text + index) - ptr;
            ptr = text + index;

            /* and update matches from the new position */
            matches = search_patterns (matches,
                                 ptr, length,
                                 offset, index,
                                 NULL);
      }

      if (matches) {
            /* matches should have been consumed completely */
            g_assert_not_reached ();
      }

      PROFILE ({
            g_timer_stop (timer);
            seconds += g_timer_elapsed (timer, NULL);
            g_message ("%g bytes/sec", acc_length / seconds);
      });
}

static void 
highlight_region (GtkSourceBuffer *source_buffer,
              GtkTextIter     *start,
              GtkTextIter     *end)
{
      GtkTextIter b_iter, e_iter;
      gint b_off, e_off, end_offset;
      GtkSyntaxTag *current_tag;
      SyntaxDelimiter *delim;
      GArray *table;
      gint region;
      gchar *slice, *slice_ptr;
      GTimer *timer;

      timer = NULL;
      PROFILE ({
            timer = g_timer_new ();
            g_message ("highlighting from %d to %d",
                     gtk_text_iter_get_offset (start),
                     gtk_text_iter_get_offset (end));
      });

      
      table = source_buffer->priv->syntax_regions;
      g_return_if_fail (table != NULL);
      
      /* remove_all_tags is not efficient: for different positions
         in the buffer it takes different times to complete, taking
         longer if the slice is at the beginning */
      gtk_source_buffer_remove_all_source_tags (source_buffer, start, end);

      slice_ptr = slice = gtk_text_iter_get_slice (start, end);
      end_offset = gtk_text_iter_get_offset (end);
      
      /* get starting syntax region */
      b_off = gtk_text_iter_get_offset (start);
      region = bsearch_offset (table, b_off);
      delim = region > 0 && region <= table->len ?
            &g_array_index (table, SyntaxDelimiter, region - 1) :
            NULL;

      e_iter = *start;
      e_off = b_off;

      do {
            /* select region to work on */
            b_iter = e_iter;
            b_off = e_off;
            current_tag = delim ? delim->tag : NULL;
            region++;
            delim = region <= table->len ?
                  &g_array_index (table, SyntaxDelimiter, region - 1) :
                  NULL;

            if (delim)
                  e_off = MIN (delim->offset, end_offset);
            else
                  e_off = end_offset;
            gtk_text_iter_forward_chars (&e_iter, (e_off - b_off));

            /* do the highlighting for the selected region */
            if (current_tag) {
                  /* apply syntax tag from b_iter to e_iter */
                  gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (source_buffer),
                                       GTK_TEXT_TAG (current_tag),
                                       &b_iter,
                                       &e_iter);

                  slice_ptr = g_utf8_offset_to_pointer (slice_ptr,
                                                e_off - b_off);
            
            } else {
                  gchar *tmp;
                  
                  /* highlight from b_iter through e_iter using
                     non-syntax patterns */
                  tmp = g_utf8_offset_to_pointer (slice_ptr,
                                          e_off - b_off);
                  check_pattern (source_buffer, &b_iter,
                               slice_ptr, tmp - slice_ptr);
                  slice_ptr = tmp;
            }

      } while (gtk_text_iter_compare (&b_iter, end) < 0);

      g_free (slice);

      PROFILE ({
            g_message ("highlighting took %g ms",
                     g_timer_elapsed (timer, NULL) * 1000);
            g_timer_destroy (timer);
      });
}

static void
refresh_range (GtkSourceBuffer *buffer,
             GtkTextIter     *start, 
             GtkTextIter     *end)
{
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      /* Add the region to the refresh region */
      gtk_text_region_add (buffer->priv->refresh_region, start, end);

      /* Notify views of the updated highlight region */
      g_signal_emit (buffer, buffer_signals [HIGHLIGHT_UPDATED], 0, start, end);
}

static void 
ensure_highlighted (GtkSourceBuffer   *source_buffer,
                const GtkTextIter *start,
                const GtkTextIter *end)
{
      GtkTextRegion *region;
      
#if 0
      DEBUG (g_message ("ensure_highlighted %d to %d",
                    gtk_text_iter_get_offset (start),
                    gtk_text_iter_get_offset (end)));
#endif
      
      /* get the subregions not yet highlighted */
      region = gtk_text_region_intersect (
            source_buffer->priv->refresh_region, start, end);
      if (region) {
            GtkTextIter iter1, iter2;
            gint i;
            
            /* highlight all subregions from the intersection.
                   hopefully this will only be one subregion */
            for (i = 0; i < gtk_text_region_subregions (region); i++) {
                  gtk_text_region_nth_subregion (region, i,
                                           &iter1, &iter2);
                  highlight_region (source_buffer, &iter1, &iter2);
            }
            gtk_text_region_destroy (region, TRUE);
            /* remove the just highlighted region */
            gtk_text_region_substract (source_buffer->priv->refresh_region,
                                 start, 
                                 end);
            gtk_text_region_clear_zero_length_subregions (
                  source_buffer->priv->refresh_region);
      }
}

static void 
highlight_queue (GtkSourceBuffer   *source_buffer,
             const GtkTextIter *start,
             const GtkTextIter *end)
{
      gtk_text_region_add (source_buffer->priv->highlight_requests,
                       start,
                       end);

      DEBUG (g_message ("queueing highlight [%d, %d]",
                    gtk_text_iter_get_offset (start),
                    gtk_text_iter_get_offset (end)));
}

void 
_gtk_source_buffer_highlight_region (GtkSourceBuffer   *source_buffer,
                             const GtkTextIter *start,
                             const GtkTextIter *end,
                             gboolean           highlight_now)
{
      g_return_if_fail (source_buffer != NULL);
      g_return_if_fail (start != NULL);
            g_return_if_fail (end != NULL);

      if (!source_buffer->priv->highlight)
            return;

#ifndef LAZIEST_MODE
      if (source_buffer->priv->worker_last_offset < 0 ||
          source_buffer->priv->worker_last_offset >= gtk_text_iter_get_offset (end)) {
            ensure_highlighted (source_buffer, start, end);
      } else
#endif
      {
            if (highlight_now)
            {
                  build_syntax_regions_table (source_buffer, end);
                  ensure_highlighted (source_buffer, start, end);
            }
            else
            {
                  highlight_queue (source_buffer, start, end);
                  install_idle_worker (source_buffer);
            }
      }
}

/* This is a modified version of the gtk_text_buffer_remove_all_tags
 * function from gtk/gtktextbuffer.c
 *
 * Copyright (C) 2000 Red Hat, Inc.
 */

static gint
pointer_cmp (gconstpointer a, gconstpointer b)
{
      if (a < b)
            return -1;
      else if (a > b)
            return 1;
      else
            return 0;
}

/**
 * gtk_source_buffer_remove_all_source_tags:
 * @buffer: a #GtkSourceBuffer
 * @start: one bound of range to be untagged
 * @end: other bound of range to be untagged
 * 
 * Removes all tags in the range between @start and @end.  Be careful
 * with this function; it could remove tags added in code unrelated to
 * the code you're currently writing. That is, using this function is
 * probably a bad idea if you have two or more unrelated code sections
 * that add tags.
 **/
static void
gtk_source_buffer_remove_all_source_tags (GtkSourceBuffer   *buffer,
                                const GtkTextIter *start,
                                const GtkTextIter *end)
{
      GtkTextIter first, second, tmp;
      GSList *tags;
      GSList *tmp_list;
      GSList *tmp_list2;
      GSList *prev;
      GtkTextTag *tag;
  
      /*
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (start != NULL);
      g_return_if_fail (end != NULL);
      g_return_if_fail (gtk_text_iter_get_buffer (start) == GTK_TEXT_BUFFER (buffer));
      g_return_if_fail (gtk_text_iter_get_buffer (end) == GTK_TEXT_BUFFER (buffer));
      */
      
      first = *start;
      second = *end;

      gtk_text_iter_order (&first, &second);

      /* Get all tags turned on at the start */
      tags = NULL;
      tmp_list = gtk_text_iter_get_tags (&first);
      tmp_list2 = tmp_list;
      
      while (tmp_list2 != NULL)
      {
            if (GTK_IS_SOURCE_TAG (tmp_list2->data))
            {
                  tags = g_slist_prepend (tags, tmp_list2->data);
            }

            tmp_list2 = g_slist_next (tmp_list2);
      }
      
      g_slist_free (tmp_list);
      
      /* Find any that are toggled on within the range */
      tmp = first;
      while (gtk_text_iter_forward_to_tag_toggle (&tmp, NULL))
      {
            GSList *toggled;
            
            if (gtk_text_iter_compare (&tmp, &second) >= 0)
                  break; /* past the end of the range */
      
            toggled = gtk_text_iter_get_toggled_tags (&tmp, TRUE);

            /* We could end up with a really big-ass list here.
             * Fix it someday.
             */
            tmp_list2 = toggled;
            while (tmp_list2 != NULL)
            {
                  if (GTK_IS_SOURCE_TAG (tmp_list2->data))
                  {
                        tags = g_slist_prepend (tags, tmp_list2->data);
                  }

                  tmp_list2 = g_slist_next (tmp_list2);
            }

            g_slist_free (toggled);
      }
  
      /* Sort the list */
      tags = g_slist_sort (tags, pointer_cmp);

      /* Strip duplicates */
      tag = NULL;
      prev = NULL;
      tmp_list = tags;

      while (tmp_list != NULL)
      {
            if (tag == tmp_list->data)
            {
                  /* duplicate */
                  if (prev)
                        prev->next = tmp_list->next;

                  tmp_list->next = NULL;

                  g_slist_free (tmp_list);

                  tmp_list = prev->next;
                  /* prev is unchanged */
            }
            else
            {
                  /* not a duplicate */
                  tag = GTK_TEXT_TAG (tmp_list->data);
                  prev = tmp_list;
                  tmp_list = tmp_list->next;
            }
      }

      g_slist_foreach (tags, (GFunc) g_object_ref, NULL);
  
      tmp_list = tags;
      while (tmp_list != NULL)
      {
            tag = GTK_TEXT_TAG (tmp_list->data);

            gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (buffer), 
                                  tag,
                                  &first,
                                  &second);
      
            tmp_list = tmp_list->next;
      }

      g_slist_foreach (tags, (GFunc) g_object_unref, NULL);
  
      g_slist_free (tags);
}

/**
 * gtk_source_buffer_set_language:
 * @buffer: a #GtkSourceBuffer.
 * @language: a #GtkSourceLanguage to set, or NULL.
 * 
 * Sets the #GtkSourceLanguage the source buffer will use, adding
 * #GtkSourceTag tags with the language's patterns and setting the
 * escape character with gtk_source_buffer_set_escape_char().  Note
 * that this will remove any #GtkSourceTag tags currently in the
 * buffer's tag table.  The buffer holds a reference to the @language
 * set.
 **/
void
gtk_source_buffer_set_language (GtkSourceBuffer   *buffer, 
                        GtkSourceLanguage *language)
{
      GtkSourceTagTable *table;

      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));

      if (buffer->priv->language == language)
            return;

      if (language != NULL)
            g_object_ref (language);

      if (buffer->priv->language != NULL)
            g_object_unref (buffer->priv->language);

      buffer->priv->language = language;

      /* remove previous tags */
      table = GTK_SOURCE_TAG_TABLE (gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer)));
      gtk_source_tag_table_remove_source_tags (table);

      if (language != NULL)
      {
            GSList *list = NULL;
                  
            list = gtk_source_language_get_tags (language);       
            gtk_source_tag_table_add_tags (table, list);

            g_slist_foreach (list, (GFunc)g_object_unref, NULL);
            g_slist_free (list);

            gtk_source_buffer_set_escape_char (
                  buffer, gtk_source_language_get_escape_char (language));
      }

      g_object_notify (G_OBJECT (buffer), "language");
}

/**
 * gtk_source_buffer_get_language:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Determines the #GtkSourceLanguage used by the buffer.  The returned
 * object should not be unreferenced by the user.
 * 
 * Return value: the #GtkSourceLanguage set by
 * gtk_source_buffer_set_language(), or NULL.
 **/
GtkSourceLanguage *
gtk_source_buffer_get_language (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);

      return buffer->priv->language;
}

/**
 * gtk_source_buffer_get_escape_char:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Determines the escaping character used by the source buffer
 * highlighting engine.
 * 
 * Return value: the UTF-8 character for the escape character the
 * buffer is using.
 **/
gunichar 
gtk_source_buffer_get_escape_char (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (buffer != NULL && GTK_IS_SOURCE_BUFFER (buffer), 0);

      return buffer->priv->escape_char;
}

/**
 * gtk_source_buffer_set_escape_char:
 * @buffer: a #GtkSourceBuffer.
 * @escape_char: the escape character the buffer should use.
 * 
 * Sets the escape character to be used by the highlighting engine.
 *
 * When performing the initial analysis, the engine will discard a
 * matching syntax pattern if it's prefixed with an odd number of
 * escape characters.  This allows for example to correctly highlight
 * strings with escaped quotes embedded.
 *
 * This setting affects only syntax patterns (i.e. those defined in
 * #GtkSyntaxTag tags).
 **/
void 
gtk_source_buffer_set_escape_char (GtkSourceBuffer *buffer,
                           gunichar         escape_char)
{
      g_return_if_fail (buffer != NULL && GTK_IS_SOURCE_BUFFER (buffer));

      if (escape_char != buffer->priv->escape_char)
      {
            buffer->priv->escape_char = escape_char;
            if (buffer->priv->highlight)
                  invalidate_syntax_regions (buffer, NULL, 0);
            g_object_notify (G_OBJECT (buffer), "escape_char");
      }
}

/* Markers functionality */

/**
 * markers_binary_search:
 * @buffer: the GtkSourceBuffer where the markers are
 * @iter: the position to search for
 * @last_cmp: where to return the value of the last comparision made (optional)
 * 
 * Performs a binary search among the markers in @buffer for the
 * position of the @iter.  Returns the nearest matching marker (its
 * index in the markers array) and optionally the value of the
 * comparision between the returned marker and the given iter.
 * 
 * Return value: an index in the markers array or -1 if the array is
 * empty
 **/
static gint
markers_binary_search (GtkSourceBuffer *buffer, GtkTextIter *iter, gint *last_cmp)
{
      GtkTextIter check_iter;
      GtkSourceMarker **check, **p;
      GArray *markers = buffer->priv->markers;
      gint n_markers = markers->len;
      gint cmp, i;
      
      if (n_markers == 0)
            return -1;

      check = p = &g_array_index (markers, GtkSourceMarker *, 0);
      p--;
      cmp = 0;
      while (n_markers)
      {
            i = (n_markers + 1) >> 1;
            check = p + i;
            gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
                                      &check_iter,
                                      GTK_TEXT_MARK (*check));
            cmp = gtk_text_iter_compare (iter, &check_iter);
            if (cmp > 0)
            {
                  n_markers -= i;
                  p = check;
            }
            else if (cmp < 0)
                  n_markers = i - 1;
            else /* if (cmp == 0) */
                  break;
      }

      i = check - &g_array_index (markers, GtkSourceMarker *, 0);
      if (last_cmp)
            *last_cmp = cmp;

      return i;
}

/**
 * markers_linear_lookup:
 * @buffer: the source buffer where the markers are
 * @marker: which marker to search for
 * @start: index from where to start looking
 * @direction: direction to search for
 * 
 * Search the markers array of @buffer starting from @start for
 * markers at the same position as the one at @start.  If @marker is
 * non-NULL search for that marker specifically, otherwise return the
 * first or the last marker at the staring position, depending on
 * @direction.
 *
 * @direction < 0 means left, @direction > 0 means right,
 * 0 means both and is mostly useful when looking for a specific
 * @marker.
 * 
 * Return value: the index of the searched marker
 **/
static gint 
markers_linear_lookup (GtkSourceBuffer *buffer,
                   GtkSourceMarker *marker,
                   gint             start,
                   gint             direction)
{
      GArray *markers = buffer->priv->markers;
      gint left, right;
      gint cmp;
      GtkTextIter iter;
      GtkSourceMarker *tmp;

      tmp = g_array_index (markers, GtkSourceMarker *, start);
      if (tmp == marker)
            return start;

      gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
                                &iter,
                                GTK_TEXT_MARK (tmp));
      
      if (direction == 0)
      {
            left = start - 1;
            right = start + 1;
      }
      else if (direction > 0)
      {
            left = -1;
            right = start + 1;
      }
      else
      {
            left = start - 1;
            right = markers->len;
      }
      
      while (left >= 0 || right < markers->len)
      {
            GtkTextIter iter_tmp;
            
            if (left >= 0)
            {
                  tmp = g_array_index (markers, GtkSourceMarker *, left);
                  if (tmp == marker)
                        return left;
                  
                  gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
                                            &iter_tmp,
                                            GTK_TEXT_MARK (tmp));
                  cmp = gtk_text_iter_compare (&iter, &iter_tmp);
                  if (cmp != 0)
                  {
                        if (marker)
                              /* searching for a particular marker */
                              left = -1;
                        else
                              /* searching for the first
                               * marker at a given
                               * position */
                              return left + 1;
                  } else
                        left--;
            }

            if (right < markers->len)
            {
                  tmp = g_array_index (markers, GtkSourceMarker *, right);
                  if (tmp == marker)
                        return right;
                  
                  gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
                                            &iter_tmp,
                                            GTK_TEXT_MARK (tmp));
                  cmp = gtk_text_iter_compare (&iter, &iter_tmp);
                  if (cmp != 0)
                  {
                        if (marker)
                              /* searching for a particular marker */
                              right = markers->len;
                        else
                              /* searching for the last
                               * marker at a given
                               * position */
                              return right - 1;
                  }
                  else
                        right++;
            }
      }
      if (marker)
            return -1;
      else
            return start;
}

static void
markers_insert (GtkSourceBuffer *buffer, GtkSourceMarker *marker)
{
      GtkTextIter iter;
      gint index, cmp;

      gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
                                &iter,
                                GTK_TEXT_MARK (marker));
      
      index = markers_binary_search (buffer, &iter, &cmp);
      if (index >= 0)
      {
            _gtk_source_marker_link (marker, g_array_index (buffer->priv->markers,
                                                GtkSourceMarker *,
                                                index),     (cmp > 0));
            if (cmp > 0)
                  index++;
      }
      else
            index = 0;
      
      g_array_insert_val (buffer->priv->markers, index, marker);
}

/**
 * gtk_source_buffer_create_marker:
 * @buffer: a #GtkSourceBuffer.
 * @name: the name of the marker, or NULL.
 * @type: a string defining the marker type, or NULL.
 * @where: location to place the marker.
 * 
 * Creates a marker in the @buffer of type @type.  A marker is
 * semantically very similar to a #GtkTextMark, except it has a type
 * which is used by the #GtkSourceView displaying the buffer to show a
 * pixmap on the left margin, at the line the marker is in.  Because
 * of this, a marker is generally associated to a line and not a
 * character position.  Markers are also accessible through a position
 * or range in the buffer.
 *
 * Markers are implemented using #GtkTextMark, so all characteristics
 * and restrictions to marks apply to markers too.  These includes
 * life cycle issues and "mark-set" and "mark-deleted" signal
 * emissions.
 *
 * Like a #GtkTextMark, a #GtkSourceMarker can be anonymous if the
 * passed @name is NULL.  Also, the buffer owns the markers so you
 * shouldn't unreference it.
 *
 * Markers always have left gravity and are moved to the beginning of
 * the line when the user deletes the line they were in.  Also, if the
 * user deletes a region of text which contained lines with markers,
 * those are deleted.
 *
 * Typical uses for a marker are bookmarks, breakpoints, current
 * executing instruction indication in a source file, etc..
 * 
 * Return value: a new #GtkSourceMarker, owned by the buffer.
 **/
GtkSourceMarker *
gtk_source_buffer_create_marker (GtkSourceBuffer   *buffer,
                         const gchar       *name,
                         const gchar       *type,
                         const GtkTextIter *where)
{
      GtkTextMark *text_mark;

      g_return_val_if_fail (buffer != NULL, NULL);
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);
      g_return_val_if_fail (where != NULL, NULL);

      text_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer),
                                     name,
                                     where,
                                     TRUE);
      if (text_mark)
      {
            g_object_ref (text_mark);

            gtk_source_marker_set_marker_type (GTK_SOURCE_MARKER (text_mark), type);
            markers_insert (buffer, GTK_SOURCE_MARKER (text_mark));
            _gtk_source_marker_changed (GTK_SOURCE_MARKER (text_mark));

            return GTK_SOURCE_MARKER (text_mark);
      }

      return NULL;
}

static gint
markers_lookup (GtkSourceBuffer *buffer, GtkSourceMarker *marker)
{
      gint index, cmp;
      GtkTextIter iter;
      
      gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
                                &iter,
                                GTK_TEXT_MARK (marker));
      
      index = markers_binary_search (buffer, &iter, &cmp);
      if (index >= 0 && cmp == 0)
      {
            if (g_array_index (buffer->priv->markers,
                           GtkSourceMarker *,
                           index) == marker)
                  return index;
            return markers_linear_lookup (buffer, marker, index, 0);
      }
      return -1;
}

/**
 * gtk_source_buffer_move_marker:
 * @buffer: a #GtkSourceBuffer.
 * @marker: a #GtkSourceMarker in @buffer.
 * @where: the new location for the marker.
 * 
 * Moves @marker to the new location @where.
 **/
void 
gtk_source_buffer_move_marker (GtkSourceBuffer   *buffer,
                         GtkSourceMarker   *marker,
                         const GtkTextIter *where)
{
      gint index;
      
      g_return_if_fail (buffer != NULL && marker != NULL);
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (GTK_IS_SOURCE_MARKER (marker));
      g_return_if_fail (!gtk_text_mark_get_deleted (GTK_TEXT_MARK (marker)));
      g_return_if_fail (where != NULL);

      index = markers_lookup (buffer, marker);
      
      g_return_if_fail (index >= 0);
      
      /* unlink the marker first */
      _gtk_source_marker_changed (marker);
      _gtk_source_marker_unlink (marker);
      g_array_remove_index (buffer->priv->markers, index);

      gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (buffer),
                           GTK_TEXT_MARK (marker),
                           where);

      /* re-link the marker using the new position */
      markers_insert (buffer, marker);

      /* FIXME: emit even the line number hasn't changed? - Gustavo */
      _gtk_source_marker_changed (marker);
}

/**
 * gtk_source_buffer_delete_marker:
 * @buffer: a #GtkSourceBuffer.
 * @marker: a #GtkSourceMarker in the @buffer.
 * 
 * Deletes @marker from the source buffer.  The same conditions as for
 * #GtkTextMark apply here.  The marker is no longer accessible from
 * the buffer, but if you held a reference to it, it will not be
 * destroyed.
 **/
void 
gtk_source_buffer_delete_marker (GtkSourceBuffer *buffer,
                         GtkSourceMarker *marker)
{
      gint index;
      
      g_return_if_fail (buffer != NULL && marker != NULL);
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (GTK_IS_SOURCE_MARKER (marker));
      g_return_if_fail (!gtk_text_mark_get_deleted (GTK_TEXT_MARK (marker)));

      index = markers_lookup (buffer, marker);
      
      g_return_if_fail (index >= 0);
      
      _gtk_source_marker_changed (marker);
      _gtk_source_marker_unlink (marker);
      g_array_remove_index (buffer->priv->markers, index);

      g_object_unref (marker);
      gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer),
                             GTK_TEXT_MARK (marker));
}

/**
 * gtk_source_buffer_get_marker:
 * @buffer: a #GtkSourceBuffer.
 * @name: name of the marker to retrieve.
 * 
 * Looks up the #GtkSourceMarker named @name in @buffer, returning
 * NULL if it doesn't exists.
 * 
 * Return value: the #GtkSourceMarker whose name is @name, or NULL.
 **/
GtkSourceMarker *
gtk_source_buffer_get_marker (GtkSourceBuffer *buffer,
                        const gchar     *name)
{
      GtkTextMark *text_mark;
      
      g_return_val_if_fail (buffer != NULL, NULL);
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);
      g_return_val_if_fail (name != NULL, NULL);

      text_mark = gtk_text_buffer_get_mark (GTK_TEXT_BUFFER (buffer), name);
      
      if (text_mark && markers_lookup (buffer, GTK_SOURCE_MARKER (text_mark)) < 0)
            text_mark = NULL;

      if (text_mark)
            return GTK_SOURCE_MARKER (text_mark);
      else
            return NULL;
}

#ifdef ENABLE_DEBUG
static void
marker_print (GtkSourceBuffer *buffer, GtkSourceMarker *marker)
{
      GtkTextIter iter;
      gchar *type;
      
      type = gtk_source_marker_get_marker_type (marker);
      gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, marker);
      
      g_print ("Marker [%p] at %d (type: %s)\n",
             marker,
             gtk_text_iter_get_offset (&iter),
             type);
      g_free (type);
}

static void
markers_debug (GtkSourceBuffer *buffer)
{
      GtkSourceMarker *marker, *tmp;
      GArray *markers = buffer->priv->markers;
      gint i;
      
      if (markers->len == 0)
      {
            g_print ("No markers\n");
            return;
      }
      
      g_print ("Array contents:\n");
      for (i = 0; i < markers->len; i++)
            marker_print (buffer, g_array_index (markers, GtkSourceMarker *, i));

      g_print ("Linked list:\n");
      marker = g_array_index (markers, GtkSourceMarker *, 0);
      
      while ((tmp = gtk_source_marker_prev (marker)) != NULL)
            marker = tmp;
      while (marker)
      {
            marker_print (buffer, marker);
            marker = gtk_source_marker_next (marker);
      }
}
#endif

/**
 * gtk_source_buffer_get_markers_in_region:
 * @buffer: a #GtkSourceBuffer.
 * @begin: beginning of the range.
 * @end: end of the range.
 * 
 * Returns an <emphasis>ordered</emphasis> (by position) #GSList of
 * #GtkSourceMarker objects inside the region delimited by the
 * #GtkTextIter @begin and @end.  The iters may be in any order.
 * 
 * Return value: a #GSList of the #GtkSourceMarker inside the range.
 **/
GSList * 
gtk_source_buffer_get_markers_in_region (GtkSourceBuffer   *buffer,
                               const GtkTextIter *begin,
                               const GtkTextIter *end)
{
      GSList *result;
      GtkTextIter iter1, iter2;
      gint i, j, cmp;
      GArray *markers;
      
      g_return_val_if_fail (buffer != NULL, NULL);
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);
      g_return_val_if_fail (begin != NULL && end != NULL, NULL);

      DEBUG (g_print ("Getting markers for [%d,%d]\n",
                  gtk_text_iter_get_offset (begin),
                  gtk_text_iter_get_offset (end)));
      DEBUG (markers_debug (buffer));

      iter1 = *begin;
      iter2 = *end;
      gtk_text_iter_order (&iter1, &iter2);

      result = NULL;
      markers = buffer->priv->markers;

      i = markers_binary_search (buffer, &iter1, &cmp);
      if (i < 0)
            return NULL;
      
      if (cmp == 0)
            /* we got an exact match, which means the iter was at
             * the position of the returned marker.  now we have
             * to search backward for other markers at the same
             * position */
            i = markers_linear_lookup (buffer, NULL, i, -1);
      else if (cmp > 0)
            i++;

      if (i >= markers->len)
            /* no markers to return */
            return NULL;

      j = markers_binary_search (buffer, &iter2, &cmp);
      if (cmp == 0)
            /* we got an exact match, which means the iter was at
             * the position of the returned marker.  now we have
             * to search forward for other markers at the same
             * position */
            j = markers_linear_lookup (buffer, NULL, j, 1);
      else if (cmp < 0)
            j--;

      if (j < 0 || i > j)
            /* no markers to return */
            return NULL;

      /* build the resulting list */
      while (j >= i)
      {
            result = g_slist_prepend (result, g_array_index (markers, GtkSourceMarker *, j));
            j--;
      }

      DEBUG({
            GSList *l;
            
            g_print ("Returned list:\n");
            for (l = result; l; l = l->next)
                  marker_print (buffer, l->data);
      });

      return result;
}

/**
 * gtk_source_buffer_get_first_marker:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Returns the first (nearest to the top of the buffer) marker in
 * @buffer.
 * 
 * Return value: a reference to the first #GtkSourceMarker, or NULL if
 * there are no markers in the buffer.
 **/
GtkSourceMarker *
gtk_source_buffer_get_first_marker (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (buffer != NULL, NULL);
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);

      if (buffer->priv->markers->len == 0)
            return NULL;

      return g_array_index (buffer->priv->markers, GtkSourceMarker *, 0);
}

/**
 * gtk_source_buffer_get_last_marker:
 * @buffer: a #GtkSourceBuffer.
 * 
 * Returns the last (nearest to the bottom of the buffer) marker in
 * @buffer.
 * 
 * Return value: a reference to the last #GtkSourceMarker, or NULL if
 * there are no markers in the buffer.
 **/
GtkSourceMarker *
gtk_source_buffer_get_last_marker (GtkSourceBuffer *buffer)
{
      g_return_val_if_fail (buffer != NULL, NULL);
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);

      if (buffer->priv->markers->len == 0)
            return NULL;

      return g_array_index (buffer->priv->markers,
                        GtkSourceMarker *,
                        buffer->priv->markers->len - 1);
}

/**
 * gtk_source_buffer_get_iter_at_marker:
 * @buffer: a #GtkSourceBuffer.
 * @iter: a #GtkTextIter to initialize.
 * @marker: a #GtkSourceMarker of @buffer.
 * 
 * Initializes @iter at the location of @marker.
 **/
void 
gtk_source_buffer_get_iter_at_marker (GtkSourceBuffer *buffer,
                              GtkTextIter     *iter,
                              GtkSourceMarker *marker)
{
      g_return_if_fail (buffer != NULL && marker != NULL);
      g_return_if_fail (GTK_IS_SOURCE_BUFFER (buffer));
      g_return_if_fail (GTK_IS_SOURCE_MARKER (marker));
      g_return_if_fail (!gtk_text_mark_get_deleted (GTK_TEXT_MARK (marker)));

      gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
                                iter,
                                GTK_TEXT_MARK (marker));
}

/**
 * gtk_source_buffer_get_next_marker:
 * @buffer: a #GtkSourceBuffer.
 * @iter: the location to start searching from.
 * 
 * Returns the nearest marker to the right of @iter.  If there are
 * multiple markers at the same position, this function will always
 * return the first one (from the internal linked list), even if
 * starting the search exactly at its location.  You can get the
 * others using gtk_source_marker_next().
 * 
 * Return value: the #GtkSourceMarker nearest to the right of @iter,
 * or NULL if there are no more markers after @iter.
 **/
GtkSourceMarker *
gtk_source_buffer_get_next_marker (GtkSourceBuffer *buffer,
                           GtkTextIter     *iter)
{
      GtkSourceMarker *marker;
      GArray *markers;
      gint i, cmp;
      
      g_return_val_if_fail (buffer != NULL, NULL);
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);
      g_return_val_if_fail (iter != NULL, NULL);

      marker = NULL;
      markers = buffer->priv->markers;

      i = markers_binary_search (buffer, iter, &cmp);
      if (i < 0)
            return NULL;
      
      if (cmp == 0)
            /* return the first marker at the iter position */
            i = markers_linear_lookup (buffer, NULL, i, -1);
      else if (cmp > 0)
            i++;

      if (i < markers->len)
      {
            marker = g_array_index (markers, GtkSourceMarker *, i);
            gtk_source_buffer_get_iter_at_marker (buffer, iter, marker);
      }
            
      return marker;
}

/**
 * gtk_source_buffer_get_prev_marker:
 * @buffer: a #GtkSourceBuffer.
 * @iter: the location to start searching from.
 * 
 * Returns the nearest marker to the left of @iter.  If there are
 * multiple markers at the same position, this function will always
 * return the last one (from the internal linked list), even if
 * starting the search exactly at its location.  You can get the
 * others using gtk_source_marker_prev().
 * 
 * Return value: the #GtkSourceMarker nearest to the left of @iter,
 * or NULL if there are no more markers before @iter.
 **/
GtkSourceMarker *
gtk_source_buffer_get_prev_marker (GtkSourceBuffer *buffer,
                           GtkTextIter     *iter)
{
      GtkSourceMarker *marker;
      GArray *markers;
      gint i, cmp;
      
      g_return_val_if_fail (buffer != NULL, NULL);
      g_return_val_if_fail (GTK_IS_SOURCE_BUFFER (buffer), NULL);
      g_return_val_if_fail (iter != NULL, NULL);

      marker = NULL;
      markers = buffer->priv->markers;

      i = markers_binary_search (buffer, iter, &cmp);
      if (i < 0)
            return NULL;
      
      if (cmp == 0)
            /* return the last marker at the iter position */
            i = markers_linear_lookup (buffer, NULL, i, 1);
      else if (cmp < 0)
            i--;

      if (i >= 0)
      {
            marker = g_array_index (markers, GtkSourceMarker *, i);
            gtk_source_buffer_get_iter_at_marker (buffer, iter, marker);
      }
            
      return marker;
}


Generated by  Doxygen 1.6.0   Back to index