[gnome-builder] libide: implement brace helpers for IdeSourceView
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] libide: implement brace helpers for IdeSourceView
- Date: Mon, 23 Mar 2015 23:41:22 +0000 (UTC)
commit de298b0dc21a503f135de7749e9b416e2816e143
Author: Christian Hergert <christian hergert me>
Date: Tue Feb 24 18:51:32 2015 -0800
libide: implement brace helpers for IdeSourceView
Implements
- IdeSourceView:insert-matching-brace
- IdeSourceView:overwrite-braces
libide/ide-source-view.c | 452 ++++++++++++++++++++++++++++++++++++++++--
libide/ide-source-view.h | 31 ++--
tests/test-ide-source-view.c | 2 +
3 files changed, 454 insertions(+), 31 deletions(-)
---
diff --git a/libide/ide-source-view.c b/libide/ide-source-view.c
index 9aa643d..7e735e3 100644
--- a/libide/ide-source-view.c
+++ b/libide/ide-source-view.c
@@ -45,6 +45,8 @@ typedef struct
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;
} IdeSourceViewPrivate;
@@ -56,6 +58,8 @@ enum {
PROP_AUTO_INDENT,
PROP_FONT_NAME,
PROP_FONT_DESC,
+ PROP_INSERT_MATCHING_BRACE,
+ PROP_OVERWRITE_BRACES,
PROP_SHOW_GRID_LINES,
PROP_SHOW_LINE_CHANGES,
LAST_PROP
@@ -311,6 +315,326 @@ ide_source_view_notify_buffer (IdeSourceView *self,
}
}
+static gunichar
+peek_previous_char (const GtkTextIter *iter)
+{
+ GtkTextIter copy = *iter;
+ gunichar ch = 0;
+
+ if (!gtk_text_iter_is_start (©))
+ {
+ gtk_text_iter_backward_char (©);
+ ch = gtk_text_iter_get_char (©);
+ }
+
+ return ch;
+}
+
+static void
+ide_source_view_maybe_overwrite (IdeSourceView *self,
+ GdkEventKey *event)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ GtkTextMark *mark;
+ GtkTextBuffer *buffer;
+ GtkTextIter iter;
+ gunichar ch;
+ gunichar prev_ch;
+ gboolean ignore = FALSE;
+
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+ g_return_if_fail (event);
+
+ /*
+ * Some auto-indenters will perform triggers on certain key-press that we
+ * would hijack by otherwise "doing nothing" during this key-press. So to
+ * avoid that, we actually delete the previous value and then allow this
+ * key-press event to continue.
+ */
+
+ if (!priv->overwrite_braces)
+ return;
+
+ /*
+ * WORKAROUND:
+ *
+ * If we are inside of a snippet, then let's not do anything. It really
+ * 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);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
+
+ ch = gtk_text_iter_get_char (&iter);
+ prev_ch = peek_previous_char (&iter);
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_parenright:
+ ignore = (ch == ')');
+ break;
+
+ case GDK_KEY_bracketright:
+ ignore = (ch == ']');
+ break;
+
+ case GDK_KEY_braceright:
+ ignore = (ch == '}');
+ break;
+
+ case GDK_KEY_quotedbl:
+ ignore = (ch == '"') && (prev_ch != '\\');
+ break;
+
+ case GDK_KEY_quoteleft:
+ case GDK_KEY_quoteright:
+ ignore = (ch == '\'');
+ break;
+
+ default:
+ break;
+ }
+
+ if (ignore && !gtk_text_buffer_get_has_selection (buffer))
+ {
+ GtkTextIter next = iter;
+
+ if (!gtk_text_iter_forward_char (&next))
+ gtk_text_buffer_get_end_iter (buffer, &next);
+
+ gtk_text_buffer_select_range (buffer, &iter, &next);
+ }
+}
+
+static gboolean
+is_closing_char (gunichar ch)
+{
+ switch (ch)
+ {
+ case '}':
+ case ')':
+ case '"':
+ case '\'':
+ case ']':
+ return TRUE;
+
+ default:
+ return FALSE;
+ }
+}
+
+static guint
+count_chars_on_line (IdeSourceView *view,
+ gunichar expected_char,
+ const GtkTextIter *iter)
+{
+ GtkTextIter cur;
+ guint count = 0;
+
+ g_return_val_if_fail (IDE_IS_SOURCE_VIEW (view), 0);
+ g_return_val_if_fail (iter, 0);
+
+ cur = *iter;
+
+ gtk_text_iter_set_line_offset (&cur, 0);
+
+ while (!gtk_text_iter_ends_line (&cur))
+ {
+ gunichar ch;
+
+ ch = gtk_text_iter_get_char (&cur);
+
+ if (ch == '\\')
+ {
+ gtk_text_iter_forward_chars (&cur, 2);
+ continue;
+ }
+
+ count += (ch == expected_char);
+ gtk_text_iter_forward_char (&cur);
+ }
+
+ return count;
+}
+
+static gboolean
+ide_source_view_maybe_insert_match (IdeSourceView *self,
+ GdkEventKey *event)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ GtkSourceBuffer *sbuf;
+ GtkTextBuffer *buffer;
+ GtkTextMark *insert;
+ GtkTextIter iter;
+ GtkTextIter prev_iter;
+ GtkTextIter next_iter;
+ gunichar next_ch = 0;
+ gchar ch = 0;
+
+ /*
+ * TODO: I think we should put this into a base class for auto
+ * indenters. It would make some things a lot more convenient, like
+ * changing which characters we won't add matching characters for.
+ */
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+ g_assert (event);
+
+ /*
+ * If we are disabled, then do nothing.
+ */
+ if (!priv->insert_matching_brace)
+ return FALSE;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+ sbuf = GTK_SOURCE_BUFFER (buffer);
+
+ insert = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+ next_ch = gtk_text_iter_get_char (&iter);
+
+ prev_iter = iter;
+ gtk_text_iter_backward_chars (&prev_iter, 2);
+
+ /*
+ * If the source language has marked this region as a string or comment,
+ * then do nothing.
+ */
+ if (gtk_source_buffer_iter_has_context_class (sbuf, &prev_iter, "string") ||
+ gtk_source_buffer_iter_has_context_class (sbuf, &prev_iter, "comment"))
+ return FALSE;
+
+ switch (event->keyval)
+ {
+ case GDK_KEY_braceleft:
+ ch = '}';
+ break;
+
+ case GDK_KEY_parenleft:
+ ch = ')';
+ break;
+
+ case GDK_KEY_bracketleft:
+ ch = ']';
+ break;
+
+ case GDK_KEY_quotedbl:
+ ch = '"';
+ break;
+
+#if 0
+ /*
+ * TODO: We should avoid this when we are in comments, etc. That will
+ * require some communication with the syntax engine.
+ */
+ case GDK_KEY_quoteleft:
+ case GDK_KEY_quoteright:
+ ch = '\'';
+ break;
+#endif
+
+ default:
+ return FALSE;
+ }
+
+ /*
+ * Insert the match if one of the following is true:
+ *
+ * - We are at EOF
+ * - The next character is whitespace
+ * - The next character is a closing brace.
+ * - If the char is ", then there must be an even number already on
+ * the current line.
+ */
+
+ next_iter = iter;
+ if (gtk_text_iter_forward_char (&next_iter))
+ next_ch = gtk_text_iter_get_char (&next_iter);
+
+ if (!next_ch || g_unichar_isspace (next_ch) || is_closing_char (next_ch))
+ {
+ /*
+ * Special case for working with double quotes.
+ *
+ * Ignore double quote if we just added enough to make there be an
+ * even number on this line. However, if it was the first quote on
+ * the line, we still need to include a second.
+ */
+ if (ch == '"')
+ {
+ guint count;
+
+ count = count_chars_on_line (self, '"', &iter);
+ if ((count > 1) && ((count % 2) == 0))
+ return FALSE;
+ }
+
+ gtk_text_buffer_insert_at_cursor (buffer, &ch, 1);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+ gtk_text_iter_backward_char (&iter);
+ gtk_text_buffer_select_range (buffer, &iter, &iter);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+ide_source_view_maybe_delete_match (IdeSourceView *self,
+ GdkEventKey *event)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+ GtkTextBuffer *buffer;
+ GtkTextMark *insert;
+ GtkTextIter iter;
+ GtkTextIter prev;
+ gunichar ch;
+ gunichar match;
+
+ g_assert (IDE_IS_SOURCE_VIEW (self));
+ g_assert (event);
+ g_assert_cmpint (event->keyval, ==, GDK_KEY_BackSpace);
+
+ if (!priv->insert_matching_brace)
+ return FALSE;
+
+ buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+ insert = gtk_text_buffer_get_insert (buffer);
+ gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
+ prev = iter;
+ if (!gtk_text_iter_backward_char (&prev))
+ return FALSE;
+
+ ch = gtk_text_iter_get_char (&prev);
+
+ switch (ch)
+ {
+ case '[': match = ']'; break;
+ case '{': match = '}'; break;
+ case '(': match = ')'; break;
+ case '"': match = '"'; break;
+ case '\'': match = '\''; break;
+ default: match = 0; break;
+ }
+
+ if (gtk_text_iter_get_char (&iter) == match)
+ {
+ gtk_text_iter_forward_char (&iter);
+ gtk_text_buffer_delete (buffer, &prev, &iter);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
static gboolean
ide_source_view_key_press_event (GtkWidget *widget,
GdkEventKey *event)
@@ -324,32 +648,32 @@ ide_source_view_key_press_event (GtkWidget *widget,
/*
* Handle movement through the tab stops of the current snippet if needed.
*/
-#if 0
+#ifdef TODO_AFTER_SNIPPETS
if ((snippet = g_queue_peek_head (priv->snippets)))
{
switch ((gint) event->keyval)
{
case GDK_KEY_Escape:
- gb_source_view_block_handlers (view);
- gb_source_view_pop_snippet (view);
- gb_source_view_scroll_to_insert (view);
- gb_source_view_unblock_handlers (view);
+ 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);
return TRUE;
case GDK_KEY_KP_Tab:
case GDK_KEY_Tab:
- gb_source_view_block_handlers (view);
+ ide_source_view_block_handlers (view);
if (!gb_source_snippet_move_next (snippet))
- gb_source_view_pop_snippet (view);
- gb_source_view_scroll_to_insert (view);
- gb_source_view_unblock_handlers (view);
+ ide_source_view_pop_snippet (view);
+ ide_source_view_scroll_to_insert (view);
+ ide_source_view_unblock_handlers (view);
return TRUE;
case GDK_KEY_ISO_Left_Tab:
- gb_source_view_block_handlers (view);
+ ide_source_view_block_handlers (view);
gb_source_snippet_move_previous (snippet);
- gb_source_view_scroll_to_insert (view);
- gb_source_view_unblock_handlers (view);
+ ide_source_view_scroll_to_insert (view);
+ ide_source_view_unblock_handlers (view);
return TRUE;
default:
@@ -370,9 +694,15 @@ ide_source_view_key_press_event (GtkWidget *widget,
* buffer, we may want to remove it first. This allows us to still trigger
* the auto-indent engine (instead of just short-circuiting the key-press).
*/
-#if 0
- gb_source_view_maybe_overwrite (view, event);
-#endif
+ ide_source_view_maybe_overwrite (self, event);
+
+ /*
+ * If we are backspacing, and the next character is the matching brace,
+ * then we might want to delete it too.
+ */
+ if (event->keyval == GDK_KEY_BackSpace)
+ if (ide_source_view_maybe_delete_match (self, event))
+ return TRUE;
/*
* If we have an auto-indenter and the event is for a trigger key, then we
@@ -438,10 +768,8 @@ ide_source_view_key_press_event (GtkWidget *widget,
ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->key_press_event (widget, event);
-#if 0
if (ret)
- gb_source_view_maybe_insert_match (view, event);
-#endif
+ ide_source_view_maybe_insert_match (self, event);
return ret;
}
@@ -505,6 +833,14 @@ ide_source_view_get_property (GObject *object,
g_value_set_boxed (value, ide_source_view_get_font_desc (self));
break;
+ case PROP_INSERT_MATCHING_BRACE:
+ g_value_set_boolean (value, ide_source_view_get_insert_matching_brace (self));
+ break;
+
+ case PROP_OVERWRITE_BRACES:
+ g_value_set_boolean (value, ide_source_view_get_overwrite_braces (self));
+ break;
+
case PROP_SHOW_GRID_LINES:
g_value_set_boolean (value, ide_source_view_get_show_grid_lines (self));
break;
@@ -542,6 +878,14 @@ ide_source_view_set_property (GObject *object,
ide_source_view_set_font_desc (self, g_value_get_boxed (value));
break;
+ case PROP_INSERT_MATCHING_BRACE:
+ ide_source_view_set_insert_matching_brace (self, g_value_get_boolean (value));
+ break;
+
+ case PROP_OVERWRITE_BRACES:
+ ide_source_view_set_overwrite_braces (self, g_value_get_boolean (value));
+ break;
+
case PROP_SHOW_GRID_LINES:
ide_source_view_set_show_grid_lines (self, g_value_get_boolean (value));
break;
@@ -588,6 +932,24 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
g_object_class_install_property (object_class, PROP_FONT_NAME,
gParamSpecs [PROP_FONT_NAME]);
+ gParamSpecs [PROP_INSERT_MATCHING_BRACE] =
+ g_param_spec_boolean ("insert-matching-brace",
+ _("Insert Matching Brace"),
+ _("Insert a matching brace/bracket/quotation/paren."),
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_INSERT_MATCHING_BRACE,
+ gParamSpecs [PROP_INSERT_MATCHING_BRACE]);
+
+ gParamSpecs [PROP_OVERWRITE_BRACES] =
+ g_param_spec_boolean ("overwrite-braces",
+ _("Overwrite Braces"),
+ _("Overwrite a matching brace/bracket/quotation/paren."),
+ FALSE,
+ (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_OVERWRITE_BRACES,
+ gParamSpecs [PROP_OVERWRITE_BRACES]);
+
gParamSpecs [PROP_SHOW_GRID_LINES] =
g_param_spec_boolean ("show-grid-lines",
_("Show Grid Lines"),
@@ -723,3 +1085,57 @@ ide_source_view_set_show_grid_lines (IdeSourceView *self,
g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_SHOW_GRID_LINES]);
}
}
+
+gboolean
+ide_source_view_get_insert_matching_brace (IdeSourceView *self)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
+
+ return priv->insert_matching_brace;
+}
+
+gboolean
+ide_source_view_get_overwrite_braces (IdeSourceView *self)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
+
+ return priv->overwrite_braces;
+}
+
+void
+ide_source_view_set_insert_matching_brace (IdeSourceView *self,
+ gboolean insert_matching_brace)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+
+ insert_matching_brace = !!insert_matching_brace;
+
+ if (insert_matching_brace != priv->insert_matching_brace)
+ {
+ priv->insert_matching_brace = insert_matching_brace;
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_INSERT_MATCHING_BRACE]);
+ }
+}
+
+void
+ide_source_view_set_overwrite_braces (IdeSourceView *self,
+ gboolean overwrite_braces)
+{
+ IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+ g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+
+ overwrite_braces = !!overwrite_braces;
+
+ if (overwrite_braces != priv->overwrite_braces)
+ {
+ priv->overwrite_braces = overwrite_braces;
+ g_object_notify_by_pspec (G_OBJECT (self), gParamSpecs [PROP_OVERWRITE_BRACES]);
+ }
+}
diff --git a/libide/ide-source-view.h b/libide/ide-source-view.h
index f1683c1..4c7a977 100644
--- a/libide/ide-source-view.h
+++ b/libide/ide-source-view.h
@@ -46,19 +46,24 @@ struct _IdeSourceViewClass
GtkSourceViewClass parent_class;
};
-GType ide_source_view_get_type (void);
-
-const PangoFontDescription *ide_source_view_get_font_desc (IdeSourceView *self);
-gboolean ide_source_view_get_show_grid_lines (IdeSourceView *self);
-gboolean ide_source_view_get_show_line_changes (IdeSourceView *self);
-void ide_source_view_set_font_desc (IdeSourceView *self,
- const PangoFontDescription *font_desc);
-void ide_source_view_set_font_name (IdeSourceView *self,
- const gchar *font_name);
-void ide_source_view_set_show_grid_lines (IdeSourceView *self,
- gboolean
show_grid_lines);
-void ide_source_view_set_show_line_changes (IdeSourceView *self,
- gboolean
show_line_changes);
+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);
+GType ide_source_view_get_type (void);
+void ide_source_view_set_font_desc (IdeSourceView *self,
+ const PangoFontDescription
*font_desc);
+void ide_source_view_set_font_name (IdeSourceView *self,
+ const gchar
*font_name);
+void ide_source_view_set_insert_matching_brace (IdeSourceView *self,
+ gboolean
insert_matching_brace);
+void ide_source_view_set_overwrite_braces (IdeSourceView *self,
+ gboolean
overwrite_braces);
+void ide_source_view_set_show_grid_lines (IdeSourceView *self,
+ gboolean
show_grid_lines);
+void ide_source_view_set_show_line_changes (IdeSourceView *self,
+ gboolean
show_line_changes);
G_END_DECLS
diff --git a/tests/test-ide-source-view.c b/tests/test-ide-source-view.c
index 95c2bfe..f59602e 100644
--- a/tests/test-ide-source-view.c
+++ b/tests/test-ide-source-view.c
@@ -117,6 +117,8 @@ main (gint argc,
gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (scroller));
source_view = g_object_new (IDE_TYPE_SOURCE_VIEW,
"auto-indent", TRUE,
+ "overwrite-braces", TRUE,
+ "insert-matching-brace", TRUE,
"sensitive", FALSE,
"show-grid-lines", TRUE,
"show-line-changes", TRUE,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]