[gnome-builder/wip/chergert/grep: 8/10] grep: add helper to get line info



commit 1e7c98b6237e3f9898f2cbb8c630cef01c0fedd4
Author: Christian Hergert <chergert redhat com>
Date:   Mon Oct 29 12:55:51 2018 -0700

    grep: add helper to get line info

 src/plugins/grep/gbp-grep-model.c | 202 ++++++++++++++++++++++++++++++++++++++
 src/plugins/grep/gbp-grep-model.h |  82 ++++++++++------
 2 files changed, 252 insertions(+), 32 deletions(-)
---
diff --git a/src/plugins/grep/gbp-grep-model.c b/src/plugins/grep/gbp-grep-model.c
index 4f2ae2c27..72fd2be03 100644
--- a/src/plugins/grep/gbp-grep-model.c
+++ b/src/plugins/grep/gbp-grep-model.c
@@ -34,11 +34,37 @@ struct _GbpGrepModel
 {
   IdeObject parent_instance;
 
+  /* The root directory to start searching from. */
   GFile *directory;
+
+  /* The query text, which we use to send to grep as well as use with
+   * GRegex to extract the match positions from a specific line.
+   */
   gchar *query;
+
+  /* We need to do client-side processing to extract the exact message
+   * locations after grep gives us the matching lines. This allows us to
+   * create IdeProjectEdit source ranges later as well as creating the
+   * match positions for highlighting in the treeview cell renderers.
+   */
+  GRegex *message_regex;
+
+  /* Our index of matches, which can be compiled off the main thread
+   * and then assigned to the model after it has completed building.
+   */
   Index *index;
+
+  /* We store the index of the toggled items here, and use that to
+   * reverse their selection from a base "all" or "nothing" mode.
+   */
   GHashTable *toggled;
 
+  /* We cache the last line we parsed, because the view will parse
+   * the same line repeatedly as it builds the cells for display.
+   * This saves us a bunch of repeated work.
+   */
+  GbpGrepModelLine prev_line;
+
   guint mode;
 
   guint has_scanned : 1;
@@ -70,6 +96,7 @@ enum {
 };
 
 static GParamSpec *properties [N_PROPS];
+static GRegex     *line_regex;
 
 static void
 index_free (gpointer data)
@@ -81,6 +108,87 @@ index_free (gpointer data)
   g_slice_free (Index, idx);
 }
 
