gem-graph-client/libide/sourceview/ide-snippet.c

1426 lines
35 KiB
C

/* ide-snippet.c
*
* Copyright 2013-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-snippet"
#include "config.h"
#include <dazzle.h>
#include <glib/gi18n.h>
#include "ide-completion-proposal.h"
#include "ide-snippet.h"
#include "ide-snippet-private.h"
#include "ide-snippet-chunk.h"
#include "ide-snippet-context.h"
/**
* SECTION:ide-snippet
* @title: IdeSnippet
* @short_description: A snippet to be inserted into a file
*
* The #IdeSnippet represents a single snippet that may be inserted
* into the #IdeSourceView.
*
* Since: 3.32
*/
#define TAG_SNIPPET_TAB_STOP "snippet::tab-stop"
struct _IdeSnippet
{
GObject parent_instance;
IdeSnippetContext *snippet_context;
GtkTextBuffer *buffer;
GPtrArray *chunks;
GArray *runs;
GtkTextMark *mark_begin;
GtkTextMark *mark_end;
gchar *trigger;
const gchar *language;
gchar *description;
gint tab_stop;
gint max_tab_stop;
gint current_chunk;
guint inserted : 1;
};
enum {
PROP_0,
PROP_BUFFER,
PROP_DESCRIPTION,
PROP_LANGUAGE,
PROP_MARK_BEGIN,
PROP_MARK_END,
PROP_TAB_STOP,
PROP_TRIGGER,
LAST_PROP
};
G_DEFINE_FINAL_TYPE (IdeSnippet, ide_snippet, G_TYPE_OBJECT)
DZL_DEFINE_COUNTER (instances, "Snippets", "N Snippets", "Number of IdeSnippet instances.");
static GParamSpec * properties[LAST_PROP];
/**
* ide_snippet_new:
* @trigger: (nullable): the trigger word
* @language: (nullable): the source language
*
* Creates a new #IdeSnippet
*
* Returns: (transfer full): A new #IdeSnippet
*
* Since: 3.32
*/
IdeSnippet *
ide_snippet_new (const gchar *trigger,
const gchar *language)
{
return g_object_new (IDE_TYPE_SNIPPET,
"trigger", trigger,
"language", language,
NULL);
}
/**
* ide_snippet_copy:
* @self: an #IdeSnippet
*
* Does a deep copy of the snippet.
*
* Returns: (transfer full): An #IdeSnippet.
*
* Since: 3.32
*/
IdeSnippet *
ide_snippet_copy (IdeSnippet *self)
{
IdeSnippetChunk *chunk;
IdeSnippet *ret;
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
ret = g_object_new (IDE_TYPE_SNIPPET,
"trigger", self->trigger,
"language", self->language,
"description", self->description,
NULL);
for (guint i = 0; i < self->chunks->len; i++)
{
chunk = g_ptr_array_index (self->chunks, i);
chunk = ide_snippet_chunk_copy (chunk);
ide_snippet_add_chunk (ret, chunk);
g_object_unref (chunk);
}
return ret;
}
/**
* ide_snippet_get_tab_stop:
* @self: a #IdeSnippet
*
* Gets the current tab stop for the snippet. This is changed
* as the user Tab's through the edit points.
*
* Returns: The tab stop, or -1 if unset.
*
* Since: 3.32
*/
gint
ide_snippet_get_tab_stop (IdeSnippet *self)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), -1);
return self->tab_stop;
}
/**
* ide_snippet_get_n_chunks:
* @self: a #IdeSnippet
*
* Gets the number of chunks in the snippet. Not all chunks
* are editable.
*
* Returns: The number of chunks.
*
* Since: 3.32
*/
guint
ide_snippet_get_n_chunks (IdeSnippet *self)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), 0);
return self->chunks->len;
}
/**
* ide_snippet_get_nth_chunk:
* @self: an #IdeSnippet
* @n: the nth chunk to get
*
* Gets the chunk at @n.
*
* Returns: (transfer none): an #IdeSnippetChunk
*
* Since: 3.32
*/
IdeSnippetChunk *
ide_snippet_get_nth_chunk (IdeSnippet *self,
guint n)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), 0);
if (n < self->chunks->len)
return g_ptr_array_index (self->chunks, n);
return NULL;
}
/**
* ide_snippet_get_trigger:
* @self: a #IdeSnippet
*
* Gets the trigger for the source snippet
*
* Returns: (nullable): A trigger if specified
*
* Since: 3.32
*/
const gchar *
ide_snippet_get_trigger (IdeSnippet *self)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
return self->trigger;
}
/**
* ide_snippet_set_trigger:
* @self: a #IdeSnippet
* @trigger: the trigger word
*
* Sets the trigger for the snippet.
*
* Since: 3.32
*/
void
ide_snippet_set_trigger (IdeSnippet *self,
const gchar *trigger)
{
g_return_if_fail (IDE_IS_SNIPPET (self));
if (self->trigger != trigger)
{
g_free (self->trigger);
self->trigger = g_strdup (trigger);
}
}
/**
* ide_snippet_get_language:
* @self: a #IdeSnippet
*
* Gets the language used for the source snippet.
*
* The language identifier matches the #GtkSourceLanguage:id
* property.
*
* Returns: the language identifier
*
* Since: 3.32
*/
const gchar *
ide_snippet_get_language (IdeSnippet *self)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
return self->language;
}
/**
* ide_snippet_set_language:
* @self: a #IdeSnippet
*
* Sets the language identifier for the snippet.
*
* This should match the #GtkSourceLanguage:id identifier.
*
* Since: 3.32
*/
void
ide_snippet_set_language (IdeSnippet *self,
const gchar *language)
{
g_return_if_fail (IDE_IS_SNIPPET (self));
language = g_intern_string (language);
if (self->language != language)
{
self->language = language;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_LANGUAGE]);
}
}
/**
* ide_snippet_get_description:
* @self: a #IdeSnippet
*
* Gets the description for the snippet.
*
* Since: 3.32
*/
const gchar *
ide_snippet_get_description (IdeSnippet *self)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
return self->description;
}
/**
* ide_snippet_set_description:
* @self: a #IdeSnippet
* @description: the snippet description
*
* Sets the description for the snippet.
*
* Since: 3.32
*/
void
ide_snippet_set_description (IdeSnippet *self,
const gchar *description)
{
g_return_if_fail (IDE_IS_SNIPPET (self));
if (self->description != description)
{
g_free (self->description);
self->description = g_strdup (description);
}
}
static gint
ide_snippet_get_offset (IdeSnippet *self,
GtkTextIter *iter)
{
GtkTextIter begin;
gint ret;
g_return_val_if_fail (IDE_IS_SNIPPET (self), 0);
g_return_val_if_fail (iter, 0);
gtk_text_buffer_get_iter_at_mark (self->buffer, &begin, self->mark_begin);
ret = gtk_text_iter_get_offset (iter) - gtk_text_iter_get_offset (&begin);
ret = MAX (0, ret);
return ret;
}
static gint
ide_snippet_get_index (IdeSnippet *self,
GtkTextIter *iter)
{
gint offset;
gint run;
g_return_val_if_fail (IDE_IS_SNIPPET (self), 0);
g_return_val_if_fail (iter, 0);
offset = ide_snippet_get_offset (self, iter);
for (guint i = 0; i < self->runs->len; i++)
{
run = g_array_index (self->runs, gint, i);
offset -= run;
if (offset <= 0)
{
/*
* HACK: This is the central part of the hack by using offsets
* instead of textmarks (which gives us lots of gravity grief).
* We guess which snippet it is based on the current chunk.
*/
if (self->current_chunk > -1 && (i + 1) == (guint)self->current_chunk)
return (i + 1);
return i;
}
}
return (self->runs->len - 1);
}
static gboolean
ide_snippet_within_bounds (IdeSnippet *self,
GtkTextIter *iter)
{
GtkTextIter begin;
GtkTextIter end;
gboolean ret;
g_return_val_if_fail (IDE_IS_SNIPPET (self), FALSE);
g_return_val_if_fail (iter, FALSE);
gtk_text_buffer_get_iter_at_mark (self->buffer, &begin, self->mark_begin);
gtk_text_buffer_get_iter_at_mark (self->buffer, &end, self->mark_end);
ret = ((gtk_text_iter_compare (&begin, iter) <= 0) &&
(gtk_text_iter_compare (&end, iter) >= 0));
return ret;
}
gboolean
ide_snippet_insert_set (IdeSnippet *self,
GtkTextMark *mark)
{
GtkTextIter iter;
g_return_val_if_fail (IDE_IS_SNIPPET (self), FALSE);
g_return_val_if_fail (GTK_IS_TEXT_MARK (mark), FALSE);
gtk_text_buffer_get_iter_at_mark (self->buffer, &iter, mark);
if (!ide_snippet_within_bounds (self, &iter))
return FALSE;
self->current_chunk = ide_snippet_get_index (self, &iter);
return TRUE;
}
static void
ide_snippet_get_nth_chunk_range (IdeSnippet *self,
gint n,
GtkTextIter *begin,
GtkTextIter *end)
{
gint run;
gint i;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (n >= 0);
g_return_if_fail (begin);
g_return_if_fail (end);
gtk_text_buffer_get_iter_at_mark (self->buffer, begin, self->mark_begin);
for (i = 0; i < n; i++)
{
run = g_array_index (self->runs, gint, i);
gtk_text_iter_forward_chars (begin, run);
}
gtk_text_iter_assign (end, begin);
run = g_array_index (self->runs, gint, n);
gtk_text_iter_forward_chars (end, run);
}
void
ide_snippet_get_chunk_range (IdeSnippet *self,
IdeSnippetChunk *chunk,
GtkTextIter *begin,
GtkTextIter *end)
{
IdeSnippetChunk *item;
guint i;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (IDE_IS_SNIPPET_CHUNK (chunk));
for (i = 0; i < self->chunks->len; i++)
{
item = g_ptr_array_index (self->chunks, i);
if (item == chunk)
{
ide_snippet_get_nth_chunk_range (self, i, begin, end);
return;
}
}
g_warning ("Chunk does not belong to snippet.");
}
static void
ide_snippet_select_chunk (IdeSnippet *self,
gint n)
{
GtkTextIter begin;
GtkTextIter end;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (n >= 0);
g_return_if_fail ((guint)n < self->runs->len);
ide_snippet_get_nth_chunk_range (self, n, &begin, &end);
gtk_text_iter_order (&begin, &end);
IDE_TRACE_MSG ("Selecting chunk %d with range %d:%d to %d:%d (offset %d+%d)",
n,
gtk_text_iter_get_line (&begin) + 1,
gtk_text_iter_get_line_offset (&begin) + 1,
gtk_text_iter_get_line (&end) + 1,
gtk_text_iter_get_line_offset (&end) + 1,
gtk_text_iter_get_offset (&begin),
gtk_text_iter_get_offset (&end) - gtk_text_iter_get_offset (&begin));
gtk_text_buffer_select_range (self->buffer, &begin, &end);
#ifdef IDE_ENABLE_TRACE
{
GtkTextIter set_begin;
GtkTextIter set_end;
gtk_text_buffer_get_selection_bounds (self->buffer, &set_begin, &set_end);
g_assert (gtk_text_iter_equal (&set_begin, &begin));
g_assert (gtk_text_iter_equal (&set_end, &end));
}
#endif
self->current_chunk = n;
IDE_EXIT;
}
gboolean
ide_snippet_move_next (IdeSnippet *self)
{
GtkTextIter iter;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_SNIPPET (self), FALSE);
if (self->tab_stop > self->max_tab_stop)
IDE_RETURN (FALSE);
self->tab_stop++;
for (guint i = 0; i < self->chunks->len; i++)
{
IdeSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
if (ide_snippet_chunk_get_tab_stop (chunk) == self->tab_stop)
{
ide_snippet_select_chunk (self, i);
IDE_RETURN (TRUE);
}
}
for (guint i = 0; i < self->chunks->len; i++)
{
IdeSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
if (ide_snippet_chunk_get_tab_stop (chunk) == 0)
{
ide_snippet_select_chunk (self, i);
IDE_RETURN (FALSE);
}
}
IDE_TRACE_MSG ("No more tab stops, moving to end of snippet");
gtk_text_buffer_get_iter_at_mark (self->buffer, &iter, self->mark_end);
gtk_text_buffer_select_range (self->buffer, &iter, &iter);
self->current_chunk = self->chunks->len - 1;
IDE_RETURN (FALSE);
}
gboolean
ide_snippet_move_previous (IdeSnippet *self)
{
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_SNIPPET (self), FALSE);
self->tab_stop = MAX (1, self->tab_stop - 1);
for (guint i = 0; i < self->chunks->len; i++)
{
IdeSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
if (ide_snippet_chunk_get_tab_stop (chunk) == self->tab_stop)
{
ide_snippet_select_chunk (self, i);
IDE_RETURN (TRUE);
}
}
IDE_TRACE_MSG ("No previous tab stop to select, ignoring");
IDE_RETURN (FALSE);
}
static void
ide_snippet_update_context (IdeSnippet *self)
{
IdeSnippetContext *context;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SNIPPET (self));
if (self->chunks == NULL || self->chunks->len == 0)
IDE_EXIT;
context = ide_snippet_get_context (self);
ide_snippet_context_emit_changed (context);
for (guint i = 0; i < self->chunks->len; i++)
{
IdeSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
gint tab_stop;
g_assert (IDE_IS_SNIPPET_CHUNK (chunk));
tab_stop = ide_snippet_chunk_get_tab_stop (chunk);
if (tab_stop > 0)
{
const gchar *text;
if (NULL != (text = ide_snippet_chunk_get_text (chunk)))
{
gchar key[12];
g_snprintf (key, sizeof key, "%d", tab_stop);
key[sizeof key - 1] = '\0';
ide_snippet_context_add_variable (context, key, text);
}
}
}
ide_snippet_context_emit_changed (context);
IDE_EXIT;
}
static void
ide_snippet_clear_tags (IdeSnippet *self)
{
g_assert (IDE_IS_SNIPPET (self));
if (self->mark_begin != NULL && self->mark_end != NULL)
{
GtkTextBuffer *buffer;
GtkTextIter begin;
GtkTextIter end;
buffer = gtk_text_mark_get_buffer (self->mark_begin);
gtk_text_buffer_get_iter_at_mark (buffer, &begin, self->mark_begin);
gtk_text_buffer_get_iter_at_mark (buffer, &end, self->mark_end);
gtk_text_buffer_remove_tag_by_name (buffer,
TAG_SNIPPET_TAB_STOP,
&begin, &end);
}
}
static void
ide_snippet_update_tags (IdeSnippet *self)
{
GtkTextBuffer *buffer;
guint i;
g_assert (IDE_IS_SNIPPET (self));
ide_snippet_clear_tags (self);
buffer = gtk_text_mark_get_buffer (self->mark_begin);
for (i = 0; i < self->chunks->len; i++)
{
IdeSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
gint tab_stop = ide_snippet_chunk_get_tab_stop (chunk);
if (tab_stop >= 0)
{
GtkTextIter begin;
GtkTextIter end;
ide_snippet_get_chunk_range (self, chunk, &begin, &end);
gtk_text_buffer_apply_tag_by_name (buffer,
TAG_SNIPPET_TAB_STOP,
&begin, &end);
}
}
}
gboolean
ide_snippet_begin (IdeSnippet *self,
GtkTextBuffer *buffer,
GtkTextIter *iter)
{
IdeSnippetContext *context;
gboolean ret;
g_return_val_if_fail (IDE_IS_SNIPPET (self), FALSE);
g_return_val_if_fail (!self->buffer, FALSE);
g_return_val_if_fail (!self->mark_begin, FALSE);
g_return_val_if_fail (!self->mark_end, FALSE);
g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), FALSE);
g_return_val_if_fail (iter, FALSE);
self->inserted = TRUE;
context = ide_snippet_get_context (self);
ide_snippet_update_context (self);
ide_snippet_context_emit_changed (context);
ide_snippet_update_context (self);
self->buffer = g_object_ref (buffer);
self->mark_begin = gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE);
g_object_add_weak_pointer (G_OBJECT (self->mark_begin),
(gpointer *) &self->mark_begin);
gtk_text_buffer_begin_user_action (buffer);
for (guint i = 0; i < self->chunks->len; i++)
{
IdeSnippetChunk *chunk;
const gchar *text;
chunk = g_ptr_array_index (self->chunks, i);
if ((text = ide_snippet_chunk_get_text (chunk)))
{
gint len;
len = g_utf8_strlen (text, -1);
g_array_append_val (self->runs, len);
gtk_text_buffer_insert (buffer, iter, text, -1);
}
}
self->mark_end = gtk_text_buffer_create_mark (buffer, NULL, iter, FALSE);
g_object_add_weak_pointer (G_OBJECT (self->mark_end),
(gpointer *) &self->mark_end);
g_object_ref (self->mark_begin);
g_object_ref (self->mark_end);
gtk_text_buffer_end_user_action (buffer);
ide_snippet_update_tags (self);
ret = ide_snippet_move_next (self);
return ret;
}
void
ide_snippet_finish (IdeSnippet *self)
{
g_return_if_fail (IDE_IS_SNIPPET (self));
ide_snippet_clear_tags (self);
g_clear_object (&self->mark_begin);
g_clear_object (&self->mark_end);
g_clear_object (&self->buffer);
}
void
ide_snippet_pause (IdeSnippet *self)
{
g_return_if_fail (IDE_IS_SNIPPET (self));
}
void
ide_snippet_unpause (IdeSnippet *self)
{
g_return_if_fail (IDE_IS_SNIPPET (self));
}
void
ide_snippet_add_chunk (IdeSnippet *self,
IdeSnippetChunk *chunk)
{
gint tab_stop;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (IDE_IS_SNIPPET_CHUNK (chunk));
g_return_if_fail (!self->inserted);
g_ptr_array_add (self->chunks, g_object_ref (chunk));
ide_snippet_chunk_set_context (chunk, self->snippet_context);
tab_stop = ide_snippet_chunk_get_tab_stop (chunk);
self->max_tab_stop = MAX (self->max_tab_stop, tab_stop);
}
static gchar *
ide_snippet_get_nth_text (IdeSnippet *self,
gint n)
{
GtkTextIter iter;
GtkTextIter end;
gchar *ret;
gint i;
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
g_return_val_if_fail (n >= 0, NULL);
gtk_text_buffer_get_iter_at_mark (self->buffer, &iter, self->mark_begin);
for (i = 0; i < n; i++)
gtk_text_iter_forward_chars (&iter, g_array_index (self->runs, gint, i));
gtk_text_iter_assign (&end, &iter);
gtk_text_iter_forward_chars (&end, g_array_index (self->runs, gint, n));
ret = gtk_text_buffer_get_text (self->buffer, &iter, &end, TRUE);
return ret;
}
static void
ide_snippet_replace_chunk_text (IdeSnippet *self,
gint n,
const gchar *text)
{
GtkTextIter begin;
GtkTextIter end;
gint diff = 0;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (n >= 0);
g_return_if_fail (text);
/*
* This replaces the text for the snippet. We insert new text before
* we delete the old text to ensure things are more stable as we
* manipulate the runs. Avoiding zero-length runs, even temporarily
* can be helpful.
*/
ide_snippet_get_nth_chunk_range (self, n, &begin, &end);
if (!gtk_text_iter_equal (&begin, &end))
{
gtk_text_iter_order (&begin, &end);
diff = gtk_text_iter_get_offset (&end) - gtk_text_iter_get_offset (&begin);
}
g_array_index (self->runs, gint, n) += g_utf8_strlen (text, -1);
gtk_text_buffer_insert (self->buffer, &begin, text, -1);
/* At this point, begin should be updated to the end of where we inserted
* our new text. If `diff` is non-zero, then we need to remove those
* characters immediately after `begin`.
*/
if (diff != 0)
{
end = begin;
gtk_text_iter_forward_chars (&end, diff);
g_array_index (self->runs, gint, n) -= diff;
gtk_text_buffer_delete (self->buffer, &begin, &end);
}
}
static void
ide_snippet_rewrite_updated_chunks (IdeSnippet *self)
{
g_return_if_fail (IDE_IS_SNIPPET (self));
for (guint i = 0; i < self->chunks->len; i++)
{
IdeSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
g_autofree gchar *real_text = NULL;
const gchar *text;
text = ide_snippet_chunk_get_text (chunk);
real_text = ide_snippet_get_nth_text (self, i);
if (!dzl_str_equal0 (text, real_text))
ide_snippet_replace_chunk_text (self, i, text);
}
}
void
ide_snippet_before_insert_text (IdeSnippet *self,
GtkTextBuffer *buffer,
GtkTextIter *iter,
gchar *text,
gint len)
{
gint utf8_len;
gint n;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (self->current_chunk >= 0);
g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
g_return_if_fail (iter);
n = ide_snippet_get_index (self, iter);
utf8_len = g_utf8_strlen (text, len);
g_array_index (self->runs, gint, n) += utf8_len;
#if 0
g_print ("I: ");
for (n = 0; n < self->runs->len; n++)
g_print ("%d ", g_array_index (self->runs, gint, n));
g_print ("\n");
#endif
IDE_EXIT;
}
void
ide_snippet_after_insert_text (IdeSnippet *self,
GtkTextBuffer *buffer,
GtkTextIter *iter,
gchar *text,
gint len)
{
IdeSnippetChunk *chunk;
GtkTextMark *here;
gchar *new_text;
gint n;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (self->current_chunk >= 0);
g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
g_return_if_fail (iter);
n = ide_snippet_get_index (self, iter);
chunk = g_ptr_array_index (self->chunks, n);
new_text = ide_snippet_get_nth_text (self, n);
ide_snippet_chunk_set_text (chunk, new_text);
ide_snippet_chunk_set_text_set (chunk, TRUE);
g_free (new_text);
here = gtk_text_buffer_create_mark (buffer, NULL, iter, TRUE);
ide_snippet_update_context (self);
ide_snippet_update_context (self);
ide_snippet_rewrite_updated_chunks (self);
gtk_text_buffer_get_iter_at_mark (buffer, iter, here);
gtk_text_buffer_delete_mark (buffer, here);
ide_snippet_update_tags (self);
#if 0
ide_snippet_context_dump (self->snippet_context);
#endif
IDE_EXIT;
}
void
ide_snippet_before_delete_range (IdeSnippet *self,
GtkTextBuffer *buffer,
GtkTextIter *begin,
GtkTextIter *end)
{
gint *run;
gint len;
gint n;
gint i;
gint lower_bound = -1;
gint upper_bound = -1;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
g_return_if_fail (begin);
g_return_if_fail (end);
len = gtk_text_iter_get_offset (end) - gtk_text_iter_get_offset (begin);
n = ide_snippet_get_index (self, begin);
if (n < 0)
IDE_EXIT;
self->current_chunk = n;
while (len != 0 && (guint)n < self->runs->len)
{
if (lower_bound == -1 || n < lower_bound)
lower_bound = n;
if (n > upper_bound)
upper_bound = n;
run = &g_array_index (self->runs, gint, n);
if (len > *run)
{
len -= *run;
*run = 0;
n++;
continue;
}
*run -= len;
break;
}
if (lower_bound == -1 || upper_bound == -1)
return;
for (i = lower_bound; i <= upper_bound; i++)
{
IdeSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
g_autofree gchar *new_text = NULL;
new_text = ide_snippet_get_nth_text (self, i);
ide_snippet_chunk_set_text (chunk, new_text);
ide_snippet_chunk_set_text_set (chunk, TRUE);
}
#if 0
g_print ("D: ");
for (n = 0; n < self->runs->len; n++)
g_print ("%d ", g_array_index (self->runs, gint, n));
g_print ("\n");
#endif
IDE_EXIT;
}
void
ide_snippet_after_delete_range (IdeSnippet *self,
GtkTextBuffer *buffer,
GtkTextIter *begin,
GtkTextIter *end)
{
GtkTextMark *here;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (GTK_IS_TEXT_BUFFER (buffer));
g_return_if_fail (begin);
g_return_if_fail (end);
here = gtk_text_buffer_create_mark (buffer, NULL, begin, TRUE);
ide_snippet_update_context (self);
ide_snippet_update_context (self);
ide_snippet_rewrite_updated_chunks (self);
gtk_text_buffer_get_iter_at_mark (buffer, begin, here);
gtk_text_buffer_get_iter_at_mark (buffer, end, here);
gtk_text_buffer_delete_mark (buffer, here);
ide_snippet_update_tags (self);
#if 0
ide_snippet_context_dump (self->snippet_context);
#endif
IDE_EXIT;
}
/**
* ide_snippet_get_mark_begin:
* @self: an #IdeSnippet
*
* Gets the begin text mark, which is only set when the snippet is
* actively being edited.
*
* Returns: (transfer none) (nullable): a #GtkTextMark or %NULL
*
* Since: 3.32
*/
GtkTextMark *
ide_snippet_get_mark_begin (IdeSnippet *self)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
return self->mark_begin;
}
/**
* ide_snippet_get_mark_end:
* @self: an #IdeSnippet
*
* Gets the end text mark, which is only set when the snippet is
* actively being edited.
*
* Returns: (transfer none) (nullable): a #GtkTextMark or %NULL
*
* Since: 3.32
*/
GtkTextMark *
ide_snippet_get_mark_end (IdeSnippet *self)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
return self->mark_end;
}
/**
* ide_snippet_get_context:
* @self: an #IdeSnippet
*
* Get's the context used for expanding the snippet.
*
* Returns: (nullable) (transfer none): an #IdeSnippetContext
*
* Since: 3.32
*/
IdeSnippetContext *
ide_snippet_get_context (IdeSnippet *self)
{
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
if (!self->snippet_context)
{
IdeSnippetChunk *chunk;
guint i;
self->snippet_context = ide_snippet_context_new ();
for (i = 0; i < self->chunks->len; i++)
{
chunk = g_ptr_array_index (self->chunks, i);
ide_snippet_chunk_set_context (chunk, self->snippet_context);
}
}
return self->snippet_context;
}
static void
ide_snippet_dispose (GObject *object)
{
IdeSnippet *self = (IdeSnippet *)object;
if (self->mark_begin)
{
g_object_remove_weak_pointer (G_OBJECT (self->mark_begin),
(gpointer *) &self->mark_begin);
gtk_text_buffer_delete_mark (self->buffer, self->mark_begin);
self->mark_begin = NULL;
}
if (self->mark_end)
{
g_object_remove_weak_pointer (G_OBJECT (self->mark_end),
(gpointer *) &self->mark_end);
gtk_text_buffer_delete_mark (self->buffer, self->mark_end);
self->mark_end = NULL;
}
g_clear_pointer (&self->runs, g_array_unref);
g_clear_pointer (&self->chunks, g_ptr_array_unref);
g_clear_object (&self->buffer);
g_clear_object (&self->snippet_context);
G_OBJECT_CLASS (ide_snippet_parent_class)->dispose (object);
}
static void
ide_snippet_finalize (GObject *object)
{
IdeSnippet *self = (IdeSnippet *)object;
g_clear_pointer (&self->description, g_free);
g_clear_pointer (&self->trigger, g_free);
g_clear_object (&self->buffer);
G_OBJECT_CLASS (ide_snippet_parent_class)->finalize (object);
DZL_COUNTER_DEC (instances);
}
static void
ide_snippet_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeSnippet *self = IDE_SNIPPET (object);
switch (prop_id)
{
case PROP_BUFFER:
g_value_set_object (value, self->buffer);
break;
case PROP_MARK_BEGIN:
g_value_set_object (value, self->mark_begin);
break;
case PROP_MARK_END:
g_value_set_object (value, self->mark_end);
break;
case PROP_TRIGGER:
g_value_set_string (value, self->trigger);
break;
case PROP_LANGUAGE:
g_value_set_string (value, self->language);
break;
case PROP_DESCRIPTION:
g_value_set_string (value, self->description);
break;
case PROP_TAB_STOP:
g_value_set_uint (value, self->tab_stop);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_snippet_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeSnippet *self = IDE_SNIPPET (object);
switch (prop_id)
{
case PROP_TRIGGER:
ide_snippet_set_trigger (self, g_value_get_string (value));
break;
case PROP_LANGUAGE:
ide_snippet_set_language (self, g_value_get_string (value));
break;
case PROP_DESCRIPTION:
ide_snippet_set_description (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_snippet_class_init (IdeSnippetClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = ide_snippet_dispose;
object_class->finalize = ide_snippet_finalize;
object_class->get_property = ide_snippet_get_property;
object_class->set_property = ide_snippet_set_property;
properties[PROP_BUFFER] =
g_param_spec_object ("buffer",
"Buffer",
"The GtkTextBuffer for the snippet.",
GTK_TYPE_TEXT_BUFFER,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties[PROP_MARK_BEGIN] =
g_param_spec_object ("mark-begin",
"Mark Begin",
"The beginning text mark.",
GTK_TYPE_TEXT_MARK,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties[PROP_MARK_END] =
g_param_spec_object ("mark-end",
"Mark End",
"The ending text mark.",
GTK_TYPE_TEXT_MARK,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties[PROP_TRIGGER] =
g_param_spec_string ("trigger",
"Trigger",
"The trigger for the snippet.",
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_LANGUAGE] =
g_param_spec_string ("language",
"Language",
"The language for the snippet.",
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_DESCRIPTION] =
g_param_spec_string ("description",
"Description",
"The description for the snippet.",
NULL,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties[PROP_TAB_STOP] =
g_param_spec_int ("tab-stop",
"Tab Stop",
"The current tab stop.",
-1,
G_MAXINT,
-1,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, LAST_PROP, properties);
}
static void
ide_snippet_init (IdeSnippet *self)
{
DZL_COUNTER_INC (instances);
self->max_tab_stop = -1;
self->chunks = g_ptr_array_new_with_free_func (g_object_unref);
self->runs = g_array_new (FALSE, FALSE, sizeof (gint));
}
/**
* ide_snippet_get_full_text:
* @self: a #IdeSnippet
*
* Gets the contents of the snippet as currently edited by the user.
*
* Returns: (transfer full): a newly allocated string containing the full
* contents of all the snippet chunks.
*
* Since: 3.32
*/
gchar *
ide_snippet_get_full_text (IdeSnippet *self)
{
GtkTextIter begin;
GtkTextIter end;
g_return_val_if_fail (IDE_IS_SNIPPET (self), NULL);
if (self->mark_begin == NULL || self->mark_end == NULL)
return NULL;
gtk_text_buffer_get_iter_at_mark (self->buffer, &begin, self->mark_begin);
gtk_text_buffer_get_iter_at_mark (self->buffer, &end, self->mark_end);
return gtk_text_iter_get_slice (&begin, &end);
}
/**
* ide_snippet_replace_current_chunk_text:
* @self: a #IdeSnippet
* @new_text: the text to use as the replacement
*
* This replaces the current chunk (if any) to contain the contents of
* @new_text.
*
* This function is primarily useful to the #IdeSourceView as it updates
* content as the user types.
*
* Since: 3.32
*/
void
ide_snippet_replace_current_chunk_text (IdeSnippet *self,
const gchar *new_text)
{
IdeSnippetChunk *chunk;
gint utf8_len;
g_return_if_fail (IDE_IS_SNIPPET (self));
g_return_if_fail (self->chunks != NULL);
if (self->current_chunk < 0 || self->current_chunk >= self->chunks->len)
return;
chunk = g_ptr_array_index (self->chunks, self->current_chunk);
ide_snippet_chunk_set_text (chunk, new_text);
ide_snippet_chunk_set_text_set (chunk, TRUE);
g_assert (self->current_chunk >= 0);
g_assert (self->current_chunk < self->runs->len);
utf8_len = g_utf8_strlen (new_text, -1);
g_array_index (self->runs, gint, self->current_chunk) = utf8_len;
}
/**
* ide_snippet_dump:
* @self: a #IdeSnippet
*
* This is a debugging function to print information about a chunk to stderr.
* Plugin developers might use this to track down issues when using a snippet.
*
* Since: 3.32
*/
void
ide_snippet_dump (IdeSnippet *self)
{
guint offset = 0;
g_return_if_fail (IDE_IS_SNIPPET (self));
/* For debugging purposes */
g_printerr ("Snippet(trigger=%s, language=%s, tab_stop=%d, current_chunk=%d)\n",
self->trigger, self->language ?: "none", self->tab_stop, self->current_chunk);
g_assert (self->chunks->len == self->runs->len);
for (guint i = 0; i < self->chunks->len; i++)
{
IdeSnippetChunk *chunk = g_ptr_array_index (self->chunks, i);
g_autofree gchar *spec_escaped = NULL;
g_autofree gchar *text_escaped = NULL;
const gchar *spec;
const gchar *text;
gint run_length = g_array_index (self->runs, gint, i);
g_assert (IDE_IS_SNIPPET_CHUNK (chunk));
text = ide_snippet_chunk_get_text (chunk);
text_escaped = g_strescape (text, NULL);
spec = ide_snippet_chunk_get_spec (chunk);
spec_escaped = g_strescape (spec, NULL);
g_printerr (" Chunk(nth=%d, tab_stop=%d, position=%d (%d), spec=%s, text=%s)\n",
i,
ide_snippet_chunk_get_tab_stop (chunk),
offset, run_length,
spec_escaped,
text_escaped);
offset += run_length;
}
}