788 lines
18 KiB
C
788 lines
18 KiB
C
/* ide-snippet-parser.c
|
|
*
|
|
* Copyright 2013-2019 Christian Hergert <christian@hergert.me>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "ide-snippet-parser"
|
|
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <glib/gi18n.h>
|
|
#include <libide-io.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "ide-snippet.h"
|
|
#include "ide-snippet-chunk.h"
|
|
#include "ide-snippet-parser.h"
|
|
#include "ide-snippet-private.h"
|
|
|
|
/**
|
|
* SECTION:ide-snippet-parser
|
|
* @title: IdeSnippetParser
|
|
* @short_description: A parser for Builder's snippet text format
|
|
*
|
|
* The #IdeSnippetParser can be used to parse ".snippets" formatted
|
|
* text files. This is generally only used internally by Builder, but can
|
|
* be used by plugins under certain situations.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
|
|
struct _IdeSnippetParser
|
|
{
|
|
GObject parent_instance;
|
|
|
|
GList *snippets;
|
|
|
|
gint lineno;
|
|
GList *chunks;
|
|
GList *scope;
|
|
gchar *cur_name;
|
|
gchar *cur_desc;
|
|
GString *cur_text;
|
|
GString *snippet_text;
|
|
|
|
GFile *current_file;
|
|
|
|
guint had_error : 1;
|
|
};
|
|
|
|
G_DEFINE_FINAL_TYPE (IdeSnippetParser, ide_snippet_parser, G_TYPE_OBJECT)
|
|
|
|
enum {
|
|
PARSING_ERROR,
|
|
N_SIGNALS
|
|
};
|
|
|
|
static guint signals [N_SIGNALS];
|
|
|
|
IdeSnippetParser *
|
|
ide_snippet_parser_new (void)
|
|
{
|
|
return g_object_new (IDE_TYPE_SNIPPET_PARSER, NULL);
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_flush_chunk (IdeSnippetParser *parser)
|
|
{
|
|
IdeSnippetChunk *chunk;
|
|
|
|
if (parser->cur_text->len)
|
|
{
|
|
chunk = ide_snippet_chunk_new ();
|
|
ide_snippet_chunk_set_spec (chunk, parser->cur_text->str);
|
|
parser->chunks = g_list_append (parser->chunks, chunk);
|
|
g_string_truncate (parser->cur_text, 0);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_store (IdeSnippetParser *parser)
|
|
{
|
|
IdeSnippet *snippet;
|
|
GList *scope_iter;
|
|
GList *chunck_iter;
|
|
|
|
ide_snippet_parser_flush_chunk (parser);
|
|
for (scope_iter = parser->scope; scope_iter; scope_iter = scope_iter->next)
|
|
{
|
|
snippet = ide_snippet_new (parser->cur_name, scope_iter->data);
|
|
ide_snippet_set_description (snippet, parser->cur_desc);
|
|
|
|
for (chunck_iter = parser->chunks; chunck_iter; chunck_iter = chunck_iter->next)
|
|
{
|
|
#if 0
|
|
g_printerr ("%s: Tab: %02d Link: %02d Text: %s\n",
|
|
parser->cur_name,
|
|
ide_snippet_chunk_get_tab_stop (chunck_iter->data),
|
|
ide_snippet_chunk_get_linked_chunk (chunck_iter->data),
|
|
ide_snippet_chunk_get_text (chunck_iter->data));
|
|
#endif
|
|
ide_snippet_add_chunk (snippet, chunck_iter->data);
|
|
}
|
|
|
|
parser->snippets = g_list_append (parser->snippets, snippet);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_finish (IdeSnippetParser *parser)
|
|
{
|
|
if (parser->cur_name)
|
|
ide_snippet_parser_store(parser);
|
|
|
|
g_clear_pointer (&parser->cur_name, g_free);
|
|
|
|
g_string_truncate (parser->cur_text, 0);
|
|
g_string_truncate (parser->snippet_text, 0);
|
|
|
|
g_list_foreach (parser->chunks, (GFunc) g_object_unref, NULL);
|
|
g_list_free (parser->chunks);
|
|
parser->chunks = NULL;
|
|
|
|
g_list_free_full (parser->scope, g_free);
|
|
parser->scope = NULL;
|
|
|
|
g_free (parser->cur_desc);
|
|
parser->cur_desc = NULL;
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_do_part_simple (IdeSnippetParser *parser,
|
|
const gchar *line)
|
|
{
|
|
g_string_append (parser->cur_text, line);
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_do_part_n (IdeSnippetParser *parser,
|
|
gint n,
|
|
const gchar *inner)
|
|
{
|
|
IdeSnippetChunk *chunk;
|
|
|
|
g_return_if_fail (IDE_IS_SNIPPET_PARSER (parser));
|
|
g_return_if_fail (n >= -1);
|
|
g_return_if_fail (inner);
|
|
|
|
chunk = ide_snippet_chunk_new ();
|
|
ide_snippet_chunk_set_spec (chunk, n ? inner : "");
|
|
ide_snippet_chunk_set_tab_stop (chunk, n);
|
|
parser->chunks = g_list_append (parser->chunks, chunk);
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_do_part_linked (IdeSnippetParser *parser,
|
|
gint n)
|
|
{
|
|
IdeSnippetChunk *chunk;
|
|
gchar text[12];
|
|
|
|
chunk = ide_snippet_chunk_new ();
|
|
if (n)
|
|
{
|
|
g_snprintf (text, sizeof text, "$%d", n);
|
|
text[sizeof text - 1] = '\0';
|
|
ide_snippet_chunk_set_spec (chunk, text);
|
|
}
|
|
else
|
|
{
|
|
ide_snippet_chunk_set_spec (chunk, "");
|
|
ide_snippet_chunk_set_tab_stop (chunk, 0);
|
|
}
|
|
parser->chunks = g_list_append (parser->chunks, chunk);
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_do_part_named (IdeSnippetParser *parser,
|
|
const gchar *name)
|
|
{
|
|
IdeSnippetChunk *chunk;
|
|
gchar *spec;
|
|
|
|
chunk = ide_snippet_chunk_new ();
|
|
spec = g_strdup_printf ("$%s", name);
|
|
ide_snippet_chunk_set_spec (chunk, spec);
|
|
ide_snippet_chunk_set_tab_stop (chunk, -1);
|
|
parser->chunks = g_list_append (parser->chunks, chunk);
|
|
g_free (spec);
|
|
}
|
|
|
|
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
|
|
ide_snippet_parser_do_part (IdeSnippetParser *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, '$')))
|
|
{
|
|
ide_snippet_parser_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));
|
|
ide_snippet_parser_do_part_simple (parser, str);
|
|
g_free (str);
|
|
line = dollar;
|
|
}
|
|
|
|
parse_dollar:
|
|
inner = NULL;
|
|
|
|
if (!parse_variable (line, &n, &inner, &line, &name))
|
|
{
|
|
ide_snippet_parser_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
|
|
|
|
ide_snippet_parser_flush_chunk (parser);
|
|
|
|
if (inner)
|
|
{
|
|
ide_snippet_parser_do_part_n (parser, n, inner);
|
|
g_free (inner);
|
|
inner = NULL;
|
|
}
|
|
else if (n == -2 && name)
|
|
ide_snippet_parser_do_part_named (parser, name);
|
|
else
|
|
ide_snippet_parser_do_part_linked (parser, n);
|
|
|
|
g_free (name);
|
|
|
|
if (line)
|
|
{
|
|
if (*line == '$')
|
|
{
|
|
goto parse_dollar;
|
|
}
|
|
else
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_do_snippet (IdeSnippetParser *parser,
|
|
const gchar *line)
|
|
{
|
|
parser->cur_name = g_strstrip (g_strdup (&line[8]));
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_do_snippet_scope (IdeSnippetParser *parser,
|
|
const gchar *line)
|
|
{
|
|
gchar **scope_list;
|
|
GList *iter;
|
|
gint i;
|
|
gboolean add_scope;
|
|
|
|
scope_list = g_strsplit (&line[8], ",", -1);
|
|
|
|
for (i = 0; scope_list[i]; i++)
|
|
{
|
|
add_scope = TRUE;
|
|
for (iter = parser->scope; iter; iter = iter->next)
|
|
{
|
|
if (g_strcmp0 (iter->data, scope_list[i]) == 0)
|
|
{
|
|
add_scope = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (add_scope)
|
|
parser->scope = g_list_append(parser->scope, g_strstrip (g_strdup (scope_list[i])));
|
|
}
|
|
|
|
g_strfreev(scope_list);
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_do_snippet_description (IdeSnippetParser *parser,
|
|
const gchar *line)
|
|
{
|
|
if (parser->cur_desc)
|
|
{
|
|
g_free(parser->cur_desc);
|
|
parser->cur_desc = NULL;
|
|
}
|
|
|
|
parser->cur_desc = g_strstrip (g_strdup (&line[7]));
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_feed_line (IdeSnippetParser *parser,
|
|
const gchar *basename,
|
|
const gchar *line)
|
|
{
|
|
const gchar *orig = line;
|
|
|
|
g_assert (parser);
|
|
g_assert (basename);
|
|
g_assert (line);
|
|
|
|
parser->lineno++;
|
|
|
|
switch (*line)
|
|
{
|
|
case '\0':
|
|
if (parser->cur_name)
|
|
g_string_append_c (parser->cur_text, '\n');
|
|
break;
|
|
|
|
case '#':
|
|
break;
|
|
|
|
case '\t':
|
|
if (parser->cur_name)
|
|
{
|
|
GList *iter;
|
|
gboolean add_default_scope = TRUE;
|
|
for (iter = parser->scope; iter; iter = iter->next)
|
|
{
|
|
if (g_strcmp0(iter->data, basename) == 0)
|
|
{
|
|
add_default_scope = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (add_default_scope)
|
|
parser->scope = g_list_append(parser->scope,
|
|
g_strstrip (g_strdup (basename)));
|
|
|
|
if (parser->cur_text->len || parser->chunks)
|
|
g_string_append_c (parser->cur_text, '\n');
|
|
ide_snippet_parser_do_part (parser, line);
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
if (g_str_has_prefix (line, "snippet"))
|
|
{
|
|
ide_snippet_parser_finish (parser);
|
|
ide_snippet_parser_do_snippet (parser, line);
|
|
break;
|
|
}
|
|
|
|
/* Fall through */
|
|
case '-':
|
|
if (parser->cur_text->len || parser->chunks)
|
|
{
|
|
ide_snippet_parser_store(parser);
|
|
|
|
g_string_truncate (parser->cur_text, 0);
|
|
|
|
g_list_foreach (parser->chunks, (GFunc) g_object_unref, NULL);
|
|
g_list_free (parser->chunks);
|
|
parser->chunks = NULL;
|
|
|
|
g_list_free_full(parser->scope, g_free);
|
|
parser->scope = NULL;
|
|
}
|
|
|
|
if (g_str_has_prefix(line, "- scope"))
|
|
{
|
|
ide_snippet_parser_do_snippet_scope (parser, line);
|
|
break;
|
|
}
|
|
|
|
if (g_str_has_prefix(line, "- desc"))
|
|
{
|
|
ide_snippet_parser_do_snippet_description (parser, line);
|
|
break;
|
|
}
|
|
|
|
/* Fall through */
|
|
default:
|
|
g_signal_emit (parser, signals [PARSING_ERROR], 0,
|
|
parser->current_file, parser->lineno, line);
|
|
parser->had_error = TRUE;
|
|
break;
|
|
}
|
|
|
|
g_string_append (parser->snippet_text, orig);
|
|
g_string_append_c (parser->snippet_text, '\n');
|
|
}
|
|
|
|
gboolean
|
|
ide_snippet_parser_load_from_file (IdeSnippetParser *parser,
|
|
GFile *file,
|
|
GError **error)
|
|
{
|
|
GFileInputStream *file_stream;
|
|
g_autoptr(GDataInputStream) data_stream = NULL;
|
|
GError *local_error = NULL;
|
|
gchar *line;
|
|
gchar *basename = NULL;
|
|
|
|
g_return_val_if_fail (IDE_IS_SNIPPET_PARSER (parser), FALSE);
|
|
g_return_val_if_fail (G_IS_FILE (file), FALSE);
|
|
|
|
basename = g_file_get_basename (file);
|
|
|
|
if (basename)
|
|
{
|
|
if (strstr (basename, "."))
|
|
*strstr (basename, ".") = '\0';
|
|
}
|
|
|
|
file_stream = g_file_read (file, NULL, error);
|
|
if (!file_stream)
|
|
return FALSE;
|
|
|
|
data_stream = g_data_input_stream_new (G_INPUT_STREAM (file_stream));
|
|
g_object_unref (file_stream);
|
|
|
|
g_set_object (&parser->current_file, file);
|
|
|
|
again:
|
|
if (parser->had_error)
|
|
{
|
|
/* TODO: Better error messages */
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"%s:%d: invalid snippet",
|
|
basename, parser->lineno);
|
|
return FALSE;
|
|
}
|
|
|
|
line = g_data_input_stream_read_line_utf8 (data_stream, NULL, NULL, &local_error);
|
|
if (!line && local_error)
|
|
{
|
|
g_propagate_error (error, local_error);
|
|
g_set_object (&parser->current_file, NULL);
|
|
return FALSE;
|
|
}
|
|
else if (line)
|
|
{
|
|
ide_snippet_parser_feed_line (parser, basename, line);
|
|
g_free (line);
|
|
goto again;
|
|
}
|
|
|
|
ide_snippet_parser_finish (parser);
|
|
g_free (basename);
|
|
|
|
g_set_object (&parser->current_file, NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
ide_snippet_parser_load_from_data (IdeSnippetParser *parser,
|
|
const gchar *default_language,
|
|
const gchar *data,
|
|
gssize data_len,
|
|
GError **error)
|
|
{
|
|
IdeLineReader reader;
|
|
gchar *line;
|
|
gsize line_len;
|
|
|
|
g_return_val_if_fail (IDE_IS_SNIPPET_PARSER (parser), FALSE);
|
|
g_return_val_if_fail (data != NULL, FALSE);
|
|
|
|
if (data_len < 0)
|
|
data_len = strlen (data);
|
|
|
|
ide_line_reader_init (&reader, (gchar *)data, data_len);
|
|
|
|
while ((line = ide_line_reader_next (&reader, &line_len)))
|
|
{
|
|
g_autofree gchar *copy = NULL;
|
|
|
|
if (parser->had_error)
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"<data>:%d: invalid snippet",
|
|
parser->lineno);
|
|
return FALSE;
|
|
}
|
|
|
|
copy = g_strndup (line, line_len);
|
|
ide_snippet_parser_feed_line (parser, default_language, copy);
|
|
}
|
|
|
|
ide_snippet_parser_finish (parser);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* ide_snippet_parser_get_snippets:
|
|
* @parser: a #IdeSnippetParser
|
|
*
|
|
* Get the list of all the snippets loaded.
|
|
*
|
|
* Returns: (transfer none) (element-type Ide.Snippet): a #GList of #IdeSnippets items.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
GList *
|
|
ide_snippet_parser_get_snippets (IdeSnippetParser *parser)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_SNIPPET_PARSER (parser), NULL);
|
|
return parser->snippets;
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_finalize (GObject *object)
|
|
{
|
|
IdeSnippetParser *self = IDE_SNIPPET_PARSER (object);
|
|
|
|
g_list_foreach (self->snippets, (GFunc) g_object_unref, NULL);
|
|
g_list_free (self->snippets);
|
|
self->snippets = NULL;
|
|
|
|
g_list_foreach (self->chunks, (GFunc) g_object_unref, NULL);
|
|
g_list_free (self->chunks);
|
|
self->chunks = NULL;
|
|
|
|
g_list_free_full(self->scope, g_free);
|
|
self->scope = NULL;
|
|
|
|
if (self->cur_text)
|
|
g_string_free (self->cur_text, TRUE);
|
|
self->cur_text = NULL;
|
|
|
|
if (self->snippet_text)
|
|
g_string_free (self->snippet_text, TRUE);
|
|
self->snippet_text = NULL;
|
|
|
|
g_free (self->cur_name);
|
|
self->cur_name = NULL;
|
|
|
|
if (self->cur_desc)
|
|
{
|
|
g_free (self->cur_desc);
|
|
self->cur_desc = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (ide_snippet_parser_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_class_init (IdeSnippetParserClass *klass)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
object_class = G_OBJECT_CLASS (klass);
|
|
object_class->finalize = ide_snippet_parser_finalize;
|
|
|
|
signals [PARSING_ERROR] =
|
|
g_signal_new ("parsing-error",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
NULL,
|
|
G_TYPE_NONE,
|
|
3,
|
|
G_TYPE_FILE,
|
|
G_TYPE_UINT,
|
|
G_TYPE_STRING);
|
|
}
|
|
|
|
static void
|
|
ide_snippet_parser_init (IdeSnippetParser *parser)
|
|
{
|
|
parser->lineno = -1;
|
|
parser->cur_text = g_string_new (NULL);
|
|
parser->snippet_text = g_string_new (NULL);
|
|
parser->scope = NULL;
|
|
parser->cur_desc = NULL;
|
|
}
|
|
|
|
/**
|
|
* ide_snippet_parser_parse_one:
|
|
* @data: the data to parse
|
|
* @data_len: the length of data, or -1 for %NULL terminated
|
|
* @error: a location for an error
|
|
*
|
|
* Returns: (transfer full): an #IdeSnippet
|
|
*
|
|
* Since: 3.36
|
|
*/
|
|
IdeSnippet *
|
|
ide_snippet_parser_parse_one (const char *data,
|
|
gssize data_len,
|
|
GError **error)
|
|
{
|
|
g_autoptr(IdeSnippetParser) parser = NULL;
|
|
IdeLineReader reader;
|
|
const gchar *line;
|
|
gsize line_len;
|
|
|
|
g_return_val_if_fail (data != NULL, NULL);
|
|
|
|
parser = ide_snippet_parser_new ();
|
|
ide_snippet_parser_feed_line (parser, "", "snippet dummy");
|
|
|
|
ide_line_reader_init (&reader, (gchar *)data, data_len);
|
|
while ((line = ide_line_reader_next (&reader, &line_len)))
|
|
{
|
|
g_autofree gchar *copy = NULL;
|
|
|
|
if (parser->had_error)
|
|
{
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"<data>:%d: invalid snippet",
|
|
parser->lineno);
|
|
return NULL;
|
|
}
|
|
|
|
copy = g_malloc (line_len + 2);
|
|
copy[0] = '\t';
|
|
memcpy (©[1], line, line_len);
|
|
copy[1+line_len] = 0;
|
|
|
|
ide_snippet_parser_feed_line (parser, "", copy);
|
|
}
|
|
|
|
ide_snippet_parser_finish (parser);
|
|
|
|
if (parser->snippets != NULL)
|
|
return g_object_ref (parser->snippets->data);
|
|
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_DATA,
|
|
"<data>:%d: invalid snippet",
|
|
parser->lineno);
|
|
|
|
return NULL;
|
|
}
|