From 48966efcf4125fa1af72bdbb7ec8378a29494b90 Mon Sep 17 00:00:00 2001
From: Xavier Claessens <xavier.claessens@collabora.com>
Date: Fri, 8 Sep 2023 16:06:18 -0400
Subject: [PATCH] NautilusWindowSlot: Connect signals on query editor only once

It is wasteful to connect/disconnect signals each time the editor is
shown/hidden. This also make possible to receive those signals when the
query changed while being hidden for type ahead.
---
 src/nautilus-window-slot.c | 20 +++++++++-----------
 1 file changed, 9 insertions(+), 11 deletions(-)

diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c
index a251e0248a..4f588acfd8 100644
--- a/src/nautilus-window-slot.c
+++ b/src/nautilus-window-slot.c
@@ -431,8 +431,6 @@ hide_query_editor (NautilusWindowSlot *self)
 
     view = nautilus_window_slot_get_current_view (self);
 
-    g_signal_handlers_disconnect_by_data (self->query_editor, self);
-
     nautilus_query_editor_set_query (self->query_editor, NULL);
 
     if (nautilus_view_is_searching (view))
@@ -496,15 +494,6 @@ show_query_editor (NautilusWindowSlot *self)
     }
 
     gtk_widget_grab_focus (GTK_WIDGET (self->query_editor));
-
-    g_signal_connect (self->query_editor, "changed",
-                      G_CALLBACK (query_editor_changed_callback), self);
-    g_signal_connect (self->query_editor, "cancel",
-                      G_CALLBACK (query_editor_cancel_callback), self);
-    g_signal_connect (self->query_editor, "activated",
-                      G_CALLBACK (query_editor_activated_callback), self);
-    g_signal_connect (self->query_editor, "focus-view",
-                      G_CALLBACK (query_editor_focus_view_callback), self);
 }
 
 static void
@@ -912,6 +901,15 @@ nautilus_window_slot_constructed (GObject *object)
     gtk_box_append (GTK_BOX (self->vbox), extras_vbox);
 
     self->query_editor = NAUTILUS_QUERY_EDITOR (nautilus_query_editor_new ());
+    g_signal_connect (self->query_editor, "changed",
+                      G_CALLBACK (query_editor_changed_callback), self);
+    g_signal_connect (self->query_editor, "cancel",
+                      G_CALLBACK (query_editor_cancel_callback), self);
+    g_signal_connect (self->query_editor, "activated",
+                      G_CALLBACK (query_editor_activated_callback), self);
+    g_signal_connect (self->query_editor, "focus-view",
+                      G_CALLBACK (query_editor_focus_view_callback), self);
+
     /* We want to keep alive the query editor betwen additions and removals on the
      * UI, specifically when the toolbar adds or removes it */
     g_object_ref_sink (self->query_editor);
-- 
GitLab


From 05f8e6e9bee25c894d2f3582649750737081253b Mon Sep 17 00:00:00 2001
From: Xavier Claessens <xavier.claessens@collabora.com>
Date: Mon, 28 Nov 2022 15:18:15 -0500
Subject: [PATCH] Support type ahead for fast browsing

Searching is too slow when fast browsing directory tree. Even when
recursing searching is disabled, it still takes time to load the new
view and it breaks the browsing work flow.

The use-case is when user has a very well known tree and want to open a
file, for example "sources/nautilus/src/meson.build", they would type
*VERY* fast: "s" <enter> "n" <enter> "s" <enter> "m" <enter>. When going
many times per day into the same location, it becomes muscle memory,
just like tab in a terminal.

This is similar to how at least Windows file browser works.
---
 data/org.gnome.nautilus.gschema.xml           |   5 +
 src/nautilus-files-view.c                     |  16 +++
 src/nautilus-files-view.h                     |   5 +
 src/nautilus-global-preferences.h             |   1 +
 src/nautilus-preferences-dialog.c             |   5 +
 src/nautilus-query-editor.c                   |   6 +
 src/nautilus-window-slot.c                    | 113 +++++++++++++++---
 .../ui/nautilus-preferences-dialog.ui         |  15 +++
 8 files changed, 151 insertions(+), 15 deletions(-)

