/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
/* gtksourcebuffer.c
 * This file is part of GtkSourceView
 *
 * Copyright (C) 1999-2002 - Mikael Hermansson <tyan@linux.se>,
 *                           Chris Phelps <chicane@reninet.com> and
 *                           Jeroen Zwartepoorte <jeroen@xs4all.nl>
 * Copyright (C) 2003 - Paolo Maggi <paolo.maggi@polito.it> and
 *                      Gustavo Giráldez <gustavo.giraldez@gmx.net>
 *
 * GtkSourceView is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * GtkSourceView 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

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

#include "gtksourcebuffer.h"
#include "gtksourcebuffer-private.h"

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

#include "gtksourcelanguage.h"
#include "gtksourcelanguage-private.h"
#include "gtksourceundomanager.h"
#include "gtksourceundomanagerdefault.h"
#include "gtksourcestyle.h"
#include "gtksourcestylescheme.h"
#include "gtksourcestyleschememanager.h"
#include "gtksourcemark.h"
#include "gtksourcemarkssequence.h"
#include "gtksourcesearchcontext.h"
#include "gtksourcetag.h"
#include "gtksourceview-i18n.h"
#include "gtksourceview-enumtypes.h"

/**
 * SECTION:buffer
 * @Short_description: Stores the text for display in a GtkSourceView
 * @Title: GtkSourceBuffer
 * @See_also: #GtkTextBuffer, #GtkSourceView
 *
 * A #GtkSourceBuffer object is the model for #GtkSourceView widgets.
 * It extends the #GtkTextBuffer class by adding features useful to display
 * and edit source code such as syntax highlighting and bracket matching. It
 * also implements support for the undo/redo.
 *
 * To create a #GtkSourceBuffer use gtk_source_buffer_new() or
 * gtk_source_buffer_new_with_language(). The second form is just a convenience
 * function which allows you to initially set a #GtkSourceLanguage. You can also
 * directly create a #GtkSourceView and get its #GtkSourceBuffer with
 * gtk_text_view_get_buffer().
 *
 * The highlighting is enabled by default, but you can disable it with
 * gtk_source_buffer_set_highlight_syntax().
 *
 * # Undo/Redo
 *
 * A custom #GtkSourceUndoManager can be implemented and set with
 * gtk_source_buffer_set_undo_manager(). However the default implementation
 * should be suitable for most uses, so you can use the API provided by
 * #GtkSourceBuffer instead of using #GtkSourceUndoManager. By default, actions
 * that can be undone or redone are defined as groups of operations between a
 * call to gtk_text_buffer_begin_user_action() and
 * gtk_text_buffer_end_user_action(). In general, this happens whenever the user
 * presses any key which modifies the buffer. But the default undo manager will
 * try to merge similar consecutive actions into one undo/redo level. The
 * merging is done word by word, so after writing a new sentence (character by
 * character), each undo will remove the previous word.
 *
 * The default undo manager remembers the "modified" state of the buffer, and
 * restores it when an action is undone or redone. It can be useful in a text
 * editor to know whether the file is saved. See gtk_text_buffer_get_modified()
 * and gtk_text_buffer_set_modified().
 *
 * The default undo manager also restores the selected text (or cursor
 * position), if the selection was related to the action. For example if the
 * user selects some text and deletes it, an undo will restore the selection. On
 * the other hand, if some text is selected but a deletion occurs elsewhere (the
 * deletion was done programmatically), an undo will not restore the selection,
 * it will only moves the cursor (the cursor is moved so that the user sees the
 * undo's effect). Warning: the selection restoring behavior might change in the
 * future.
 *
 * # Context Classes # {#context-classes}
 *
 * It is possible to retrieve some information from the syntax highlighting
 * engine. The default context classes that are applied to regions of a
 * #GtkSourceBuffer:
 *  - <emphasis>comment</emphasis>: the region delimits a comment;
 *  - <emphasis>no-spell-check</emphasis>: the region should not be spell checked;
 *  - <emphasis>path</emphasis>: the region delimits a path to a file;
 *  - <emphasis>string</emphasis>: the region delimits a string.
 *
 * Custom language definition files can create their own context classes,
 * since the functions like gtk_source_buffer_iter_has_context_class() take
 * a string parameter as the context class.
 *
 * #GtkSourceBuffer provides an API to access the context classes:
 * gtk_source_buffer_iter_has_context_class(),
 * gtk_source_buffer_get_context_classes_at_iter(),
 * gtk_source_buffer_iter_forward_to_context_class_toggle() and
 * gtk_source_buffer_iter_backward_to_context_class_toggle().
 *
 * And the #GtkSourceBuffer::highlight-updated signal permits to be notified
 * when a context class region changes.
 *
 * Each context class has also an associated #GtkTextTag with the name
 * <emphasis>gtksourceview:context-classes:&lt;name&gt;</emphasis>. For example to
 * retrieve the #GtkTextTag for the string context class, one can write:
 * |[
 * GtkTextTagTable *tag_table;
 * GtkTextTag *tag;
 *
 * tag_table = gtk_text_buffer_get_tag_table (buffer);
 * tag = gtk_text_tag_table_lookup (tag_table, "gtksourceview:context-classes:string");
 * ]|
 *
 * The tag must be used for read-only purposes.
 *
 * Accessing a context class via the associated #GtkTextTag is less
 * convenient than the #GtkSourceBuffer API, because:
 *  - The tag doesn't always exist, you need to listen to the
 *    #GtkTextTagTable::tag-added and #GtkTextTagTable::tag-removed signals.
 *  - Instead of the #GtkSourceBuffer::highlight-updated signal, you can listen
 *    to the #GtkTextBuffer::apply-tag and #GtkTextBuffer::remove-tag signals.
 *
 * A possible use-case for accessing a context class via the associated
 * #GtkTextTag is to read the region but without adding a hard dependency on the
 * GtkSourceView library (for example for a spell-checking library that wants to
 * read the no-spell-check region).
 */

/*
#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 UPDATE_BRACKET_DELAY		50
#define BRACKET_MATCHING_CHARS_LIMIT	10000
#define CONTEXT_CLASSES_PREFIX		"gtksourceview:context-classes:"

enum
{
	HIGHLIGHT_UPDATED,
	SOURCE_MARK_UPDATED,
	UNDO,
	REDO,
	BRACKET_MATCHED,
	N_SIGNALS
};

enum
{
	PROP_0,
	PROP_CAN_UNDO,
	PROP_CAN_REDO,
	PROP_HIGHLIGHT_SYNTAX,
	PROP_HIGHLIGHT_MATCHING_BRACKETS,
	PROP_MAX_UNDO_LEVELS,
	PROP_LANGUAGE,
	PROP_STYLE_SCHEME,
	PROP_UNDO_MANAGER,
	PROP_IMPLICIT_TRAILING_NEWLINE,
	N_PROPERTIES
};

struct _GtkSourceBufferPrivate
{
	GtkTextTag *bracket_match_tag;
	GtkSourceBracketMatchType bracket_match_state;
	guint bracket_highlighting_timeout_id;

	/* Hash table: category -> MarksSequence */
	GHashTable *source_marks;
	GtkSourceMarksSequence *all_source_marks;

	GtkSourceStyleScheme *style_scheme;
	GtkSourceLanguage *language;
	GtkSourceEngine *highlight_engine;

	GtkSourceUndoManager *undo_manager;
	gint max_undo_levels;

	GtkTextMark *tmp_insert_mark;
	GtkTextMark *tmp_selection_bound_mark;

	GList *search_contexts;

	GtkTextTag *invalid_char_tag;

	guint highlight_syntax : 1;
	guint highlight_brackets : 1;
	guint implicit_trailing_newline : 1;
};

static guint buffer_signals[N_SIGNALS];
static GParamSpec *buffer_properties[N_PROPERTIES];

G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceBuffer, gtk_source_buffer, GTK_TYPE_TEXT_BUFFER)

/* Prototypes */
static void 	 gtk_source_buffer_dispose		(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    *manager,
							 GtkSourceBuffer         *buffer);
static void 	 gtk_source_buffer_can_redo_handler	(GtkSourceUndoManager    *manager,
							 GtkSourceBuffer         *buffer);
static void 	 gtk_source_buffer_real_insert_text 	(GtkTextBuffer           *buffer,
							 GtkTextIter             *iter,
							 const gchar             *text,
							 gint                     len);
static void	 gtk_source_buffer_real_insert_pixbuf	(GtkTextBuffer           *buffer,
							 GtkTextIter             *pos,
							 GdkPixbuf               *pixbuf);
static void	 gtk_source_buffer_real_insert_child_anchor
							(GtkTextBuffer           *buffer,
							 GtkTextIter             *pos,
							 GtkTextChildAnchor      *anchor);
static void 	 gtk_source_buffer_real_delete_range 	(GtkTextBuffer           *buffer,
							 GtkTextIter             *iter,
							 GtkTextIter             *end);
static void 	 gtk_source_buffer_real_mark_set	(GtkTextBuffer		 *buffer,
							 const GtkTextIter	 *location,
							 GtkTextMark		 *mark);

static void 	 gtk_source_buffer_real_mark_deleted	(GtkTextBuffer		 *buffer,
							 GtkTextMark		 *mark);

static void	 gtk_source_buffer_real_undo		(GtkSourceBuffer	 *buffer);
static void	 gtk_source_buffer_real_redo		(GtkSourceBuffer	 *buffer);

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

static void
gtk_source_buffer_constructed (GObject *object)
{
	GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (object);

	if (buffer->priv->undo_manager == NULL)
	{
		/* This will install the default undo manager */
		gtk_source_buffer_set_undo_manager (buffer, NULL);
	}

	G_OBJECT_CLASS (gtk_source_buffer_parent_class)->constructed (object);
}