+static void
+clear_line (GbpGrepModelLine *cl)
+{
+  cl->start_of_line = NULL;
+  cl->line = 0;
+  g_clear_pointer (&cl->path, g_free);
+  g_clear_pointer (&cl->matches, g_array_unref);
+}
+
+static gboolean
+gbp_grep_model_line_parse (GbpGrepModelLine *cl,
+                           const gchar      *line,
+                           GRegex           *message_regex)
+{
+  g_autoptr(GMatchInfo) match = NULL;
+  gsize line_len;
+
+  g_assert (cl != NULL);
+  g_assert (line != NULL);
+  g_assert (line_regex != NULL);
+  g_assert (message_regex != NULL);
+
+  line_len = strlen (line);
+
+  if (g_regex_match_full (line_regex, line, line_len, 0, 0, &match, NULL))
+    {
+      g_autofree gchar *pathstr = NULL;
+      g_autofree gchar *linestr = NULL;
+      gint msg_begin = -1;
+      gint msg_end = -1;
+
+      pathstr = g_match_info_fetch (match, 1);
+      linestr = g_match_info_fetch (match, 2);
+
+      if (g_match_info_fetch_pos (match, 3, &msg_begin, &msg_end))
+        {
+          g_autoptr(GMatchInfo) msg_match = NULL;
+          gsize msg_len;
+
+          /* Make sure we parsed the message offset */
+          if (msg_begin < 0)
+            return FALSE;
+
+          cl->start_of_line = line;
+          cl->start_of_message = line + msg_begin;
+          cl->path = g_steal_pointer (&pathstr);
+          cl->line = g_ascii_strtoll (linestr, NULL, 10);
+          cl->matches = g_array_new (FALSE, FALSE, sizeof (GbpGrepModelMatch));
+
+          /* Now parse the matches for the line so that we can highlight
+           * them in the treeview and also determine the IdeProjectEdit
+           * source range when editing files.
+           */
+
+          msg_len = line_len - msg_begin;
+
+          while (g_regex_match_full (message_regex, cl->start_of_message, msg_len, 0, 0, &msg_match, NULL))
+            {
+              gint match_begin = -1;
+              gint match_end = -1;
+
+              if (g_match_info_fetch_pos (msg_match, 0, &match_begin, &match_end))
+                {
+                  GbpGrepModelMatch cm;
+
+                  cm.match_begin = match_begin;
+                  cm.match_end = match_end;
+
+                  g_array_append_val (cl->matches, cm);
+                }
+
+              g_clear_pointer (&msg_match, g_match_info_free);
+            }
+        }
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
 GbpGrepModel *
 gbp_grep_model_new (IdeContext *context)
 {
@@ -96,10 +204,13 @@ gbp_grep_model_finalize (GObject *object)
 {
   GbpGrepModel *self = (GbpGrepModel *)object;
 
+  clear_line (&self->prev_line);
+
   g_clear_object (&self->directory);
   g_clear_pointer (&self->index, index_free);
   g_clear_pointer (&self->query, g_free);
   g_clear_pointer (&self->toggled, g_hash_table_unref);
+  g_clear_pointer (&self->message_regex, g_regex_unref);
 
   G_OBJECT_CLASS (gbp_grep_model_parent_class)->finalize (object);
 }
@@ -221,6 +332,9 @@ gbp_grep_model_class_init (GbpGrepModelClass *klass)
                          (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, N_PROPS, properties);
+
+  line_regex = g_regex_new ("([a-zA-Z0-9\\+\\-\\.\\/_]+):(\\d+):(.*)", 0, 0, NULL);
+  g_assert (line_regex != NULL);
 }
 
 static void
@@ -230,6 +344,45 @@ gbp_grep_model_init (GbpGrepModel *self)
   self->toggled = g_hash_table_new (NULL, NULL);
 }
 
+static void
+gbp_grep_model_clear_regex (GbpGrepModel *self)
+{
+  g_assert (GBP_IS_GREP_MODEL (self));
+
+  g_clear_pointer (&self->message_regex, g_regex_unref);
+}
+
+static gboolean
+gbp_grep_model_rebuild_regex (GbpGrepModel *self)
+{
+  GRegexCompileFlags compile_flags = G_REGEX_OPTIMIZE;
+  g_autoptr(GRegex) regex = NULL;
+  g_autoptr(GError) error = NULL;
+  g_autofree gchar *escaped = NULL;
+  const gchar *query;
+
+  g_assert (GBP_IS_GREP_MODEL (self));
+  g_assert (self->message_regex == NULL);
+
+  if (self->use_regex)
+    query = self->query;
+  else
+    query = escaped = g_regex_escape_string (self->query, -1);
+
+  if (self->case_sensitive)
+    compile_flags |= G_REGEX_CASELESS;
+
+  if (!(regex = g_regex_new (query, compile_flags, 0, &error)))
+    {
+      g_warning ("Failed to compile regex for match: %s", error->message);
+      return FALSE;
+    }
+
+  self->message_regex = g_steal_pointer (&regex);
+
+  return TRUE;
+}
+
 const gchar *
 gbp_grep_model_get_query (GbpGrepModel *self)
 {
@@ -248,6 +401,7 @@ gbp_grep_model_set_query (GbpGrepModel *self,
     {
       g_free (self->query);
       self->query = g_strdup (query);
+      gbp_grep_model_clear_regex (self);
       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_QUERY]);
     }
 }