diff --git a/data/org.gnome.nautilus.gschema.xml b/data/org.gnome.nautilus.gschema.xml
index 2c0f45638b..45d793ac37 100644
--- a/data/org.gnome.nautilus.gschema.xml
+++ b/data/org.gnome.nautilus.gschema.xml
@@ -82,6 +82,11 @@
       <summary>Always use the location entry, instead of the pathbar</summary>
       <description>If set to true, Files will always use a textual input entry for the location toolbar, instead of the pathbar.</description>
     </key>
+    <key type="b" name="type-ahead-search">
+      <default>true</default>
+      <summary>Start searching on type ahead</summary>
+      <description>If set to true, typing on the files viewer will start searching. Otherwise it select first matching file.</description>
+    </key>
     <key name="recursive-search" enum="org.gnome.nautilus.SpeedTradeoff">
       <default>'local-only'</default>
       <summary>Where to perform recursive search</summary>
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index f23380497f..66045a2bb4 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -10105,3 +10105,19 @@ nautilus_files_view_new (guint               id,
 
     return view;
 }
+
+void
+nautilus_files_view_get_sort_state(NautilusFilesView *view,
+                                   GQuark            *sort_attribute,
+                                   gboolean          *reversed,
+                                   gboolean          *directories_first)
+{
+    NautilusFilesViewPrivate *priv = nautilus_files_view_get_instance_private (view);
+
+    g_autoptr(GVariant) value = nautilus_list_base_get_sort_state(priv->list_base);
+    const gchar *target_name;
+    g_variant_get(value, "(&sb)", &target_name, reversed);
+    *sort_attribute = g_quark_from_string(target_name);
+    *directories_first = g_settings_get_boolean(gtk_filechooser_preferences,
+                                                NAUTILUS_PREFERENCES_SORT_DIRECTORIES_FIRST);
+}
diff --git a/src/nautilus-files-view.h b/src/nautilus-files-view.h
index b149c3d4a7..30b8859995 100644
--- a/src/nautilus-files-view.h
+++ b/src/nautilus-files-view.h
@@ -161,4 +161,9 @@ void              nautilus_files_view_update_context_menus       (NautilusFilesV
 void              nautilus_files_view_update_toolbar_menus       (NautilusFilesView      *view);
 void              nautilus_files_view_update_actions_state       (NautilusFilesView      *view);
 
+void              nautilus_files_view_get_sort_state             (NautilusFilesView      *view,
+                                                                  GQuark                 *sort_attribute,
+                                                                  gboolean               *reversed,
+                                                                  gboolean               *directories_first);
+
 G_END_DECLS
diff --git a/src/nautilus-global-preferences.h b/src/nautilus-global-preferences.h
index 0c1bbdad26..2bce2c477d 100644
--- a/src/nautilus-global-preferences.h
+++ b/src/nautilus-global-preferences.h
@@ -118,6 +118,7 @@ typedef enum
 
 /* Search behaviour */
 #define NAUTILUS_PREFERENCES_RECURSIVE_SEARCH "recursive-search"
+#define NAUTILUS_PREFERENCES_TYPE_AHEAD_SEARCH "type-ahead-search"
 
 /* Context menu options */
 #define NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY "show-delete-permanently"
diff --git a/src/nautilus-preferences-dialog.c b/src/nautilus-preferences-dialog.c
index b399948d52..d7987720cb 100644
--- a/src/nautilus-preferences-dialog.c
+++ b/src/nautilus-preferences-dialog.c
@@ -42,6 +42,8 @@
         "show_create_link_row"
 #define NAUTILUS_PREFERENCES_DIALOG_LIST_VIEW_USE_TREE_WIDGET                  \
         "use_tree_view_row"
+#define NAUTILUS_PREFERENCES_DIALOG_TYPE_AHEAD_WIDGET                          \
+        "type_ahead_search"
 
 /* combo preferences */
 #define NAUTILUS_PREFERENCES_DIALOG_OPEN_ACTION_COMBO                          \
@@ -361,6 +363,9 @@ nautilus_preferences_dialog_setup (GtkBuilder *builder)
     bind_builder_bool (builder, nautilus_preferences,
                        NAUTILUS_PREFERENCES_DIALOG_DELETE_PERMANENTLY_WIDGET,
                        NAUTILUS_PREFERENCES_SHOW_DELETE_PERMANENTLY);
+    bind_builder_bool (builder, nautilus_preferences,
+                       NAUTILUS_PREFERENCES_DIALOG_TYPE_AHEAD_WIDGET,
+                       NAUTILUS_PREFERENCES_TYPE_AHEAD_SEARCH);
 
     setup_detailed_date (builder);
 
diff --git a/src/nautilus-query-editor.c b/src/nautilus-query-editor.c
index 9284152656..e729098a9c 100644
--- a/src/nautilus-query-editor.c
+++ b/src/nautilus-query-editor.c
@@ -876,6 +876,12 @@ nautilus_query_editor_set_query (NautilusQueryEditor *self,
 
     g_return_if_fail (NAUTILUS_IS_QUERY_EDITOR (self));
 
+    /* Setting query to NULL causes reentry to set it to an empty query */
+    if (self->change_frozen) {
+        g_set_object (&self->query, query);
+        return;
+    }
+
     if (query != NULL)
     {
         text = nautilus_query_get_text (query);
diff --git a/src/nautilus-window-slot.c b/src/nautilus-window-slot.c
index 4f588acfd8..821c140db7 100644
--- a/src/nautilus-window-slot.c
+++ b/src/nautilus-window-slot.c
@@ -79,6 +79,9 @@ enum
 
 static guint signals[LAST_SIGNAL];
 
+/* In type ahead mode, clear entry if it did not change for a while */
+#define CLEAR_QUERY_EDITOR_TIMEOUT 1000
+
 struct _NautilusWindowSlot
 {
     AdwBin parent_instance;
@@ -124,6 +127,7 @@ struct _NautilusWindowSlot
     /* Query editor */
     NautilusQueryEditor *query_editor;
     NautilusQuery *pending_search_query;
+    guint clear_query_editor_timeout_id;
 
     /* Banner */
     AdwBanner *banner;
@@ -409,6 +413,48 @@ query_editor_focus_view_callback (NautilusQueryEditor *editor,
     }
 }
 
+static GFile *
+nautilus_window_slot_get_current_location(NautilusWindowSlot *self)
+{
+    if (self->pending_location != NULL)
+    {
+        return self->pending_location;
+    }
+
+    return self->location;
+}
+
+static gboolean
+type_ahead_search (void)
+{
+    return g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_TYPE_AHEAD_SEARCH);
+}
+
+static gboolean
+clear_query_editor_timeout_callback (NautilusWindowSlot *self)
+{
+    nautilus_query_editor_set_query (self->query_editor, NULL);
+    self->clear_query_editor_timeout_id = 0;
+    return G_SOURCE_REMOVE;
+}
+
+typedef struct {
+    GQuark sort_attribute;
+    gboolean directories_first;
+    gboolean reversed;
+} FileCompareForTypeAheadContext;
+
+static int
+file_compare_for_type_ahead (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+    FileCompareForTypeAheadContext *ctx = user_data;
+    return nautilus_file_compare_for_sort_by_attribute_q(NAUTILUS_FILE (a),
+                                                         NAUTILUS_FILE (b),
+                                                         ctx->sort_attribute,
+                                                         ctx->directories_first,
+                                                         ctx->reversed);
+}
+
 static void
 query_editor_changed_callback (NautilusQueryEditor *editor,
                                NautilusQuery       *query,
@@ -419,9 +465,54 @@ query_editor_changed_callback (NautilusQueryEditor *editor,
 
     view = nautilus_window_slot_get_current_view (self);
 
-    /* Setting search query may cause the view to load a new location. */
-    nautilus_view_set_search_query (view, query);
-    nautilus_window_slot_set_location (self, nautilus_view_get_location (view));
+    if (nautilus_window_slot_get_search_visible (self))
+    {
+        /* Setting search query may cause the view to load a new location. */
+        nautilus_view_set_search_query (view, query);
+        nautilus_window_slot_set_location (self, nautilus_view_get_location (view));
+    }
+    else
+    {
+        /* Find all files with a display name that starts with the query, case insensitive. */
+        GFile *location = nautilus_window_slot_get_current_location (self);
+        g_autoptr (NautilusDirectory) directory = nautilus_directory_get (location);
+        const gchar *text = nautilus_query_get_text (query);
+        g_autofree gchar *text_casefold = g_utf8_casefold (text, -1);
+        g_autofree gchar *text_collate = g_utf8_collate_key_for_filename (text_casefold, -1);
+        gsize text_len = strlen (text);
+        g_autolist (NautilusFile) files = nautilus_directory_get_file_list (directory);
+        g_autolist (NautilusFile) matches = NULL;
+        GList *l;
+
+        for (l = files; l; l = l->next)
+        {
+            NautilusFile *file = NAUTILUS_FILE (l->data);
+            const gchar *name = nautilus_file_get_display_name(file);
+            g_autofree gchar *name_casefold = g_utf8_casefold(name, text_len);
+            g_autofree gchar *name_collate = g_utf8_collate_key_for_filename(name_casefold, -1);
+
+            if (g_str_equal (name_collate, text_collate))
+            {
+                matches = g_list_prepend (matches, nautilus_file_ref (file));
+            }
+        }
+
+        /* Select the first match */
+        if (matches != NULL)
+        {
+            FileCompareForTypeAheadContext ctx;
+            nautilus_files_view_get_sort_state (NAUTILUS_FILES_VIEW (view), &ctx.sort_attribute, &ctx.reversed, &ctx.directories_first);
+            matches = g_list_sort_with_data (matches, file_compare_for_type_ahead, &ctx);
+            g_autolist(NautilusFile) selection = g_list_prepend (NULL, g_object_ref (matches->data));
+            nautilus_view_set_selection (self->content_view, selection);
+        }
+
+        /* Reset timeout that clears type ahead query */
+        g_clear_handle_id (&self->clear_query_editor_timeout_id, g_source_remove);
+        self->clear_query_editor_timeout_id = g_timeout_add (CLEAR_QUERY_EDITOR_TIMEOUT,
+                                                             G_SOURCE_FUNC (clear_query_editor_timeout_callback),
+                                                             self);
+    }
 }
 
 static void
@@ -459,17 +550,6 @@ hide_query_editor (NautilusWindowSlot *self)
     }
 }
 
-static GFile *
-nautilus_window_slot_get_current_location (NautilusWindowSlot *self)
-{
-    if (self->pending_location != NULL)
-    {
-        return self->pending_location;
-    }
-
-    return self->location;
-}
-
 static void
 show_query_editor (NautilusWindowSlot *self)
 {
@@ -628,7 +708,7 @@ nautilus_window_slot_handle_event (NautilusWindowSlot    *self,
                                                      state);
     }
 
-    if (retval)
+    if (retval && type_ahead_search ())
     {
         nautilus_window_slot_set_search_visible (self, TRUE);
     }
@@ -2149,6 +2229,9 @@ setup_view (NautilusWindowSlot *self,
 
     nautilus_window_slot_disconnect_content_view (self);
 
+    nautilus_query_editor_set_query (self->query_editor, NULL);
+    g_clear_handle_id (&self->clear_query_editor_timeout_id, g_source_remove);
+
     self->new_content_view = view;
 
     nautilus_window_slot_connect_new_content_view (self);
diff --git a/src/resources/ui/nautilus-preferences-dialog.ui b/src/resources/ui/nautilus-preferences-dialog.ui
index fffaa6803f..4e0bb0f796 100644
--- a/src/resources/ui/nautilus-preferences-dialog.ui
+++ b/src/resources/ui/nautilus-preferences-dialog.ui
@@ -27,6 +27,21 @@
                 <property name="use_underline">True</property>
               </object>
             </child>
+            <child>
+              <object class="AdwActionRow">
+                <property name="activatable_widget">type_ahead_search</property>
+                <property name="subtitle_lines">0</property>
+                <property name="title" translatable="yes">Search on type ahead</property>
+                <property name="title_lines">0</property>
+                <property name="use_underline">True</property>
+                <property name="visible">True</property>
+                <child>
+                  <object class="GtkSwitch" id="type_ahead_search">
+                    <property name="valign">center</property>
+                  </object>
+                </child>
+              </object>
+            </child>
           </object>
         </child>
         <child>
-- 
GitLab