static void
gtk_source_buffer_class_init (GtkSourceBufferClass *klass)
{
	GObjectClass *object_class;
	GtkTextBufferClass *text_buffer_class;

	object_class = G_OBJECT_CLASS (klass);
	text_buffer_class = GTK_TEXT_BUFFER_CLASS (klass);

	object_class->constructed = gtk_source_buffer_constructed;
	object_class->dispose = gtk_source_buffer_dispose;
	object_class->get_property = gtk_source_buffer_get_property;
	object_class->set_property = gtk_source_buffer_set_property;

	text_buffer_class->delete_range = gtk_source_buffer_real_delete_range;
	text_buffer_class->insert_text = gtk_source_buffer_real_insert_text;
	text_buffer_class->insert_pixbuf = gtk_source_buffer_real_insert_pixbuf;
	text_buffer_class->insert_child_anchor = gtk_source_buffer_real_insert_child_anchor;
	text_buffer_class->mark_set = gtk_source_buffer_real_mark_set;
	text_buffer_class->mark_deleted = gtk_source_buffer_real_mark_deleted;

	klass->undo = gtk_source_buffer_real_undo;
	klass->redo = gtk_source_buffer_real_redo;

	/**
	 * GtkSourceBuffer:highlight-syntax:
	 *
	 * Whether to highlight syntax in the buffer.
	 */
	buffer_properties[PROP_HIGHLIGHT_SYNTAX] =
		g_param_spec_boolean ("highlight-syntax",
				      "Highlight Syntax",
				      "Whether to highlight syntax in the buffer",
				      TRUE,
				      G_PARAM_READWRITE |
				      G_PARAM_STATIC_STRINGS);

	/**
	 * GtkSourceBuffer:highlight-matching-brackets:
	 *
	 * Whether to highlight matching brackets in the buffer.
	 */
	buffer_properties[PROP_HIGHLIGHT_MATCHING_BRACKETS] =
		g_param_spec_boolean ("highlight-matching-brackets",
				      "Highlight Matching Brackets",
				      "Whether to highlight matching brackets",
				      TRUE,
				      G_PARAM_READWRITE |
				      G_PARAM_STATIC_STRINGS);

	/**
	 * GtkSourceBuffer:max-undo-levels:
	 *
	 * Number of undo levels for the buffer. -1 means no limit. This property
	 * will only affect the default undo manager.
	 */
	buffer_properties[PROP_MAX_UNDO_LEVELS] =
		g_param_spec_int ("max-undo-levels",
				  "Maximum Undo Levels",
				  "Number of undo levels for the buffer",
				  -1,
				  G_MAXINT,
				  -1,
				  G_PARAM_READWRITE |
				  G_PARAM_STATIC_STRINGS);

	buffer_properties[PROP_LANGUAGE] =
		g_param_spec_object ("language",
				     "Language",
				     "Language object to get highlighting patterns from",
				     GTK_SOURCE_TYPE_LANGUAGE,
				     G_PARAM_READWRITE |
				     G_PARAM_STATIC_STRINGS);

	buffer_properties[PROP_CAN_UNDO] =
		g_param_spec_boolean ("can-undo",
				      "Can undo",
				      "Whether Undo operation is possible",
				      FALSE,
				      G_PARAM_READABLE |
				      G_PARAM_STATIC_STRINGS);

	buffer_properties[PROP_CAN_REDO] =
		g_param_spec_boolean ("can-redo",
				      "Can redo",
				      "Whether Redo operation is possible",
				      FALSE,
				      G_PARAM_READABLE |
				      G_PARAM_STATIC_STRINGS);

	/**
	 * GtkSourceBuffer:style-scheme:
	 *
	 * Style scheme. It contains styles for syntax highlighting, optionally
	 * foreground, background, cursor color, current line color, and matching
	 * brackets style.
	 */
	buffer_properties[PROP_STYLE_SCHEME] =
		g_param_spec_object ("style-scheme",
				     "Style scheme",
				     "Style scheme",
				     GTK_SOURCE_TYPE_STYLE_SCHEME,
				     G_PARAM_READWRITE |
				     G_PARAM_STATIC_STRINGS);

	buffer_properties[PROP_UNDO_MANAGER] =
		g_param_spec_object ("undo-manager",
				     "Undo manager",
				     "The buffer undo manager",
				     GTK_SOURCE_TYPE_UNDO_MANAGER,
				     G_PARAM_READWRITE |
				     G_PARAM_CONSTRUCT |
				     G_PARAM_STATIC_STRINGS);

	/**
	 * GtkSourceBuffer:implicit-trailing-newline:
	 *
	 * Whether the buffer has an implicit trailing newline. See
	 * gtk_source_buffer_set_implicit_trailing_newline().
	 *
	 * Since: 3.14
	 */
	buffer_properties[PROP_IMPLICIT_TRAILING_NEWLINE] =
		g_param_spec_boolean ("implicit-trailing-newline",
				      "Implicit trailing newline",
				      "",
				      TRUE,
				      G_PARAM_READWRITE |
				      G_PARAM_CONSTRUCT |
				      G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, N_PROPERTIES, buffer_properties);

	/**
	 * GtkSourceBuffer::highlight-updated:
	 * @buffer: the buffer that received the signal
	 * @start: the start of the updated region
	 * @end: the end of the updated region
	 *
	 * The ::highlight-updated signal is emitted when the syntax
	 * highlighting and [context classes][context-classes] are updated in a
	 * certain region of the @buffer.
	 */
	buffer_signals[HIGHLIGHT_UPDATED] =
	    g_signal_new_class_handler ("highlight-updated",
	                                G_OBJECT_CLASS_TYPE (object_class),
	                                G_SIGNAL_RUN_LAST,
	                                G_CALLBACK (gtk_source_buffer_real_highlight_updated),
					NULL, NULL, NULL,
	                                G_TYPE_NONE,
	                                2,
	                                GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE,
	                                GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);

	/**
	 * GtkSourceBuffer::source-mark-updated:
	 * @buffer: the buffer that received the signal
	 * @mark: the #GtkSourceMark
	 *
	 * The ::source-mark-updated signal is emitted each time
	 * a mark is added to, moved or removed from the @buffer.
	 */
	buffer_signals[SOURCE_MARK_UPDATED] =
	    g_signal_new ("source-mark-updated",
			   G_OBJECT_CLASS_TYPE (object_class),
			   G_SIGNAL_RUN_LAST,
			   0,
			   NULL, NULL, NULL,
			   G_TYPE_NONE,
			   1, GTK_TYPE_TEXT_MARK);

	/**
	 * GtkSourceBuffer::undo:
	 * @buffer: the buffer that received the signal
	 *
	 * The ::undo signal is emitted to undo the last user action which
	 * modified the buffer.
	 */
	buffer_signals[UNDO] =
	    g_signal_new ("undo",
			  G_OBJECT_CLASS_TYPE (object_class),
			  G_SIGNAL_RUN_LAST,
			  G_STRUCT_OFFSET (GtkSourceBufferClass, undo),
			  NULL, NULL, NULL,
			  G_TYPE_NONE, 0);

	/**
	 * GtkSourceBuffer::redo:
	 * @buffer: the buffer that received the signal
	 *
	 * The ::redo signal is emitted to redo the last undo operation.
	 */
	buffer_signals[REDO] =
	    g_signal_new ("redo",
			  G_OBJECT_CLASS_TYPE (object_class),
			  G_SIGNAL_RUN_LAST,
			  G_STRUCT_OFFSET (GtkSourceBufferClass, redo),
			  NULL, NULL, NULL,
			  G_TYPE_NONE, 0);

	/**
	 * GtkSourceBuffer::bracket-matched:
	 * @buffer: a #GtkSourceBuffer.
	 * @iter: (nullable): if found, the location of the matching bracket.
	 * @state: state of bracket matching.
	 *
	 * @iter is set to a valid iterator pointing to the matching bracket
	 * if @state is %GTK_SOURCE_BRACKET_MATCH_FOUND. Otherwise @iter is
	 * meaningless.
	 *
	 * The signal is emitted only when the @state changes, typically when
	 * the cursor moves.
	 *
	 * A use-case for this signal is to show messages in a #GtkStatusbar.
	 *
	 * Since: 2.12
	 */
	buffer_signals[BRACKET_MATCHED] =
	    g_signal_new ("bracket-matched",
			  G_OBJECT_CLASS_TYPE (object_class),
			  G_SIGNAL_RUN_LAST,
			  G_STRUCT_OFFSET (GtkSourceBufferClass, bracket_matched),
			  NULL, NULL, NULL,
			  G_TYPE_NONE, 2,
			  GTK_TYPE_TEXT_ITER,
			  GTK_SOURCE_TYPE_BRACKET_MATCH_TYPE);
}

static void
set_undo_manager (GtkSourceBuffer      *buffer,
                  GtkSourceUndoManager *manager)
{
	if (manager == buffer->priv->undo_manager)
	{
		return;
	}

	if (buffer->priv->undo_manager != NULL)
	{
		g_signal_handlers_disconnect_by_func (buffer->priv->undo_manager,
		                                      G_CALLBACK (gtk_source_buffer_can_undo_handler),
		                                      buffer);

		g_signal_handlers_disconnect_by_func (buffer->priv->undo_manager,
		                                      G_CALLBACK (gtk_source_buffer_can_redo_handler),
		                                      buffer);

		g_object_unref (buffer->priv->undo_manager);
		buffer->priv->undo_manager = NULL;
	}

	if (manager != NULL)
	{
		buffer->priv->undo_manager = g_object_ref (manager);

		g_signal_connect (buffer->priv->undo_manager,
		                  "can-undo-changed",
		                  G_CALLBACK (gtk_source_buffer_can_undo_handler),
		                  buffer);

		g_signal_connect (buffer->priv->undo_manager,
		                  "can-redo-changed",
		                  G_CALLBACK (gtk_source_buffer_can_redo_handler),
		                  buffer);

		/* Notify possible changes in the can-undo/redo state */
		g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_CAN_UNDO]);
		g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_CAN_REDO]);
	}
}

static void
search_context_weak_notify_cb (GtkSourceBuffer *buffer,
			       GObject         *search_context)
{
	buffer->priv->search_contexts = g_list_remove (buffer->priv->search_contexts,
						       search_context);
}

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

	buffer->priv = priv;

	priv->highlight_syntax = TRUE;
	priv->highlight_brackets = TRUE;
	priv->bracket_match_state = GTK_SOURCE_BRACKET_MATCH_NONE;
	priv->max_undo_levels = -1;

	priv->source_marks = g_hash_table_new_full (g_str_hash,
						    g_str_equal,
						    (GDestroyNotify)g_free,
						    (GDestroyNotify)g_object_unref);

	priv->all_source_marks = _gtk_source_marks_sequence_new (GTK_TEXT_BUFFER (buffer));

	priv->style_scheme = _gtk_source_style_scheme_get_default ();

	if (priv->style_scheme != NULL)
	{
		g_object_ref (priv->style_scheme);
	}
}

static void
gtk_source_buffer_dispose (GObject *object)
{
	GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (object);
	GList *l;

	if (buffer->priv->bracket_highlighting_timeout_id != 0)
	{
		g_source_remove (buffer->priv->bracket_highlighting_timeout_id);
		buffer->priv->bracket_highlighting_timeout_id = 0;
	}

	if (buffer->priv->undo_manager != NULL)
	{
		set_undo_manager (buffer, NULL);
	}

	if (buffer->priv->highlight_engine != NULL)
	{
		_gtk_source_engine_attach_buffer (buffer->priv->highlight_engine, NULL);
	}

	g_clear_object (&buffer->priv->highlight_engine);
	g_clear_object (&buffer->priv->language);
	g_clear_object (&buffer->priv->style_scheme);

	for (l = buffer->priv->search_contexts; l != NULL; l = l->next)
	{
		GtkSourceSearchContext *search_context = l->data;

		g_object_weak_unref (G_OBJECT (search_context),
				     (GWeakNotify)search_context_weak_notify_cb,
				     buffer);
	}

	g_list_free (buffer->priv->search_contexts);
	buffer->priv->search_contexts = NULL;

	g_clear_object (&buffer->priv->all_source_marks);

	if (buffer->priv->source_marks != NULL)
	{
		g_hash_table_unref (buffer->priv->source_marks);
		buffer->priv->source_marks = NULL;
	}

	G_OBJECT_CLASS (gtk_source_buffer_parent_class)->dispose (object);
}

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

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (object));

	buffer = GTK_SOURCE_BUFFER (object);

	switch (prop_id)
	{
		case PROP_HIGHLIGHT_SYNTAX:
			gtk_source_buffer_set_highlight_syntax (buffer, g_value_get_boolean (value));
			break;

		case PROP_HIGHLIGHT_MATCHING_BRACKETS:
			gtk_source_buffer_set_highlight_matching_brackets (buffer, g_value_get_boolean (value));
			break;

		case PROP_MAX_UNDO_LEVELS:
			gtk_source_buffer_set_max_undo_levels (buffer, g_value_get_int (value));
			break;

		case PROP_LANGUAGE:
			gtk_source_buffer_set_language (buffer, g_value_get_object (value));
			break;

		case PROP_STYLE_SCHEME:
			gtk_source_buffer_set_style_scheme (buffer, g_value_get_object (value));
			break;

		case PROP_UNDO_MANAGER:
			gtk_source_buffer_set_undo_manager (buffer, g_value_get_object (value));
			break;

		case PROP_IMPLICIT_TRAILING_NEWLINE:
			gtk_source_buffer_set_implicit_trailing_newline (buffer, g_value_get_boolean (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 *buffer;

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (object));

	buffer = GTK_SOURCE_BUFFER (object);

	switch (prop_id)
	{
		case PROP_HIGHLIGHT_SYNTAX:
			g_value_set_boolean (value, buffer->priv->highlight_syntax);
			break;

		case PROP_HIGHLIGHT_MATCHING_BRACKETS:
			g_value_set_boolean (value, buffer->priv->highlight_brackets);
			break;

		case PROP_MAX_UNDO_LEVELS:
			g_value_set_int (value, buffer->priv->max_undo_levels);
			break;

		case PROP_LANGUAGE:
			g_value_set_object (value, buffer->priv->language);
			break;

		case PROP_STYLE_SCHEME:
			g_value_set_object (value, buffer->priv->style_scheme);
			break;

		case PROP_CAN_UNDO:
			g_value_set_boolean (value, gtk_source_buffer_can_undo (buffer));
			break;

		case PROP_CAN_REDO:
			g_value_set_boolean (value, gtk_source_buffer_can_redo (buffer));
			break;

		case PROP_UNDO_MANAGER:
			g_value_set_object (value, buffer->priv->undo_manager);
			break;

		case PROP_IMPLICIT_TRAILING_NEWLINE:
			g_value_set_boolean (value, buffer->priv->implicit_trailing_newline);
			break;

		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
			break;
	}
}

/**
 * gtk_source_buffer_new:
 * @table: (nullable): a #GtkTextTagTable, or %NULL to create a new one.
 *
 * Creates a new source buffer.
 *
 * Returns: a new source buffer.
 */
GtkSourceBuffer *
gtk_source_buffer_new (GtkTextTagTable *table)
{
	return g_object_new (GTK_SOURCE_TYPE_BUFFER,
			     "tag-table", table,
			     NULL);
}

/**
 * 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
 * a new tag table and then calling gtk_source_buffer_set_language().
 *
 * Returns: a new source buffer which will highlight text
 * according to the highlighting patterns in @language.
 */
GtkSourceBuffer *
gtk_source_buffer_new_with_language (GtkSourceLanguage *language)
{
	g_return_val_if_fail (GTK_SOURCE_IS_LANGUAGE (language), NULL);

	return g_object_new (GTK_SOURCE_TYPE_BUFFER,
			     "tag-table", NULL,
			     "language", language,
			     NULL);
}

static void
gtk_source_buffer_can_undo_handler (GtkSourceUndoManager *manager,
                                    GtkSourceBuffer      *buffer)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_CAN_UNDO]);
}

