[gnome-text-editor] page: add overlay to jump to line and optional column



commit 4cd27a03f7db31ef14dd9f7829d5cbc5bf1dd2a2
Author: Christian Hergert <chergert redhat com>
Date:   Tue Oct 5 13:03:23 2021 -0700

    page: add overlay to jump to line and optional column
    
    Ctrl+I to activate, similar to gedit. Displays an overlay similar to the
    search bar which allows inputing line numbers in the form of:
    
     line
     line:column
    
    both are 1 index based to match the visual display of columns. If no
    column is provided, the cursor is placed on the first non-whitespace
    character or the end of line if no non-whitespace character is found.
    
    Also added shortcut to Movements section of help-overlay.ui
    
    If/when https://gitlab.gnome.org/GNOME/gtk/-/issues/4315 is fixed, we can
    remove the workaround to attach to the GtkText for suppressing non-integral
    characters.
    
    Fixes #168

 src/editor-page-actions.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++
 src/editor-page-private.h |  2 ++
 src/editor-page.c         | 61 +++++++++++++++++++++++++++++++++++++++++++++
 src/editor-page.ui        | 57 ++++++++++++++++++++++++++++++++++++++++++
 src/help-overlay.ui       |  6 +++++
 5 files changed, 189 insertions(+)
---
diff --git a/src/editor-page-actions.c b/src/editor-page-actions.c
index 63d5de8..57bee49 100644
--- a/src/editor-page-actions.c
+++ b/src/editor-page-actions.c
@@ -49,6 +49,7 @@ editor_page_actions_search_hide (GtkWidget  *widget,
 
   g_assert (EDITOR_IS_PAGE (self));
 
+  gtk_revealer_set_reveal_child (self->goto_line_revealer, FALSE);
   _editor_page_hide_search (self);
   editor_page_grab_focus (self);
 }
@@ -110,6 +111,64 @@ editor_page_actions_replace_all (GtkWidget  *widget,
     }
 }
 
