[gnome-builder/wip/libide] libide: add support for snippets to IdeSourceView
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/libide] libide: add support for snippets to IdeSourceView
- Date: Wed, 25 Feb 2015 20:22:35 +0000 (UTC)
commit 817de4a781f485793b2b3ceb3cdf88977e3a2fd1
Author: Christian Hergert <christian hergert me>
Date: Wed Feb 25 12:18:05 2015 -0800
libide: add support for snippets to IdeSourceView
Pretty straightforward port of GbSourceView snippet support down into
LibIDE.
You can enable/disable snippet completion with the
IdeSourceView:snippet-completion gproperty.
libide/ide-source-view.c | 712 +++++++++++++++++++++++++++++++++++++++++++---
libide/ide-source-view.h | 16 +
2 files changed, 693 insertions(+), 35 deletions(-)
---
diff --git a/libide/ide-source-view.c b/libide/ide-source-view.c
index d896537..f1fc1b5 100644
--- a/libide/ide-source-view.c
+++ b/libide/ide-source-view.c
@@ -20,7 +20,10 @@
#include <glib/gi18n.h>
+#include "ide-animation.h"
+#include "ide-box-theatric.h"
#include "ide-buffer.h"
+#include "ide-context.h"
#include "ide-file.h"
#include "ide-file-settings.h"
#include "ide-highlighter.h"
@@ -28,27 +31,43 @@
#include "ide-language.h"
#include "ide-line-change-gutter-renderer.h"
#include "ide-pango.h"
+#include "ide-source-snippet.h"
+#include "ide-source-snippet-chunk.h"
+#include "ide-source-snippet-completion-provider.h"
+#include "ide-source-snippet-context.h"
+#include "ide-source-snippet-private.h"
+#include "ide-source-snippets-manager.h"
#include "ide-source-view.h"
#define DEFAULT_FONT_DESC "Monospace 11"
+#define ANIMATION_X_GROW 50
+#define ANIMATION_Y_GROW 30
typedef struct
{
- IdeBuffer *buffer;
- GtkCssProvider *css_provider;
- PangoFontDescription *font_desc;
- IdeIndenter *indenter;
- GtkSourceGutterRenderer *line_change_renderer;
-
- gulong buffer_changed_handler;
- gulong buffer_notify_file_handler;
- gulong buffer_notify_language_handler;
-
- guint auto_indent : 1;
- guint insert_matching_brace : 1;
- guint overwrite_braces : 1;
- guint show_grid_lines : 1;
- guint show_line_changes : 1;
+ IdeBuffer *buffer;
+ GtkCssProvider *css_provider;
+ PangoFontDescription *font_desc;
+ IdeIndenter *indenter;
+ GtkSourceGutterRenderer *line_change_renderer;
+ GQueue *snippets;
+ GtkSourceCompletionProvider *snippets_provider;
+
+ gulong buffer_changed_handler;
+ gulong buffer_delete_range_after_handler;
+ gulong buffer_delete_range_handler;
+ gulong buffer_insert_text_after_handler;
+ gulong buffer_insert_text_handler;
+ gulong buffer_mark_set_handler;
+ gulong buffer_notify_file_handler;
+ gulong buffer_notify_language_handler;
+
+ guint auto_indent : 1;
+ guint insert_matching_brace : 1;
+ guint overwrite_braces : 1;
+ guint show_grid_lines : 1;
+ guint show_line_changes : 1;
+ guint snippet_completion : 1;
} IdeSourceViewPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (IdeSourceView, ide_source_view, GTK_SOURCE_TYPE_VIEW)
@@ -62,10 +81,222 @@ enum {
PROP_OVERWRITE_BRACES,
PROP_SHOW_GRID_LINES,
PROP_SHOW_LINE_CHANGES,
+ PROP_SNIPPET_COMPLETION,
LAST_PROP
};
+enum {
+ PUSH_SNIPPET,
+ POP_SNIPPET,
+ LAST_SIGNAL
+};
+
static GParamSpec *gParamSpecs [LAST_PROP];
+static guint gSignals [LAST_SIGNAL];
+
+static void
+ide_source_view_block_handlers (IdeSourceView *self)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ if (priv->buffer)
+ {
+ g_signal_handler_block (priv->buffer, priv->buffer_insert_text_handler);
+ g_signal_handler_block (priv->buffer, priv->buffer_insert_text_after_handler);
+ g_signal_handler_block (priv->buffer, priv->buffer_delete_range_handler);
+ g_signal_handler_block (priv->buffer, priv->buffer_delete_range_after_handler);
+ g_signal_handler_block (priv->buffer, priv->buffer_mark_set_handler);
+ }
+}
+
+static void
+ide_source_view_unblock_handlers (IdeSourceView *self)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ if (priv->buffer)
+ {
+ g_signal_handler_unblock (priv->buffer, priv->buffer_insert_text_handler);
+ g_signal_handler_unblock (priv->buffer, priv->buffer_insert_text_after_handler);
+ g_signal_handler_unblock (priv->buffer, priv->buffer_delete_range_handler);
+ g_signal_handler_unblock (priv->buffer, priv->buffer_delete_range_after_handler);
+ g_signal_handler_unblock (priv->buffer, priv->buffer_mark_set_handler);
+ }
+}
+
+static void
+get_rect_for_iters (GtkTextView *text_view,
+ const GtkTextIter *iter1,
+ const GtkTextIter *iter2,
+ GdkRectangle *rect,
+ GtkTextWindowType window_type)
+{
+ GdkRectangle area;
+ GdkRectangle tmp;
+ GtkTextIter iter;
+
+ g_assert (GTK_IS_TEXT_VIEW (text_view));
+ g_assert (iter1);
+ g_assert (iter2);
+ g_assert (rect);
+
+ gtk_text_view_get_iter_location (text_view, iter1, &area);
+
+ gtk_text_iter_assign (&iter, iter1);
+
+ do
+ {
+ gtk_text_view_get_iter_location (text_view, &iter, &tmp);
+ gdk_rectangle_union (&area, &tmp, &area);
+
+ gtk_text_iter_forward_to_line_end (&iter);
+ gtk_text_view_get_iter_location (text_view, &iter, &tmp);
+ gdk_rectangle_union (&area, &tmp, &area);
+
+ if (!gtk_text_iter_forward_char (&iter))
+ break;
+ }
+ while (gtk_text_iter_compare (&iter, iter2) <= 0);
+
+ gtk_text_view_buffer_to_window_coords (text_view, window_type, area.x, area.y, &area.x, &area.y);
+
+ *rect = area;
+}
+
+static void
+animate_in (IdeSourceView *self,
+ const GtkTextIter *begin,
+ const GtkTextIter *end)
+{
+ IdeBoxTheatric *theatric;
+ GtkAllocation alloc;
+ GdkRectangle rect = { 0 };
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+ g_assert (begin);
+ g_assert (end);
+
+ get_rect_for_iters (GTK_TEXT_VIEW (self), begin, end, &rect, GTK_TEXT_WINDOW_WIDGET);
+ gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
+ rect.height = MIN (rect.height, alloc.height - rect.y);
+
+ theatric = g_object_new (IDE_TYPE_BOX_THEATRIC,
+ "alpha", 0.3,
+ "background", "#729fcf",
+ "height", rect.height,
+ "target", self,
+ "width", rect.width,
+ "x", rect.x,
+ "y", rect.y,
+ NULL);
+
+ ide_object_animate_full (theatric,
+ IDE_ANIMATION_EASE_IN_CUBIC,
+ 250,
+ gtk_widget_get_frame_clock (GTK_WIDGET (self)),
+ g_object_unref,
+ theatric,
+ "x", rect.x - ANIMATION_X_GROW,
+ "width", rect.width + (ANIMATION_X_GROW * 2),
+ "y", rect.y - ANIMATION_Y_GROW,
+ "height", rect.height + (ANIMATION_Y_GROW * 2),
+ "alpha", 0.0,
+ NULL);
+}
+
+static void
+ide_source_view_scroll_to_insert (IdeSourceView *self)
+{
+ GtkTextBuffer *buffer;
+ GtkTextMark *mark;
+ GtkTextIter iter;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+ mark = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
+ gtk_text_view_scroll_to_iter (GTK_TEXT_VIEW (self), &iter, 0.0, FALSE, 0.0, 0.0);
+}
+
+static void
+ide_source_view_invalidate_window (IdeSourceView *self)
+{
+ GdkWindow *window;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ if ((window = gtk_text_view_get_window (GTK_TEXT_VIEW (self), GTK_TEXT_WINDOW_WIDGET)))
+ {
+ gdk_window_invalidate_rect (window, NULL, TRUE);
+ gtk_widget_queue_draw (GTK_WIDGET (self));
+ }
+}
+
+static gchar *
+text_iter_get_line_prefix (const GtkTextIter *iter)
+{
+ GtkTextIter begin;
+ GString *str;
+
+ g_assert (iter);
+
+ gtk_text_iter_assign (&begin, iter);
+ gtk_text_iter_set_line_offset (&begin, 0);
+
+ str = g_string_new (NULL);
+
+ if (gtk_text_iter_compare (&begin, iter) != 0)
+ {
+ do
+ {
+ gunichar c;
+
+ c = gtk_text_iter_get_char (&begin);
+
+ switch (c)
+ {
+ case '\t':
+ case ' ':
+ g_string_append_unichar (str, c);
+ break;
+ default:
+ g_string_append_c (str, ' ');
+ break;
+ }
+ }
+ while (gtk_text_iter_forward_char (&begin) &&
+ (gtk_text_iter_compare (&begin, iter) < 0));
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static void
+ide_source_view_reload_snippets (IdeSourceView *self)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ IdeSourceSnippets *snippets = NULL;
+ IdeContext *context = NULL;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ if ((priv->buffer != NULL) && (context = ide_buffer_get_context (priv->buffer)))
+ {
+ IdeSourceSnippetsManager *manager;
+ GtkSourceLanguage *source_language;
+
+ manager = ide_context_get_snippets_manager (context);
+ source_language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (priv->buffer));
+ snippets = ide_source_snippets_manager_get_for_language (manager, source_language);
+ }
+
+ g_object_set (priv->snippets_provider, "snippets", snippets, NULL);
+}
static void
ide_source_view_reload_indenter (IdeSourceView *self)
@@ -184,6 +415,7 @@ ide_source_view__buffer_notify_file_cb (IdeSourceView *self,
ide_source_view_reload_language (self);
ide_source_view_reload_file_settings (self);
+ ide_source_view_reload_snippets (self);
}
static void
@@ -234,6 +466,152 @@ ide_source_view_rebuild_css (IdeSourceView *self)
}
static void
+ide_source_view_invalidate_range_mark (IdeSourceView *self,
+ GtkTextMark *mark_begin,
+ GtkTextMark *mark_end)
+{
+ GtkTextBuffer *buffer;
+ GdkRectangle rect;
+ GtkTextIter begin;
+ GtkTextIter end;
+ GdkWindow *window;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+ g_assert (GTK_IS_TEXT_MARK (mark_begin));
+ g_assert (GTK_IS_TEXT_MARK (mark_end));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+ gtk_text_buffer_get_iter_at_mark (buffer, &begin, mark_begin);
+ gtk_text_buffer_get_iter_at_mark (buffer, &end, mark_end);
+
+ get_rect_for_iters (GTK_TEXT_VIEW (self), &begin, &end, &rect, GTK_TEXT_WINDOW_TEXT);
+ window = gtk_text_view_get_window (GTK_TEXT_VIEW (self), GTK_TEXT_WINDOW_TEXT);
+ gdk_window_invalidate_rect (window, &rect, FALSE);
+}
+
+static void
+ide_source_view__buffer_insert_text_cb (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ gchar *text,
+ gint len,
+ gpointer user_data)
+{
+ IdeSourceView *self= user_data;
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ IdeSourceSnippet *snippet;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ ide_source_view_block_handlers (self);
+
+ if ((snippet = g_queue_peek_head (priv->snippets)))
+ ide_source_snippet_before_insert_text (snippet, buffer, iter, text, len);
+
+ ide_source_view_unblock_handlers (self);
+}
+
+static void
+ide_source_view__buffer_insert_text_after_cb (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ gchar *text,
+ gint len,
+ gpointer user_data)
+{
+ IdeSourceView *self = user_data;
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ IdeSourceSnippet *snippet;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ if ((snippet = g_queue_peek_head (priv->snippets)))
+ {
+ GtkTextMark *begin;
+ GtkTextMark *end;
+
+ ide_source_view_block_handlers (self);
+ ide_source_snippet_after_insert_text (snippet, buffer, iter, text, len);
+ ide_source_view_unblock_handlers (self);
+
+ begin = ide_source_snippet_get_mark_begin (snippet);
+ end = ide_source_snippet_get_mark_end (snippet);
+ ide_source_view_invalidate_range_mark (self, begin, end);
+ }
+}
+
+static void
+ide_source_view__buffer_delete_range_cb (GtkTextBuffer *buffer,
+ GtkTextIter *begin,
+ GtkTextIter *end,
+ gpointer user_data)
+{
+ IdeSourceView *self = user_data;
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ IdeSourceSnippet *snippet;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ if ((snippet = g_queue_peek_head (priv->snippets)))
+ {
+ GtkTextMark *begin_mark;
+ GtkTextMark *end_mark;
+
+ ide_source_view_block_handlers (self);
+ ide_source_snippet_before_delete_range (snippet, buffer, begin, end);
+ ide_source_view_unblock_handlers (self);
+
+ begin_mark = ide_source_snippet_get_mark_begin (snippet);
+ end_mark = ide_source_snippet_get_mark_end (snippet);
+ ide_source_view_invalidate_range_mark (self, begin_mark, end_mark);
+ }
+}
+
+static void
+ide_source_view__buffer_delete_range_after_cb (GtkTextBuffer *buffer,
+ GtkTextIter *begin,
+ GtkTextIter *end,
+ gpointer user_data)
+{
+ IdeSourceView *self = user_data;
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ IdeSourceSnippet *snippet;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+
+ ide_source_view_block_handlers (self);
+
+ if ((snippet = g_queue_peek_head (priv->snippets)))
+ ide_source_snippet_after_delete_range (snippet, buffer, begin, end);
+
+ ide_source_view_unblock_handlers (self);
+}
+
+static void
+ide_source_view__buffer_mark_set_cb (GtkTextBuffer *buffer,
+ GtkTextIter *iter,
+ GtkTextMark *mark,
+ gpointer user_data)
+{
+ IdeSourceView *self = user_data;
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ IdeSourceSnippet *snippet;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+ g_assert (iter);
+ g_assert (GTK_IS_TEXT_MARK (mark));
+
+ ide_source_view_block_handlers (self);
+
+ if (mark == gtk_text_buffer_get_insert (buffer))
+ {
+ while ((snippet = g_queue_peek_head (priv->snippets)) &&
+ !ide_source_snippet_insert_set (snippet, mark))
+ ide_source_view_pop_snippet (self);
+ }
+
+ ide_source_view_unblock_handlers (self);
+}
+
+static void
ide_source_view_connect_buffer (IdeSourceView *self,
IdeBuffer *buffer)
{
@@ -263,6 +641,41 @@ ide_source_view_connect_buffer (IdeSourceView *self,
self,
G_CONNECT_SWAPPED);
+ priv->buffer_insert_text_handler =
+ g_signal_connect_object (buffer,
+ "insert-text",
+ G_CALLBACK (ide_source_view__buffer_insert_text_cb),
+ self,
+ 0);
+
+ priv->buffer_insert_text_after_handler =
+ g_signal_connect_object (buffer,
+ "insert-text",
+ G_CALLBACK (ide_source_view__buffer_insert_text_after_cb),
+ self,
+ G_CONNECT_AFTER);
+
+ priv->buffer_delete_range_handler =
+ g_signal_connect_object (buffer,
+ "delete-range",
+ G_CALLBACK (ide_source_view__buffer_delete_range_cb),
+ self,
+ 0);
+
+ priv->buffer_delete_range_after_handler =
+ g_signal_connect_object (buffer,
+ "delete-range",
+ G_CALLBACK (ide_source_view__buffer_delete_range_after_cb),
+ self,
+ G_CONNECT_AFTER);
+
+ priv->buffer_mark_set_handler =
+ g_signal_connect_object (buffer,
+ "mark-set",
+ G_CALLBACK (ide_source_view__buffer_mark_set_cb),
+ self,
+ 0);
+
ide_source_view__buffer_notify_language_cb (self, NULL, buffer);
ide_source_view__buffer_notify_file_cb (self, NULL, buffer);
}
@@ -276,8 +689,11 @@ ide_source_view_disconnect_buffer (IdeSourceView *self,
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
- ide_clear_signal_handler (buffer, &priv->buffer_changed_handler);
- ide_clear_signal_handler (buffer, &priv->buffer_notify_file_handler);
+ ide_clear_signal_handler (buffer, &priv->buffer_delete_range_after_handler);
+ ide_clear_signal_handler (buffer, &priv->buffer_delete_range_handler);
+ ide_clear_signal_handler (buffer, &priv->buffer_insert_text_after_handler);
+ ide_clear_signal_handler (buffer, &priv->buffer_insert_text_handler);
+ ide_clear_signal_handler (buffer, &priv->buffer_mark_set_handler);
ide_clear_signal_handler (buffer, &priv->buffer_notify_language_handler);
ide_source_view_set_indenter (self, NULL);
@@ -362,10 +778,8 @@ ide_source_view_maybe_overwrite (IdeSourceView *self,
* messes with the position tracking. Once we can better integrate these
* things, go ahead and remove this.
*/
-#ifdef TODO_AFTER_SNIPPETS
if (priv->snippets->length)
return;
-#endif
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
mark = gtk_text_buffer_get_insert (buffer);
@@ -642,6 +1056,7 @@ ide_source_view_key_press_event (GtkWidget *widget,
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
+ IdeSourceSnippet *snippet;
gboolean ret = FALSE;
g_assert (IDE_IS_SOURCE_VIEW (self));
@@ -651,39 +1066,37 @@ ide_source_view_key_press_event (GtkWidget *widget,
/*
* Handle movement through the tab stops of the current snippet if needed.
*/
-#ifdef TODO_AFTER_SNIPPETS
if ((snippet = g_queue_peek_head (priv->snippets)))
{
switch ((gint) event->keyval)
{
case GDK_KEY_Escape:
- ide_source_view_block_handlers (view);
- ide_source_view_pop_snippet (view);
- ide_source_view_scroll_to_insert (view);
- ide_source_view_unblock_handlers (view);
+ ide_source_view_block_handlers (self);
+ ide_source_view_pop_snippet (self);
+ ide_source_view_scroll_to_insert (self);
+ ide_source_view_unblock_handlers (self);
return TRUE;
case GDK_KEY_KP_Tab:
case GDK_KEY_Tab:
- ide_source_view_block_handlers (view);
- if (!gb_source_snippet_move_next (snippet))
- ide_source_view_pop_snippet (view);
- ide_source_view_scroll_to_insert (view);
- ide_source_view_unblock_handlers (view);
+ ide_source_view_block_handlers (self);
+ if (!ide_source_snippet_move_next (snippet))
+ ide_source_view_pop_snippet (self);
+ ide_source_view_scroll_to_insert (self);
+ ide_source_view_unblock_handlers (self);
return TRUE;
case GDK_KEY_ISO_Left_Tab:
- ide_source_view_block_handlers (view);
- gb_source_snippet_move_previous (snippet);
- ide_source_view_scroll_to_insert (view);
- ide_source_view_unblock_handlers (view);
+ ide_source_view_block_handlers (self);
+ ide_source_snippet_move_previous (snippet);
+ ide_source_view_scroll_to_insert (self);
+ ide_source_view_unblock_handlers (self);
return TRUE;
default:
break;
}
}
-#endif
/*
* Allow the Input Method Context to potentially filter this keystroke.
@@ -802,8 +1215,11 @@ ide_source_view_dispose (GObject *object)
IdeSourceView *self = (IdeSourceView *)object;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ ide_source_view_clear_snippets (self);
+
g_clear_object (&priv->indenter);
g_clear_object (&priv->line_change_renderer);
+ g_clear_object (&priv->snippets_provider);
g_clear_object (&priv->css_provider);
if (priv->buffer)
@@ -812,9 +1228,19 @@ ide_source_view_dispose (GObject *object)
g_clear_object (&priv->buffer);
}
+ G_OBJECT_CLASS (ide_source_view_parent_class)->dispose (object);
+}
+
+static void
+ide_source_view_finalize (GObject *object)
+{
+ IdeSourceView *self = (IdeSourceView *)object;
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
g_clear_pointer (&priv->font_desc, pango_font_description_free);
+ g_clear_pointer (&priv->snippets, g_queue_free);
- G_OBJECT_CLASS (ide_source_view_parent_class)->dispose (object);
+ G_OBJECT_CLASS (ide_source_view_parent_class)->finalize (object);
}
static void
@@ -852,6 +1278,10 @@ ide_source_view_get_property (GObject *object,
g_value_set_boolean (value, ide_source_view_get_show_line_changes (self));
break;
+ case PROP_SNIPPET_COMPLETION:
+ g_value_set_boolean (value, ide_source_view_get_snippet_completion (self));
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -897,6 +1327,10 @@ ide_source_view_set_property (GObject *object,
ide_source_view_set_show_line_changes (self, g_value_get_boolean (value));
break;
+ case PROP_SNIPPET_COMPLETION:
+ ide_source_view_set_snippet_completion (self, g_value_get_boolean (value));
+ break;
+
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
@@ -910,6 +1344,7 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
object_class->constructed = ide_source_view_constructed;
object_class->dispose = ide_source_view_dispose;
+ object_class->finalize = ide_source_view_finalize;
object_class->get_property = ide_source_view_get_property;
object_class->set_property = ide_source_view_set_property;
@@ -970,11 +1405,44 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (object_class, PROP_SHOW_LINE_CHANGES,
gParamSpecs [PROP_SHOW_LINE_CHANGES]);
+
+ gParamSpecs [PROP_SNIPPET_COMPLETION] =
+ g_param_spec_boolean ("snippet-completion",
+ _("Snippet Completion"),
+ _("If snippet expansion should be enabled via the completion window."),
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_SNIPPET_COMPLETION,
+ gParamSpecs [PROP_SNIPPET_COMPLETION]);
+
+ gSignals [POP_SNIPPET] = g_signal_new ("pop-snippet",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeSourceViewClass, pop_snippet),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ IDE_TYPE_SOURCE_SNIPPET);
+
+ gSignals [PUSH_SNIPPET] = g_signal_new ("push-snippet",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (IdeSourceViewClass, push_snippet),
+ NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 3,
+ IDE_TYPE_SOURCE_SNIPPET,
+ IDE_TYPE_SOURCE_SNIPPET_CONTEXT,
+ GTK_TYPE_TEXT_ITER);
}
static void
ide_source_view_init (IdeSourceView *self)
{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ priv->snippets = g_queue_new ();
+
g_signal_connect (self,
"notify::buffer",
G_CALLBACK (ide_source_view_notify_buffer),
@@ -1142,3 +1610,177 @@ ide_source_view_set_overwrite_braces (IdeSourceView *self,
g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_OVERWRITE_BRACES]);
}
}
+
+void
+ide_source_view_pop_snippet (IdeSourceView *self)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ IdeSourceSnippet *snippet;
+
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+
+ if ((snippet = g_queue_pop_head (priv->snippets)))
+ {
+ ide_source_snippet_finish (snippet);
+ g_signal_emit (self, gSignals [POP_SNIPPET], 0, snippet);
+ g_object_unref (snippet);
+ }
+
+ if ((snippet = g_queue_peek_head (priv->snippets)))
+ ide_source_snippet_unpause (snippet);
+
+ ide_source_view_invalidate_window (self);
+}
+
+void
+ide_source_view_clear_snippets (IdeSourceView *self)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+
+ while (priv->snippets->length)
+ ide_source_view_pop_snippet (self);
+}
+
+void
+ide_source_view_push_snippet (IdeSourceView *self,
+ IdeSourceSnippet *snippet)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ IdeSourceSnippetContext *context;
+ IdeSourceSnippet *previous;
+ GtkTextBuffer *buffer;
+ GtkTextMark *mark;
+ GtkTextIter iter;
+ gboolean has_more_tab_stops;
+ gboolean insert_spaces;
+ gchar *line_prefix;
+ guint tab_width;
+
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+ g_return_if_fail (IDE_IS_SOURCE_SNIPPET (snippet));
+
+ context = ide_source_snippet_get_context (snippet);
+
+ if ((previous = g_queue_peek_head (priv->snippets)))
+ ide_source_snippet_pause (previous);
+
+ g_queue_push_head (priv->snippets, g_object_ref (snippet));
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+ mark = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
+
+ insert_spaces = gtk_source_view_get_insert_spaces_instead_of_tabs (GTK_SOURCE_VIEW (self));
+ ide_source_snippet_context_set_use_spaces (context, insert_spaces);
+
+ tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (self));
+ ide_source_snippet_context_set_tab_width (context, tab_width);
+
+ line_prefix = text_iter_get_line_prefix (&iter);
+ ide_source_snippet_context_set_line_prefix (context, line_prefix);
+ g_free (line_prefix);
+
+ g_signal_emit (self, gSignals [PUSH_SNIPPET], 0, snippet, context, &iter);
+
+ ide_source_view_block_handlers (self);
+ has_more_tab_stops = ide_source_snippet_begin (snippet, buffer, &iter);
+ ide_source_view_scroll_to_insert (self);
+ ide_source_view_unblock_handlers (self);
+
+ {
+ GtkTextMark *mark_begin;
+ GtkTextMark *mark_end;
+ GtkTextIter begin;
+ GtkTextIter end;
+
+ mark_begin = ide_source_snippet_get_mark_begin (snippet);
+ mark_end = ide_source_snippet_get_mark_end (snippet);
+
+ gtk_text_buffer_get_iter_at_mark (buffer, &begin, mark_begin);
+ gtk_text_buffer_get_iter_at_mark (buffer, &end, mark_end);
+
+ /*
+ * HACK:
+ *
+ * We need to let the GtkTextView catch up with us so that we can get a realistic area back for
+ * the location of the end iter. Without pumping the main loop, GtkTextView will clamp the
+ * result to the height of the insert line.
+ */
+ while (gtk_events_pending ())
+ gtk_main_iteration ();
+
+ animate_in (self, &begin, &end);
+ }
+
+ if (!has_more_tab_stops)
+ ide_source_view_pop_snippet (self);
+
+ ide_source_view_invalidate_window (self);
+}
+
+/**
+ * ide_source_view_get_snippet_completion:
+ *
+ * Gets the #IdeSourceView:snippet-completion property.
+ *
+ * If enabled, snippet expansion can be performed via the auto completion drop down.
+ */
+gboolean
+ide_source_view_get_snippet_completion (IdeSourceView *self)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
+
+ return priv->snippet_completion;
+}
+
+/**
+ * ide_source_view_set_snippet_completion:
+ *
+ * Sets the #IdeSourceView:snippet-completion property. By setting this property to %TRUE,
+ * snippets will be loaded for the currently activated source code language. See #IdeSourceSnippet
+ * for more information on what can be provided via a snippet.
+ *
+ * See also: ide_source_view_get_snippet_completion()
+ */
+void
+ide_source_view_set_snippet_completion (IdeSourceView *self,
+ gboolean snippet_completion)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+
+ snippet_completion = !!snippet_completion;
+
+ if (snippet_completion != priv->snippet_completion)
+ {
+ GtkSourceCompletion *completion;
+
+ priv->snippet_completion = snippet_completion;
+
+ completion = gtk_source_view_get_completion (GTK_SOURCE_VIEW (self));
+
+ if (snippet_completion)
+ {
+ if (!priv->snippets_provider)
+ {
+ priv->snippets_provider = g_object_new (IDE_TYPE_SOURCE_SNIPPET_COMPLETION_PROVIDER,
+ "source-view", self,
+ NULL);
+ ide_source_view_reload_snippets (self);
+ }
+
+ gtk_source_completion_add_provider (completion, priv->snippets_provider, NULL);
+ }
+ else
+ {
+ gtk_source_completion_remove_provider (completion, priv->snippets_provider, NULL);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_SNIPPET_COMPLETION]);
+ }
+}
diff --git a/libide/ide-source-view.h b/libide/ide-source-view.h
index 4c7a977..0bf3b42 100644
--- a/libide/ide-source-view.h
+++ b/libide/ide-source-view.h
@@ -21,6 +21,8 @@
#include <gtksourceview/gtksource.h>
+#include "ide-types.h"
+
G_BEGIN_DECLS
#define IDE_TYPE_SOURCE_VIEW (ide_source_view_get_type())
@@ -44,14 +46,26 @@ struct _IdeSourceView
struct _IdeSourceViewClass
{
GtkSourceViewClass parent_class;
+
+ void (*pop_snippet) (IdeSourceView *self,
+ IdeSourceSnippet *snippet);
+ void (*push_snippet) (IdeSourceView *self,
+ IdeSourceSnippet *snippet,
+ IdeSourceSnippetContext *context,
+ const GtkTextIter *location);
};
+void ide_source_view_clear_snippets (IdeSourceView *self);
const PangoFontDescription *ide_source_view_get_font_desc (IdeSourceView *self);
gboolean ide_source_view_get_insert_matching_brace (IdeSourceView *self);
gboolean ide_source_view_get_overwrite_braces (IdeSourceView *self);
gboolean ide_source_view_get_show_grid_lines (IdeSourceView *self);
gboolean ide_source_view_get_show_line_changes (IdeSourceView *self);
+gboolean ide_source_view_get_snippet_completion (IdeSourceView *self);
GType ide_source_view_get_type (void);
+void ide_source_view_pop_snippet (IdeSourceView *self);
+void ide_source_view_push_snippet (IdeSourceView *self,
+ IdeSourceSnippet *snippet);
void ide_source_view_set_font_desc (IdeSourceView *self,
const PangoFontDescription
*font_desc);
void ide_source_view_set_font_name (IdeSourceView *self,
@@ -64,6 +78,8 @@ void ide_source_view_set_show_grid_lines (IdeSource
gboolean
show_grid_lines);
void ide_source_view_set_show_line_changes (IdeSourceView *self,
gboolean
show_line_changes);
+void ide_source_view_set_snippet_completion (IdeSourceView *self,
+ gboolean
snippet_completion);
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]