[gtksourceview/wip/chergert/snippets] parse snippet text



commit ac0401f1c806ae654c986457a6c4c6047eabb859
Author: Christian Hergert <chergert redhat com>
Date:   Tue Jan 28 16:36:13 2020 -0800

    parse snippet text

 gtksourceview/gtksourcesnippetbundle-parser.c  | 362 +++++++++++++++++++++++++
 gtksourceview/gtksourcesnippetbundle-private.h |  29 +-
 gtksourceview/gtksourcesnippetbundle.c         |  33 ++-
 gtksourceview/meson.build                      |   1 +
 4 files changed, 411 insertions(+), 14 deletions(-)
---
diff --git a/gtksourceview/gtksourcesnippetbundle-parser.c b/gtksourceview/gtksourcesnippetbundle-parser.c
new file mode 100644
index 00000000..317f45e9
--- /dev/null
+++ b/gtksourceview/gtksourcesnippetbundle-parser.c
@@ -0,0 +1,362 @@
+/*
+ * This file is part of GtkSourceView
+ *
+ * Copyright 2020 Christian Hergert <chergert redhat com>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+
+#include "gtksourcesnippetbundle-private.h"
+#include "gtksourcesnippetchunk.h"
+
+typedef struct
+{
+       GString   *cur_text;
+       GPtrArray *chunks;
+       guint      lineno;
+} TextParser;
+
+static void
+flush_chunk (TextParser *parser)
+{
+       g_assert (parser != NULL);
+       g_assert (parser->chunks != NULL);
+
+       if (parser->cur_text->len > 0)
+       {
+               GtkSourceSnippetChunk *chunk = gtk_source_snippet_chunk_new ();
+
+               gtk_source_snippet_chunk_set_spec (chunk, parser->cur_text->str);
+               g_ptr_array_add (parser->chunks, g_object_ref_sink (chunk));
+               g_string_truncate (parser->cur_text, 0);
+       }
+}
+
+static void
+do_part_named (TextParser  *parser,
+               const gchar *name)
+{
+       GtkSourceSnippetChunk *chunk = gtk_source_snippet_chunk_new ();
+       gchar *spec = g_strdup_printf ("$%s", name);
+
+       gtk_source_snippet_chunk_set_spec (chunk, spec);
+       gtk_source_snippet_chunk_set_focus_position (chunk, -1);
+
+       g_ptr_array_add (parser->chunks, g_object_ref_sink (chunk));
+
+       g_free (spec);
+}
+
+static void
+do_part_linked (TextParser *parser,
+                gint        n)
+{
+       GtkSourceSnippetChunk *chunk = gtk_source_snippet_chunk_new ();
+
+       if (n > 0)
+       {
+               gchar text[12];
+
+               g_snprintf (text, sizeof text, "$%u", n);
+               text[sizeof text - 1] = '\0';
+
+               gtk_source_snippet_chunk_set_spec (chunk, text);
+       }
+       else
+       {
+               gtk_source_snippet_chunk_set_spec (chunk, "");
+               gtk_source_snippet_chunk_set_focus_position (chunk, 0);
+       }
+
+       g_ptr_array_add (parser->chunks, g_object_ref_sink (chunk));
+}
+
+static void
+do_part_simple (TextParser  *parser,
+                const gchar *line)
+{
+       g_string_append (parser->cur_text, line);
+}
+
+static void
+do_part_n (TextParser  *parser,
+           gint         n,
+           const gchar *inner)
+{
+       GtkSourceSnippetChunk *chunk;
+
+       g_assert (parser != NULL);
+       g_assert (n >= -1);
+       g_assert (inner != NULL);
+
+       chunk = gtk_source_snippet_chunk_new ();
+       gtk_source_snippet_chunk_set_spec (chunk, n ? inner : "");
+       gtk_source_snippet_chunk_set_focus_position (chunk, n);
+
+       g_ptr_array_add (parser->chunks, g_object_ref_sink (chunk));
+}
+
+static gboolean
+parse_variable (const gchar  *line,
+                glong        *n,
+                gchar       **inner,
+                const gchar **endptr,
+                gchar       **name)
+{
+       gboolean has_inner = FALSE;
+       char *end = NULL;
+       gint brackets;
+       gint i;
+
+       *n = -1;
+       *inner = NULL;
+       *endptr = NULL;
+       *name = NULL;
+
+       g_assert (*line == '$');
+
+       line++;
+
+       *endptr = line;
+
+       if (!*line)
+       {
+               *endptr = NULL;
+               return FALSE;
+       }
+
+       if (*line == '{')
+       {
+               has_inner = TRUE;
+               line++;
+       }
+
+       if (g_ascii_isdigit (*line))
+       {
+               errno = 0;
+               *n = strtol (line, &end, 10);
+
+               if (((*n == LONG_MIN) || (*n == LONG_MAX)) && errno == ERANGE)
+                       return FALSE;
+               else if (*n < 0)
+                       return FALSE;
+
+               line = end;
+       }
+       else if (g_ascii_isalpha (*line))
+       {
+               const gchar *cur;
+
+               for (cur = line; *cur; cur++)
+               {
+                       if (g_ascii_isalnum (*cur))
+                               continue;
+                       break;
+               }
+
+               *endptr = cur;
+               *name = g_strndup (line, cur - line);
+               *n = -2;
+               return TRUE;
+       }
+
+       if (has_inner)
+       {
+               if (*line == ':')
+                       line++;
+
+               brackets = 1;
+
+               for (i = 0; line[i]; i++)
+               {
+                       switch (line[i])
+                       {
+                       case '{':
+                               brackets++;
+                               break;
+
+                       case '}':
+                               brackets--;
+                               break;
+
+                       default:
+                               break;
+                       }
+
+                       if (!brackets)
+                       {
+                               *inner = g_strndup (line, i);
+                               *endptr = &line[i + 1];
+                               return TRUE;
+                       }
+               }
+
+               return FALSE;
+       }
+
+       *endptr = line;
+
+       return TRUE;
+}
+
+static void
+do_part (TextParser  *parser,
+         const gchar *line)
+{
+       const gchar *dollar;
+       gchar *str;
+       gchar *inner;
+       gchar *name;
+       glong n;
+
+       g_assert (line);
+       g_assert (*line == '\t');
+
+       line++;
+
+again:
+       if (!*line)
+               return;
+
+       if (!(dollar = strchr (line, '$')))
+       {
+               do_part_simple (parser, line);
+               return;
+       }
+
+       /*
+        * Parse up to the next $ as a simple.
+        * If it is $N or ${N} then it is a linked chunk w/o tabstop.
+        * If it is ${N:""} then it is a chunk w/ tabstop.
+        * If it is ${blah|upper} then it is a non-tab stop chunk performing
+        * some sort of of expansion.
+        */
+
+       g_assert (dollar >= line);
+
+       if (dollar != line)
+       {
+               str = g_strndup (line, (dollar - line));
+               do_part_simple (parser, str);
+               g_free (str);
+               line = dollar;
+       }
+
+parse_dollar:
+       inner = NULL;
+
+       if (!parse_variable (line, &n, &inner, &line, &name))
+       {
+               do_part_simple (parser, line);
+               return;
+       }
+
+#if 0
+       g_printerr ("Parse Variable: N=%d  inner=\"%s\"\n", n, inner);
+       g_printerr ("  Left over: \"%s\"\n", line);
+#endif
+
+       flush_chunk (parser);
+
+       if (inner != NULL)
+       {
+               do_part_n (parser, n, inner);
+               g_clear_pointer (&inner, g_free);
+       }
+       else if (n == -2 && name)
+       {
+               do_part_named (parser, name);
+       }
+       else
+       {
+               do_part_linked (parser, n);
+       }
+
+       g_free (name);
+
+       if (line != NULL)
+       {
+               if (*line == '$')
+               {
+                       goto parse_dollar;
+               }
+               else
+               {
+                       goto again;
+               }
+       }
+}
+
+static gboolean
+feed_line (TextParser  *parser,
+           const gchar *line)
+{
+       g_assert (parser != NULL);
+       g_assert (line != NULL);
+
+       if (parser->cur_text->len || parser->chunks->len > 0)
+       {
+               g_string_append_c (parser->cur_text, '\n');
+       }
+
+       do_part (parser, line);
+
+       return TRUE;
+}
+
+GPtrArray *
+_gtk_source_snippet_bundle_parse_text (const gchar  *text,
+                                       GError      **error)
+{
+       TextParser parser;
+       gchar **lines = NULL;
+
+       g_return_val_if_fail (text != NULL, NULL);
+
+       /* Setup the parser */
+       parser.cur_text = g_string_new (NULL);
+       parser.lineno = 0;
+       parser.chunks = g_ptr_array_new_with_free_func (g_object_unref);
+
+       lines = g_strsplit (text, "\n", 0);
+       for (guint i = 0; lines[i] != NULL; i++)
+       {
+               parser.lineno++;
+
+               if (!feed_line (&parser, lines[i]))
+               {
+                       goto handle_error;
+               }
+       }
+
+       goto finish;
+
+handle_error:
+       g_set_error (error,
+                    G_IO_ERROR,
+                    G_IO_ERROR_INVALID_DATA,
+                    "Failed to parse snippet text at line %u",
+                    parser.lineno);
+       g_clear_pointer (&parser.chunks, g_ptr_array_unref);
+
+finish:
+       g_string_free (parser.cur_text, TRUE);
+       g_strfreev (lines);
+
+       return g_steal_pointer (&parser.chunks);
+}
diff --git a/gtksourceview/gtksourcesnippetbundle-private.h b/gtksourceview/gtksourcesnippetbundle-private.h
index 1cb71d79..775d2ac9 100644
--- a/gtksourceview/gtksourcesnippetbundle-private.h
+++ b/gtksourceview/gtksourcesnippetbundle-private.h
@@ -43,22 +43,25 @@ G_DECLARE_FINAL_TYPE (GtkSourceSnippetBundle, _gtk_source_snippet_bundle, GTK_SO
 G_GNUC_INTERNAL
 GtkSourceSnippetBundle  *_gtk_source_snippet_bundle_new           (void);
 G_GNUC_INTERNAL
-GtkSourceSnippetBundle  *_gtk_source_snippet_bundle_new_from_file (const gchar             *path,
-                                                                   GtkSourceSnippetManager *manager);
+GtkSourceSnippetBundle  *_gtk_source_snippet_bundle_new_from_file (const gchar              *path,
+                                                                   GtkSourceSnippetManager  *manager);
 G_GNUC_INTERNAL
-void                     _gtk_source_snippet_bundle_merge         (GtkSourceSnippetBundle  *self,
-                                                                   GtkSourceSnippetBundle  *other);
+void                     _gtk_source_snippet_bundle_merge         (GtkSourceSnippetBundle   *self,
+                                                                   GtkSourceSnippetBundle   *other);
 G_GNUC_INTERNAL
-const gchar            **_gtk_source_snippet_bundle_list_groups   (GtkSourceSnippetBundle  *self);
+const gchar            **_gtk_source_snippet_bundle_list_groups   (GtkSourceSnippetBundle   *self);
 G_GNUC_INTERNAL
-GtkSourceSnippet        *_gtk_source_snippet_bundle_get_snippet   (GtkSourceSnippetBundle  *self,
-                                                                   const gchar             *group,
-                                                                   const gchar             *language_id,
-                                                                   const gchar             *trigger);
+GtkSourceSnippet        *_gtk_source_snippet_bundle_get_snippet   (GtkSourceSnippetBundle   *self,
+                                                                   const gchar              *group,
+                                                                   const gchar              *language_id,
+                                                                   const gchar              *trigger);
 G_GNUC_INTERNAL
-GListModel              *_gtk_source_snippet_bundle_list_matching (GtkSourceSnippetBundle  *self,
-                                                                   const gchar             *group,
-                                                                   const gchar             *language_id,
-                                                                   const gchar             *trigger_prefix);
+GListModel              *_gtk_source_snippet_bundle_list_matching (GtkSourceSnippetBundle   *self,
+                                                                   const gchar              *group,
+                                                                   const gchar              *language_id,
+                                                                   const gchar              *trigger_prefix);
+G_GNUC_INTERNAL
+GPtrArray               *_gtk_source_snippet_bundle_parse_text    (const gchar              *text,
+                                                                   GError                  **error);
 
 G_END_DECLS
diff --git a/gtksourceview/gtksourcesnippetbundle.c b/gtksourceview/gtksourcesnippetbundle.c
index 2ee758aa..0bd5d55b 100644
--- a/gtksourceview/gtksourcesnippetbundle.c
+++ b/gtksourceview/gtksourcesnippetbundle.c
@@ -20,6 +20,7 @@
 #include "config.h"
 
 #include "gtksourcesnippet.h"
+#include "gtksourcesnippetchunk.h"
 #include "gtksourcesnippetbundle-private.h"
 #include "gtksourcesnippetmanager-private.h"
 
@@ -496,13 +497,43 @@ static GtkSourceSnippet *
 create_snippet_from_info (const GtkSourceSnippetInfo *info)
 {
        GtkSourceSnippet *snippet;
+       GPtrArray *chunks = NULL;
 
        g_assert (info != NULL);
 
+       if (info->text != NULL)
+       {
+               chunks = _gtk_source_snippet_bundle_parse_text (info->text, NULL);
+
+               if (chunks == NULL)
+               {
+                       GtkSourceSnippetChunk *chunk;
+
+                       /* If we failed to parse, then show the text unprocessed
+                        * to the user so they at least get something in the
+                        * editor to help them debug the issue.
+                        */
+                       chunks = g_ptr_array_new_with_free_func (g_object_unref);
+                       chunk = gtk_source_snippet_chunk_new ();
+                       gtk_source_snippet_chunk_set_text (chunk, info->text);
+                       gtk_source_snippet_chunk_set_text_set (chunk, TRUE);
+                       g_ptr_array_add (chunks, g_object_ref_sink (chunk));
+               }
+       }
+
        snippet = gtk_source_snippet_new (info->trigger, info->language);
        gtk_source_snippet_set_description (snippet, info->description);
 
-       g_warning ("TODO: Parse snippet text");
+       if (chunks != NULL)
+       {
+               for (guint i = 0; i < chunks->len; i++)
+               {
+                       GtkSourceSnippetChunk *chunk = g_ptr_array_index (chunks, i);
+                       gtk_source_snippet_add_chunk (snippet, chunk);
+               }
+       }
+
+       g_clear_pointer (&chunks, g_ptr_array_unref);
 
        return g_steal_pointer (&snippet);
 }
diff --git a/gtksourceview/meson.build b/gtksourceview/meson.build
index aa2b5d54..a42c2a5a 100644
--- a/gtksourceview/meson.build
+++ b/gtksourceview/meson.build
@@ -109,6 +109,7 @@ core_private_c = files([
   'gtksourcepixbufhelper.c',
   'gtksourceregex.c',
   'gtksourcesnippetbundle.c',
+  'gtksourcesnippetbundle-parser.c',
   'gtksourceview-snippets.c',
 ])
 


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