static void
gtk_source_buffer_can_redo_handler (GtkSourceUndoManager *manager,
                                    GtkSourceBuffer      *buffer)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_CAN_REDO]);
}

static void
update_bracket_match_style (GtkSourceBuffer *buffer)
{
	GtkSourceStyle *style = NULL;

	if (buffer->priv->bracket_match_tag == NULL)
	{
		return;
	}

	if (buffer->priv->style_scheme != NULL)
	{
		style = _gtk_source_style_scheme_get_matching_brackets_style (buffer->priv->style_scheme);
	}

	gtk_source_style_apply (style, buffer->priv->bracket_match_tag);
}

static GtkTextTag *
get_bracket_match_tag (GtkSourceBuffer *buffer)
{
	if (buffer->priv->bracket_match_tag == NULL)
	{
		buffer->priv->bracket_match_tag =
			gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
						    NULL,
						    NULL);
		update_bracket_match_style (buffer);
	}

	return buffer->priv->bracket_match_tag;
}

/* This is private, just used by the print compositor to not print bracket
 * matches. Note that unlike get_bracket_match_tag() it returns NULL
 * if the tag is not set.
 */
GtkTextTag *
_gtk_source_buffer_get_bracket_match_tag (GtkSourceBuffer *buffer)
{
	return buffer->priv->bracket_match_tag;
}

static gunichar
bracket_pair (gunichar  base_char,
	      gint     *direction)
{
	gint dir;
	gunichar pair;

	switch (base_char)
	{
		case '{':
			dir = 1;
			pair = '}';
			break;

		case '(':
			dir = 1;
			pair = ')';
			break;

		case '[':
			dir = 1;
			pair = ']';
			break;

		case '<':
			dir = 1;
			pair = '>';
			break;

		case '}':
			dir = -1;
			pair = '{';
			break;

		case ')':
			dir = -1;
			pair = '(';
			break;

		case ']':
			dir = -1;
			pair = '[';
			break;

		case '>':
			dir = -1;
			pair = '<';
			break;

		default:
			dir = 0;
			pair = 0;
			break;
	}

	if (direction != NULL)
	{
		*direction = dir;
	}

	return pair;
}

/*
 * This function works similar to gtk_text_buffer_remove_tag() except that
 * instead of taking the optimization to make removing tags fast in terms
 * of wall clock time, it tries to avoiding to much time of the screen
 * by minimizing the damage regions. This results in fewer full-redraws
 * when updating the text marks. To see the difference, compare this to
 * gtk_text_buffer_remove_tag() and enable the "show pixel cache" feature
 * the GTK+ inspector.
 */
static void
remove_tag_with_minimal_damage (GtkTextBuffer     *buffer,
                                GtkTextTag        *tag,
                                const GtkTextIter *begin,
                                const GtkTextIter *end)
{
	GtkTextIter tag_begin = *begin;
	GtkTextIter tag_end;

	if (!gtk_text_iter_starts_tag (&tag_begin, tag))
	{
		if (!gtk_text_iter_forward_to_tag_toggle (&tag_begin, tag))
		{
			return;
		}
	}

	while (gtk_text_iter_starts_tag (&tag_begin, tag) &&
	       gtk_text_iter_compare (&tag_begin, end) < 0)
	{
		gint count = 1;

		tag_end = tag_begin;

		/*
		 * We might have found the start of another tag embedded
		 * inside this tag. So keep scanning forward until we have
		 * reached the right number of end tags.
		 */

		while (gtk_text_iter_forward_to_tag_toggle (&tag_end, tag))
		{
			if (gtk_text_iter_starts_tag (&tag_end, tag))
			{
				count++;
			}
			else if (gtk_text_iter_ends_tag (&tag_end, tag))
			{
				if (--count == 0)
				{
					break;
				}
			}
		}

		if (gtk_text_iter_ends_tag (&tag_end, tag))
		{
			gtk_text_buffer_remove_tag (buffer, tag, &tag_begin, &tag_end);

			tag_begin = tag_end;

			/*
			 * Move to the next start tag. It's possible to have an overlapped
			 * end tag, which would be non-ideal, but possible.
			 */
			if (!gtk_text_iter_starts_tag (&tag_begin, tag))
			{
				while (gtk_text_iter_forward_to_tag_toggle (&tag_begin, tag))
				{
					if (gtk_text_iter_starts_tag (&tag_begin, tag))
					{
						break;
					}
				}
			}
		}
	}
}

static void
update_bracket_highlighting (GtkSourceBuffer *source_buffer)
{
	GtkTextBuffer *buffer;
	GtkTextIter insert_iter;
	GtkTextIter bracket;
	GtkTextIter bracket_match;
	GtkSourceBracketMatchType previous_state;

	buffer = GTK_TEXT_BUFFER (source_buffer);

	if (source_buffer->priv->bracket_match_tag != NULL)
	{
		GtkTextIter start;
		GtkTextIter end;

		gtk_text_buffer_get_bounds (buffer, &start, &end);

		remove_tag_with_minimal_damage (GTK_TEXT_BUFFER (source_buffer),
		                                source_buffer->priv->bracket_match_tag,
		                                &start,
		                                &end);
	}

	if (!source_buffer->priv->highlight_brackets)
	{
		if (source_buffer->priv->bracket_match_tag != NULL)
		{
			GtkTextTagTable *table;

			table = gtk_text_buffer_get_tag_table (buffer);
			gtk_text_tag_table_remove (table, source_buffer->priv->bracket_match_tag);
			source_buffer->priv->bracket_match_tag = NULL;
		}

		return;
	}

	gtk_text_buffer_get_iter_at_mark (buffer,
					  &insert_iter,
					  gtk_text_buffer_get_insert (buffer));

	previous_state = source_buffer->priv->bracket_match_state;
	source_buffer->priv->bracket_match_state =
		_gtk_source_buffer_find_bracket_match (source_buffer,
						       &insert_iter,
						       &bracket,
						       &bracket_match);

	if (source_buffer->priv->bracket_match_state == GTK_SOURCE_BRACKET_MATCH_FOUND)
	{
		GtkTextIter next_iter;

		g_signal_emit (source_buffer,
			       buffer_signals[BRACKET_MATCHED],
			       0,
			       &bracket_match,
			       GTK_SOURCE_BRACKET_MATCH_FOUND);

		next_iter = bracket_match;
		gtk_text_iter_forward_char (&next_iter);
		gtk_text_buffer_apply_tag (buffer,
					   get_bracket_match_tag (source_buffer),
					   &bracket_match,
					   &next_iter);

		next_iter = bracket;
		gtk_text_iter_forward_char (&next_iter);
		gtk_text_buffer_apply_tag (buffer,
					   get_bracket_match_tag (source_buffer),
					   &bracket,
					   &next_iter);
		return;
	}

	/* Don't emit the signal at all if chars at previous and current
	 * positions are nonbrackets.
	 */
	if (previous_state != GTK_SOURCE_BRACKET_MATCH_NONE ||
	    source_buffer->priv->bracket_match_state != GTK_SOURCE_BRACKET_MATCH_NONE)
	{
		g_signal_emit (source_buffer,
			       buffer_signals[BRACKET_MATCHED],
			       0,
			       NULL,
			       source_buffer->priv->bracket_match_state);
	}
}

static gboolean
bracket_highlighting_timeout_cb (gpointer user_data)
{
	GtkSourceBuffer *buffer = GTK_SOURCE_BUFFER (user_data);

	update_bracket_highlighting (buffer);

	buffer->priv->bracket_highlighting_timeout_id = 0;
	return G_SOURCE_REMOVE;
}

static void
queue_bracket_highlighting_update (GtkSourceBuffer *buffer)
{
	if (buffer->priv->bracket_highlighting_timeout_id != 0)
	{
		g_source_remove (buffer->priv->bracket_highlighting_timeout_id);
	}

	/* Queue an update to the bracket location instead of doing it
	 * immediately. We are likely going to be servicing a draw deadline
	 * immediately, so blocking to find the match and invalidating
	 * visible regions causes animations to stutter. Instead, give
	 * ourself just a little bit of a delay to catch up.
	 *
	 * The value for this delay was found experimentally, as 25msec
	 * resulted in continuing to see frame stutter, but 50 was not
	 * distinguishable from having matching-brackets disabled.
	 * The animation in gtkscrolledwindow is 200, but that creates
	 * an undesireable delay before the match is shown to the user.
	 * 50msec errors on the side of "immediate", but without the
	 * frame stutter.
	 *
	 * If we had access to a GdkFrameClock, we might consider using
	 * ::update() or ::after-paint() to synchronize this.
	 */
	buffer->priv->bracket_highlighting_timeout_id =
		gdk_threads_add_timeout_full (G_PRIORITY_LOW,
					      UPDATE_BRACKET_DELAY,
					      bracket_highlighting_timeout_cb,
					      buffer,
					      NULL);
}

/* Although this function is not really useful
 * (queue_bracket_highlighting_update() could be called directly), the name
 * cursor_moved() is more meaningful.
 */
static void
cursor_moved (GtkSourceBuffer *buffer)
{
	queue_bracket_highlighting_update (buffer);
}

static void
gtk_source_buffer_real_highlight_updated (GtkSourceBuffer *buffer,
					  GtkTextIter     *start,
					  GtkTextIter     *end)
{
	queue_bracket_highlighting_update (buffer);
}