@@ -298,6 +452,7 @@ gbp_grep_model_set_use_regex (GbpGrepModel *self,
   if (use_regex != self->use_regex)
     {
       self->use_regex = use_regex;
+      gbp_grep_model_clear_regex (self);
       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_USE_REGEX]);
     }
 }
@@ -346,6 +501,7 @@ gbp_grep_model_set_case_sensitive (GbpGrepModel *self,
   if (case_sensitive != self->case_sensitive)
     {
       self->case_sensitive = case_sensitive;
+      gbp_grep_model_clear_regex (self);
       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CASE_SENSITIVE]);
     }
 }
@@ -370,6 +526,7 @@ gbp_grep_model_set_at_word_boundaries (GbpGrepModel *self,
   if (at_word_boundaries != self->at_word_boundaries)
     {
       self->at_word_boundaries = at_word_boundaries;
+      gbp_grep_model_clear_regex (self);
       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_AT_WORD_BOUNDARIES]);
     }
 }
@@ -949,3 +1106,48 @@ gbp_grep_model_create_edits (GbpGrepModel *self)
 
   return g_steal_pointer (&edits);
 }
+
+/**
+ * gbp_grep_model_get_line:
+ * @self: a #GbpGrepModel
+ * @iter: a #GtkTextIter
+ * @line: (out): a location for the line info
+ *
+ * Gets information about the line that @iter points at.
+ */
+void
+gbp_grep_model_get_line (GbpGrepModel            *self,
+                         GtkTreeIter             *iter,
+                         const GbpGrepModelLine **line)
+{
+  const gchar *str;
+  guint index_;
+
+  g_return_if_fail (GBP_IS_GREP_MODEL (self));
+  g_return_if_fail (iter != NULL);
+  g_return_if_fail (line != NULL);
+  g_return_if_fail (self->index != NULL);
+  g_return_if_fail (self->index->rows != NULL);
+
+  *line = NULL;
+
+  index_ = GPOINTER_TO_UINT (iter->user_data);
+  g_return_if_fail (index_ < self->index->rows->len);
+
+  str = g_ptr_array_index (self->index->rows, index_);
+
+  if (str != self->prev_line.start_of_line)
+    {
+      clear_line (&self->prev_line);
+
+      if (self->message_regex == NULL)
+        {
+          if (!gbp_grep_model_rebuild_regex (self))
+            return;
+        }
+
+      gbp_grep_model_line_parse (&self->prev_line, str, self->message_regex);
+    }
+
+  *line = &self->prev_line;
+}
diff --git a/src/plugins/grep/gbp-grep-model.h b/src/plugins/grep/gbp-grep-model.h
index 8b20d7390..454b62867 100644
--- a/src/plugins/grep/gbp-grep-model.h
+++ b/src/plugins/grep/gbp-grep-model.h
@@ -26,39 +26,57 @@ G_BEGIN_DECLS
 
 #define GBP_TYPE_GREP_MODEL (gbp_grep_model_get_type())
 
+typedef struct
+{
+  gint match_begin;
+  gint match_end;
+} GbpGrepModelMatch;
+
+typedef struct
+{
+  const gchar *start_of_line;
+  const gchar *start_of_message;
+  gchar       *path;
+  GArray      *matches;
+  guint        line;
+} GbpGrepModelLine;
+
 G_DECLARE_FINAL_TYPE (GbpGrepModel, gbp_grep_model, GBP, GREP_MODEL, IdeObject)
 
