901 lines
28 KiB
C
901 lines
28 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
|
|
*
|
|
* mootextbuffer.c
|
|
*
|
|
* Copyright (C) 2004-2005 by Yevgen Muntyan <muntyan@math.tamu.edu>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* See COPYING file that comes with this distribution.
|
|
*/
|
|
|
|
#define MOOEDIT_COMPILATION
|
|
#include "mooedit/moolinebuffer.h"
|
|
#include "mooedit/mootextiter.h"
|
|
#include "mooedit/moohighlighter.h"
|
|
#include "mooutils/moomarshals.h"
|
|
|
|
|
|
struct _MooTextBufferPrivate {
|
|
gboolean has_selection;
|
|
gboolean has_text;
|
|
|
|
MooHighlighter *hl;
|
|
MooLang *lang;
|
|
gboolean may_apply_tag;
|
|
gboolean do_highlight;
|
|
|
|
gboolean check_brackets;
|
|
guint num_brackets;
|
|
gunichar *left_brackets, *right_brackets;
|
|
GtkTextTag *correct_match_tag;
|
|
GtkTextTag *incorrect_match_tag;
|
|
GtkTextMark *bracket_mark[4];
|
|
MooBracketMatchType bracket_found;
|
|
|
|
LineBuffer *line_buf;
|
|
};
|
|
|
|
|
|
static void moo_text_buffer_finalize (GObject *object);
|
|
|
|
static void moo_text_buffer_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void moo_text_buffer_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static void moo_text_buffer_cursor_moved (MooTextBuffer *buffer,
|
|
const GtkTextIter *iter);
|
|
static void moo_text_buffer_mark_set (GtkTextBuffer *buffer,
|
|
const GtkTextIter *iter,
|
|
GtkTextMark *mark);
|
|
static void moo_text_buffer_insert_text (GtkTextBuffer *buffer,
|
|
GtkTextIter *pos,
|
|
const gchar *text,
|
|
gint length);
|
|
static void moo_text_buffer_apply_tag (GtkTextBuffer *buffer,
|
|
GtkTextTag *tag,
|
|
const GtkTextIter *start,
|
|
const GtkTextIter *end);
|
|
static void moo_text_buffer_delete_range (GtkTextBuffer *buffer,
|
|
GtkTextIter *start,
|
|
GtkTextIter *end);
|
|
|
|
static void moo_text_buffer_queue_highlight (MooTextBuffer *buffer);
|
|
|
|
|
|
enum {
|
|
CURSOR_MOVED,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL];
|
|
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_CHECK_BRACKETS,
|
|
PROP_BRACKET_MATCH_STYLE,
|
|
PROP_BRACKET_MISMATCH_STYLE,
|
|
PROP_HAS_TEXT,
|
|
PROP_HAS_SELECTION,
|
|
PROP_LANG
|
|
};
|
|
|
|
|
|
/* MOO_TYPE_TEXT_BUFFER */
|
|
G_DEFINE_TYPE (MooTextBuffer, moo_text_buffer, GTK_TYPE_TEXT_BUFFER)
|
|
|
|
|
|
static void
|
|
moo_text_buffer_class_init (MooTextBufferClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GtkTextBufferClass *buffer_class = GTK_TEXT_BUFFER_CLASS (klass);
|
|
|
|
gobject_class->set_property = moo_text_buffer_set_property;
|
|
gobject_class->get_property = moo_text_buffer_get_property;
|
|
gobject_class->finalize = moo_text_buffer_finalize;
|
|
|
|
buffer_class->mark_set = moo_text_buffer_mark_set;
|
|
buffer_class->insert_text = moo_text_buffer_insert_text;
|
|
buffer_class->delete_range = moo_text_buffer_delete_range;
|
|
buffer_class->apply_tag = moo_text_buffer_apply_tag;
|
|
|
|
klass->cursor_moved = moo_text_buffer_cursor_moved;
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CHECK_BRACKETS,
|
|
g_param_spec_boolean ("check-brackets",
|
|
"check-brackets",
|
|
"check-brackets",
|
|
TRUE,
|
|
G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_BRACKET_MATCH_STYLE,
|
|
g_param_spec_boxed ("bracket-match-style",
|
|
"bracket-match-style",
|
|
"bracket-match-style",
|
|
MOO_TYPE_TEXT_STYLE,
|
|
G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_BRACKET_MISMATCH_STYLE,
|
|
g_param_spec_boxed ("bracket-mismatch-style",
|
|
"bracket-mismatch-style",
|
|
"bracket-mismatch-style",
|
|
MOO_TYPE_TEXT_STYLE,
|
|
G_PARAM_CONSTRUCT | G_PARAM_WRITABLE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_HAS_TEXT,
|
|
g_param_spec_boolean ("has-text",
|
|
"has-text",
|
|
"has-text",
|
|
FALSE,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_HAS_SELECTION,
|
|
g_param_spec_boolean ("has-selection",
|
|
"has-selection",
|
|
"has-selection",
|
|
FALSE,
|
|
G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_LANG,
|
|
g_param_spec_boxed ("lang",
|
|
"lang",
|
|
"lang",
|
|
MOO_TYPE_LANG,
|
|
G_PARAM_READWRITE));
|
|
|
|
signals[CURSOR_MOVED] =
|
|
g_signal_new ("cursor-moved",
|
|
G_OBJECT_CLASS_TYPE (klass),
|
|
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
|
|
G_STRUCT_OFFSET (MooTextBufferClass, cursor_moved),
|
|
NULL, NULL,
|
|
_moo_marshal_VOID__BOXED,
|
|
G_TYPE_NONE, 1,
|
|
GTK_TYPE_TEXT_ITER);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_init (MooTextBuffer *buffer)
|
|
{
|
|
buffer->priv = g_new0 (MooTextBufferPrivate, 1);
|
|
buffer->priv->line_buf = moo_line_buffer_new ();
|
|
buffer->priv->do_highlight = TRUE;
|
|
buffer->priv->hl = moo_highlighter_new (GTK_TEXT_BUFFER (buffer),
|
|
buffer->priv->line_buf, NULL);
|
|
buffer->priv->bracket_found = MOO_BRACKET_MATCH_NONE;
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_finalize (GObject *object)
|
|
{
|
|
MooTextBuffer *buffer = MOO_TEXT_BUFFER (object);
|
|
|
|
moo_highlighter_destroy (buffer->priv->hl, FALSE);
|
|
moo_line_buffer_free (buffer->priv->line_buf);
|
|
|
|
g_free (buffer->priv->left_brackets);
|
|
g_free (buffer->priv->right_brackets);
|
|
|
|
g_free (buffer->priv);
|
|
buffer->priv = NULL;
|
|
|
|
G_OBJECT_CLASS (moo_text_buffer_parent_class)->finalize (object);
|
|
}
|
|
|
|
|
|
GtkTextBuffer*
|
|
moo_text_buffer_new (GtkTextTagTable *table)
|
|
{
|
|
return g_object_new (MOO_TYPE_TEXT_BUFFER,
|
|
"tag-table", table, NULL);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_mark_set (GtkTextBuffer *text_buffer,
|
|
const GtkTextIter *iter,
|
|
GtkTextMark *mark)
|
|
{
|
|
GtkTextMark *insert = gtk_text_buffer_get_insert (text_buffer);
|
|
GtkTextMark *sel_bound = gtk_text_buffer_get_selection_bound (text_buffer);
|
|
MooTextBuffer *buffer = MOO_TEXT_BUFFER (text_buffer);
|
|
gboolean has_selection = buffer->priv->has_selection;
|
|
|
|
if (GTK_TEXT_BUFFER_CLASS(moo_text_buffer_parent_class)->mark_set)
|
|
GTK_TEXT_BUFFER_CLASS(moo_text_buffer_parent_class)->mark_set (text_buffer, iter, mark);
|
|
|
|
if (mark == insert)
|
|
{
|
|
GtkTextIter iter2;
|
|
gtk_text_buffer_get_iter_at_mark (text_buffer, &iter2, sel_bound);
|
|
has_selection = gtk_text_iter_compare (iter, &iter2) ? TRUE : FALSE;
|
|
}
|
|
else if (mark == sel_bound)
|
|
{
|
|
GtkTextIter iter2;
|
|
gtk_text_buffer_get_iter_at_mark (text_buffer, &iter2, insert);
|
|
has_selection = gtk_text_iter_compare (iter, &iter2) ? TRUE : FALSE;
|
|
}
|
|
|
|
if (has_selection != buffer->priv->has_selection)
|
|
{
|
|
buffer->priv->has_selection = has_selection;
|
|
g_object_notify (G_OBJECT (buffer), "has-selection");
|
|
}
|
|
|
|
if (mark == insert)
|
|
g_signal_emit (buffer, signals[CURSOR_MOVED], 0, iter);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_insert_text (GtkTextBuffer *text_buffer,
|
|
GtkTextIter *pos,
|
|
const gchar *text,
|
|
gint length)
|
|
{
|
|
MooTextBuffer *buffer = MOO_TEXT_BUFFER (text_buffer);
|
|
int first_line, last_line;
|
|
gboolean starts_line, ins_line;
|
|
|
|
if (!text[0])
|
|
return;
|
|
|
|
first_line = gtk_text_iter_get_line (pos);
|
|
starts_line = gtk_text_iter_starts_line (pos);
|
|
ins_line = (text[0] == '\n' || text[0] == '\r');
|
|
|
|
GTK_TEXT_BUFFER_CLASS(moo_text_buffer_parent_class)->insert_text (text_buffer, pos, text, length);
|
|
|
|
last_line = gtk_text_iter_get_line (pos);
|
|
|
|
if (last_line == first_line)
|
|
{
|
|
moo_line_buffer_invalidate (buffer->priv->line_buf, first_line);
|
|
}
|
|
else
|
|
{
|
|
moo_line_buffer_insert_range (buffer->priv->line_buf,
|
|
first_line + 1,
|
|
last_line - first_line);
|
|
moo_line_buffer_invalidate (buffer->priv->line_buf, first_line);
|
|
moo_line_buffer_invalidate (buffer->priv->line_buf, last_line);
|
|
}
|
|
|
|
moo_text_buffer_queue_highlight (buffer);
|
|
|
|
g_signal_emit (buffer, signals[CURSOR_MOVED], 0, pos);
|
|
|
|
if (!buffer->priv->has_text)
|
|
{
|
|
buffer->priv->has_text = TRUE;
|
|
g_object_notify (G_OBJECT (buffer), "has-text");
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_delete_range (GtkTextBuffer *text_buffer,
|
|
GtkTextIter *start,
|
|
GtkTextIter *end)
|
|
{
|
|
MooTextBuffer *buffer = MOO_TEXT_BUFFER (text_buffer);
|
|
int first_line, last_line;
|
|
|
|
gtk_text_buffer_remove_all_tags (text_buffer, start, end);
|
|
|
|
first_line = gtk_text_iter_get_line (start);
|
|
last_line = gtk_text_iter_get_line (end);
|
|
|
|
GTK_TEXT_BUFFER_CLASS(moo_text_buffer_parent_class)->delete_range (text_buffer, start, end);
|
|
|
|
if (first_line < last_line)
|
|
moo_line_buffer_delete_range (buffer->priv->line_buf,
|
|
first_line + 1,
|
|
last_line - first_line);
|
|
|
|
moo_line_buffer_invalidate (buffer->priv->line_buf, first_line);
|
|
moo_text_buffer_queue_highlight (buffer);
|
|
|
|
g_signal_emit (buffer, signals[CURSOR_MOVED], 0, start);
|
|
|
|
if (buffer->priv->has_text)
|
|
{
|
|
buffer->priv->has_text = moo_text_buffer_has_text (buffer);
|
|
if (!buffer->priv->has_text)
|
|
g_object_notify (G_OBJECT (buffer), "has-text");
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
moo_text_buffer_set_lang (MooTextBuffer *buffer,
|
|
MooLang *lang)
|
|
{
|
|
MooLang *old_lang;
|
|
|
|
g_return_if_fail (MOO_IS_TEXT_BUFFER (buffer));
|
|
|
|
if (buffer->priv->lang == lang)
|
|
return;
|
|
|
|
old_lang = buffer->priv->lang;
|
|
|
|
if (old_lang)
|
|
moo_lang_unref (old_lang);
|
|
|
|
moo_highlighter_destroy (buffer->priv->hl, TRUE);
|
|
|
|
buffer->priv->lang = lang;
|
|
|
|
if (lang)
|
|
moo_lang_ref (lang);
|
|
|
|
buffer->priv->hl = moo_highlighter_new (GTK_TEXT_BUFFER (buffer),
|
|
buffer->priv->line_buf, lang);
|
|
|
|
if (old_lang)
|
|
moo_line_buffer_invalidate_all (buffer->priv->line_buf);
|
|
|
|
moo_text_buffer_queue_highlight (buffer);
|
|
|
|
moo_text_buffer_set_brackets (buffer, lang ? lang->brackets : NULL);
|
|
}
|
|
|
|
|
|
MooLang*
|
|
moo_text_buffer_get_lang (MooTextBuffer *buffer)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_TEXT_BUFFER (buffer), NULL);
|
|
return buffer->priv->lang;
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_queue_highlight (MooTextBuffer *buffer)
|
|
{
|
|
if (buffer->priv->lang && buffer->priv->do_highlight)
|
|
moo_highlighter_queue_compute (buffer->priv->hl, FALSE);
|
|
}
|
|
|
|
|
|
void
|
|
moo_text_buffer_set_highlight (MooTextBuffer *buffer,
|
|
gboolean highlight)
|
|
{
|
|
g_return_if_fail (MOO_IS_TEXT_BUFFER (buffer));
|
|
|
|
if ((highlight && buffer->priv->do_highlight) || (!highlight && !buffer->priv->do_highlight))
|
|
return;
|
|
|
|
if (buffer->priv->do_highlight && buffer->priv->lang)
|
|
moo_highlighter_destroy (buffer->priv->hl, TRUE);
|
|
else
|
|
moo_highlighter_destroy (buffer->priv->hl, FALSE);
|
|
|
|
buffer->priv->do_highlight = highlight;
|
|
|
|
if (!highlight || !buffer->priv->lang)
|
|
{
|
|
buffer->priv->hl = moo_highlighter_new (GTK_TEXT_BUFFER (buffer),
|
|
buffer->priv->line_buf, NULL);
|
|
}
|
|
else
|
|
{
|
|
buffer->priv->hl = moo_highlighter_new (GTK_TEXT_BUFFER (buffer),
|
|
buffer->priv->line_buf,
|
|
buffer->priv->lang);
|
|
moo_line_buffer_invalidate_all (buffer->priv->line_buf);
|
|
moo_text_buffer_queue_highlight (buffer);
|
|
}
|
|
}
|
|
|
|
|
|
gboolean
|
|
moo_text_buffer_get_highlight (MooTextBuffer *buffer)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_TEXT_BUFFER (buffer), FALSE);
|
|
return buffer->priv->do_highlight ? TRUE : FALSE;
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MooTextBuffer *buffer = MOO_TEXT_BUFFER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CHECK_BRACKETS:
|
|
moo_text_buffer_set_check_brackets (buffer, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_BRACKET_MATCH_STYLE:
|
|
moo_text_buffer_set_bracket_match_style (buffer, g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_BRACKET_MISMATCH_STYLE:
|
|
moo_text_buffer_set_bracket_mismatch_style (buffer, g_value_get_boxed (value));
|
|
break;
|
|
|
|
case PROP_LANG:
|
|
moo_text_buffer_set_lang (buffer, g_value_get_boxed (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
MooTextBuffer *buffer = MOO_TEXT_BUFFER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CHECK_BRACKETS:
|
|
g_value_set_boolean (value, buffer->priv->check_brackets);
|
|
break;
|
|
|
|
case PROP_LANG:
|
|
g_value_set_boxed (value, buffer->priv->lang);
|
|
break;
|
|
|
|
case PROP_HAS_TEXT:
|
|
g_value_set_boolean (value, moo_text_buffer_has_text (buffer));
|
|
break;
|
|
|
|
case PROP_HAS_SELECTION:
|
|
g_value_set_boolean (value, moo_text_buffer_has_selection (buffer));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
gboolean
|
|
moo_text_buffer_has_text (MooTextBuffer *buffer)
|
|
{
|
|
GtkTextIter start, end;
|
|
|
|
g_return_val_if_fail (MOO_IS_TEXT_BUFFER (buffer), TRUE);
|
|
|
|
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &start, &end);
|
|
return gtk_text_iter_compare (&start, &end) ? TRUE : FALSE;
|
|
}
|
|
|
|
|
|
gboolean
|
|
moo_text_buffer_has_selection (MooTextBuffer *buffer)
|
|
{
|
|
g_return_val_if_fail (MOO_IS_TEXT_BUFFER (buffer), FALSE);
|
|
return gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (buffer), NULL, NULL);
|
|
}
|
|
|
|
|
|
void
|
|
_moo_text_buffer_ensure_highlight (MooTextBuffer *buffer,
|
|
int first_line,
|
|
int last_line)
|
|
{
|
|
g_return_if_fail (MOO_IS_TEXT_BUFFER (buffer));
|
|
moo_highlighter_apply_tags (buffer->priv->hl, first_line, last_line);
|
|
}
|
|
|
|
|
|
static void
|
|
moo_text_buffer_apply_tag (GtkTextBuffer *buffer,
|
|
GtkTextTag *tag,
|
|
const GtkTextIter *start,
|
|
const GtkTextIter *end)
|
|
{
|
|
if (MOO_IS_SYNTAX_TAG (tag) && !MOO_TEXT_BUFFER(buffer)->priv->may_apply_tag)
|
|
return;
|
|
|
|
GTK_TEXT_BUFFER_CLASS(moo_text_buffer_parent_class)->apply_tag (buffer, tag, start, end);
|
|
}
|
|
|
|
|
|
void
|
|
_moo_text_buffer_apply_syntax_tag (MooTextBuffer *buffer,
|
|
GtkTextTag *tag,
|
|
const GtkTextIter *start,
|
|
const GtkTextIter *end)
|
|
{
|
|
buffer->priv->may_apply_tag = TRUE;
|
|
gtk_text_buffer_apply_tag (GTK_TEXT_BUFFER (buffer), tag, start, end);
|
|
buffer->priv->may_apply_tag = FALSE;
|
|
}
|
|
|
|
|
|
void
|
|
moo_text_buffer_apply_scheme (MooTextBuffer *buffer,
|
|
MooTextStyleScheme *scheme)
|
|
{
|
|
g_return_if_fail (scheme != NULL);
|
|
g_return_if_fail (MOO_IS_TEXT_BUFFER (buffer));
|
|
|
|
moo_text_buffer_set_bracket_match_style (buffer, scheme->bracket_match);
|
|
moo_text_buffer_set_bracket_mismatch_style (buffer, scheme->bracket_mismatch);
|
|
moo_highlighter_apply_scheme (buffer->priv->hl, scheme);
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Matching brackets
|
|
*/
|
|
|
|
#define FIND_BRACKETS_LIMIT 10000
|
|
|
|
static void
|
|
moo_text_buffer_cursor_moved (MooTextBuffer *buffer,
|
|
const GtkTextIter *where)
|
|
{
|
|
GtkTextIter iter[4];
|
|
MooBracketMatchType bracket_match;
|
|
GtkTextTag *tag;
|
|
GtkTextBuffer *text_buffer = GTK_TEXT_BUFFER (buffer);
|
|
int i;
|
|
|
|
g_return_if_fail (where != NULL);
|
|
|
|
switch (buffer->priv->bracket_found)
|
|
{
|
|
case MOO_BRACKET_MATCH_CORRECT:
|
|
tag = buffer->priv->correct_match_tag;
|
|
break;
|
|
case MOO_BRACKET_MATCH_INCORRECT:
|
|
tag = buffer->priv->incorrect_match_tag;
|
|
break;
|
|
default:
|
|
tag = NULL;
|
|
}
|
|
|
|
if (tag)
|
|
{
|
|
for (i = 0; i < 4; ++i)
|
|
gtk_text_buffer_get_iter_at_mark (text_buffer, &iter[i],
|
|
buffer->priv->bracket_mark[i]);
|
|
gtk_text_buffer_remove_tag (text_buffer, tag, &iter[0], &iter[1]);
|
|
gtk_text_buffer_remove_tag (text_buffer, tag, &iter[2], &iter[3]);
|
|
}
|
|
|
|
if (!buffer->priv->check_brackets)
|
|
return;
|
|
|
|
iter[0] = *where;
|
|
|
|
buffer->priv->bracket_found = MOO_BRACKET_MATCH_NONE;
|
|
|
|
if (!moo_text_iter_at_bracket (&iter[0]))
|
|
return;
|
|
|
|
iter[2] = iter[0];
|
|
bracket_match = moo_text_iter_find_matching_bracket (&iter[2], FIND_BRACKETS_LIMIT);
|
|
|
|
buffer->priv->bracket_found = bracket_match;
|
|
|
|
switch (bracket_match)
|
|
{
|
|
case MOO_BRACKET_MATCH_CORRECT:
|
|
tag = buffer->priv->correct_match_tag;
|
|
break;
|
|
case MOO_BRACKET_MATCH_INCORRECT:
|
|
tag = buffer->priv->incorrect_match_tag;
|
|
break;
|
|
default:
|
|
tag = NULL;
|
|
}
|
|
|
|
if (tag != NULL)
|
|
{
|
|
iter[1] = iter[0];
|
|
gtk_text_iter_forward_char (&iter[1]);
|
|
iter[3] = iter[2];
|
|
gtk_text_iter_forward_char (&iter[3]);
|
|
|
|
if (!buffer->priv->bracket_mark[0])
|
|
for (i = 0; i < 4; ++i)
|
|
buffer->priv->bracket_mark[i] =
|
|
gtk_text_buffer_create_mark (text_buffer, NULL, &iter[i], FALSE);
|
|
else
|
|
for (i = 0; i < 4; ++i)
|
|
gtk_text_buffer_move_mark (text_buffer, buffer->priv->bracket_mark[i], &iter[i]);
|
|
|
|
gtk_text_buffer_apply_tag (text_buffer, tag, &iter[0], &iter[1]);
|
|
gtk_text_buffer_apply_tag (text_buffer, tag, &iter[2], &iter[3]);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
moo_text_buffer_set_bracket_match_style (MooTextBuffer *buffer,
|
|
const MooTextStyle *style)
|
|
{
|
|
static MooTextStyle empty;
|
|
|
|
g_return_if_fail (MOO_IS_TEXT_BUFFER (buffer));
|
|
|
|
if (!style)
|
|
style = ∅
|
|
|
|
if (!buffer->priv->correct_match_tag)
|
|
buffer->priv->correct_match_tag =
|
|
gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), NULL, NULL);
|
|
|
|
_moo_style_set_tag_style (style, buffer->priv->correct_match_tag);
|
|
g_object_notify (G_OBJECT (buffer), "bracket-match-style");
|
|
}
|
|
|
|
|
|
void
|
|
moo_text_buffer_set_bracket_mismatch_style (MooTextBuffer *buffer,
|
|
const MooTextStyle *style)
|
|
{
|
|
static MooTextStyle empty;
|
|
|
|
g_return_if_fail (MOO_IS_TEXT_BUFFER (buffer));
|
|
|
|
if (!style)
|
|
style = ∅
|
|
|
|
if (!buffer->priv->incorrect_match_tag)
|
|
buffer->priv->incorrect_match_tag =
|
|
gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), NULL, NULL);
|
|
|
|
_moo_style_set_tag_style (style, buffer->priv->incorrect_match_tag);
|
|
g_object_notify (G_OBJECT (buffer), "bracket-mismatch-style");
|
|
}
|
|
|
|
|
|
static gboolean
|
|
parse_brackets (const char *string,
|
|
gunichar **left_brackets,
|
|
gunichar **right_brackets,
|
|
guint *num)
|
|
{
|
|
long len, i;
|
|
const char *p;
|
|
gunichar *left, *right;
|
|
|
|
g_return_val_if_fail (g_utf8_validate (string, -1, NULL), FALSE);
|
|
len = g_utf8_strlen (string, -1);
|
|
g_return_val_if_fail (len > 0 && (len / 2) * 2 == len, FALSE);
|
|
|
|
len /= 2;
|
|
p = string;
|
|
left = g_new (gunichar, len);
|
|
right = g_new (gunichar, len);
|
|
|
|
for (i = 0; i < len; ++i)
|
|
{
|
|
left[i] = g_utf8_get_char (p);
|
|
p = g_utf8_next_char (p);
|
|
right[i] = g_utf8_get_char (p);
|
|
p = g_utf8_next_char (p);
|
|
}
|
|
|
|
*left_brackets = left;
|
|
*right_brackets = right;
|
|
*num = len;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
void
|
|
moo_text_buffer_set_brackets (MooTextBuffer *buffer,
|
|
const gchar *string)
|
|
{
|
|
g_return_if_fail (MOO_IS_TEXT_BUFFER (buffer));
|
|
|
|
buffer->priv->num_brackets = 0;
|
|
g_free (buffer->priv->left_brackets);
|
|
buffer->priv->left_brackets = NULL;
|
|
g_free (buffer->priv->right_brackets);
|
|
buffer->priv->right_brackets = NULL;
|
|
|
|
if (!string)
|
|
{
|
|
buffer->priv->num_brackets = 3;
|
|
buffer->priv->left_brackets = g_new (gunichar, 3);
|
|
buffer->priv->right_brackets = g_new (gunichar, 3);
|
|
buffer->priv->left_brackets[0] = '(';
|
|
buffer->priv->left_brackets[1] = '{';
|
|
buffer->priv->left_brackets[2] = '[';
|
|
buffer->priv->right_brackets[0] = ')';
|
|
buffer->priv->right_brackets[1] = '}';
|
|
buffer->priv->right_brackets[2] = ']';
|
|
return;
|
|
}
|
|
else if (!string[0])
|
|
{
|
|
return;
|
|
}
|
|
|
|
parse_brackets (string,
|
|
&(buffer->priv->left_brackets),
|
|
&(buffer->priv->right_brackets),
|
|
&(buffer->priv->num_brackets));
|
|
}
|
|
|
|
|
|
void
|
|
moo_text_buffer_set_check_brackets (MooTextBuffer *buffer,
|
|
gboolean check)
|
|
{
|
|
g_return_if_fail (MOO_IS_TEXT_BUFFER (buffer));
|
|
buffer->priv->check_brackets = check ? TRUE : FALSE;
|
|
g_object_notify (G_OBJECT (buffer), "check-brackets");
|
|
}
|
|
|
|
|
|
inline static gboolean
|
|
find (gunichar *vec, guint size, gunichar c)
|
|
{
|
|
guint i;
|
|
|
|
for (i = 0; i < size; ++i)
|
|
if (c == vec[i])
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
gboolean
|
|
moo_text_iter_at_bracket (GtkTextIter *iter)
|
|
{
|
|
gunichar c;
|
|
GtkTextIter b;
|
|
MooTextBuffer *buffer;
|
|
|
|
buffer = MOO_TEXT_BUFFER (gtk_text_iter_get_buffer (iter));
|
|
g_return_val_if_fail (MOO_IS_TEXT_BUFFER (buffer), FALSE);
|
|
|
|
c = gtk_text_iter_get_char (iter);
|
|
|
|
if (find (buffer->priv->left_brackets, buffer->priv->num_brackets, c) ||
|
|
find (buffer->priv->right_brackets, buffer->priv->num_brackets, c))
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
b = *iter;
|
|
|
|
if (!gtk_text_iter_starts_line (&b) && gtk_text_iter_backward_char (&b))
|
|
{
|
|
c = gtk_text_iter_get_char (&b);
|
|
|
|
if (find (buffer->priv->left_brackets, buffer->priv->num_brackets, c) ||
|
|
find (buffer->priv->right_brackets, buffer->priv->num_brackets, c))
|
|
{
|
|
*iter = b;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
MooBracketMatchType
|
|
moo_text_iter_find_matching_bracket (GtkTextIter *iter,
|
|
int limit)
|
|
{
|
|
int addition = 0;
|
|
int count;
|
|
gunichar *same_direction = NULL, *inverse_direction = NULL;
|
|
guint to_find;
|
|
gunichar bracket = gtk_text_iter_get_char (iter);
|
|
gunichar bracket_to_find = 0;
|
|
MooContext *ctx;
|
|
guint stack = 0;
|
|
GtkTextIter b;
|
|
MooTextBuffer *buffer;
|
|
|
|
buffer = MOO_TEXT_BUFFER (gtk_text_iter_get_buffer (iter));
|
|
g_return_val_if_fail (MOO_IS_TEXT_BUFFER (buffer), MOO_BRACKET_MATCH_NOT_AT_BRACKET);
|
|
|
|
for (to_find = 0; to_find < buffer->priv->num_brackets; ++to_find)
|
|
{
|
|
if (bracket == buffer->priv->left_brackets[to_find])
|
|
{
|
|
bracket_to_find = buffer->priv->right_brackets[to_find];
|
|
addition = 1;
|
|
inverse_direction = buffer->priv->right_brackets;
|
|
same_direction = buffer->priv->left_brackets;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (to_find == buffer->priv->num_brackets)
|
|
{
|
|
for (to_find = 0; to_find < buffer->priv->num_brackets; ++to_find)
|
|
{
|
|
if (bracket == buffer->priv->right_brackets[to_find])
|
|
{
|
|
bracket_to_find = buffer->priv->left_brackets[to_find];
|
|
addition = -1;
|
|
same_direction = buffer->priv->right_brackets;
|
|
inverse_direction = buffer->priv->left_brackets;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (to_find == buffer->priv->num_brackets)
|
|
return MOO_BRACKET_MATCH_NOT_AT_BRACKET;
|
|
|
|
ctx = _moo_text_iter_get_context (iter);
|
|
stack = 0;
|
|
b = *iter;
|
|
|
|
if (limit < 0)
|
|
limit = G_MAXINT;
|
|
count = 0;
|
|
|
|
while (gtk_text_iter_forward_chars (&b, addition) && count++ < limit)
|
|
{
|
|
if (ctx == _moo_text_iter_get_context (&b))
|
|
{
|
|
gunichar c = gtk_text_iter_get_char (&b);
|
|
|
|
if (c == bracket_to_find && !stack)
|
|
{
|
|
*iter = b;
|
|
return MOO_BRACKET_MATCH_CORRECT;
|
|
}
|
|
|
|
if (find (same_direction, buffer->priv->num_brackets, c))
|
|
{
|
|
++stack;
|
|
}
|
|
else if (find (inverse_direction, buffer->priv->num_brackets, c))
|
|
{
|
|
if (stack)
|
|
{
|
|
--stack;
|
|
}
|
|
else
|
|
{
|
|
*iter = b;
|
|
return MOO_BRACKET_MATCH_INCORRECT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return MOO_BRACKET_MATCH_NONE;
|
|
}
|