static void
gtk_source_buffer_content_inserted (GtkTextBuffer *buffer,
				    gint           start_offset,
				    gint           end_offset)
{
	GtkSourceBuffer *source_buffer = GTK_SOURCE_BUFFER (buffer);

	cursor_moved (source_buffer);

	if (source_buffer->priv->highlight_engine != NULL)
	{
		_gtk_source_engine_text_inserted (source_buffer->priv->highlight_engine,
						  start_offset,
						  end_offset);
	}
}

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_SOURCE_IS_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.
	 */
	GTK_TEXT_BUFFER_CLASS (gtk_source_buffer_parent_class)->insert_text (buffer, iter, text, len);

	gtk_source_buffer_content_inserted (buffer,
					    start_offset,
					    gtk_text_iter_get_offset (iter));
}

/* insert_pixbuf and insert_child_anchor do nothing except notifying
 * the highlighting engine about the change, because engine's idea
 * of buffer char count must be correct at all times.
 */
static void
gtk_source_buffer_real_insert_pixbuf (GtkTextBuffer *buffer,
				      GtkTextIter   *iter,
				      GdkPixbuf     *pixbuf)
{
	gint start_offset;

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (iter != 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.
	 */
	GTK_TEXT_BUFFER_CLASS (gtk_source_buffer_parent_class)->insert_pixbuf (buffer, iter, pixbuf);

	gtk_source_buffer_content_inserted (buffer,
					    start_offset,
					    gtk_text_iter_get_offset (iter));
}

static void
gtk_source_buffer_real_insert_child_anchor (GtkTextBuffer      *buffer,
					    GtkTextIter        *iter,
					    GtkTextChildAnchor *anchor)
{
	gint start_offset;

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (iter != 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.
	 */
	GTK_TEXT_BUFFER_CLASS (gtk_source_buffer_parent_class)->insert_child_anchor (buffer, iter, anchor);

	gtk_source_buffer_content_inserted (buffer,
					    start_offset,
					    gtk_text_iter_get_offset (iter));
}

static void
gtk_source_buffer_real_delete_range (GtkTextBuffer *buffer,
				     GtkTextIter   *start,
				     GtkTextIter   *end)
{
	gint offset, length;
	GtkSourceBuffer *source_buffer = GTK_SOURCE_BUFFER (buffer);

	g_return_if_fail (GTK_SOURCE_IS_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);
	offset = gtk_text_iter_get_offset (start);
	length = gtk_text_iter_get_offset (end) - offset;

	GTK_TEXT_BUFFER_CLASS (gtk_source_buffer_parent_class)->delete_range (buffer, start, end);

	cursor_moved (source_buffer);

	/* emit text deleted for engines */
	if (source_buffer->priv->highlight_engine != NULL)
	{
		_gtk_source_engine_text_deleted (source_buffer->priv->highlight_engine,
						 offset, length);
	}
}

static gint
get_bracket_matching_context_class_mask (GtkSourceBuffer *buffer,
					 GtkTextIter     *iter)
{
	gint mask = 0;
	guint i;

	/* This describes a mask of relevant context classes for highlighting
	 * matching brackets.
	 */
	const gchar *cclass_mask_definitions[] = {
		"comment",
		"string",
	};

	for (i = 0; i < G_N_ELEMENTS (cclass_mask_definitions); ++i)
	{
		gboolean has_class;

		has_class = gtk_source_buffer_iter_has_context_class (buffer,
								      iter,
								      cclass_mask_definitions[i]);

		mask |= has_class << i;
	}

	return mask;
}

/* Note that we only look BRACKET_MATCHING_CHARS_LIMIT at most.
 * @pos is moved to the bracket match, if found.
 */
static GtkSourceBracketMatchType
find_bracket_match_real (GtkSourceBuffer *buffer,
			 GtkTextIter     *pos)
{
	GtkTextIter iter;
	gunichar base_char;
	gunichar search_char;
	gint direction;
	gint bracket_count;
	gint char_count;
	gint cclass_mask;
	gboolean found;

	base_char = gtk_text_iter_get_char (pos);
	search_char = bracket_pair (base_char, &direction);

	if (direction == 0)
	{
		return GTK_SOURCE_BRACKET_MATCH_NONE;
	}

	cclass_mask = get_bracket_matching_context_class_mask (buffer, pos);

	iter = *pos;
	bracket_count = 0;
	char_count = 0;
	found = FALSE;

	do
	{
		gunichar cur_char;
		gint cur_mask;

		gtk_text_iter_forward_chars (&iter, direction);
		cur_char = gtk_text_iter_get_char (&iter);
		char_count++;

		cur_mask = get_bracket_matching_context_class_mask (buffer, &iter);

		/* Check if we lost a class, which means we don't look any
		 * further.
		 */
		if ((cclass_mask & cur_mask) != cclass_mask)
		{
			found = FALSE;
			break;
		}

		if (cclass_mask != cur_mask)
		{
			continue;
		}

		if (cur_char == search_char)
		{
			if (bracket_count == 0)
			{
				found = TRUE;
				break;
			}

			bracket_count--;
		}
		else if (cur_char == base_char)
		{
			bracket_count++;
		}
	}
	while (!gtk_text_iter_is_end (&iter) &&
	       !gtk_text_iter_is_start (&iter) &&
	       char_count < BRACKET_MATCHING_CHARS_LIMIT);

	if (found)
	{
		*pos = iter;
		return GTK_SOURCE_BRACKET_MATCH_FOUND;
	}

	if (char_count >= BRACKET_MATCHING_CHARS_LIMIT)
	{
		return GTK_SOURCE_BRACKET_MATCH_OUT_OF_RANGE;
	}

	return GTK_SOURCE_BRACKET_MATCH_NOT_FOUND;
}

/* Note that we take into account both the character following @pos and the one
 * preceding it. If there are brackets on both sides, the one following @pos
 * takes precedence.
 * @bracket and @bracket_match are valid only if GTK_SOURCE_BRACKET_MATCH_FOUND
 * is returned. @bracket is set either to @pos or @pos-1.
 */
GtkSourceBracketMatchType
_gtk_source_buffer_find_bracket_match (GtkSourceBuffer   *buffer,
				       const GtkTextIter *pos,
				       GtkTextIter       *bracket,
				       GtkTextIter       *bracket_match)
{
	GtkSourceBracketMatchType result_right;
	GtkSourceBracketMatchType result_left;
	GtkTextIter prev;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), GTK_SOURCE_BRACKET_MATCH_NONE);
	g_return_val_if_fail (pos != NULL, GTK_SOURCE_BRACKET_MATCH_NONE);
	g_return_val_if_fail (bracket_match != NULL, GTK_SOURCE_BRACKET_MATCH_NONE);

	*bracket_match = *pos;
	result_right = find_bracket_match_real (buffer, bracket_match);

	if (result_right == GTK_SOURCE_BRACKET_MATCH_FOUND)
	{
		if (bracket != NULL)
		{
			*bracket = *pos;
		}

		return GTK_SOURCE_BRACKET_MATCH_FOUND;
	}

	prev = *pos;
	if (!gtk_text_iter_starts_line (&prev) &&
	    gtk_text_iter_backward_cursor_position (&prev))
	{
		*bracket_match = prev;
		result_left = find_bracket_match_real (buffer, bracket_match);
	}
	else
	{
		result_left = GTK_SOURCE_BRACKET_MATCH_NONE;
	}

	if (result_left == GTK_SOURCE_BRACKET_MATCH_FOUND)
	{
		if (bracket != NULL)
		{
			*bracket = prev;
		}

		return GTK_SOURCE_BRACKET_MATCH_FOUND;
	}

	/* If there is a bracket, the expected return value is for the bracket,
	 * not the other character.
	 */
	if (result_right == GTK_SOURCE_BRACKET_MATCH_NONE)
	{
		return result_left;
	}
	if (result_left == GTK_SOURCE_BRACKET_MATCH_NONE)
	{
		return result_right;
	}

	/* There are brackets on both sides, and none was successful. The one on
	 * the right takes precedence.
	 */
	return result_right;
}

/**
 * gtk_source_buffer_can_undo:
 * @buffer: a #GtkSourceBuffer.
 *
 * Determines whether a source buffer can undo the last action.
 *
 * Returns: %TRUE if it's possible to undo the last action.
 */
gboolean
gtk_source_buffer_can_undo (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_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).
 *
 * Returns: %TRUE if a redo is possible.
 */
gboolean
gtk_source_buffer_can_redo (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_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.
 *
 * This function emits the #GtkSourceBuffer::undo signal.
 */
void
gtk_source_buffer_undo (GtkSourceBuffer *buffer)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	g_signal_emit (buffer, buffer_signals[UNDO], 0);
}

/**
 * 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.
 *
 * This function emits the #GtkSourceBuffer::redo signal.
 */
void
gtk_source_buffer_redo (GtkSourceBuffer *buffer)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	g_signal_emit (buffer, buffer_signals[REDO], 0);
}

/**
 * gtk_source_buffer_get_max_undo_levels:
 * @buffer: a #GtkSourceBuffer.
 *
 * Determines the number of undo levels the buffer will track for buffer edits.
 *
 * Returns: the maximum number of possible undo levels or -1 if no limit is set.
 */
gint
gtk_source_buffer_get_max_undo_levels (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), 0);

	return buffer->priv->max_undo_levels;
}

/**
 * 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.
 *
 * If @max_undo_levels is -1, the undo/redo is unlimited.
 *
 * If @max_undo_levels is 0, the undo/redo is disabled.
 */
void
gtk_source_buffer_set_max_undo_levels (GtkSourceBuffer *buffer,
				       gint             max_undo_levels)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	if (buffer->priv->max_undo_levels == max_undo_levels)
	{
		return;
	}

	buffer->priv->max_undo_levels = max_undo_levels;

	if (GTK_SOURCE_IS_UNDO_MANAGER_DEFAULT (buffer->priv->undo_manager))
	{
		gtk_source_undo_manager_default_set_max_undo_levels (GTK_SOURCE_UNDO_MANAGER_DEFAULT (buffer->priv->undo_manager),
		                                                     max_undo_levels);
	}

	g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_MAX_UNDO_LEVELS]);
}

gboolean
_gtk_source_buffer_is_undo_redo_enabled (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);

	if (buffer->priv->undo_manager == NULL)
	{
		return FALSE;
	}

	/* A custom UndoManager is not forced to follow max_undo_levels. */
	if (!GTK_SOURCE_IS_UNDO_MANAGER_DEFAULT (buffer->priv->undo_manager))
	{
		return TRUE;
	}

	return buffer->priv->max_undo_levels != 0;
}

/**
 * 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_SOURCE_IS_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_SOURCE_IS_BUFFER (buffer));

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

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

	return buffer->priv->highlight_brackets;
}

/**
 * gtk_source_buffer_set_highlight_matching_brackets:
 * @buffer: a #GtkSourceBuffer.
 * @highlight: %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.
 */
void
gtk_source_buffer_set_highlight_matching_brackets (GtkSourceBuffer *buffer,
						   gboolean         highlight)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	highlight = highlight != FALSE;

	if (highlight != buffer->priv->highlight_brackets)
	{
		buffer->priv->highlight_brackets = highlight;

		update_bracket_highlighting (buffer);

		g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_HIGHLIGHT_MATCHING_BRACKETS]);
	}
}

/**
 * gtk_source_buffer_get_highlight_syntax:
 * @buffer: a #GtkSourceBuffer.
 *
 * Determines whether syntax highlighting is activated in the source
 * buffer.
 *
 * Returns: %TRUE if syntax highlighting is enabled, %FALSE otherwise.
 */