+static void
+editor_page_actions_show_goto_line (GtkWidget  *widget,
+                                    const char *action_name,
+                                    GVariant   *param)
+{
+  EditorPage *self = (EditorPage *)widget;
+  char str[12];
+  guint line, column;
+
+  g_assert (EDITOR_IS_PAGE (self));
+
+  editor_page_get_visual_position (self, &line, &column);
+  g_snprintf (str, sizeof str, "%u", line + 1);
+  gtk_editable_set_text (GTK_EDITABLE (self->goto_line_entry), str);
+
+  gtk_revealer_set_reveal_child (self->goto_line_revealer, TRUE);
+  gtk_widget_grab_focus (GTK_WIDGET (self->goto_line_entry));
+}
+
+static void
+editor_page_actions_goto_line (GtkWidget  *widget,
+                               const char *action_name,
+                               GVariant   *param)
+{
+  EditorPage *self = (EditorPage *)widget;
+  const char *str;
+  guint line = 0, column = 0;
+  int count;
+
+  g_assert (EDITOR_IS_PAGE (self));
+
+  str = gtk_editable_get_text (GTK_EDITABLE (self->goto_line_entry));
+  count = sscanf (str, "%u:%u", &line, &column);
+
+  if (count >= 1)
+    {
+      EditorDocument *document = editor_page_get_document (self);
+      GtkTextIter iter;
+
+      if (line > 0)
+        --line;
+
+      if (column > 0)
+        --column;
+
+      gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (document), &iter, line, column);
+      while (count == 1 &&
+             !gtk_text_iter_is_end (&iter) &&
+             !gtk_text_iter_ends_line (&iter) &&
+             g_unichar_isspace (gtk_text_iter_get_char (&iter)))
+        gtk_text_iter_forward_char (&iter);
+      gtk_text_buffer_select_range (GTK_TEXT_BUFFER (document), &iter, &iter);
+    }
+
+  gtk_revealer_set_reveal_child (self->goto_line_revealer, FALSE);
+  editor_page_grab_focus (self);
+}
+
 static void
 on_notify_can_move_cb (EditorPage      *self,
                        GParamSpec      *pspec,
@@ -147,6 +206,10 @@ _editor_page_class_actions_init (EditorPageClass *klass)
 
   gtk_widget_class_install_action (widget_class, "page.language", NULL,
                                    editor_page_actions_language);
+  gtk_widget_class_install_action (widget_class, "page.show-goto-line", NULL,
+                                   editor_page_actions_show_goto_line);
+  gtk_widget_class_install_action (widget_class, "page.goto-line", NULL,
+                                   editor_page_actions_goto_line);
   gtk_widget_class_install_action (widget_class, "search.hide", NULL,
                                    editor_page_actions_search_hide);
   gtk_widget_class_install_action (widget_class, "search.move-next", NULL,
diff --git a/src/editor-page-private.h b/src/editor-page-private.h
index 4469090..506ab52 100644
--- a/src/editor-page-private.h
+++ b/src/editor-page-private.h
@@ -51,6 +51,8 @@ struct _EditorPage
   GtkSourceView           *view;
   GtkSourceMap            *map;
   GtkProgressBar          *progress_bar;
+  GtkRevealer             *goto_line_revealer;
+  GtkEntry                *goto_line_entry;
   GtkRevealer             *search_revealer;
   EditorSearchBar         *search_bar;
   GtkInfoBar              *changed_infobar;
diff --git a/src/editor-page.c b/src/editor-page.c
index ca7a4ef..3316600 100644
--- a/src/editor-page.c
+++ b/src/editor-page.c
@@ -358,6 +358,48 @@ editor_page_drop_target_drop (EditorPage     *self,
   return FALSE;
 }
 
+static void
+goto_line_entry_activate_cb (EditorPage *self,
+                             GtkEntry   *entry)
+{
+  g_assert (EDITOR_IS_PAGE (self));
+  g_assert (GTK_IS_ENTRY (entry));
+
+  gtk_widget_activate_action (GTK_WIDGET (self), "page.goto-line", NULL);
+}
+
+static void
+goto_line_entry_insert_text_cb (EditorPage *self,
+                                const char *new_text,
+                                int         length,
+                                int        *position,
+                                GtkText    *entry)
+{
+  const char *text;
+  GString *str;
+
+  g_assert (EDITOR_IS_PAGE (self));
+  g_assert (position != NULL);
+  g_assert (GTK_IS_TEXT (entry));
+
+  text = gtk_editable_get_text (GTK_EDITABLE (entry));
+  str = g_string_new (text);
+
+  if (position < 0)
+    g_string_insert_len (str, *position, new_text, length);
+
+  for (const char *c = str->str; *c; c = g_utf8_next_char (c))
+    {
+      gunichar ch = g_utf8_get_char (c);
+
+      if (ch == ':' || (ch >= '0' && ch <= '9'))
+        continue;
+
+      g_signal_stop_emission_by_name (entry, "insert-text");
+      return;
+    }
+}
+
 static gboolean
 get_child_position_cb (GtkOverlay    *overlay,
                        GtkWidget     *child,
@@ -554,6 +596,8 @@ editor_page_class_init (EditorPageClass *klass)
   gtk_widget_class_set_css_name (widget_class, "page");
   gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/TextEditor/ui/editor-page.ui");
   gtk_widget_class_bind_template_child (widget_class, EditorPage, box);
+  gtk_widget_class_bind_template_child (widget_class, EditorPage, goto_line_entry);
+  gtk_widget_class_bind_template_child (widget_class, EditorPage, goto_line_revealer);
   gtk_widget_class_bind_template_child (widget_class, EditorPage, infobar);
   gtk_widget_class_bind_template_child (widget_class, EditorPage, map);
   gtk_widget_class_bind_template_child (widget_class, EditorPage, overlay);
@@ -563,8 +607,10 @@ editor_page_class_init (EditorPageClass *klass)
   gtk_widget_class_bind_template_child (widget_class, EditorPage, search_revealer);
   gtk_widget_class_bind_template_child (widget_class, EditorPage, view);
   gtk_widget_class_bind_template_callback (widget_class, get_child_position_cb);
+  gtk_widget_class_bind_template_callback (widget_class, goto_line_entry_activate_cb);
 
   gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "search.hide", NULL);
+  gtk_widget_class_add_binding_action (widget_class, GDK_KEY_i, GDK_CONTROL_MASK, "page.show-goto-line", 
NULL);
 
   g_type_ensure (EDITOR_TYPE_INFO_BAR);
   g_type_ensure (EDITOR_TYPE_SEARCH_BAR);
@@ -578,6 +624,21 @@ editor_page_init (EditorPage *self)
 
   gtk_widget_init_template (GTK_WIDGET (self));
 
+  /* Work around https://gitlab.gnome.org/GNOME/gtk/-/issues/4315
+   * by connecting to the GtkText to intercept insert-text() emission.
+   */
+  for (GtkWidget *child = gtk_widget_get_first_child (GTK_WIDGET (self->goto_line_entry));
+       child;
+       child = gtk_widget_get_next_sibling (child))
+    {
+      if (GTK_IS_TEXT (child))
+        g_signal_connect_object (child,
+                                 "insert-text",
+                                 G_CALLBACK (goto_line_entry_insert_text_cb),
+                                 self,
+                                 G_CONNECT_SWAPPED);
+    }
+
   g_object_bind_property (self, "document", self->infobar, "document", 0);
 
   dest = gtk_drop_target_new (G_TYPE_FILE, GDK_ACTION_COPY);
diff --git a/src/editor-page.ui b/src/editor-page.ui
index 41a45cd..f324637 100644
--- a/src/editor-page.ui
+++ b/src/editor-page.ui
@@ -68,6 +68,63 @@
                 </child>
               </object>
             </child>
+            <child type="overlay">
+              <object class="GtkRevealer" id="goto_line_revealer">
+                <property name="halign">end</property>
+                <property name="margin-end">6</property>
+                <property name="hexpand">false</property>
+                <property name="valign">start</property>
+                <property name="vexpand">false</property>
+                <property name="transition-duration">300</property>
+                <property name="transition-type">slide-down</property>
+                <property name="reveal-child">false</property>
+                <layout>
+                  <property name="measure">true</property>
+                </layout>
+                <child>
+                  <object class="GtkBox" id="goto_line_bar">
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">6</property>
+                    <style>
+                      <class name="searchbar"/>
+                    </style>
+                    <child>
+                      <object class="GtkBox">
+                        <property name="spacing">6</property>
+                        <child>
+                          <object class="GtkLabel">
+                            <property name="label" translatable="yes">Go to Line</property>
+                            <property name="margin-start">3</property>
+                            <property name="margin-end">3</property>
+                            <property name="halign">start</property>
+                            <attributes>
+                              <attribute name="weight" value="bold"/>
+                            </attributes>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkEntry" id="goto_line_entry">
+                            <property name="width-chars">3</property>
+                            <property name="max-width-chars">7</property>
+                            <signal name="activate" handler="goto_line_entry_activate_cb" swapped="true"/>
+                          </object>
+                        </child>
+                        <child>
+                          <object class="GtkButton" id="goto_line_button">
+                            <property name="label" translatable="yes">Go</property>
+                            <property name="width-request">50</property>
+                            <property name="action-name">page.goto-line</property>
+                            <style>
+                              <class name="suggested-action"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
             <child type="overlay">
               <object class="GtkProgressBar" id="progress_bar">
                 <style>
diff --git a/src/help-overlay.ui b/src/help-overlay.ui
index 91070ef..e6f56ef 100644
--- a/src/help-overlay.ui
+++ b/src/help-overlay.ui
@@ -266,6 +266,12 @@
                 <property name="title" translatable="yes" context="shortcut window">Move current or selected 
lines down</property>
               </object>
             </child>
+            <child>
+              <object class="GtkShortcutsShortcut">
+                <property name="accelerator">&lt;ctrl&gt;i</property>
+                <property name="title" translatable="yes" context="shortcut window">Go to line</property>
+              </object>
+            </child>
           </object>
         </child>
         <child>


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