gem-graph-client/libide/code/ide-highlight-engine.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);
}
}