3320 lines
89 KiB
C
3320 lines
89 KiB
C
/* -*- 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:<name></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;
|
||
}
|