gboolean
gtk_source_buffer_get_highlight_syntax (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);

	return buffer->priv->highlight_syntax;
}

/**
 * gtk_source_buffer_set_highlight_syntax:
 * @buffer: a #GtkSourceBuffer.
 * @highlight: %TRUE to enable syntax highlighting, %FALSE to disable it.
 *
 * Controls whether syntax is highlighted in the buffer.
 *
 * If @highlight is %TRUE, the text will be highlighted according to the syntax
 * patterns specified in the #GtkSourceLanguage set with
 * gtk_source_buffer_set_language().
 *
 * If @highlight is %FALSE, syntax highlighting is disabled and all the
 * #GtkTextTag objects that have been added by the syntax highlighting engine
 * are removed from the buffer.
 */
void
gtk_source_buffer_set_highlight_syntax (GtkSourceBuffer *buffer,
					gboolean         highlight)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	highlight = highlight != FALSE;

	if (buffer->priv->highlight_syntax != highlight)
	{
		buffer->priv->highlight_syntax = highlight;
		g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_HIGHLIGHT_SYNTAX]);
	}
}

/**
 * gtk_source_buffer_set_language:
 * @buffer: a #GtkSourceBuffer.
 * @language: (nullable): a #GtkSourceLanguage to set, or %NULL.
 *
 * Associates a #GtkSourceLanguage with the buffer.
 *
 * Note that a #GtkSourceLanguage affects not only the syntax highlighting, but
 * also the [context classes][context-classes]. If you want to disable just the
 * syntax highlighting, see gtk_source_buffer_set_highlight_syntax().
 *
 * The buffer holds a reference to @language.
 */
void
gtk_source_buffer_set_language (GtkSourceBuffer   *buffer,
				GtkSourceLanguage *language)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (GTK_SOURCE_IS_LANGUAGE (language) || language == NULL);

	if (!g_set_object (&buffer->priv->language, language))
	{
		return;
	}

	if (buffer->priv->highlight_engine != NULL)
	{
		/* disconnect the old engine */
		_gtk_source_engine_attach_buffer (buffer->priv->highlight_engine, NULL);
		g_object_unref (buffer->priv->highlight_engine);
		buffer->priv->highlight_engine = NULL;
	}

	if (language != NULL)
	{
		/* get a new engine */
		buffer->priv->highlight_engine = _gtk_source_language_create_engine (language);

		if (buffer->priv->highlight_engine != NULL)
		{
			_gtk_source_engine_attach_buffer (buffer->priv->highlight_engine,
							  GTK_TEXT_BUFFER (buffer));

			if (buffer->priv->style_scheme != NULL)
			{
				_gtk_source_engine_set_style_scheme (buffer->priv->highlight_engine,
								     buffer->priv->style_scheme);
			}
		}
	}

	g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_LANGUAGE]);
}

/**
 * gtk_source_buffer_get_language:
 * @buffer: a #GtkSourceBuffer.
 *
 * Returns the #GtkSourceLanguage associated with the buffer,
 * see gtk_source_buffer_set_language().  The returned object should not be
 * unreferenced by the user.
 *
 * Returns: (nullable) (transfer none): the #GtkSourceLanguage associated
 * with the buffer, or %NULL.
 */
GtkSourceLanguage *
gtk_source_buffer_get_language (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);

	return buffer->priv->language;
}

/*
 * _gtk_source_buffer_update_syntax_highlight:
 * @buffer: a #GtkSourceBuffer.
 * @start: start of the area to highlight.
 * @end: end of the area to highlight.
 * @synchronous: whether the area should be highlighted synchronously.
 *
 * Asks the buffer to analyze and highlight given area.
 */
void
_gtk_source_buffer_update_syntax_highlight (GtkSourceBuffer   *buffer,
					    const GtkTextIter *start,
					    const GtkTextIter *end,
					    gboolean           synchronous)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	if (buffer->priv->highlight_engine != NULL)
	{
		_gtk_source_engine_update_highlight (buffer->priv->highlight_engine,
						     start,
						     end,
						     synchronous);
	}
}

void
_gtk_source_buffer_update_search_highlight (GtkSourceBuffer   *buffer,
					    const GtkTextIter *start,
					    const GtkTextIter *end,
					    gboolean           synchronous)
{
	GList *l;

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	for (l = buffer->priv->search_contexts; l != NULL; l = l->next)
	{
		GtkSourceSearchContext *search_context = l->data;

		_gtk_source_search_context_update_highlight (search_context,
							     start,
							     end,
							     synchronous);
	}
}

/**
 * gtk_source_buffer_ensure_highlight:
 * @buffer: a #GtkSourceBuffer.
 * @start: start of the area to highlight.
 * @end: end of the area to highlight.
 *
 * Forces buffer to analyze and highlight the given area synchronously.
 *
 * <note>
 *   <para>
 *     This is a potentially slow operation and should be used only
 *     when you need to make sure that some text not currently
 *     visible is highlighted, for instance before printing.
 *   </para>
 * </note>
 **/
void
gtk_source_buffer_ensure_highlight (GtkSourceBuffer   *buffer,
				    const GtkTextIter *start,
				    const GtkTextIter *end)
{
	_gtk_source_buffer_update_syntax_highlight (buffer, start, end, TRUE);
	_gtk_source_buffer_update_search_highlight (buffer, start, end, TRUE);
}

/**
 * gtk_source_buffer_set_style_scheme:
 * @buffer: a #GtkSourceBuffer.
 * @scheme: (nullable): a #GtkSourceStyleScheme or %NULL.
 *
 * Sets a #GtkSourceStyleScheme to be used by the buffer and the view.
 *
 * Note that a #GtkSourceStyleScheme affects not only the syntax highlighting,
 * but also other #GtkSourceView features such as highlighting the current line,
 * matching brackets, the line numbers, etc.
 *
 * Instead of setting a %NULL @scheme, it is better to disable syntax
 * highlighting with gtk_source_buffer_set_highlight_syntax(), and setting the
 * #GtkSourceStyleScheme with the "classic" or "tango" ID, because those two
 * style schemes follow more closely the GTK+ theme (for example for the
 * background color).
 *
 * The buffer holds a reference to @scheme.
 */
void
gtk_source_buffer_set_style_scheme (GtkSourceBuffer      *buffer,
				    GtkSourceStyleScheme *scheme)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (GTK_SOURCE_IS_STYLE_SCHEME (scheme) || scheme == NULL);

	if (g_set_object (&buffer->priv->style_scheme, scheme))
	{
		update_bracket_match_style (buffer);

		if (buffer->priv->highlight_engine != NULL)
		{
			_gtk_source_engine_set_style_scheme (buffer->priv->highlight_engine, scheme);
		}

		g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_STYLE_SCHEME]);
	}
}

/**
 * gtk_source_buffer_get_style_scheme:
 * @buffer: a #GtkSourceBuffer.
 *
 * Returns the #GtkSourceStyleScheme associated with the buffer,
 * see gtk_source_buffer_set_style_scheme().
 * The returned object should not be unreferenced by the user.
 *
 * Returns: (nullable) (transfer none): the #GtkSourceStyleScheme
 * associated with the buffer, or %NULL.
 */
GtkSourceStyleScheme *
gtk_source_buffer_get_style_scheme (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);

	return buffer->priv->style_scheme;
}

static void
add_source_mark (GtkSourceBuffer *buffer,
		 GtkSourceMark   *mark)
{
	const gchar *category;
	GtkSourceMarksSequence *seq;

	_gtk_source_marks_sequence_add (buffer->priv->all_source_marks,
					GTK_TEXT_MARK (mark));

	category = gtk_source_mark_get_category (mark);
	seq = g_hash_table_lookup (buffer->priv->source_marks, category);

	if (seq == NULL)
	{
		seq = _gtk_source_marks_sequence_new (GTK_TEXT_BUFFER (buffer));

		g_hash_table_insert (buffer->priv->source_marks,
				     g_strdup (category),
				     seq);
	}

	_gtk_source_marks_sequence_add (seq, GTK_TEXT_MARK (mark));
}

static void
gtk_source_buffer_real_mark_set	(GtkTextBuffer     *buffer,
				 const GtkTextIter *location,
				 GtkTextMark       *mark)
{
	if (GTK_SOURCE_IS_MARK (mark))
	{
		add_source_mark (GTK_SOURCE_BUFFER (buffer),
				 GTK_SOURCE_MARK (mark));

		g_signal_emit (buffer, buffer_signals[SOURCE_MARK_UPDATED], 0, mark);
	}
	else if (mark == gtk_text_buffer_get_insert (buffer))
	{
		cursor_moved (GTK_SOURCE_BUFFER (buffer));
	}

	GTK_TEXT_BUFFER_CLASS (gtk_source_buffer_parent_class)->mark_set (buffer, location, mark);
}

static void
gtk_source_buffer_real_mark_deleted (GtkTextBuffer *buffer,
				     GtkTextMark   *mark)
{
	if (GTK_SOURCE_IS_MARK (mark))
	{
		GtkSourceBuffer *source_buffer = GTK_SOURCE_BUFFER (buffer);
		const gchar *category;
		GtkSourceMarksSequence *seq;

		category = gtk_source_mark_get_category (GTK_SOURCE_MARK (mark));
		seq = g_hash_table_lookup (source_buffer->priv->source_marks, category);

		if (_gtk_source_marks_sequence_is_empty (seq))
		{
			g_hash_table_remove (source_buffer->priv->source_marks, category);
		}

		g_signal_emit (buffer, buffer_signals[SOURCE_MARK_UPDATED], 0, mark);
	}

	if (GTK_TEXT_BUFFER_CLASS (gtk_source_buffer_parent_class)->mark_deleted != NULL)
	{
		GTK_TEXT_BUFFER_CLASS (gtk_source_buffer_parent_class)->mark_deleted (buffer, mark);
	}
}

static void
gtk_source_buffer_real_undo (GtkSourceBuffer *buffer)
{
	g_return_if_fail (gtk_source_undo_manager_can_undo (buffer->priv->undo_manager));

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

static void
gtk_source_buffer_real_redo (GtkSourceBuffer *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_create_source_mark:
 * @buffer: a #GtkSourceBuffer.
 * @name: (nullable): the name of the mark, or %NULL.
 * @category: a string defining the mark category.
 * @where: location to place the mark.
 *
 * Creates a source mark in the @buffer of category @category.  A source mark is
 * a #GtkTextMark but organised into categories. Depending on the category
 * a pixbuf can be specified that will be displayed along the line of the mark.
 *
 * Like a #GtkTextMark, a #GtkSourceMark can be anonymous if the
 * passed @name is %NULL.  Also, the buffer owns the marks so you
 * shouldn't unreference it.
 *
 * Marks always have left gravity and are moved to the beginning of
 * the line when the user deletes the line they were in.
 *
 * Typical uses for a source mark are bookmarks, breakpoints, current
 * executing instruction indication in a source file, etc..
 *
 * Returns: (transfer none): a new #GtkSourceMark, owned by the buffer.
 *
 * Since: 2.2
 */
GtkSourceMark *
gtk_source_buffer_create_source_mark (GtkSourceBuffer   *buffer,
				      const gchar       *name,
				      const gchar       *category,
				      const GtkTextIter *where)
{
	GtkSourceMark *mark;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
	g_return_val_if_fail (category != NULL, NULL);
	g_return_val_if_fail (where != NULL, NULL);

	mark = gtk_source_mark_new (name, category);
	gtk_text_buffer_add_mark (GTK_TEXT_BUFFER (buffer),
				  GTK_TEXT_MARK (mark),
				  where);

	/* We want to return a borrowed reference and the mark is already
	 * owned by @buffer due to gtk_text_buffer_add_mark(). Therefore
	 * it is safe to unref immediately.
	 */
	g_object_unref (mark);

	return mark;
}

static GtkSourceMarksSequence *
get_marks_sequence (GtkSourceBuffer *buffer,
		    const gchar     *category)
{
	return category == NULL ?
		buffer->priv->all_source_marks :
		g_hash_table_lookup (buffer->priv->source_marks, category);
}

GtkSourceMark *
_gtk_source_buffer_source_mark_next (GtkSourceBuffer *buffer,
				     GtkSourceMark   *mark,
				     const gchar     *category)
{
	GtkSourceMarksSequence *seq;
	GtkTextMark *next_mark;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);

	seq = get_marks_sequence (buffer, category);

	if (seq == NULL)
	{
		return NULL;
	}

	next_mark = _gtk_source_marks_sequence_next (seq, GTK_TEXT_MARK (mark));

	return next_mark == NULL ? NULL : GTK_SOURCE_MARK (next_mark);
}

GtkSourceMark *
_gtk_source_buffer_source_mark_prev (GtkSourceBuffer *buffer,
				     GtkSourceMark   *mark,
				     const gchar     *category)
{
	GtkSourceMarksSequence *seq;
	GtkTextMark *prev_mark;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);

	seq = get_marks_sequence (buffer, category);

	if (seq == NULL)
	{
		return NULL;
	}

	prev_mark = _gtk_source_marks_sequence_prev (seq, GTK_TEXT_MARK (mark));

	return prev_mark == NULL ? NULL : GTK_SOURCE_MARK (prev_mark);
}

