/* ide-snippet-context.c * * Copyright 2013-2019 Christian Hergert * * 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 . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-snippets-context" #include "config.h" #include #include #include #include "ide-snippet-context.h" /** * SECTION:ide-snippet-context * @title: IdeSnippetContext * @short_description: Context for expanding #IdeSnippetChunk * * This class is currently used primary as a hashtable. However, the longer * term goal is to have it hold onto a GjsContext as well as other languages * so that #IdeSnippetChunk can expand themselves by executing * script within the context. * * The #IdeSnippet will build the context and then expand each of the * chunks during the insertion/edit phase. * * Since: 3.32 */ struct _IdeSnippetContext { GObject parent_instance; GHashTable *shared; GHashTable *variables; gchar *line_prefix; gint tab_width; guint use_spaces : 1; }; struct _IdeSnippetContextClass { GObjectClass parent; }; G_DEFINE_FINAL_TYPE (IdeSnippetContext, ide_snippet_context, G_TYPE_OBJECT) enum { CHANGED, LAST_SIGNAL }; typedef gchar *(*InputFilter) (const gchar *input); static GHashTable *filters; static guint signals[LAST_SIGNAL]; IdeSnippetContext * ide_snippet_context_new (void) { return g_object_new (IDE_TYPE_SNIPPET_CONTEXT, NULL); } void ide_snippet_context_dump (IdeSnippetContext *context) { GHashTableIter iter; gpointer key; gpointer value; g_return_if_fail (IDE_IS_SNIPPET_CONTEXT (context)); g_hash_table_iter_init (&iter, context->variables); while (g_hash_table_iter_next (&iter, &key, &value)) g_print (" %s=%s\n", (gchar *) key, (gchar *) value); } void ide_snippet_context_clear_variables (IdeSnippetContext *context) { g_return_if_fail (IDE_IS_SNIPPET_CONTEXT (context)); g_hash_table_remove_all (context->variables); } void ide_snippet_context_add_variable (IdeSnippetContext *context, const gchar *key, const gchar *value) { g_return_if_fail (IDE_IS_SNIPPET_CONTEXT (context)); g_return_if_fail (key); g_hash_table_replace (context->variables, g_strdup (key), g_strdup (value)); } void ide_snippet_context_add_shared_variable (IdeSnippetContext *context, const gchar *key, const gchar *value) { g_return_if_fail (IDE_IS_SNIPPET_CONTEXT (context)); g_return_if_fail (key); g_hash_table_replace (context->shared, g_strdup (key), g_strdup (value)); } const gchar * ide_snippet_context_get_variable (IdeSnippetContext *context, const gchar *key) { const gchar *ret; g_return_val_if_fail (IDE_IS_SNIPPET_CONTEXT (context), NULL); if (!(ret = g_hash_table_lookup (context->variables, key))) ret = g_hash_table_lookup (context->shared, key); return ret; } static gchar * filter_lower (const gchar *input) { return g_utf8_strdown (input, -1); } static gchar * filter_upper (const gchar *input) { return g_utf8_strup (input, -1); } static gchar * filter_capitalize (const gchar *input) { gunichar c; GString *str; if (!*input) return g_strdup (""); c = g_utf8_get_char (input); if (g_unichar_isupper (c)) return g_strdup (input); str = g_string_new (NULL); input = g_utf8_next_char (input); g_string_append_unichar (str, g_unichar_toupper (c)); if (*input) g_string_append (str, input); return g_string_free (str, FALSE); } static gchar * filter_decapitalize (const gchar *input) { gunichar c; GString *str; c = g_utf8_get_char (input); if (g_unichar_islower (c)) return g_strdup (input); str = g_string_new (NULL); input = g_utf8_next_char (input); g_string_append_unichar (str, g_unichar_tolower (c)); g_string_append (str, input); return g_string_free (str, FALSE); } static gchar * filter_html (const gchar *input) { gunichar c; GString *str; str = g_string_new (NULL); for (; *input; input = g_utf8_next_char (input)) { c = g_utf8_get_char (input); switch (c) { case '<': g_string_append_len (str, "<", 4); break; case '>': g_string_append_len (str, ">", 4); break; default: g_string_append_unichar (str, c); break; } } return g_string_free (str, FALSE); } static gchar * filter_camelize (const gchar *input) { gboolean next_is_upper = TRUE; gboolean skip = FALSE; gunichar c; GString *str; if (!strchr (input, '_') && !strchr (input, ' ') && !strchr (input, '-')) return filter_capitalize (input); str = g_string_new (NULL); for (; *input; input = g_utf8_next_char (input)) { c = g_utf8_get_char (input); switch (c) { case '_': case '-': case ' ': next_is_upper = TRUE; skip = TRUE; break; default: break; } if (skip) { skip = FALSE; continue; } if (next_is_upper) { c = g_unichar_toupper (c); next_is_upper = FALSE; } else c = g_unichar_tolower (c); g_string_append_unichar (str, c); } if (g_str_has_suffix (str->str, "Private")) g_string_truncate (str, str->len - strlen ("Private")); return g_string_free (str, FALSE); } static gchar * filter_functify (const gchar *input) { gunichar last = 0; gunichar c; gunichar n; GString *str; str = g_string_new (NULL); for (; *input; input = g_utf8_next_char (input)) { c = g_utf8_get_char (input); n = g_utf8_get_char (g_utf8_next_char (input)); if (last) { if ((g_unichar_islower (last) && g_unichar_isupper (c)) || (g_unichar_isupper (c) && g_unichar_islower (n))) g_string_append_c (str, '_'); } if ((c == ' ') || (c == '-')) c = '_'; g_string_append_unichar (str, g_unichar_tolower (c)); last = c; } if (g_str_has_suffix (str->str, "_private") || g_str_has_suffix (str->str, "_PRIVATE")) g_string_truncate (str, str->len - strlen ("_private")); return g_string_free (str, FALSE); } static gchar * filter_namespace (const gchar *input) { gunichar last = 0; gunichar c; gunichar n; GString *str; gboolean first_is_lower = FALSE; str = g_string_new (NULL); for (; *input; input = g_utf8_next_char (input)) { c = g_utf8_get_char (input); n = g_utf8_get_char (g_utf8_next_char (input)); if (c == '_') break; if (last) { if ((g_unichar_islower (last) && g_unichar_isupper (c)) || (g_unichar_isupper (c) && g_unichar_islower (n))) break; } else first_is_lower = g_unichar_islower (c); if ((c == ' ') || (c == '-')) break; g_string_append_unichar (str, c); last = c; } if (first_is_lower) { gchar *ret; ret = filter_capitalize (str->str); g_string_free (str, TRUE); return ret; } return g_string_free (str, FALSE); } static gchar * filter_class (const gchar *input) { gchar *camel; gchar *ns; gchar *ret = NULL; camel = filter_camelize (input); ns = filter_namespace (input); if (g_str_has_prefix (camel, ns)) ret = g_strdup (camel + strlen (ns)); else { ret = camel; camel = NULL; } g_free (camel); g_free (ns); return ret; } static gchar * filter_instance (const gchar *input) { const gchar *tmp; gchar *funct = NULL; gchar *ret; if (!strchr (input, '_')) { funct = filter_functify (input); input = funct; } if ((tmp = strrchr (input, '_'))) ret = g_strdup (tmp+1); else ret = g_strdup (input); g_free (funct); return ret; } static gchar * filter_space (const gchar *input) { GString *str; str = g_string_new (NULL); for (; *input; input = g_utf8_next_char (input)) g_string_append_c (str, ' '); return g_string_free (str, FALSE); } static gchar * filter_descend_path (const gchar *input) { const gchar *pos; if (input == NULL) return NULL; while (*input == G_DIR_SEPARATOR) input++; if ((pos = strchr (input, G_DIR_SEPARATOR))) return g_strdup (pos + 1); return NULL; } static gchar * filter_stripsuffix (const gchar *input) { const gchar *endpos; g_return_val_if_fail (input, NULL); endpos = strrchr (input, '.'); if (endpos) return g_strndup (input, (endpos - input)); return g_strdup (input); } static gchar * filter_slash_to_dots (const gchar *input) { GString *str; gunichar ch; if (input == NULL) return NULL; str = g_string_new (NULL); for (; *input; input = g_utf8_next_char (input)) { ch = g_utf8_get_char (input); if (ch == G_DIR_SEPARATOR) g_string_append_c (str, '.'); else g_string_append_unichar (str, ch); } return g_string_free (str, FALSE); } static gchar * apply_filter (gchar *input, const gchar *filter) { InputFilter filter_func; gchar *tmp; if ((filter_func = g_hash_table_lookup (filters, filter))) { tmp = input; input = filter_func (input); g_free (tmp); } return input; } static gchar * apply_filters (GString *str, const gchar *filters_list) { gchar **filter_names; gchar *input = g_string_free (str, FALSE); gint i; filter_names = g_strsplit (filters_list, "|", 0); for (i = 0; filter_names[i]; i++) input = apply_filter (input, filter_names[i]); g_strfreev (filter_names); return input; } static gchar * scan_forward (const gchar *input, const gchar **endpos, gunichar needle) { const gchar *begin = input; for (; *input; input = g_utf8_next_char (input)) { gunichar c = g_utf8_get_char (input); if (c == needle) { *endpos = input; return g_strndup (begin, (input - begin)); } } *endpos = NULL; return NULL; } gchar * ide_snippet_context_expand (IdeSnippetContext *context, const gchar *input) { const gchar *expand; gunichar c; gboolean is_dynamic; GString *str; gchar key[12]; glong n; gint i; g_return_val_if_fail (IDE_IS_SNIPPET_CONTEXT (context), NULL); g_return_val_if_fail (input, NULL); is_dynamic = (*input == '$'); str = g_string_new (NULL); for (; *input; input = g_utf8_next_char (input)) { c = g_utf8_get_char (input); if (c == '\\') { input = g_utf8_next_char (input); if (!*input) break; c = g_utf8_get_char (input); } else if (is_dynamic && c == '$') { input = g_utf8_next_char (input); if (!*input) break; c = g_utf8_get_char (input); if (g_unichar_isdigit (c)) { errno = 0; n = strtol (input, (gchar * *) &input, 10); if (((n == LONG_MIN) || (n == LONG_MAX)) && errno == ERANGE) break; input--; g_snprintf (key, sizeof key, "%ld", n); key[sizeof key - 1] = '\0'; expand = ide_snippet_context_get_variable (context, key); if (expand) g_string_append (str, expand); continue; } else { if (strchr (input, '|')) { g_autofree gchar *lkey = NULL; lkey = g_strndup (input, strchr (input, '|') - input); expand = ide_snippet_context_get_variable (context, lkey); if (expand) { g_string_append (str, expand); input = strchr (input, '|') - 1; } else input += strlen (input) - 1; } else { expand = ide_snippet_context_get_variable (context, input); if (expand) g_string_append (str, expand); else { g_string_append_c (str, '$'); g_string_append (str, input); } input += strlen (input) - 1; } continue; } } else if (is_dynamic && c == '|') return apply_filters (str, input + 1); else if (c == '`') { const gchar *endpos = NULL; gchar *slice; slice = scan_forward (input + 1, &endpos, '`'); if (slice) { gchar *expanded; input = endpos; expanded = ide_snippet_context_expand (context, slice); g_string_append (str, expanded); g_free (expanded); g_free (slice); continue; } } else if (c == '\t') { if (context->use_spaces) for (i = 0; i < context->tab_width; i++) g_string_append_c (str, ' '); else g_string_append_c (str, '\t'); continue; } else if (c == '\n') { g_string_append_c (str, '\n'); if (context->line_prefix) g_string_append (str, context->line_prefix); continue; } g_string_append_unichar (str, c); } return g_string_free (str, FALSE); } void ide_snippet_context_set_tab_width (IdeSnippetContext *context, gint tab_width) { g_return_if_fail (IDE_IS_SNIPPET_CONTEXT (context)); context->tab_width = tab_width; } void ide_snippet_context_set_use_spaces (IdeSnippetContext *context, gboolean use_spaces) { g_return_if_fail (IDE_IS_SNIPPET_CONTEXT (context)); context->use_spaces = !!use_spaces; } void ide_snippet_context_set_line_prefix (IdeSnippetContext *context, const gchar *line_prefix) { g_return_if_fail (IDE_IS_SNIPPET_CONTEXT (context)); g_free (context->line_prefix); context->line_prefix = g_strdup (line_prefix); } void ide_snippet_context_emit_changed (IdeSnippetContext *context) { g_return_if_fail (IDE_IS_SNIPPET_CONTEXT (context)); g_signal_emit (context, signals[CHANGED], 0); } static void ide_snippet_context_finalize (GObject *object) { IdeSnippetContext *context = (IdeSnippetContext *)object; g_clear_pointer (&context->shared, g_hash_table_unref); g_clear_pointer (&context->variables, g_hash_table_unref); g_clear_pointer (&context->line_prefix, g_free); G_OBJECT_CLASS (ide_snippet_context_parent_class)->finalize (object); } static void ide_snippet_context_class_init (IdeSnippetContextClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = ide_snippet_context_finalize; signals[CHANGED] = g_signal_new ("changed", IDE_TYPE_SNIPPET_CONTEXT, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); filters = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_insert (filters, (gpointer) "lower", filter_lower); g_hash_table_insert (filters, (gpointer) "upper", filter_upper); g_hash_table_insert (filters, (gpointer) "capitalize", filter_capitalize); g_hash_table_insert (filters, (gpointer) "decapitalize", filter_decapitalize); g_hash_table_insert (filters, (gpointer) "html", filter_html); g_hash_table_insert (filters, (gpointer) "camelize", filter_camelize); g_hash_table_insert (filters, (gpointer) "functify", filter_functify); g_hash_table_insert (filters, (gpointer) "namespace", filter_namespace); g_hash_table_insert (filters, (gpointer) "class", filter_class); g_hash_table_insert (filters, (gpointer) "space", filter_space); g_hash_table_insert (filters, (gpointer) "stripsuffix", filter_stripsuffix); g_hash_table_insert (filters, (gpointer) "instance", filter_instance); g_hash_table_insert (filters, (gpointer) "slash_to_dots", filter_slash_to_dots); g_hash_table_insert (filters, (gpointer) "descend_path", filter_descend_path); } static void ide_snippet_context_init (IdeSnippetContext *context) { GDateTime *dt; gchar *str; context->variables = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); context->shared = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); #define ADD_VARIABLE(k, v) \ g_hash_table_insert (context->shared, g_strdup (k), g_strdup (v)) ADD_VARIABLE ("username", g_get_user_name ()); ADD_VARIABLE ("fullname", g_get_real_name ()); ADD_VARIABLE ("author", g_get_real_name ()); dt = g_date_time_new_now_local (); str = g_date_time_format (dt, "%Y"); ADD_VARIABLE ("year", str); g_free (str); str = g_date_time_format (dt, "%b"); ADD_VARIABLE ("shortmonth", str); g_free (str); str = g_date_time_format (dt, "%d"); ADD_VARIABLE ("day", str); g_free (str); str = g_date_time_format (dt, "%a"); ADD_VARIABLE ("shortweekday", str); g_free (str); g_date_time_unref (dt); ADD_VARIABLE ("email", "unknown@domain.org"); #undef ADD_VARIABLE }