1199 lines
35 KiB
C
1199 lines
35 KiB
C
|
/* ide-highlight-engine.c
|
||
|
*
|
||
|
* Copyright 2015-2019 Christian Hergert <christian@hergert.me>
|
||
|
*
|
||
|
* 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 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||
|
*/
|
||
|
|
||
|
#define G_LOG_DOMAIN "ide-highlight-engine"
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <dazzle.h>
|
||
|
#include <glib/gi18n.h>
|
||
|
#include <gtksourceview/gtksource.h>
|
||
|
#include <libide-plugins.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include "ide-buffer.h"
|
||
|
#include "ide-buffer-private.h"
|
||
|
#include "ide-highlight-engine.h"
|
||
|
#include "ide-highlight-index.h"
|
||
|
#include "ide-highlighter.h"
|
||
|
|
||
|
#define HIGHLIGHT_QUANTA_USEC 5000
|
||
|
#define PRIVATE_TAG_PREFIX "gb-private-tag"
|
||
|
|
||
|
struct _IdeHighlightEngine
|
||
|
{
|
||
|
IdeObject parent_instance;
|
||
|
|
||
|
GWeakRef buffer_wref;
|
||
|
|
||
|
DzlSignalGroup *signal_group;
|
||
|
IdeHighlighter *highlighter;
|
||
|
GSettings *settings;
|
||
|
|
||
|
IdeExtensionAdapter *extension;
|
||
|
|
||
|
GtkTextMark *invalid_begin;
|
||
|
GtkTextMark *invalid_end;
|
||
|
|
||
|
GSList *private_tags;
|
||
|
GSList *public_tags;
|
||
|
|
||
|
gint64 quanta_expiration;
|
||
|
|
||
|
guint work_timeout;
|
||
|
|
||
|
guint enabled : 1;
|
||
|
};
|
||
|
|
||
|
G_DEFINE_FINAL_TYPE (IdeHighlightEngine, ide_highlight_engine, IDE_TYPE_OBJECT)
|
||
|
|
||
|
enum {
|
||
|
PROP_0,
|
||
|
PROP_BUFFER,
|
||
|
PROP_HIGHLIGHTER,
|
||
|
LAST_PROP
|
||
|
};
|
||
|
|
||
|
static GParamSpec *properties [LAST_PROP];
|
||
|
|
||
|
static gboolean
|
||
|
get_invalidation_area (GtkTextIter *begin,
|
||
|
GtkTextIter *end)
|
||
|
{
|
||
|
GtkTextIter begin_tmp;
|
||
|
GtkTextIter end_tmp;
|
||
|
|
||
|
g_assert (begin != NULL);
|
||
|
g_assert (end != NULL);
|
||
|
|
||
|
/*
|
||
|
* Move to the beginning of line.We dont use gtk_text_iter_backward_line
|
||
|
* because if begin is at the beginning of the line we dont want to
|
||
|
* move to the previous line
|
||
|
*/
|
||
|
gtk_text_iter_set_line_offset (begin, 0);
|
||
|
|
||
|
/* Move to the beginning of the next line. */
|
||
|
gtk_text_iter_forward_line (end);
|
||
|
|
||
|
/* Save the original locations. We will need them down the line. */
|
||
|
begin_tmp = *begin;
|
||
|
end_tmp = *end;
|
||
|
|
||
|
/*
|
||
|
* Fordward begin iter character by character until:
|
||
|
* - We reach a non space character
|
||
|
* - We reach end iter
|
||
|
*/
|
||
|
while (g_unichar_isspace (gtk_text_iter_get_char (begin)) &&
|
||
|
gtk_text_iter_compare (begin, &end_tmp) < 0)
|
||
|
gtk_text_iter_forward_char (begin);
|
||
|
|
||
|
|
||
|
/*
|
||
|
* If after moving forward the begin iter, we reached the end iter,
|
||
|
* there is no need to play with the end iter.
|
||
|
*/
|
||
|
if (gtk_text_iter_compare (begin, end) < 0)
|
||
|
{
|
||
|
/*
|
||
|
* Backward end iter character by character until:
|
||
|
* - We reach a non space character
|
||
|
* - We reach begin iter
|
||
|
*/
|
||
|
while (g_unichar_isspace (gtk_text_iter_get_char (end)) &&
|
||
|
gtk_text_iter_compare (end, &begin_tmp) > 0)
|
||
|
gtk_text_iter_backward_char (end);
|
||
|
|
||
|
/*
|
||
|
* If we found the character we are looking for then move one
|
||
|
* character forward in order to include it as the last
|
||
|
* character of the begin - end range.
|
||
|
*/
|
||
|
if (gtk_text_iter_compare (end, &end_tmp) < 0)
|
||
|
gtk_text_iter_forward_char (end);
|
||
|
}
|
||
|
|
||
|
return gtk_text_iter_compare (begin, end) < 0;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
sync_tag_style (GtkSourceStyleScheme *style_scheme,
|
||
|
GtkSourceLanguage *language,
|
||
|
GtkTextTag *tag)
|
||
|
{
|
||
|
g_autofree gchar *foreground = NULL;
|
||
|
g_autofree gchar *background = NULL;
|
||
|
g_autofree gchar *tag_name = NULL;
|
||
|
const char *style_name = NULL;
|
||
|
GtkSourceStyle *style = NULL;
|
||
|
const char *fallback;
|
||
|
gboolean foreground_set = FALSE;
|
||
|
gboolean background_set = FALSE;
|
||
|
gboolean bold = FALSE;
|
||
|
gboolean bold_set = FALSE;
|
||
|
gboolean underline = FALSE;
|
||
|
gboolean underline_set = FALSE;
|
||
|
gboolean italic = FALSE;
|
||
|
gboolean italic_set = FALSE;
|
||
|
gsize tag_name_len;
|
||
|
gsize prefix_len;
|
||
|
|
||
|
g_object_set (tag,
|
||
|
"foreground-set", FALSE,
|
||
|
"background-set", FALSE,
|
||
|
"weight-set", FALSE,
|
||
|
"underline-set", FALSE,
|
||
|
"style-set", FALSE,
|
||
|
NULL);
|
||
|
|
||
|
g_object_get (tag, "name", &tag_name, NULL);
|
||
|
|
||
|
if (tag_name == NULL || style_scheme == NULL)
|
||
|
return;
|
||
|
|
||
|
prefix_len = strlen (PRIVATE_TAG_PREFIX);
|
||
|
tag_name_len = strlen (tag_name);
|
||
|
style_name = tag_name;
|
||
|
|
||
|
/*
|
||
|
* Check if this is a private tag.A tag is private if it starts with
|
||
|
* PRIVATE_TAG_PREFIX "gb-private-tag".
|
||
|
* ex: gb-private-tag:c:boolean
|
||
|
* If the tag is private extract the original style name by moving the string
|
||
|
* strlen (PRIVATE_TAG_PREFIX) + 1 (the colon) characters.
|
||
|
*/
|
||
|
if (tag_name_len > prefix_len && memcmp (tag_name, PRIVATE_TAG_PREFIX, prefix_len) == 0)
|
||
|
style_name = tag_name + prefix_len + 1;
|
||
|
|
||
|
fallback = style_name;
|
||
|
while (style == NULL && fallback != NULL)
|
||
|
{
|
||
|
if (!(style = gtk_source_style_scheme_get_style (style_scheme, fallback)))
|
||
|
{
|
||
|
if (language)
|
||
|
fallback = gtk_source_language_get_style_fallback (language, fallback);
|
||
|
else
|
||
|
fallback = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g_object_get (style,
|
||
|
"background", &background,
|
||
|
"background-set", &background_set,
|
||
|
"foreground", &foreground,
|
||
|
"foreground-set", &foreground_set,
|
||
|
"bold", &bold,
|
||
|
"bold-set", &bold_set,
|
||
|
"pango-underline", &underline,
|
||
|
"underline-set", &underline_set,
|
||
|
"italic", &italic,
|
||
|
"italic-set", &italic_set,
|
||
|
NULL);
|
||
|
|
||
|
if (background_set)
|
||
|
g_object_set (tag, "background", background, NULL);
|
||
|
|
||
|
if (foreground_set)
|
||
|
g_object_set (tag, "foreground", foreground, NULL);
|
||
|
|
||
|
if (bold_set && bold)
|
||
|
g_object_set (tag, "weight", PANGO_WEIGHT_BOLD, NULL);
|
||
|
|
||
|
if (italic_set && italic)
|
||
|
g_object_set (tag, "style", PANGO_STYLE_ITALIC, NULL);
|
||
|
|
||
|
if (underline_set && underline)
|
||
|
g_object_set (tag, "underline", PANGO_UNDERLINE_SINGLE, NULL);
|
||
|
}
|
||
|
|
||
|
static GtkTextTag *
|
||
|
create_tag_from_style (IdeHighlightEngine *self,
|
||
|
const gchar *style_name)
|
||
|
{
|
||
|
g_autoptr(IdeBuffer) buffer = NULL;
|
||
|
GtkSourceStyleScheme *style_scheme;
|
||
|
GtkSourceLanguage *language;
|
||
|
GtkTextTag *tag = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (style_name != NULL);
|
||
|
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
|
||
|
if (buffer != NULL)
|
||
|
{
|
||
|
tag = gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (buffer), style_name, NULL);
|
||
|
gtk_text_tag_set_priority (tag, 0);
|
||
|
|
||
|
style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
|
||
|
language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer));
|
||
|
|
||
|
sync_tag_style (style_scheme, language, tag);
|
||
|
}
|
||
|
|
||
|
return tag;
|
||
|
}
|
||
|
|
||
|
static GtkTextTag *
|
||
|
get_tag_from_style (IdeHighlightEngine *self,
|
||
|
const gchar *style_name,
|
||
|
gboolean private_tag)
|
||
|
{
|
||
|
g_autoptr(IdeBuffer) buffer = NULL;
|
||
|
g_autofree gchar *tmp_style_name = NULL;
|
||
|
GtkTextTagTable *tag_table;
|
||
|
GtkTextTag *tag;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
|
||
|
g_return_val_if_fail (style_name != NULL, NULL);
|
||
|
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
if (buffer == NULL)
|
||
|
return NULL;
|
||
|
|
||
|
/*
|
||
|
* If is private tag prepend the PRIVATE_TAG_PREFIX (gb-private-tag)
|
||
|
* to the string.This is used because tag name is the key used
|
||
|
* for saving tags in GtkTextTagTable and we dont want conflicts between
|
||
|
* public and private tags.
|
||
|
*/
|
||
|
if (private_tag)
|
||
|
tmp_style_name = g_strdup_printf ("%s:%s", PRIVATE_TAG_PREFIX, style_name);
|
||
|
else
|
||
|
tmp_style_name = g_strdup (style_name);
|
||
|
|
||
|
tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (buffer));
|
||
|
tag = gtk_text_tag_table_lookup (tag_table, tmp_style_name);
|
||
|
|
||
|
if (tag == NULL)
|
||
|
{
|
||
|
tag = create_tag_from_style (self, tmp_style_name);
|
||
|
if (private_tag)
|
||
|
self->private_tags = g_slist_prepend (self->private_tags, tag);
|
||
|
else
|
||
|
self->public_tags = g_slist_prepend (self->public_tags, tag);
|
||
|
}
|
||
|
|
||
|
return tag;
|
||
|
}
|
||
|
|
||
|
|
||
|
static IdeHighlightResult
|
||
|
ide_highlight_engine_apply_style (const GtkTextIter *begin,
|
||
|
const GtkTextIter *end,
|
||
|
const gchar *style_name)
|
||
|
{
|
||
|
IdeHighlightEngine *self;
|
||
|
GtkTextBuffer *buffer;
|
||
|
GtkTextTag *tag;
|
||
|
|
||
|
buffer = gtk_text_iter_get_buffer (begin);
|
||
|
self = _ide_buffer_get_highlight_engine (IDE_BUFFER (buffer));
|
||
|
tag = get_tag_from_style (self, style_name, TRUE);
|
||
|
|
||
|
gtk_text_buffer_apply_tag (buffer, tag, begin, end);
|
||
|
|
||
|
if (g_get_monotonic_time () >= self->quanta_expiration)
|
||
|
return IDE_HIGHLIGHT_STOP;
|
||
|
|
||
|
return IDE_HIGHLIGHT_CONTINUE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_highlight_engine_tick (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_autoptr(GtkTextBuffer) buffer = NULL;
|
||
|
GtkTextIter iter;
|
||
|
GtkTextIter invalid_begin;
|
||
|
GtkTextIter invalid_end;
|
||
|
GSList *tags_iter;
|
||
|
|
||
|
IDE_PROBE;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (self->highlighter != NULL);
|
||
|
g_assert (self->invalid_begin != NULL);
|
||
|
g_assert (self->invalid_end != NULL);
|
||
|
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
if (buffer == NULL)
|
||
|
return G_SOURCE_REMOVE;
|
||
|
|
||
|
self->quanta_expiration = g_get_monotonic_time () + HIGHLIGHT_QUANTA_USEC;
|
||
|
|
||
|
gtk_text_buffer_get_iter_at_mark (buffer, &invalid_begin, self->invalid_begin);
|
||
|
gtk_text_buffer_get_iter_at_mark (buffer, &invalid_end, self->invalid_end);
|
||
|
|
||
|
IDE_TRACE_MSG ("Highlight Range [%u:%u,%u:%u] (%s)",
|
||
|
gtk_text_iter_get_line (&invalid_begin),
|
||
|
gtk_text_iter_get_line_offset (&invalid_begin),
|
||
|
gtk_text_iter_get_line (&invalid_end),
|
||
|
gtk_text_iter_get_line_offset (&invalid_end),
|
||
|
G_OBJECT_TYPE_NAME (self->highlighter));
|
||
|
|
||
|
if (gtk_text_iter_compare (&invalid_begin, &invalid_end) >= 0)
|
||
|
IDE_GOTO (up_to_date);
|
||
|
|
||
|
/* Clear all our tags */
|
||
|
for (tags_iter = self->private_tags; tags_iter; tags_iter = tags_iter->next)
|
||
|
gtk_text_buffer_remove_tag (buffer,
|
||
|
GTK_TEXT_TAG (tags_iter->data),
|
||
|
&invalid_begin,
|
||
|
&invalid_end);
|
||
|
|
||
|
iter = invalid_begin;
|
||
|
|
||
|
ide_highlighter_update (self->highlighter, ide_highlight_engine_apply_style,
|
||
|
&invalid_begin, &invalid_end, &iter);
|
||
|
|
||
|
if (gtk_text_iter_compare (&iter, &invalid_end) >= 0)
|
||
|
IDE_GOTO (up_to_date);
|
||
|
|
||
|
/* Stop processing until further instruction if no movement was made */
|
||
|
if (gtk_text_iter_equal (&iter, &invalid_begin))
|
||
|
return G_SOURCE_REMOVE;
|
||
|
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_begin, &iter);
|
||
|
|
||
|
return G_SOURCE_CONTINUE;
|
||
|
|
||
|
up_to_date:
|
||
|
gtk_text_buffer_get_start_iter (buffer, &iter);
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_begin, &iter);
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_end, &iter);
|
||
|
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_highlight_engine_work_timeout_handler (gpointer data)
|
||
|
{
|
||
|
IdeHighlightEngine *self = data;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
|
||
|
if (self->enabled)
|
||
|
{
|
||
|
if (ide_highlight_engine_tick (self))
|
||
|
return G_SOURCE_CONTINUE;
|
||
|
}
|
||
|
|
||
|
self->work_timeout = 0;
|
||
|
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_queue_work (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_autoptr(GtkTextBuffer) buffer = NULL;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
if (self->highlighter == NULL || buffer == NULL || self->work_timeout != 0)
|
||
|
return;
|
||
|
|
||
|
/*
|
||
|
* NOTE: It would be really nice if we could use the GdkFrameClock here to
|
||
|
* drive the next update instead of a timeout. It's possible that our
|
||
|
* callback could get scheduled right before the frame processing would
|
||
|
* begin. However, since that gets driven by something like a Wayland
|
||
|
* callback, it won't yet be scheduled. So instead our function gets
|
||
|
* called and we potentially cause a frame to drop.
|
||
|
*/
|
||
|
|
||
|
self->work_timeout = gdk_threads_add_idle_full (G_PRIORITY_LOW + 1,
|
||
|
ide_highlight_engine_work_timeout_handler,
|
||
|
self,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_highlight_engine_advance:
|
||
|
* @self: a #IdeHighlightEngine
|
||
|
*
|
||
|
* This function is useful for #IdeHighlighter implementations that need to
|
||
|
* asynchronously do work to process the highlighting.
|
||
|
*
|
||
|
* If they return from their update function without advancing, nothing will
|
||
|
* happen until they call this method to proceed.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_highlight_engine_advance (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
|
||
|
ide_highlight_engine_queue_work (self);
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
invalidate_and_highlight (IdeHighlightEngine *self,
|
||
|
GtkTextIter *begin,
|
||
|
GtkTextIter *end)
|
||
|
{
|
||
|
GtkTextBuffer *text_buffer;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (begin != NULL);
|
||
|
g_assert (end != NULL);
|
||
|
|
||
|
if (!self->enabled)
|
||
|
return FALSE;
|
||
|
|
||
|
text_buffer = gtk_text_iter_get_buffer (begin);
|
||
|
|
||
|
if (get_invalidation_area (begin, end))
|
||
|
{
|
||
|
GtkTextIter begin_tmp;
|
||
|
GtkTextIter end_tmp;
|
||
|
|
||
|
gtk_text_buffer_get_iter_at_mark (text_buffer, &begin_tmp, self->invalid_begin);
|
||
|
gtk_text_buffer_get_iter_at_mark (text_buffer, &end_tmp, self->invalid_end);
|
||
|
|
||
|
if (gtk_text_iter_equal (&begin_tmp, &end_tmp))
|
||
|
{
|
||
|
gtk_text_buffer_move_mark (text_buffer, self->invalid_begin, begin);
|
||
|
gtk_text_buffer_move_mark (text_buffer, self->invalid_end, end);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (gtk_text_iter_compare (begin, &begin_tmp) < 0)
|
||
|
gtk_text_buffer_move_mark (text_buffer, self->invalid_begin, begin);
|
||
|
if (gtk_text_iter_compare (end, &end_tmp) > 0)
|
||
|
gtk_text_buffer_move_mark (text_buffer, self->invalid_end, end);
|
||
|
}
|
||
|
|
||
|
ide_highlight_engine_queue_work (self);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_reload (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_autoptr(GtkTextBuffer) buffer = NULL;
|
||
|
GtkTextIter begin;
|
||
|
GtkTextIter end;
|
||
|
GSList *iter;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
|
||
|
dzl_clear_source (&self->work_timeout);
|
||
|
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
if (buffer == NULL)
|
||
|
IDE_EXIT;
|
||
|
|
||
|
gtk_text_buffer_get_bounds (buffer, &begin, &end);
|
||
|
|
||
|
/*
|
||
|
* Invalidate the whole buffer.
|
||
|
*/
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_begin, &begin);
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_end, &end);
|
||
|
|
||
|
/*
|
||
|
* Remove our highlight tags from the buffer.
|
||
|
*/
|
||
|
for (iter = self->private_tags; iter; iter = iter->next)
|
||
|
gtk_text_buffer_remove_tag (buffer, iter->data, &begin, &end);
|
||
|
g_clear_pointer (&self->private_tags, g_slist_free);
|
||
|
|
||
|
for (iter = self->public_tags; iter; iter = iter->next)
|
||
|
gtk_text_buffer_remove_tag (buffer, iter->data, &begin, &end);
|
||
|
g_clear_pointer (&self->public_tags, g_slist_free);
|
||
|
|
||
|
if (self->highlighter == NULL)
|
||
|
IDE_EXIT;
|
||
|
|
||
|
ide_highlight_engine_queue_work (self);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_set_highlighter (IdeHighlightEngine *self,
|
||
|
IdeHighlighter *highlighter)
|
||
|
{
|
||
|
g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_return_if_fail (!highlighter || IDE_IS_HIGHLIGHTER (highlighter));
|
||
|
|
||
|
if (g_set_object (&self->highlighter, highlighter))
|
||
|
{
|
||
|
if (highlighter != NULL)
|
||
|
{
|
||
|
IDE_HIGHLIGHTER_GET_IFACE (highlighter)->set_engine (highlighter, self);
|
||
|
ide_highlighter_load (highlighter);
|
||
|
}
|
||
|
|
||
|
ide_highlight_engine_reload (self);
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HIGHLIGHTER]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine__buffer_insert_text_cb (IdeHighlightEngine *self,
|
||
|
GtkTextIter *location,
|
||
|
gchar *text,
|
||
|
gint len,
|
||
|
IdeBuffer *buffer)
|
||
|
{
|
||
|
GtkTextIter begin;
|
||
|
GtkTextIter end;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (location);
|
||
|
g_assert (text);
|
||
|
g_assert (IDE_IS_BUFFER (buffer));
|
||
|
|
||
|
if (!self->enabled)
|
||
|
IDE_EXIT;
|
||
|
|
||
|
/*
|
||
|
* Backward the begin iter len characters from location
|
||
|
* (location points to the end of the string) in order to get
|
||
|
* the iter position where our inserted text was started.
|
||
|
*/
|
||
|
begin = *location;
|
||
|
gtk_text_iter_backward_chars (&begin, g_utf8_strlen (text, len));
|
||
|
|
||
|
end = *location;
|
||
|
|
||
|
invalidate_and_highlight (self, &begin, &end);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine__buffer_delete_range_cb (IdeHighlightEngine *self,
|
||
|
GtkTextIter *range_begin,
|
||
|
GtkTextIter *range_end,
|
||
|
IdeBuffer *buffer)
|
||
|
{
|
||
|
GtkTextIter begin;
|
||
|
GtkTextIter end;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (range_begin);
|
||
|
g_assert (IDE_IS_BUFFER (buffer));
|
||
|
|
||
|
if (!self->enabled)
|
||
|
IDE_EXIT;
|
||
|
|
||
|
/*
|
||
|
* No need to use the range_end since everything that
|
||
|
* was after range_end will now be after range_begin
|
||
|
*/
|
||
|
begin = *range_begin;
|
||
|
end = *range_begin;
|
||
|
|
||
|
invalidate_and_highlight (self, &begin, &end);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine__notify_language_cb (IdeHighlightEngine *self,
|
||
|
GParamSpec *pspec,
|
||
|
IdeBuffer *buffer)
|
||
|
{
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (IDE_IS_BUFFER (buffer));
|
||
|
|
||
|
if (self->extension != NULL)
|
||
|
{
|
||
|
GtkSourceLanguage *language;
|
||
|
const gchar *lang_id = NULL;
|
||
|
|
||
|
if ((language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer))))
|
||
|
lang_id = gtk_source_language_get_id (language);
|
||
|
|
||
|
ide_extension_adapter_set_value (self->extension, lang_id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine__notify_style_scheme_cb (IdeHighlightEngine *self,
|
||
|
GParamSpec *pspec,
|
||
|
IdeBuffer *buffer)
|
||
|
{
|
||
|
GtkSourceStyleScheme *style_scheme;
|
||
|
GtkSourceLanguage *language;
|
||
|
GSList *iter;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (IDE_IS_BUFFER (buffer));
|
||
|
|
||
|
style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
|
||
|
language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer));
|
||
|
|
||
|
for (iter = self->private_tags; iter; iter = iter->next)
|
||
|
sync_tag_style (style_scheme, language, iter->data);
|
||
|
for (iter = self->public_tags; iter; iter = iter->next)
|
||
|
sync_tag_style (style_scheme, language, iter->data);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_highlight_engine_clear (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_autoptr(GtkTextBuffer) buffer = NULL;
|
||
|
GtkTextIter begin;
|
||
|
GtkTextIter end;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
|
||
|
if (buffer != NULL)
|
||
|
{
|
||
|
gtk_text_buffer_get_bounds (buffer, &begin, &end);
|
||
|
|
||
|
for (const GSList *iter = self->public_tags; iter; iter = iter->next)
|
||
|
{
|
||
|
GtkTextTag *tag = iter->data;
|
||
|
gtk_text_buffer_remove_tag (buffer, tag, &begin, &end);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine__bind_buffer_cb (IdeHighlightEngine *self,
|
||
|
IdeBuffer *buffer,
|
||
|
DzlSignalGroup *group)
|
||
|
{
|
||
|
GtkTextBuffer *text_buffer = (GtkTextBuffer *)buffer;
|
||
|
GtkTextIter begin;
|
||
|
GtkTextIter end;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (IDE_IS_BUFFER (buffer));
|
||
|
g_assert (DZL_IS_SIGNAL_GROUP (group));
|
||
|
g_assert (self->invalid_begin == NULL);
|
||
|
g_assert (self->invalid_end == NULL);
|
||
|
|
||
|
g_weak_ref_set (&self->buffer_wref, buffer);
|
||
|
|
||
|
gtk_text_buffer_get_bounds (text_buffer, &begin, &end);
|
||
|
|
||
|
self->invalid_begin = gtk_text_buffer_create_mark (text_buffer, NULL, &begin, TRUE);
|
||
|
self->invalid_end = gtk_text_buffer_create_mark (text_buffer, NULL, &end, FALSE);
|
||
|
|
||
|
/* We can hold a full reference to the text marks, without
|
||
|
* taking a reference to the buffer. We want to avoid a reference
|
||
|
* to the buffer for cyclic reasons.
|
||
|
*/
|
||
|
g_object_ref (self->invalid_begin);
|
||
|
g_object_ref (self->invalid_end);
|
||
|
|
||
|
ide_highlight_engine__notify_style_scheme_cb (self, NULL, buffer);
|
||
|
ide_highlight_engine__notify_language_cb (self, NULL, buffer);
|
||
|
|
||
|
ide_highlight_engine_reload (self);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine__unbind_buffer_cb (IdeHighlightEngine *self,
|
||
|
DzlSignalGroup *group)
|
||
|
{
|
||
|
g_autoptr(GtkTextBuffer) text_buffer = NULL;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (DZL_IS_SIGNAL_GROUP (group));
|
||
|
|
||
|
text_buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
|
||
|
dzl_clear_source (&self->work_timeout);
|
||
|
|
||
|
if (text_buffer != NULL)
|
||
|
{
|
||
|
g_autoptr(GSList) private_tags = NULL;
|
||
|
g_autoptr(GSList) public_tags = NULL;
|
||
|
GtkTextTagTable *tag_table;
|
||
|
GtkTextIter begin;
|
||
|
GtkTextIter end;
|
||
|
|
||
|
tag_table = gtk_text_buffer_get_tag_table (text_buffer);
|
||
|
|
||
|
gtk_text_buffer_delete_mark (text_buffer, self->invalid_begin);
|
||
|
gtk_text_buffer_delete_mark (text_buffer, self->invalid_end);
|
||
|
|
||
|
gtk_text_buffer_get_bounds (text_buffer, &begin, &end);
|
||
|
|
||
|
private_tags = g_steal_pointer (&self->private_tags);
|
||
|
public_tags = g_steal_pointer (&self->public_tags);
|
||
|
|
||
|
for (const GSList *iter = private_tags; iter; iter = iter->next)
|
||
|
{
|
||
|
gtk_text_buffer_remove_tag (text_buffer, iter->data, &begin, &end);
|
||
|
gtk_text_tag_table_remove (tag_table, iter->data);
|
||
|
}
|
||
|
|
||
|
for (const GSList *iter = public_tags; iter; iter = iter->next)
|
||
|
{
|
||
|
gtk_text_buffer_remove_tag (text_buffer, iter->data, &begin, &end);
|
||
|
gtk_text_tag_table_remove (tag_table, iter->data);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g_clear_pointer (&self->public_tags, g_slist_free);
|
||
|
g_clear_pointer (&self->private_tags, g_slist_free);
|
||
|
|
||
|
g_clear_object (&self->invalid_begin);
|
||
|
g_clear_object (&self->invalid_end);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_set_buffer (IdeHighlightEngine *self,
|
||
|
IdeBuffer *buffer)
|
||
|
{
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (!buffer || GTK_IS_TEXT_BUFFER (buffer));
|
||
|
|
||
|
/* We can get GtkSourceBuffer intermittently here. */
|
||
|
if (!buffer || IDE_IS_BUFFER (buffer))
|
||
|
{
|
||
|
dzl_signal_group_set_target (self->signal_group, buffer);
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUFFER]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_settings_changed (IdeHighlightEngine *self,
|
||
|
const gchar *key,
|
||
|
GSettings *settings)
|
||
|
{
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (G_IS_SETTINGS (settings));
|
||
|
|
||
|
if (g_settings_get_boolean (settings, "semantic-highlighting"))
|
||
|
{
|
||
|
self->enabled = TRUE;
|
||
|
ide_highlight_engine_rebuild (self);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
self->enabled = FALSE;
|
||
|
ide_highlight_engine_clear (self);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine__notify_extension (IdeHighlightEngine *self,
|
||
|
GParamSpec *pspec,
|
||
|
IdeExtensionAdapter *adapter)
|
||
|
{
|
||
|
IdeHighlighter *highlighter;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
|
||
|
|
||
|
highlighter = ide_extension_adapter_get_extension (adapter);
|
||
|
g_return_if_fail (!highlighter || IDE_IS_HIGHLIGHTER (highlighter));
|
||
|
|
||
|
ide_highlight_engine_set_highlighter (self, highlighter);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_parent_set (IdeObject *object,
|
||
|
IdeObject *parent)
|
||
|
{
|
||
|
IdeHighlightEngine *self = (IdeHighlightEngine *)object;
|
||
|
|
||
|
g_assert (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_assert (!parent || IDE_IS_OBJECT (parent));
|
||
|
|
||
|
if (parent == NULL)
|
||
|
{
|
||
|
g_clear_object (&self->extension);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
self->extension = ide_extension_adapter_new (IDE_OBJECT (self),
|
||
|
NULL,
|
||
|
IDE_TYPE_HIGHLIGHTER,
|
||
|
"Highlighter-Languages",
|
||
|
NULL);
|
||
|
g_signal_connect_object (self->extension,
|
||
|
"notify::extension",
|
||
|
G_CALLBACK (ide_highlight_engine__notify_extension),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_dispose (GObject *object)
|
||
|
{
|
||
|
IdeHighlightEngine *self = (IdeHighlightEngine *)object;
|
||
|
|
||
|
g_weak_ref_set (&self->buffer_wref, NULL);
|
||
|
g_clear_object (&self->signal_group);
|
||
|
g_clear_object (&self->extension);
|
||
|
g_clear_object (&self->highlighter);
|
||
|
g_clear_object (&self->settings);
|
||
|
|
||
|
G_OBJECT_CLASS (ide_highlight_engine_parent_class)->dispose (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_finalize (GObject *object)
|
||
|
{
|
||
|
IdeHighlightEngine *self = (IdeHighlightEngine *)object;
|
||
|
|
||
|
g_weak_ref_clear (&self->buffer_wref);
|
||
|
|
||
|
G_OBJECT_CLASS (ide_highlight_engine_parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_get_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
IdeHighlightEngine *self = IDE_HIGHLIGHT_ENGINE (object);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_BUFFER:
|
||
|
g_value_set_object (value, ide_highlight_engine_get_buffer (self));
|
||
|
break;
|
||
|
|
||
|
case PROP_HIGHLIGHTER:
|
||
|
g_value_set_object (value, ide_highlight_engine_get_highlighter (self));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_set_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
const GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
IdeHighlightEngine *self = IDE_HIGHLIGHT_ENGINE (object);
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_BUFFER:
|
||
|
ide_highlight_engine_set_buffer (self, g_value_get_object (value));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_class_init (IdeHighlightEngineClass *klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
|
||
|
|
||
|
object_class->dispose = ide_highlight_engine_dispose;
|
||
|
object_class->finalize = ide_highlight_engine_finalize;
|
||
|
object_class->get_property = ide_highlight_engine_get_property;
|
||
|
object_class->set_property = ide_highlight_engine_set_property;
|
||
|
|
||
|
i_object_class->parent_set = ide_highlight_engine_parent_set;
|
||
|
|
||
|
properties [PROP_BUFFER] =
|
||
|
g_param_spec_object ("buffer",
|
||
|
"Buffer",
|
||
|
"The buffer to highlight.",
|
||
|
IDE_TYPE_BUFFER,
|
||
|
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
properties [PROP_HIGHLIGHTER] =
|
||
|
g_param_spec_object ("highlighter",
|
||
|
"Highlighter",
|
||
|
"The highlighter to use for type information.",
|
||
|
IDE_TYPE_HIGHLIGHTER,
|
||
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
g_object_class_install_properties (object_class, LAST_PROP, properties);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_highlight_engine_init (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_weak_ref_init (&self->buffer_wref, NULL);
|
||
|
|
||
|
self->settings = g_settings_new ("org.gnome.builder.code-insight");
|
||
|
self->enabled = g_settings_get_boolean (self->settings, "semantic-highlighting");
|
||
|
self->signal_group = dzl_signal_group_new (IDE_TYPE_BUFFER);
|
||
|
|
||
|
dzl_signal_group_connect_object (self->signal_group,
|
||
|
"insert-text",
|
||
|
G_CALLBACK (ide_highlight_engine__buffer_insert_text_cb),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
|
||
|
|
||
|
dzl_signal_group_connect_object (self->signal_group,
|
||
|
"delete-range",
|
||
|
G_CALLBACK (ide_highlight_engine__buffer_delete_range_cb),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
|
||
|
|
||
|
dzl_signal_group_connect_object (self->signal_group,
|
||
|
"notify::language",
|
||
|
G_CALLBACK (ide_highlight_engine__notify_language_cb),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
|
||
|
dzl_signal_group_connect_object (self->signal_group,
|
||
|
"notify::style-scheme",
|
||
|
G_CALLBACK (ide_highlight_engine__notify_style_scheme_cb),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
|
||
|
g_signal_connect_object (self->signal_group,
|
||
|
"bind",
|
||
|
G_CALLBACK (ide_highlight_engine__bind_buffer_cb),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
|
||
|
g_signal_connect_object (self->signal_group,
|
||
|
"unbind",
|
||
|
G_CALLBACK (ide_highlight_engine__unbind_buffer_cb),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
|
||
|
g_signal_connect_object (self->settings,
|
||
|
"changed::semantic-highlighting",
|
||
|
G_CALLBACK (ide_highlight_engine_settings_changed),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
}
|
||
|
|
||
|
IdeHighlightEngine *
|
||
|
ide_highlight_engine_new (IdeBuffer *buffer)
|
||
|
{
|
||
|
IdeHighlightEngine *self;
|
||
|
IdeObjectBox *box;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_BUFFER (buffer), NULL);
|
||
|
|
||
|
self = g_object_new (IDE_TYPE_HIGHLIGHT_ENGINE,
|
||
|
"buffer", buffer,
|
||
|
NULL);
|
||
|
|
||
|
box = ide_object_box_from_object (G_OBJECT (buffer));
|
||
|
ide_object_append (IDE_OBJECT (box), IDE_OBJECT (self));
|
||
|
|
||
|
return g_steal_pointer (&self);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_highlight_engine_get_highlighter:
|
||
|
* @self: an #IdeHighlightEngine.
|
||
|
*
|
||
|
* Gets the IdeHighlightEngine:highlighter property.
|
||
|
*
|
||
|
* Returns: (transfer none): An #IdeHighlighter.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
IdeHighlighter *
|
||
|
ide_highlight_engine_get_highlighter (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
|
||
|
|
||
|
return self->highlighter;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_highlight_engine_get_buffer:
|
||
|
* @self: an #IdeHighlightEngine.
|
||
|
*
|
||
|
* Gets the IdeHighlightEngine:buffer property.
|
||
|
*
|
||
|
* Returns: (transfer none): An #IdeBuffer.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
IdeBuffer *
|
||
|
ide_highlight_engine_get_buffer (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_autoptr(IdeBuffer) buffer = NULL;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self), NULL);
|
||
|
|
||
|
/* We don't need the "thread-safety" provided by GWeakRef here,
|
||
|
* (where it gives us a new object reference). It is safe to
|
||
|
* return a borrowed reference instead.
|
||
|
*/
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_highlight_engine_rebuild (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_autoptr(GtkTextBuffer) buffer = NULL;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
|
||
|
if (buffer != NULL)
|
||
|
{
|
||
|
GtkTextIter begin;
|
||
|
GtkTextIter end;
|
||
|
|
||
|
gtk_text_buffer_get_bounds (buffer, &begin, &end);
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_begin, &begin);
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_end, &end);
|
||
|
|
||
|
ide_highlight_engine_queue_work (self);
|
||
|
}
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_highlight_engine_invalidate:
|
||
|
* @self: An #IdeHighlightEngine.
|
||
|
* @begin: the beginning of the range to invalidate
|
||
|
* @end: the end of the range to invalidate
|
||
|
*
|
||
|
* This function will extend the invalidated range of the buffer to include
|
||
|
* the range of @begin to @end.
|
||
|
*
|
||
|
* The highlighter will be queued to interactively update the invalidated
|
||
|
* region.
|
||
|
*
|
||
|
* Updating the invalidated region of the buffer may take some time, as it is
|
||
|
* important that the highlighter does not block for more than 1-2 milliseconds
|
||
|
* to avoid dropping frames.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_highlight_engine_invalidate (IdeHighlightEngine *self,
|
||
|
const GtkTextIter *begin,
|
||
|
const GtkTextIter *end)
|
||
|
{
|
||
|
GtkTextBuffer *buffer;
|
||
|
GtkTextIter mark_begin;
|
||
|
GtkTextIter mark_end;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_return_if_fail (begin != NULL);
|
||
|
g_return_if_fail (end != NULL);
|
||
|
|
||
|
buffer = gtk_text_iter_get_buffer (begin);
|
||
|
|
||
|
gtk_text_buffer_get_iter_at_mark (buffer, &mark_begin, self->invalid_begin);
|
||
|
gtk_text_buffer_get_iter_at_mark (buffer, &mark_end, self->invalid_end);
|
||
|
|
||
|
if (gtk_text_iter_equal (&mark_begin, &mark_end))
|
||
|
{
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_begin, begin);
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_end, end);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (gtk_text_iter_compare (begin, &mark_begin) < 0)
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_begin, begin);
|
||
|
if (gtk_text_iter_compare (end, &mark_end) > 0)
|
||
|
gtk_text_buffer_move_mark (buffer, self->invalid_end, end);
|
||
|
}
|
||
|
|
||
|
ide_highlight_engine_queue_work (self);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_highlight_engine_get_style:
|
||
|
* @self: the #IdeHighlightEngine
|
||
|
* @style_name: the name of the style to retrieve
|
||
|
*
|
||
|
* A #GtkTextTag for @style_name.
|
||
|
*
|
||
|
* Returns: (transfer none): a #GtkTextTag.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
GtkTextTag *
|
||
|
ide_highlight_engine_get_style (IdeHighlightEngine *self,
|
||
|
const gchar *style_name)
|
||
|
{
|
||
|
return get_tag_from_style (self, style_name, FALSE);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_highlight_engine_pause (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
|
||
|
dzl_signal_group_block (self->signal_group);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
ide_highlight_engine_unpause (IdeHighlightEngine *self)
|
||
|
{
|
||
|
g_autoptr(IdeBuffer) buffer = NULL;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_HIGHLIGHT_ENGINE (self));
|
||
|
g_return_if_fail (self->signal_group != NULL);
|
||
|
|
||
|
dzl_signal_group_unblock (self->signal_group);
|
||
|
|
||
|
buffer = g_weak_ref_get (&self->buffer_wref);
|
||
|
|
||
|
if (buffer != NULL)
|
||
|
{
|
||
|
/* Notify of some blocked signals */
|
||
|
ide_highlight_engine__notify_style_scheme_cb (self, NULL, buffer);
|
||
|
ide_highlight_engine__notify_language_cb (self, NULL, buffer);
|
||
|
|
||
|
ide_highlight_engine_reload (self);
|
||
|
}
|
||
|
}
|