/**
 * gtk_source_buffer_forward_iter_to_source_mark:
 * @buffer: a #GtkSourceBuffer.
 * @iter: an iterator.
 * @category: (nullable): category to search for, or %NULL
 *
 * Moves @iter to the position of the next #GtkSourceMark of the given
 * @category. Returns %TRUE if @iter was moved. If @category is NULL, the
 * next source mark can be of any category.
 *
 * Returns: whether @iter was moved.
 *
 * Since: 2.2
 */
gboolean
gtk_source_buffer_forward_iter_to_source_mark (GtkSourceBuffer *buffer,
					       GtkTextIter     *iter,
					       const gchar     *category)
{
	GtkSourceMarksSequence *seq;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);

	seq = get_marks_sequence (buffer, category);

	if (seq == NULL)
	{
		return FALSE;
	}

	return _gtk_source_marks_sequence_forward_iter (seq, iter);
}

/**
 * gtk_source_buffer_backward_iter_to_source_mark:
 * @buffer: a #GtkSourceBuffer.
 * @iter: an iterator.
 * @category: (nullable): category to search for, or %NULL
 *
 * Moves @iter to the position of the previous #GtkSourceMark of the given
 * category. Returns %TRUE if @iter was moved. If @category is NULL, the
 * previous source mark can be of any category.
 *
 * Returns: whether @iter was moved.
 *
 * Since: 2.2
 */
gboolean
gtk_source_buffer_backward_iter_to_source_mark (GtkSourceBuffer *buffer,
						GtkTextIter     *iter,
						const gchar     *category)
{
	GtkSourceMarksSequence *seq;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);

	seq = get_marks_sequence (buffer, category);

	if (seq == NULL)
	{
		return FALSE;
	}

	return _gtk_source_marks_sequence_backward_iter (seq, iter);
}

/**
 * gtk_source_buffer_get_source_marks_at_iter:
 * @buffer: a #GtkSourceBuffer.
 * @iter: an iterator.
 * @category: (nullable): category to search for, or %NULL
 *
 * Returns the list of marks of the given category at @iter. If @category
 * is %NULL it returns all marks at @iter.
 *
 * Returns: (element-type GtkSource.Mark) (transfer container):
 * a newly allocated #GSList.
 *
 * Since: 2.2
 */
GSList *
gtk_source_buffer_get_source_marks_at_iter (GtkSourceBuffer *buffer,
					    GtkTextIter     *iter,
					    const gchar     *category)
{
	GtkSourceMarksSequence *seq;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
	g_return_val_if_fail (iter != NULL, NULL);

	seq = get_marks_sequence (buffer, category);

	if (seq == NULL)
	{
		return NULL;
	}

	return _gtk_source_marks_sequence_get_marks_at_iter (seq, iter);
}

/**
 * gtk_source_buffer_get_source_marks_at_line:
 * @buffer: a #GtkSourceBuffer.
 * @line: a line number.
 * @category: (nullable): category to search for, or %NULL
 *
 * Returns the list of marks of the given category at @line.
 * If @category is %NULL, all marks at @line are returned.
 *
 * Returns: (element-type GtkSource.Mark) (transfer container):
 * a newly allocated #GSList.
 *
 * Since: 2.2
 */
GSList *
gtk_source_buffer_get_source_marks_at_line (GtkSourceBuffer *buffer,
					    gint             line,
					    const gchar     *category)
{
	GtkSourceMarksSequence *seq;
	GtkTextIter start;
	GtkTextIter end;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);

	seq = get_marks_sequence (buffer, category);

	if (seq == NULL)
	{
		return NULL;
	}

	gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (buffer),
					  &start,
					  line);

	end = start;

	if (!gtk_text_iter_ends_line (&end))
	{
		gtk_text_iter_forward_to_line_end (&end);
	}

	return _gtk_source_marks_sequence_get_marks_in_range (seq, &start, &end);
}

/**
 * gtk_source_buffer_remove_source_marks:
 * @buffer: a #GtkSourceBuffer.
 * @start: a #GtkTextIter.
 * @end: a #GtkTextIter.
 * @category: (nullable): category to search for, or %NULL.
 *
 * Remove all marks of @category between @start and @end from the buffer.
 * If @category is NULL, all marks in the range will be removed.
 *
 * Since: 2.2
 */
void
gtk_source_buffer_remove_source_marks (GtkSourceBuffer   *buffer,
				       const GtkTextIter *start,
				       const GtkTextIter *end,
				       const gchar       *category)
{
	GtkSourceMarksSequence *seq;
	GSList *list;
	GSList *l;

 	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
 	g_return_if_fail (start != NULL);
 	g_return_if_fail (end != NULL);

	seq = get_marks_sequence (buffer, category);

	if (seq == NULL)
	{
		return;
	}

	list = _gtk_source_marks_sequence_get_marks_in_range (seq, start, end);

	for (l = list; l != NULL; l = l->next)
	{
		gtk_text_buffer_delete_mark (GTK_TEXT_BUFFER (buffer), l->data);
	}

	g_slist_free (list);
}

static GtkTextTag *
get_context_class_tag (GtkSourceBuffer *buffer,
		       const gchar     *context_class)
{
	gchar *tag_name;
	GtkTextTagTable *tag_table;
	GtkTextTag *tag;

	tag_name = g_strdup_printf (CONTEXT_CLASSES_PREFIX "%s", context_class);

	tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
	tag = gtk_text_tag_table_lookup (tag_table, tag_name);

	g_free (tag_name);
	return tag;
}

/**
 * gtk_source_buffer_iter_has_context_class:
 * @buffer: a #GtkSourceBuffer.
 * @iter: a #GtkTextIter.
 * @context_class: class to search for.
 *
 * Check if the class @context_class is set on @iter.
 *
 * See the #GtkSourceBuffer description for the list of default context classes.
 *
 * Returns: whether @iter has the context class.
 * Since: 2.10
 */
gboolean
gtk_source_buffer_iter_has_context_class (GtkSourceBuffer   *buffer,
                                          const GtkTextIter *iter,
                                          const gchar       *context_class)
{
	GtkTextTag *tag;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (context_class != NULL, FALSE);

	tag = get_context_class_tag (buffer, context_class);

	if (tag != NULL)
	{
		return gtk_text_iter_has_tag (iter, tag);
	}

	return FALSE;
}

/**
 * gtk_source_buffer_get_context_classes_at_iter:
 * @buffer: a #GtkSourceBuffer.
 * @iter: a #GtkTextIter.
 *
 * Get all defined context classes at @iter.
 *
 * See the #GtkSourceBuffer description for the list of default context classes.
 *
 * Returns: (array zero-terminated=1) (transfer full): a new %NULL
 * terminated array of context class names.
 * Use g_strfreev() to free the array if it is no longer needed.
 *
 * Since: 2.10
 */
gchar **
gtk_source_buffer_get_context_classes_at_iter (GtkSourceBuffer   *buffer,
                                               const GtkTextIter *iter)
{
	const gsize prefix_len = strlen (CONTEXT_CLASSES_PREFIX);
	GSList *tags;
	GSList *l;
	GPtrArray *ret;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
	g_return_val_if_fail (iter != NULL, NULL);

	tags = gtk_text_iter_get_tags (iter);
	ret = g_ptr_array_new ();

	for (l = tags; l != NULL; l = l->next)
	{
		GtkTextTag *tag = l->data;
		gchar *tag_name;

		g_object_get (tag, "name", &tag_name, NULL);

		if (tag_name != NULL &&
		    g_str_has_prefix (tag_name, CONTEXT_CLASSES_PREFIX))
		{
			gchar *context_class_name = g_strdup (tag_name + prefix_len);

			g_ptr_array_add (ret, context_class_name);
		}

		g_free (tag_name);
	}

	g_slist_free (tags);

	g_ptr_array_add (ret, NULL);
	return (gchar **) g_ptr_array_free (ret, FALSE);
}

/**
 * gtk_source_buffer_iter_forward_to_context_class_toggle:
 * @buffer: a #GtkSourceBuffer.
 * @iter: a #GtkTextIter.
 * @context_class: the context class.
 *
 * Moves forward to the next toggle (on or off) of the context class. If no
 * matching context class toggles are found, returns %FALSE, otherwise %TRUE.
 * Does not return toggles located at @iter, only toggles after @iter. Sets
 * @iter to the location of the toggle, or to the end of the buffer if no
 * toggle is found.
 *
 * See the #GtkSourceBuffer description for the list of default context classes.
 *
 * Returns: whether we found a context class toggle after @iter
 *
 * Since: 2.10
 */
gboolean
gtk_source_buffer_iter_forward_to_context_class_toggle (GtkSourceBuffer *buffer,
                                                        GtkTextIter     *iter,
                                                        const gchar     *context_class)
{
	GtkTextTag *tag;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (context_class != NULL, FALSE);

	tag = get_context_class_tag (buffer, context_class);

	if (tag != NULL)
	{
		return gtk_text_iter_forward_to_tag_toggle (iter, tag);
	}

	return FALSE;
}

/**
 * gtk_source_buffer_iter_backward_to_context_class_toggle:
 * @buffer: a #GtkSourceBuffer.
 * @iter: a #GtkTextIter.
 * @context_class: the context class.
 *
 * Moves backward to the next toggle (on or off) of the context class. If no
 * matching context class toggles are found, returns %FALSE, otherwise %TRUE.
 * Does not return toggles located at @iter, only toggles after @iter. Sets
 * @iter to the location of the toggle, or to the end of the buffer if no
 * toggle is found.
 *
 * See the #GtkSourceBuffer description for the list of default context classes.
 *
 * Returns: whether we found a context class toggle before @iter
 *
 * Since: 2.10
 */
gboolean
gtk_source_buffer_iter_backward_to_context_class_toggle (GtkSourceBuffer *buffer,
                                                         GtkTextIter     *iter,
                                                         const gchar     *context_class)
{
	GtkTextTag *tag;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (context_class != NULL, FALSE);

	tag = get_context_class_tag (buffer, context_class);

	if (tag != NULL)
	{
		return gtk_text_iter_backward_to_tag_toggle (iter, tag);
	}

	return FALSE;
}