-GbpGrepModel *gbp_grep_model_new                    (IdeContext           *context);
-GFile        *gbp_grep_model_get_directory          (GbpGrepModel         *self);
-void          gbp_grep_model_set_directory          (GbpGrepModel         *self,
-                                                     GFile                *directory);
-gboolean      gbp_grep_model_get_use_regex          (GbpGrepModel         *self);
-void          gbp_grep_model_set_use_regex          (GbpGrepModel         *self,
-                                                     gboolean              use_regex);
-gboolean      gbp_grep_model_get_recursive          (GbpGrepModel         *self);
-void          gbp_grep_model_set_recursive          (GbpGrepModel         *self,
-                                                     gboolean              recursive);
-gboolean      gbp_grep_model_get_case_sensitive     (GbpGrepModel         *self);
-void          gbp_grep_model_set_case_sensitive     (GbpGrepModel         *self,
-                                                     gboolean              case_sensitive);
-gboolean      gbp_grep_model_get_at_word_boundaries (GbpGrepModel         *self);
-void          gbp_grep_model_set_at_word_boundaries (GbpGrepModel         *self,
-                                                     gboolean              at_word_boundaries);
-const gchar  *gbp_grep_model_get_query              (GbpGrepModel         *self);
-void          gbp_grep_model_set_query              (GbpGrepModel         *self,
-                                                     const gchar          *query);
-GPtrArray    *gbp_grep_model_create_edits           (GbpGrepModel         *self);
-void          gbp_grep_model_select_all             (GbpGrepModel         *self);
-void          gbp_grep_model_select_none            (GbpGrepModel         *self);
-void          gbp_grep_model_toggle_mode            (GbpGrepModel         *self);
-void          gbp_grep_model_toggle_row             (GbpGrepModel         *self,
-                                                     GtkTreeIter          *iter);
-void          gbp_grep_model_scan_async             (GbpGrepModel         *self,
-                                                     GCancellable         *cancellable,
-                                                     GAsyncReadyCallback   callback,
-                                                     gpointer              user_data);
-gboolean      gbp_grep_model_scan_finish            (GbpGrepModel         *self,
-                                                     GAsyncResult         *result,
-                                                     GError              **error);
+GbpGrepModel *gbp_grep_model_new                    (IdeContext              *context);
+GFile        *gbp_grep_model_get_directory          (GbpGrepModel            *self);
+void          gbp_grep_model_set_directory          (GbpGrepModel            *self,
+                                                     GFile                   *directory);
+gboolean      gbp_grep_model_get_use_regex          (GbpGrepModel            *self);
+void          gbp_grep_model_set_use_regex          (GbpGrepModel            *self,
+                                                     gboolean                 use_regex);
+gboolean      gbp_grep_model_get_recursive          (GbpGrepModel            *self);
+void          gbp_grep_model_set_recursive          (GbpGrepModel            *self,
+                                                     gboolean                 recursive);
+gboolean      gbp_grep_model_get_case_sensitive     (GbpGrepModel            *self);
+void          gbp_grep_model_set_case_sensitive     (GbpGrepModel            *self,
+                                                     gboolean                 case_sensitive);
+gboolean      gbp_grep_model_get_at_word_boundaries (GbpGrepModel            *self);
+void          gbp_grep_model_set_at_word_boundaries (GbpGrepModel            *self,
+                                                     gboolean                 at_word_boundaries);
+const gchar  *gbp_grep_model_get_query              (GbpGrepModel            *self);
+void          gbp_grep_model_set_query              (GbpGrepModel            *self,
+                                                     const gchar             *query);
+GPtrArray    *gbp_grep_model_create_edits           (GbpGrepModel            *self);
+void          gbp_grep_model_select_all             (GbpGrepModel            *self);
+void          gbp_grep_model_select_none            (GbpGrepModel            *self);
+void          gbp_grep_model_toggle_mode            (GbpGrepModel            *self);
+void          gbp_grep_model_toggle_row             (GbpGrepModel            *self,
+                                                     GtkTreeIter             *iter);
+void          gbp_grep_model_get_line               (GbpGrepModel            *self,
+                                                     GtkTreeIter             *iter,
+                                                     const GbpGrepModelLine **line);
+void          gbp_grep_model_scan_async             (GbpGrepModel            *self,
+                                                     GCancellable            *cancellable,
+                                                     GAsyncReadyCallback      callback,
+                                                     gpointer                 user_data);
+gboolean      gbp_grep_model_scan_finish            (GbpGrepModel            *self,
+                                                     GAsyncResult            *result,
+                                                     GError                 **error);
 
 G_END_DECLS


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