[gnome-builder] libide/sourceview: implement insert matching brace



commit ed03737f14f877b5bb223f6750a765bc017b04b5
Author: Christian Hergert <chergert redhat com>
Date:   Mon Sep 5 22:46:44 2022 -0700

    libide/sourceview: implement insert matching brace
    
    Sadly, this doesn't work with vim mode though because it passes events to
    the GtkTextView IM method which seems to always filter regular key
    presses like ' or ".

 src/libide/sourceview/ide-source-view-private.h |   1 +
 src/libide/sourceview/ide-source-view.c         | 169 ++++++++++++++++++++++++
 src/libide/sourceview/ide-source-view.h         |  63 +++++----
 3 files changed, 204 insertions(+), 29 deletions(-)
---
diff --git a/src/libide/sourceview/ide-source-view-private.h b/src/libide/sourceview/ide-source-view-private.h
index 92eff4d1a..8f9b8722f 100644
--- a/src/libide/sourceview/ide-source-view-private.h
+++ b/src/libide/sourceview/ide-source-view-private.h
@@ -72,6 +72,7 @@ struct _IdeSourceView
 
   /* Bitfield values go here */
   guint highlight_current_line : 1;
+  guint insert_matching_brace : 1;
 };
 
 
diff --git a/src/libide/sourceview/ide-source-view.c b/src/libide/sourceview/ide-source-view.c
index c1948e820..d51be41fc 100644
--- a/src/libide/sourceview/ide-source-view.c
+++ b/src/libide/sourceview/ide-source-view.c
@@ -33,6 +33,7 @@ enum {
   PROP_0,
   PROP_FONT_DESC,
   PROP_FONT_SCALE,
+  PROP_INSERT_MATCHING_BRACE,
   PROP_LINE_HEIGHT,
   PROP_ZOOM_LEVEL,
   N_PROPS,
@@ -260,6 +261,122 @@ ide_source_view_click_pressed_cb (IdeSourceView   *self,
   IDE_EXIT;
 }
 
+static gboolean
+ide_source_view_maybe_delete_match (IdeSourceView *self)
+{
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextIter iter;
+  GtkTextIter prev;
+  gunichar ch;
+  gunichar match;
+
+  g_assert (IDE_IS_SOURCE_VIEW (self));
+
+  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;
+    case '<':  match = '>';  break;
+    default:   match = 0;    break;
+    }
+
+  if (match && (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_pressed_cb (IdeSourceView         *self,
+                                guint                  keyval,
+                                guint                  keycode,
+                                GdkModifierType        state,
+                                GtkEventControllerKey *key)
+{
+  GtkTextBuffer *buffer;
+
+  g_assert (IDE_IS_SOURCE_VIEW (self));
+  g_assert (GTK_IS_EVENT_CONTROLLER_KEY (key));
+
+  buffer = GTK_TEXT_BUFFER (self->buffer);
+
+  if (self->insert_matching_brace &&
+      !gtk_text_buffer_get_has_selection (buffer))
+    {
+      const char *insert = NULL;
+      GtkTextIter iter;
+
+      if (keyval == GDK_KEY_BackSpace)
+        {
+          if (ide_source_view_maybe_delete_match (self))
+            return TRUE;
+        }
+
+      gtk_text_buffer_get_selection_bounds (buffer, &iter, NULL);
+
+      switch (keyval)
+        {
+        case GDK_KEY_bracketleft:
+          insert = "[]";
+          break;
+
+        case GDK_KEY_braceleft:
+          insert = "{}";
+          break;
+
+        case GDK_KEY_apostrophe:
+          insert = "''";
+          break;
+
+        case GDK_KEY_parenleft:
+          insert = "()";
+          break;
+
+        case GDK_KEY_quotedbl:
+          insert = "\"\"";
+          break;
+
+        case GDK_KEY_less:
+          insert = "<>";
+          break;
+
+        default:
+          break;
+        }
+
+      if (insert)
+        {
+          gtk_text_buffer_begin_user_action (buffer);
+          gtk_text_buffer_insert (buffer, &iter, insert, -1);
+          gtk_text_iter_backward_char (&iter);
+          gtk_text_buffer_select_range (buffer, &iter, &iter);
+          gtk_text_buffer_end_user_action (buffer);
+
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
 static void
 ide_source_view_buffer_request_scroll_to_insert_cb (IdeSourceView *self,
                                                     IdeBuffer     *buffer)
@@ -881,6 +998,10 @@ ide_source_view_get_property (GObject    *object,
       g_value_set_boolean (value, ide_source_view_get_highlight_current_line (self));
       break;
 
+    case PROP_INSERT_MATCHING_BRACE:
+      g_value_set_boolean (value, ide_source_view_get_insert_matching_brace (self));
+      break;
+
     case PROP_LINE_HEIGHT:
       g_value_set_double (value, self->line_height);
       break;
@@ -918,6 +1039,10 @@ ide_source_view_set_property (GObject      *object,
       ide_source_view_set_highlight_current_line (self, g_value_get_boolean (value));
       break;
 
+    case PROP_INSERT_MATCHING_BRACE:
+      ide_source_view_set_insert_matching_brace (self, g_value_get_boolean (value));
+      break;
+
     case PROP_LINE_HEIGHT:
       ide_source_view_set_line_height (self, g_value_get_double (value));
       break;
@@ -968,6 +1093,13 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
                       G_MININT, G_MAXINT, 0,
                       (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
+  properties [PROP_INSERT_MATCHING_BRACE] =
+    g_param_spec_boolean ("insert-matching-brace",
+                          "Insert Matching Brace",
+                          "Insert a matching brace/bracket/quotation/parenthesis",
+                          FALSE,
+                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
   properties [PROP_ZOOM_LEVEL] =
     g_param_spec_double ("zoom-level",
                          "Zoom Level",
@@ -1020,6 +1152,7 @@ ide_source_view_init (IdeSourceView *self)
   GtkEventController *click;
   GtkEventController *focus;
   GtkEventController *scroll;
+  GtkEventController *key;
 
   g_signal_connect (self,
                     "notify::buffer",
@@ -1035,6 +1168,7 @@ ide_source_view_init (IdeSourceView *self)
 
   /* Setup a handler to emit ::populate-menu */
   click = GTK_EVENT_CONTROLLER (gtk_gesture_click_new ());
+  gtk_event_controller_set_name (click, "ide-source-view-click");
   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click), 0);
   gtk_event_controller_set_propagation_phase (GTK_EVENT_CONTROLLER (click),
                                               GTK_PHASE_CAPTURE);
@@ -1044,8 +1178,19 @@ ide_source_view_init (IdeSourceView *self)
                             self);
   gtk_widget_add_controller (GTK_WIDGET (self), click);
 
+  /* Key handler for internal features like insert-matching-braces */
+  key = gtk_event_controller_key_new ();
+  gtk_event_controller_set_name (key, "ide-source-view-key");
+  gtk_event_controller_set_propagation_phase (key, GTK_PHASE_CAPTURE);
+  g_signal_connect_swapped (key,
+                            "key-pressed",
+                            G_CALLBACK (ide_source_view_key_pressed_cb),
+                            self);
+  gtk_widget_add_controller (GTK_WIDGET (self), key);
+
   /* Setup focus tracking */
   focus = gtk_event_controller_focus_new ();
+  gtk_event_controller_set_name (focus, "ide-source-view-focus");
   g_signal_connect_swapped (focus,
                             "enter",
                             G_CALLBACK (ide_source_view_focus_enter_cb),
@@ -1058,6 +1203,7 @@ ide_source_view_init (IdeSourceView *self)
 
   /* Setup ctrl+scroll zoom */
   scroll = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL);
+  gtk_event_controller_set_name (scroll, "ide-source-view-zoom");
   gtk_event_controller_set_propagation_phase (scroll, GTK_PHASE_CAPTURE);
   g_signal_connect (scroll,
                     "scroll",
@@ -1421,3 +1567,26 @@ ide_source_view_get_iter_at_visual_position (IdeSourceView *self,
       gtk_text_iter_forward_char (iter);
     }
 }
+
+gboolean
+ide_source_view_get_insert_matching_brace (IdeSourceView *self)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
+
+  return self->insert_matching_brace;
+}
+
+void
+ide_source_view_set_insert_matching_brace (IdeSourceView *self,
+                                           gboolean       insert_matching_brace)
+{
+  g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+
+  insert_matching_brace = !!insert_matching_brace;
+
+  if (insert_matching_brace != self->insert_matching_brace)
+    {
+      self->insert_matching_brace = insert_matching_brace;
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_INSERT_MATCHING_BRACE]);
+    }
+}
diff --git a/src/libide/sourceview/ide-source-view.h b/src/libide/sourceview/ide-source-view.h
index b36bff543..85f3a7417 100644
--- a/src/libide/sourceview/ide-source-view.h
+++ b/src/libide/sourceview/ide-source-view.h
@@ -36,47 +36,52 @@ IDE_AVAILABLE_IN_ALL
 G_DECLARE_FINAL_TYPE (IdeSourceView, ide_source_view, IDE, SOURCE_VIEW, GtkSourceView)
 
 IDE_AVAILABLE_IN_ALL
-GtkWidget                  *ide_source_view_new                 (void);
+GtkWidget                  *ide_source_view_new                         (void);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_scroll_to_insert    (IdeSourceView             *self);
+void                        ide_source_view_scroll_to_insert            (IdeSourceView              *self);
 IDE_AVAILABLE_IN_ALL
-char                       *ide_source_view_dup_position_label  (IdeSourceView             *self);
+char                       *ide_source_view_dup_position_label          (IdeSourceView              *self);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_get_visual_position (IdeSourceView             *self,
-                                                                 guint                     *line,
-                                                                 guint                     *line_column);
+void                        ide_source_view_get_visual_position         (IdeSourceView              *self,
+                                                                         guint                      *line,
+                                                                         guint                      
*line_column);
 IDE_AVAILABLE_IN_ALL
-gboolean                    ide_source_view_get_highlight_current_line (IdeSourceView *self);
+gboolean                    ide_source_view_get_highlight_current_line  (IdeSourceView              *self);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_set_highlight_current_line (IdeSourceView *self,
-                                                                        gboolean highlight_current_line);
+void                        ide_source_view_set_highlight_current_line  (IdeSourceView              *self,
+                                                                         gboolean                    
highlight_current_line);
 IDE_AVAILABLE_IN_ALL
-double                      ide_source_view_get_zoom_level      (IdeSourceView             *self);
+double                      ide_source_view_get_zoom_level              (IdeSourceView              *self);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_set_font_desc       (IdeSourceView             *self,
-                                                                const PangoFontDescription *font_desc);
+void                        ide_source_view_set_font_desc               (IdeSourceView              *self,
+                                                                         const PangoFontDescription 
*font_desc);
 IDE_AVAILABLE_IN_ALL
-const PangoFontDescription *ide_source_view_get_font_desc       (IdeSourceView             *self);
+const PangoFontDescription *ide_source_view_get_font_desc               (IdeSourceView              *self);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_prepend_menu        (IdeSourceView             *self,
-                                                                 GMenuModel                *menu_model);
+void                        ide_source_view_prepend_menu                (IdeSourceView              *self,
+                                                                         GMenuModel                 
*menu_model);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_append_menu         (IdeSourceView             *self,
-                                                                 GMenuModel                *menu_model);
+void                        ide_source_view_append_menu                 (IdeSourceView              *self,
+                                                                         GMenuModel                 
*menu_model);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_remove_menu         (IdeSourceView             *self,
-                                                                 GMenuModel                *menu_model);
+void                        ide_source_view_remove_menu                 (IdeSourceView              *self,
+                                                                         GMenuModel                 
*menu_model);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_jump_to_iter        (GtkTextView               *text_view,
-                                                                 const GtkTextIter         *iter,
-                                                                 double                     within_margin,
-                                                                 gboolean                   use_align,
-                                                                 double                     xalign,
-                                                                 double                     yalign);
+void                        ide_source_view_jump_to_iter                (GtkTextView                
*text_view,
+                                                                         const GtkTextIter          *iter,
+                                                                         double                      
within_margin,
+                                                                         gboolean                    
use_align,
+                                                                         double                      xalign,
+                                                                         double                      yalign);
 IDE_AVAILABLE_IN_ALL
-void                        ide_source_view_get_iter_at_visual_position (IdeSourceView *self,
-                                                                         GtkTextIter   *iter,
-                                                                         guint          line,
-                                                                         guint          line_offset);
+void                        ide_source_view_get_iter_at_visual_position (IdeSourceView              *self,
+                                                                         GtkTextIter                *iter,
+                                                                         guint                       line,
+                                                                         guint                       
line_offset);
+IDE_AVAILABLE_IN_ALL
+gboolean                    ide_source_view_get_insert_matching_brace   (IdeSourceView              *self);
+IDE_AVAILABLE_IN_ALL
+void                        ide_source_view_set_insert_matching_brace   (IdeSourceView              *self,
+                                                                         gboolean                    
insert_matching_brace);
 
 G_END_DECLS


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]