/*
 * GtkTextView wastes a lot of time tracking the clipboard content if
 * we do insert/delete operations while there is a selection.
 * These two utilities store the current selection with marks before
 * doing an edit operation and restore it at the end.
 */
void
_gtk_source_buffer_save_and_clear_selection (GtkSourceBuffer *buffer)
{
	GtkTextBuffer *buf;

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	buf = GTK_TEXT_BUFFER (buffer);

	/* Note we cannot use buffer_get_selection_bounds since it
	 * orders the iters while we want to know the position of
	 * each mark.
	 */
	if (gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (buffer)))
	{
		GtkTextIter insert_iter;
		GtkTextIter selection_bound_iter;

		g_assert (buffer->priv->tmp_insert_mark == NULL);
		g_assert (buffer->priv->tmp_selection_bound_mark == NULL);

		gtk_text_buffer_get_iter_at_mark (buf, &insert_iter, gtk_text_buffer_get_insert (buf));
		gtk_text_buffer_get_iter_at_mark (buf, &selection_bound_iter, gtk_text_buffer_get_selection_bound (buf));
		buffer->priv->tmp_insert_mark = gtk_text_buffer_create_mark (buf, NULL, &insert_iter, FALSE);
		buffer->priv->tmp_selection_bound_mark = gtk_text_buffer_create_mark (buf, NULL, &selection_bound_iter, FALSE);

		gtk_text_buffer_place_cursor (buf, &insert_iter);
	}
}

void
_gtk_source_buffer_restore_selection (GtkSourceBuffer *buffer)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	if (buffer->priv->tmp_insert_mark != NULL &&
	    buffer->priv->tmp_selection_bound_mark != NULL)
	{
		GtkTextBuffer *buf;
		GtkTextIter insert_iter;
		GtkTextIter selection_bound_iter;

		buf = GTK_TEXT_BUFFER (buffer);

		gtk_text_buffer_get_iter_at_mark (buf, &insert_iter, buffer->priv->tmp_insert_mark);
		gtk_text_buffer_get_iter_at_mark (buf, &selection_bound_iter, buffer->priv->tmp_selection_bound_mark);

		gtk_text_buffer_select_range (buf, &insert_iter, &selection_bound_iter);

		gtk_text_buffer_delete_mark (buf, buffer->priv->tmp_insert_mark);
		gtk_text_buffer_delete_mark (buf, buffer->priv->tmp_selection_bound_mark);
		buffer->priv->tmp_insert_mark = NULL;
		buffer->priv->tmp_selection_bound_mark = NULL;
	}
}

static gchar *
do_lower_case (GtkTextBuffer     *buffer,
	       const GtkTextIter *start,
	       const GtkTextIter *end)
{
	gchar *text;
	gchar *new_text;

	text = gtk_text_buffer_get_text (buffer, start, end, TRUE);
	new_text = g_utf8_strdown (text, -1);

	g_free (text);
	return new_text;
}

static gchar *
do_upper_case (GtkTextBuffer     *buffer,
	       const GtkTextIter *start,
	       const GtkTextIter *end)
{
	gchar *text;
	gchar *new_text;

	text = gtk_text_buffer_get_text (buffer, start, end, TRUE);
	new_text = g_utf8_strup (text, -1);

	g_free (text);
	return new_text;
}

static gchar *
do_toggle_case (GtkTextBuffer     *buffer,
		const GtkTextIter *start,
		const GtkTextIter *end)
{
	GString *str;
	GtkTextIter iter_start;

	str = g_string_new (NULL);
	iter_start = *start;

	while (!gtk_text_iter_is_end (&iter_start))
	{
		GtkTextIter iter_end;
		gchar *text;
		gchar *text_down;
		gchar *text_up;

		iter_end = iter_start;
		gtk_text_iter_forward_cursor_position (&iter_end);

		if (gtk_text_iter_compare (end, &iter_end) < 0)
		{
			break;
		}

		text = gtk_text_buffer_get_text (buffer, &iter_start, &iter_end, TRUE);
		text_down = g_utf8_strdown (text, -1);
		text_up = g_utf8_strup (text, -1);

		if (g_strcmp0 (text, text_down) == 0)
		{
			g_string_append (str, text_up);
		}
		else if (g_strcmp0 (text, text_up) == 0)
		{
			g_string_append (str, text_down);
		}
		else
		{
			g_string_append (str, text);
		}

		g_free (text);
		g_free (text_down);
		g_free (text_up);

		iter_start = iter_end;
	}

	return g_string_free (str, FALSE);
}

static gchar *
do_title_case (GtkTextBuffer     *buffer,
	       const GtkTextIter *start,
	       const GtkTextIter *end)
{
	GString *str;
	GtkTextIter iter_start;

	str = g_string_new (NULL);
	iter_start = *start;

	while (!gtk_text_iter_is_end (&iter_start))
	{
		GtkTextIter iter_end;
		gchar *text;

		iter_end = iter_start;
		gtk_text_iter_forward_cursor_position (&iter_end);

		if (gtk_text_iter_compare (end, &iter_end) < 0)
		{
			break;
		}

		text = gtk_text_buffer_get_text (buffer, &iter_start, &iter_end, TRUE);

		if (gtk_text_iter_starts_word (&iter_start))
		{
			gchar *text_normalized;

			text_normalized = g_utf8_normalize (text, -1, G_NORMALIZE_DEFAULT);

			if (g_utf8_strlen (text_normalized, -1) == 1)
			{
				gunichar c;
				gunichar new_c;

				c = gtk_text_iter_get_char (&iter_start);
				new_c = g_unichar_totitle (c);

				g_string_append_unichar (str, new_c);
			}
			else
			{
				gchar *text_up;

				text_up = g_utf8_strup (text, -1);
				g_string_append (str, text_up);

				g_free (text_up);
			}

			g_free (text_normalized);
		}
		else
		{
			gchar *text_down;

			text_down = g_utf8_strdown (text, -1);
			g_string_append (str, text_down);

			g_free (text_down);
		}

		g_free (text);
		iter_start = iter_end;
	}

	return g_string_free (str, FALSE);
}

/**
 * gtk_source_buffer_change_case:
 * @buffer: a #GtkSourceBuffer.
 * @case_type: how to change the case.
 * @start: a #GtkTextIter.
 * @end: a #GtkTextIter.
 *
 * Changes the case of the text between the specified iterators.
 *
 * Since: 3.12
 */
void
gtk_source_buffer_change_case (GtkSourceBuffer         *buffer,
                               GtkSourceChangeCaseType  case_type,
                               GtkTextIter             *start,
                               GtkTextIter             *end)
{
	GtkTextBuffer *text_buffer;
	gchar *new_text;

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (start != NULL);
	g_return_if_fail (end != NULL);

	gtk_text_iter_order (start, end);

	text_buffer = GTK_TEXT_BUFFER (buffer);

	switch (case_type)
	{
		case GTK_SOURCE_CHANGE_CASE_LOWER:
			new_text = do_lower_case (text_buffer, start, end);
			break;

		case GTK_SOURCE_CHANGE_CASE_UPPER:
			new_text = do_upper_case (text_buffer, start, end);
			break;

		case GTK_SOURCE_CHANGE_CASE_TOGGLE:
			new_text = do_toggle_case (text_buffer, start, end);
			break;

		case GTK_SOURCE_CHANGE_CASE_TITLE:
			new_text = do_title_case (text_buffer, start, end);
			break;

		default:
			g_return_if_reached ();
	}

	gtk_text_buffer_begin_user_action (text_buffer);
	gtk_text_buffer_delete (text_buffer, start, end);
	gtk_text_buffer_insert (text_buffer, start, new_text, -1);
	gtk_text_buffer_end_user_action (text_buffer);

	g_free (new_text);
}

/* Move to the end of the line excluding trailing spaces. */
static void
move_to_line_text_end(GtkTextIter *iter)
{
	gint line;

	line = gtk_text_iter_get_line (iter);

	if (!gtk_text_iter_ends_line (iter))
	{
		gtk_text_iter_forward_to_line_end (iter);
	}

	while (gtk_text_iter_backward_char (iter) &&
	       (gtk_text_iter_get_line (iter) == line))
	{
		gunichar ch;

		ch = gtk_text_iter_get_char (iter);
		if (!g_unichar_isspace (ch))
		{
			break;
		}
	}

	gtk_text_iter_forward_char (iter);
}

/**
 * gtk_source_buffer_join_lines:
 * @buffer: a #GtkSourceBuffer.
 * @start: a #GtkTextIter.
 * @end: a #GtkTextIter.
 *
 * Joins the lines of text between the specified iterators.
 *
 * Since: 3.16
 */
void
gtk_source_buffer_join_lines (GtkSourceBuffer *buffer,
                              GtkTextIter     *start,
                              GtkTextIter     *end)
{
	GtkTextBuffer *text_buffer;
	GtkTextMark *end_mark;

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (start != NULL);
	g_return_if_fail (end != NULL);

	gtk_text_iter_order (start, end);

	text_buffer = GTK_TEXT_BUFFER (buffer);
	end_mark = gtk_text_buffer_create_mark (text_buffer, NULL, end, FALSE);

	_gtk_source_buffer_save_and_clear_selection (buffer);
	gtk_text_buffer_begin_user_action (text_buffer);

	move_to_line_text_end (start);
	if (!gtk_text_iter_ends_line (end))
	{
		gtk_text_iter_forward_to_line_end (end);
	}

	while (gtk_text_iter_compare (start, end) < 0)
	{
		GtkTextIter iter;
		gunichar ch;

		iter = *start;

		do
		{
			ch = gtk_text_iter_get_char (&iter);
			if (!g_unichar_isspace (ch))
			{
				break;
			}
		} while (gtk_text_iter_forward_char (&iter) &&
			 gtk_text_iter_compare (&iter, end) < 0);

		if (!gtk_text_iter_is_end (&iter))
		{
			gtk_text_buffer_delete (text_buffer, start, &iter);
			if (!gtk_text_iter_ends_line (start))
			{
				gtk_text_buffer_insert (text_buffer, start, " ", 1);
			}
		}

		move_to_line_text_end (start);

		gtk_text_buffer_get_iter_at_mark (text_buffer, end, end_mark);
	}

	gtk_text_buffer_end_user_action (text_buffer);
	_gtk_source_buffer_restore_selection (buffer);

	gtk_text_buffer_delete_mark (text_buffer, end_mark);
}

static gchar *
get_line_slice (GtkTextBuffer *buf,
		gint           line)
{
	GtkTextIter start, end;

	gtk_text_buffer_get_iter_at_line (buf, &start, line);
	end = start;

	if (!gtk_text_iter_ends_line (&start))
	{
		gtk_text_iter_forward_to_line_end (&end);
	}

	return gtk_text_buffer_get_slice (buf, &start, &end, TRUE);
}

typedef struct {
	gchar *line; /* the original text to re-insert */
	gchar *key;  /* the key to use for the comparison */
} SortLine;

static gint
compare_line (gconstpointer aptr,
              gconstpointer bptr)
{
	const SortLine *a = aptr;
	const SortLine *b = bptr;

	return g_strcmp0 (a->key, b->key);
}

static gint
compare_line_reversed (gconstpointer aptr,
                       gconstpointer bptr)
{
	const SortLine *a = aptr;
	const SortLine *b = bptr;

	return g_strcmp0 (b->key, a->key);
}

/**
 * gtk_source_buffer_sort_lines:
 * @buffer: a #GtkSourceBuffer.
 * @start: a #GtkTextIter.
 * @end: a #GtkTextIter.
 * @flags: #GtkSourceSortFlags specifying how the sort should behave
 * @column: sort considering the text starting at the given column
 *
 * Sort the lines of text between the specified iterators.
 *
 * Since: 3.18
 */
void
gtk_source_buffer_sort_lines (GtkSourceBuffer    *buffer,
                              GtkTextIter        *start,
                              GtkTextIter        *end,
                              GtkSourceSortFlags  flags,
                              gint                column)
{
	GtkTextBuffer *text_buffer;
	gint start_line;
	gint end_line;
	gint num_lines;
	SortLine *lines;
	gchar *last_line = NULL;
	gint i;

	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (start != NULL);
	g_return_if_fail (end != NULL);

	text_buffer = GTK_TEXT_BUFFER (buffer);

	gtk_text_iter_order (start, end);

	start_line = gtk_text_iter_get_line (start);
	end_line = gtk_text_iter_get_line (end);

	/* Required for gtk_text_buffer_delete() */
	if (!gtk_text_iter_starts_line (start))
	{
		gtk_text_iter_set_line_offset (start, 0);
	}

	/* if we are at line start our last line is the previus one.
	 * Otherwise the last line is the current one but we try to
	 * move the iter after the line terminator */
	if (gtk_text_iter_starts_line (end))
	{
		end_line = MAX (start_line, end_line - 1);
	}
	else
	{
		gtk_text_iter_forward_line (end);
	}

	if (start_line == end_line)
	{
		return;
	}

	num_lines = end_line - start_line + 1;
	lines = g_new0 (SortLine, num_lines);

	for (i = 0; i < num_lines; i++)
	{
		gchar *line;
		gboolean free_line = FALSE;
		glong length;

		lines[i].line = get_line_slice (text_buffer, start_line + i);

		if ((flags & GTK_SOURCE_SORT_FLAGS_CASE_SENSITIVE) != 0)
		{
			line = lines[i].line;
		}
		else
		{
			line = g_utf8_casefold (lines[i].line, -1);
			free_line = TRUE;
		}

		length = g_utf8_strlen (line, -1);

		if (length < column)
		{
			lines[i].key = NULL;
		}
		else if (column > 0)
		{
			gchar *substring;

			substring = g_utf8_offset_to_pointer (line, column);
			lines[i].key = g_utf8_collate_key (substring, -1);
		}
		else
		{
			lines[i].key = g_utf8_collate_key (line, -1);
		}

		if (free_line)
		{
			g_free (line);
		}
	}

	if ((flags & GTK_SOURCE_SORT_FLAGS_REVERSE_ORDER) != 0)
	{
		qsort (lines, num_lines, sizeof (SortLine), compare_line_reversed);
	}
	else
	{
		qsort (lines, num_lines, sizeof (SortLine), compare_line);
	}

	_gtk_source_buffer_save_and_clear_selection (buffer);
	gtk_text_buffer_begin_user_action (text_buffer);

	gtk_text_buffer_delete (text_buffer, start, end);

	for (i = 0; i < num_lines; i++)
	{
		if ((flags & GTK_SOURCE_SORT_FLAGS_REMOVE_DUPLICATES) != 0 &&
		    g_strcmp0 (last_line, lines[i].line) == 0)
		{
			continue;
		}

		gtk_text_buffer_insert (text_buffer, start, lines[i].line, -1);
		gtk_text_buffer_insert (text_buffer, start, "\n", -1);

		last_line = lines[i].line;
	}

	gtk_text_buffer_end_user_action (text_buffer);
	_gtk_source_buffer_restore_selection (buffer);

	for (i = 0; i < num_lines; i++)
	{
		g_free (lines[i].line);
		g_free (lines[i].key);
	}

	g_free (lines);
}

/**
 * gtk_source_buffer_set_undo_manager:
 * @buffer: a #GtkSourceBuffer.
 * @manager: (nullable): A #GtkSourceUndoManager or %NULL.
 *
 * Set the buffer undo manager. If @manager is %NULL the default undo manager
 * will be set.
 */
void
gtk_source_buffer_set_undo_manager (GtkSourceBuffer      *buffer,
                                    GtkSourceUndoManager *manager)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (manager == NULL || GTK_SOURCE_IS_UNDO_MANAGER (manager));

	if (manager == NULL)
	{
		manager = g_object_new (GTK_SOURCE_TYPE_UNDO_MANAGER_DEFAULT,
		                        "buffer", buffer,
		                        "max-undo-levels", buffer->priv->max_undo_levels,
		                        NULL);
	}
	else
	{
		g_object_ref (manager);
	}

	set_undo_manager (buffer, manager);
	g_object_unref (manager);

	g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_UNDO_MANAGER]);
}

/**
 * gtk_source_buffer_get_undo_manager:
 * @buffer: a #GtkSourceBuffer.
 *
 * Returns the #GtkSourceUndoManager associated with the buffer,
 * see gtk_source_buffer_set_undo_manager().  The returned object should not be
 * unreferenced by the user.
 *
 * Returns: (nullable) (transfer none): the #GtkSourceUndoManager associated
 * with the buffer, or %NULL.
 */
GtkSourceUndoManager *
gtk_source_buffer_get_undo_manager (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);

	return buffer->priv->undo_manager;
}

void
_gtk_source_buffer_add_search_context (GtkSourceBuffer        *buffer,
				       GtkSourceSearchContext *search_context)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));
	g_return_if_fail (GTK_SOURCE_IS_SEARCH_CONTEXT (search_context));
	g_return_if_fail (gtk_source_search_context_get_buffer (search_context) == buffer);

	if (g_list_find (buffer->priv->search_contexts, search_context) != NULL)
	{
		return;
	}

	buffer->priv->search_contexts = g_list_prepend (buffer->priv->search_contexts,
							search_context);

	g_object_weak_ref (G_OBJECT (search_context),
			   (GWeakNotify)search_context_weak_notify_cb,
			   buffer);
}

static void
sync_invalid_char_tag (GtkSourceBuffer *buffer,
		       GParamSpec      *pspec,
		       gpointer         data)
{
	GtkSourceStyle *style = NULL;

	if (buffer->priv->style_scheme != NULL)
	{
		style = gtk_source_style_scheme_get_style (buffer->priv->style_scheme, "def:error");
	}

	gtk_source_style_apply (style, buffer->priv->invalid_char_tag);
}

static void
text_tag_set_highest_priority (GtkTextTag    *tag,
			       GtkTextBuffer *buffer)
{
	GtkTextTagTable *table;
	gint n;

	table = gtk_text_buffer_get_tag_table (buffer);
	n = gtk_text_tag_table_get_size (table);
	gtk_text_tag_set_priority (tag, n - 1);
}

void
_gtk_source_buffer_set_as_invalid_character (GtkSourceBuffer   *buffer,
					     const GtkTextIter *start,
					     const GtkTextIter *end)
{
	if (buffer->priv->invalid_char_tag == NULL)
	{
		buffer->priv->invalid_char_tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer),
									     "invalid-char-style",
									     NULL);

		sync_invalid_char_tag (buffer, NULL, NULL);

		g_signal_connect (buffer,
		                  "notify::style-scheme",
		                  G_CALLBACK (sync_invalid_char_tag),
		                  NULL);
	}

	/* Make sure the 'error' tag has the priority over
	 * syntax highlighting tags.
	 */
	text_tag_set_highest_priority (buffer->priv->invalid_char_tag,
	                               GTK_TEXT_BUFFER (buffer));

	gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer),
	                           buffer->priv->invalid_char_tag,
	                           start,
	                           end);
}

gboolean
_gtk_source_buffer_has_invalid_chars (GtkSourceBuffer *buffer)
{
	GtkTextIter start;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), FALSE);

	if (buffer->priv->invalid_char_tag == NULL)
	{
		return FALSE;
	}

	gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &start);

	if (gtk_text_iter_starts_tag (&start, buffer->priv->invalid_char_tag) ||
	    gtk_text_iter_forward_to_tag_toggle (&start, buffer->priv->invalid_char_tag))
	{
		return TRUE;
	}

	return FALSE;
}

/**
 * gtk_source_buffer_set_implicit_trailing_newline:
 * @buffer: a #GtkSourceBuffer.
 * @implicit_trailing_newline: the new value.
 *
 * Sets whether the @buffer has an implicit trailing newline.
 *
 * If an explicit trailing newline is present in a #GtkTextBuffer, #GtkTextView
 * shows it as an empty line. This is generally not what the user expects.
 *
 * If @implicit_trailing_newline is %TRUE (the default value):
 *  - when a #GtkSourceFileLoader loads the content of a file into the @buffer,
 *    the trailing newline (if present in the file) is not inserted into the
 *    @buffer.
 *  - when a #GtkSourceFileSaver saves the content of the @buffer into a file, a
 *    trailing newline is added to the file.
 *
 * On the other hand, if @implicit_trailing_newline is %FALSE, the file's
 * content is not modified when loaded into the @buffer, and the @buffer's
 * content is not modified when saved into a file.
 *
 * Since: 3.14
 */
void
gtk_source_buffer_set_implicit_trailing_newline (GtkSourceBuffer *buffer,
						 gboolean         implicit_trailing_newline)
{
	g_return_if_fail (GTK_SOURCE_IS_BUFFER (buffer));

	implicit_trailing_newline = implicit_trailing_newline != FALSE;

	if (buffer->priv->implicit_trailing_newline != implicit_trailing_newline)
	{
		buffer->priv->implicit_trailing_newline = implicit_trailing_newline;
		g_object_notify_by_pspec (G_OBJECT (buffer), buffer_properties[PROP_IMPLICIT_TRAILING_NEWLINE]);
	}
}

/**
 * gtk_source_buffer_get_implicit_trailing_newline:
 * @buffer: a #GtkSourceBuffer.
 *
 * Returns: whether the @buffer has an implicit trailing newline.
 * Since: 3.14
 */
gboolean
gtk_source_buffer_get_implicit_trailing_newline (GtkSourceBuffer *buffer)
{
	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), TRUE);

	return buffer->priv->implicit_trailing_newline;
}

/**
 * gtk_source_buffer_create_source_tag:
 * @buffer: a #GtkSourceBuffer
 * @tag_name: (nullable): name of the new tag, or %NULL
 * @first_property_name: (nullable): name of first property to set, or %NULL
 * @...: %NULL-terminated list of property names and values
 *
 * In short, this is the same function as gtk_text_buffer_create_tag(), but
 * instead of creating a #GtkTextTag, this function creates a #GtkSourceTag.
 *
 * This function creates a #GtkSourceTag and adds it to the tag table for
 * @buffer.  Equivalent to calling gtk_text_tag_new() and then adding the tag to
 * the buffer’s tag table. The returned tag is owned by the buffer’s tag table,
 * so the ref count will be equal to one.
 *
 * If @tag_name is %NULL, the tag is anonymous.
 *
 * If @tag_name is non-%NULL, a tag called @tag_name must not already
 * exist in the tag table for this buffer.
 *
 * The @first_property_name argument and subsequent arguments are a list
 * of properties to set on the tag, as with g_object_set().
 *
 * Returns: (transfer none): a new #GtkSourceTag.
 * Since: 3.20
 */
GtkTextTag *
gtk_source_buffer_create_source_tag (GtkSourceBuffer *buffer,
				     const gchar     *tag_name,
				     const gchar     *first_property_name,
				     ...)
{
	GtkTextTag *tag;
	GtkTextTagTable *table;
	va_list list;

	g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);

	tag = gtk_source_tag_new (tag_name);

	table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
	if (!gtk_text_tag_table_add (table, tag))
	{
		g_object_unref (tag);
		return NULL;
	}

	if (first_property_name != NULL)
	{
		va_start (list, first_property_name);
		g_object_set_valist (G_OBJECT (tag), first_property_name, list);
		va_end (list);
	}

	g_object_unref (tag);

	return tag;
}
