Commits

Anonymous committed c0ac609

Active stings plugin, working

Comments (0)

Files changed (12)

 .*~
 .*\.pcs
 .*\.kdevses
+.*\.gladep
+.*\.bak

moo/mooedit/plugins/activestrings/as-plugin-prefs.c

+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
+ *
+ *   as-plugin-prefs.c
+ *
+ *   Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
+ *
+ *   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 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   See COPYING file that comes with this distribution.
+ */
+
+#include "as-plugin.h"
+#include "mooedit/plugins/activestrings/as-plugin-glade.h"
+#include "mooutils/mooprefsdialogpage.h"
+
+
+enum {
+    COLUMN_PATTERN,
+    COLUMN_SCRIPT,
+    COLUMN_LANG,
+    COLUMN_ENABLED,
+    N_COLUMNS
+};
+
+
+static void prefs_page_apply    (MooGladeXML        *xml);
+static void prefs_page_init     (MooGladeXML        *xml);
+static void prefs_page_destroy  (MooGladeXML        *xml);
+static void selection_changed   (GtkTreeSelection   *selection,
+                                 MooGladeXML        *xml);
+static void set_from_widgets    (MooGladeXML        *xml,
+                                 GtkTreeModel       *model,
+                                 GtkTreePath        *path);
+static void set_from_model      (MooGladeXML        *xml,
+                                 GtkTreeModel       *model,
+                                 GtkTreePath        *path);
+
+static void pattern_data_func   (GtkTreeViewColumn  *column,
+                                 GtkCellRenderer    *cell,
+                                 GtkTreeModel       *model,
+                                 GtkTreeIter        *iter);
+static void button_new          (MooGladeXML        *xml);
+static void button_delete       (MooGladeXML        *xml);
+static void button_up           (MooGladeXML        *xml);
+static void button_down         (MooGladeXML        *xml);
+static void pattern_changed     (MooGladeXML        *xml);
+static void enabled_changed     (MooGladeXML        *xml);
+
+
+static MooPlugin *
+get_plugin (MooGladeXML *xml)
+{
+    return g_object_get_data (G_OBJECT (xml), "as-plugin");
+}
+
+
+static void
+setup_script_view (MooTextView *script)
+{
+    MooLangMgr *mgr;
+    MooLang *lang;
+    MooIndenter *indent;
+
+    g_object_set (script, "highlight-current-line", FALSE, NULL);
+
+    mgr = moo_editor_get_lang_mgr (moo_editor_instance ());
+    lang = moo_lang_mgr_get_lang (mgr, "MooScript");
+
+    if (lang)
+        moo_text_view_set_lang (script, lang);
+
+    moo_text_view_set_font_from_string (script, "Monospace");
+
+    indent = moo_indenter_new (NULL, NULL);
+    moo_text_view_set_indenter (script, indent);
+    g_object_set (indent, "use-tabs", FALSE, "indent", 2, NULL);
+    g_object_unref (indent);
+}
+
+
+GtkWidget *
+_as_plugin_prefs_page (MooPlugin *plugin)
+{
+    GtkWidget *page;
+    MooGladeXML *xml;
+    GtkTreeSelection *selection;
+    GtkTreeView *treeview;
+    GtkTreeViewColumn *column;
+    GtkListStore *store;
+    GtkCellRenderer *cell;
+
+    xml = moo_glade_xml_new_empty ();
+    moo_glade_xml_map_id (xml, "script", MOO_TYPE_TEXT_VIEW);
+    moo_glade_xml_map_id (xml, "page", MOO_TYPE_PREFS_DIALOG_PAGE);
+    moo_glade_xml_parse_memory (xml, AS_PLUGIN_GLADE_UI, -1, "page");
+
+    page = moo_glade_xml_get_widget (xml, "page");
+    g_return_val_if_fail (page != NULL, NULL);
+    g_object_set (page, "label", "Active Strings",
+                  "icon-stock-id", GTK_STOCK_CONVERT, NULL);
+
+    g_object_set_data_full (G_OBJECT (xml), "as-plugin",
+                            g_object_ref (plugin), (GDestroyNotify) g_object_unref);
+    g_object_set_data_full (G_OBJECT (page), "moo-glade-xml", xml,
+                            (GDestroyNotify) g_object_unref);
+
+    g_signal_connect_swapped (page, "apply", G_CALLBACK (prefs_page_apply), xml);
+    g_signal_connect_swapped (page, "init", G_CALLBACK (prefs_page_init), xml);
+    g_signal_connect_swapped (page, "destroy", G_CALLBACK (prefs_page_destroy), xml);
+
+    setup_script_view (moo_glade_xml_get_widget (xml, "script"));
+
+    store = gtk_list_store_new (N_COLUMNS,
+                                G_TYPE_STRING, G_TYPE_STRING,
+                                G_TYPE_STRING, G_TYPE_BOOLEAN);
+    treeview = moo_glade_xml_get_widget (xml, "treeview");
+    selection = gtk_tree_view_get_selection (treeview);
+    g_signal_connect (selection, "changed",
+                      G_CALLBACK (selection_changed), xml);
+
+    gtk_tree_view_set_model (treeview, GTK_TREE_MODEL (store));
+    g_object_unref (store);
+
+    column = gtk_tree_view_column_new ();
+    gtk_tree_view_append_column (treeview, column);
+
+    cell = gtk_cell_renderer_text_new ();
+    gtk_tree_view_column_pack_start (column, cell, TRUE);
+    gtk_tree_view_column_set_cell_data_func (column, cell,
+                                             (GtkTreeCellDataFunc) pattern_data_func,
+                                             NULL, NULL);
+
+    g_signal_connect_swapped (moo_glade_xml_get_widget (xml, "new"),
+                              "clicked", G_CALLBACK (button_new), xml);
+    g_signal_connect_swapped (moo_glade_xml_get_widget (xml, "delete"),
+                              "clicked", G_CALLBACK (button_delete), xml);
+    g_signal_connect_swapped (moo_glade_xml_get_widget (xml, "up"),
+                              "clicked", G_CALLBACK (button_up), xml);
+    g_signal_connect_swapped (moo_glade_xml_get_widget (xml, "down"),
+                              "clicked", G_CALLBACK (button_down), xml);
+    g_signal_connect_swapped (moo_glade_xml_get_widget (xml, "pattern"),
+                              "changed", G_CALLBACK (pattern_changed), xml);
+    g_signal_connect_swapped (moo_glade_xml_get_widget (xml, "enabled"),
+                              "toggled", G_CALLBACK (enabled_changed), xml);
+
+    return page;
+}
+
+
+static void
+pattern_data_func (G_GNUC_UNUSED GtkTreeViewColumn *column,
+                   GtkCellRenderer    *cell,
+                   GtkTreeModel       *model,
+                   GtkTreeIter        *iter)
+{
+    gboolean enabled;
+    char *pattern;
+
+    gtk_tree_model_get (model, iter,
+                        COLUMN_ENABLED, &enabled,
+                        COLUMN_PATTERN, &pattern, -1);
+
+    g_object_set (cell, "text", pattern,
+                  "foreground", enabled ? NULL : "grey",
+                  "style", enabled ? PANGO_STYLE_NORMAL : PANGO_STYLE_ITALIC,
+                  NULL);
+
+    g_free (pattern);
+}
+
+
+static gboolean
+is_empty_string (const char *string)
+{
+    if (!string)
+        return TRUE;
+
+#define IS_SPACE(c) (c == ' ' || c == '\t' || c == '\r' || c == '\n')
+    while (*string)
+    {
+        if (*string && !IS_SPACE (*string))
+            return FALSE;
+        string++;
+    }
+#undef IS_SPACE
+
+    return TRUE;
+}
+
+
+static void
+save_items (GtkTreeModel *model,
+            MooMarkupDoc *doc)
+{
+    GtkTreeIter iter;
+    MooMarkupNode *root;
+
+    if (!gtk_tree_model_get_iter_first (model, &iter))
+        return;
+
+    root = moo_markup_create_element (MOO_MARKUP_NODE (doc), AS_XML_ROOT);
+
+    do
+    {
+        MooMarkupNode *node;
+        char *pattern, *lang, *script;
+        gboolean enabled;
+
+        gtk_tree_model_get (model, &iter,
+                            COLUMN_PATTERN, &pattern,
+                            COLUMN_LANG, &lang,
+                            COLUMN_SCRIPT, &script,
+                            COLUMN_ENABLED, &enabled,
+                            -1);
+
+        if (is_empty_string (script))
+        {
+            g_free (script);
+            script = NULL;
+        }
+
+        if (is_empty_string (lang))
+        {
+            g_free (lang);
+            lang = NULL;
+        }
+
+        if (is_empty_string (pattern))
+        {
+            g_free (pattern);
+            pattern = NULL;
+        }
+
+        if (script)
+            node = moo_markup_create_text_element (root, AS_XML_ITEM, script);
+        else
+            node = moo_markup_create_element (root, AS_XML_ITEM);
+
+        if (pattern)
+            moo_markup_set_prop (node, AS_XML_PROP_PATTERN, pattern);
+        if (lang)
+            moo_markup_set_prop (node, AS_XML_PROP_LANG, lang);
+        if (!enabled)
+            moo_markup_set_bool_prop (node, AS_XML_PROP_ENABLED, FALSE);
+
+        g_free (pattern);
+        g_free (lang);
+        g_free (script);
+    }
+    while (gtk_tree_model_iter_next (model, &iter));
+}
+
+
+static void
+prefs_page_apply (MooGladeXML *xml)
+{
+    GtkTreeIter iter;
+    GtkTreeModel *model;
+    GtkTreeSelection *selection;
+    MooMarkupDoc *doc;
+    MooMarkupNode *root;
+
+    doc = moo_prefs_get_markup ();
+    g_return_if_fail (doc != NULL);
+
+    selection = gtk_tree_view_get_selection (moo_glade_xml_get_widget (xml, "treeview"));
+
+    if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+        GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
+        set_from_widgets (xml, model, path);
+        gtk_tree_path_free (path);
+    }
+
+    root = moo_markup_get_element (MOO_MARKUP_NODE (doc), AS_XML_ROOT);
+
+    if (root)
+        moo_markup_delete_node (root);
+
+    save_items (model, doc);
+
+    _as_plugin_reload (get_plugin (xml));
+}
+
+
+static void
+insert_item (const char   *pattern,
+             const char   *script,
+             const char   *lang,
+             gboolean      enabled,
+             GtkListStore *store)
+{
+    GtkTreeIter iter;
+    gtk_list_store_append (store, &iter);
+    gtk_list_store_set (store, &iter,
+                        COLUMN_PATTERN, pattern,
+                        COLUMN_LANG, lang,
+                        COLUMN_SCRIPT, script,
+                        COLUMN_ENABLED, enabled,
+                        -1);
+}
+
+
+static void
+prefs_page_init (MooGladeXML *xml)
+{
+    GtkTreeIter iter;
+    GtkListStore *store;
+    GtkTreeModel *model;
+
+    model = gtk_tree_view_get_model (moo_glade_xml_get_widget (xml, "treeview"));
+    store = GTK_LIST_STORE (model);
+
+    if (gtk_tree_model_get_iter_first (model, &iter))
+        while (gtk_list_store_remove (store, &iter))
+            ;
+
+    _as_plugin_load (get_plugin (xml), (ASLoadFunc) insert_item, model);
+
+    set_from_model (xml, model, NULL);
+}
+
+
+static gboolean
+get_selected (MooGladeXML   *xml,
+              GtkTreeModel **model,
+              GtkTreeIter   *iter)
+{
+    GtkTreeSelection *selection;
+    selection = gtk_tree_view_get_selection (moo_glade_xml_get_widget (xml, "treeview"));
+    return gtk_tree_selection_get_selected (selection, model, iter);
+}
+
+
+static void
+set_from_widgets (MooGladeXML  *xml,
+                  GtkTreeModel *model,
+                  GtkTreePath  *path)
+{
+    GtkTreeIter iter;
+    GtkToggleButton *button;
+    GtkTextBuffer *buffer;
+    GtkTextIter start, end;
+    const char *pattern, *lang;
+    char *script;
+
+    gtk_tree_model_get_iter (model, &iter, path);
+
+    pattern = gtk_entry_get_text (moo_glade_xml_get_widget (xml, "pattern"));
+    lang = gtk_entry_get_text (moo_glade_xml_get_widget (xml, "lang"));
+    button = moo_glade_xml_get_widget (xml, "enabled");
+
+    buffer = gtk_text_view_get_buffer (moo_glade_xml_get_widget (xml, "script"));
+    gtk_text_buffer_get_bounds (buffer, &start, &end);
+    script = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
+
+    gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+                        COLUMN_PATTERN, pattern,
+                        COLUMN_SCRIPT, script,
+                        COLUMN_LANG, lang,
+                        COLUMN_ENABLED, gtk_toggle_button_get_active (button),
+                        -1);
+
+    g_free (script);
+}
+
+
+static void
+script_set_text (gpointer    view,
+                 const char *text)
+{
+    g_return_if_fail (MOO_IS_TEXT_VIEW (view));
+    moo_text_view_begin_not_undoable_action (view);
+    gtk_text_buffer_set_text (gtk_text_view_get_buffer (view), text, -1);
+    moo_text_view_end_not_undoable_action (view);
+}
+
+
+static void
+set_from_model (MooGladeXML  *xml,
+                GtkTreeModel *model,
+                GtkTreePath  *path)
+{
+    GtkTreeIter iter;
+    GtkWidget *button_new, *button_delete, *button_up, *button_down;
+    GtkWidget *table;
+    GtkToggleButton *button_enabled;
+    GtkEntry *entry_pattern, *entry_lang;
+    GtkTextView *script_view;
+    GtkTextBuffer *buffer;
+
+    g_signal_handlers_block_by_func (moo_glade_xml_get_widget (xml, "pattern"),
+                                     (gpointer) pattern_changed, xml);
+    g_signal_handlers_block_by_func (moo_glade_xml_get_widget (xml, "enabled"),
+                                     (gpointer) enabled_changed, xml);
+
+    button_new = moo_glade_xml_get_widget (xml, "new");
+    button_delete = moo_glade_xml_get_widget (xml, "delete");
+    button_up = moo_glade_xml_get_widget (xml, "up");
+    button_down = moo_glade_xml_get_widget (xml, "down");
+    table = moo_glade_xml_get_widget (xml, "table");
+    entry_pattern = moo_glade_xml_get_widget (xml, "pattern");
+    entry_lang = moo_glade_xml_get_widget (xml, "lang");
+    button_enabled = moo_glade_xml_get_widget (xml, "enabled");
+    script_view = moo_glade_xml_get_widget (xml, "script");
+    buffer = gtk_text_view_get_buffer (script_view);
+
+    if (path)
+        gtk_tree_model_get_iter (model, &iter, path);
+
+    if (!path)
+    {
+        gtk_widget_set_sensitive (button_delete, FALSE);
+        gtk_widget_set_sensitive (button_up, FALSE);
+        gtk_widget_set_sensitive (button_down, FALSE);
+        gtk_widget_set_sensitive (GTK_WIDGET (button_enabled), FALSE);
+        gtk_widget_set_sensitive (table, FALSE);
+        gtk_entry_set_text (entry_pattern, "");
+        gtk_entry_set_text (entry_lang, "");
+        script_set_text (script_view, "");
+        gtk_toggle_button_set_active (button_enabled, FALSE);
+    }
+    else
+    {
+        char *pattern, *lang, *script;
+        gboolean enabled;
+        int *indices;
+
+        gtk_tree_model_get (model, &iter,
+                            COLUMN_PATTERN, &pattern,
+                            COLUMN_LANG, &lang,
+                            COLUMN_SCRIPT, &script,
+                            COLUMN_ENABLED, &enabled,
+                            -1);
+
+        gtk_widget_set_sensitive (button_delete, TRUE);
+        gtk_widget_set_sensitive (GTK_WIDGET (button_enabled), TRUE);
+        gtk_widget_set_sensitive (table, TRUE);
+        gtk_entry_set_text (entry_pattern, pattern ? pattern : "");
+        gtk_entry_set_text (entry_lang, lang ? lang : "");
+        script_set_text (script_view, script ? script : "");
+        gtk_toggle_button_set_active (button_enabled, enabled);
+
+        indices = gtk_tree_path_get_indices (path);
+        gtk_widget_set_sensitive (button_up, indices[0] != 0);
+        gtk_widget_set_sensitive (button_down, indices[0] !=
+                gtk_tree_model_iter_n_children (model, NULL) - 1);
+
+    }
+
+    g_signal_handlers_unblock_by_func (moo_glade_xml_get_widget (xml, "pattern"),
+                                       (gpointer) pattern_changed, xml);
+    g_signal_handlers_unblock_by_func (moo_glade_xml_get_widget (xml, "enabled"),
+                                       (gpointer) enabled_changed, xml);
+}
+
+
+static void
+selection_changed (GtkTreeSelection *selection,
+                   MooGladeXML      *xml)
+{
+    GtkTreeRowReference *old_row;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    GtkTreePath *path, *old_path;
+
+    old_row = g_object_get_data (G_OBJECT (selection), "current-row");
+    old_path = old_row ? gtk_tree_row_reference_get_path (old_row) : NULL;
+
+    if (old_row && !old_path)
+    {
+        g_object_set_data (G_OBJECT (selection), "current-row", NULL);
+        old_row = NULL;
+    }
+
+    if (gtk_tree_selection_get_selected (selection, &model, &iter))
+    {
+        path = gtk_tree_model_get_path (model, &iter);
+
+        if (old_path && !gtk_tree_path_compare (old_path, path))
+        {
+            gtk_tree_path_free (old_path);
+            gtk_tree_path_free (path);
+            return;
+        }
+    }
+    else
+    {
+        if (!old_path)
+            return;
+
+        path = NULL;
+    }
+
+    if (old_path)
+        set_from_widgets (xml, model, old_path);
+
+    set_from_model (xml, model, path);
+
+    if (path)
+    {
+        GtkTreeRowReference *row;
+        row = gtk_tree_row_reference_new (model, path);
+        g_object_set_data_full (G_OBJECT (selection), "current-row", row,
+                                (GDestroyNotify) gtk_tree_row_reference_free);
+    }
+    else
+    {
+        g_object_set_data (G_OBJECT (selection), "current-row", NULL);
+    }
+
+    gtk_tree_path_free (path);
+    gtk_tree_path_free (old_path);
+}
+
+
+static void
+button_new (MooGladeXML *xml)
+{
+    GtkTreeModel *model;
+    GtkTreeIter iter, after;
+    GtkTreeSelection *selection;
+    GtkTreeView *treeview;
+
+    treeview = moo_glade_xml_get_widget (xml, "treeview");
+
+    if (!get_selected (xml, &model, &after))
+        gtk_list_store_append (GTK_LIST_STORE (model), &iter);
+    else
+        gtk_list_store_insert_after (GTK_LIST_STORE (model), &iter, &after);
+
+    gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+                        COLUMN_PATTERN, "?",
+                        COLUMN_ENABLED, TRUE, -1);
+
+    selection = gtk_tree_view_get_selection (treeview);
+    gtk_tree_selection_select_iter (selection, &iter);
+}
+
+
+static void
+button_delete (MooGladeXML *xml)
+{
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    GtkTreeSelection *selection;
+    GtkTreeView *treeview;
+
+    if (!get_selected (xml, &model, &iter))
+        g_return_if_reached ();
+
+    if (!gtk_list_store_remove (GTK_LIST_STORE (model), &iter))
+    {
+        int n_children;
+
+        n_children = gtk_tree_model_iter_n_children (model, NULL);
+
+        if (n_children)
+            gtk_tree_model_iter_nth_child (model, &iter, NULL,
+                                           n_children - 1);
+    }
+
+    if (gtk_tree_model_iter_n_children (model, NULL))
+    {
+        treeview = moo_glade_xml_get_widget (xml, "treeview");
+        selection = gtk_tree_view_get_selection (treeview);
+        gtk_tree_selection_select_iter (selection, &iter);
+    }
+}
+
+
+static void
+button_up (MooGladeXML *xml)
+{
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    GtkTreeIter swap_with;
+    GtkTreePath *path, *new_path;
+    int *indices;
+
+    if (!get_selected (xml, &model, &iter))
+        g_return_if_reached ();
+
+    path = gtk_tree_model_get_path (model, &iter);
+    indices = gtk_tree_path_get_indices (path);
+
+    if (!indices[0])
+        g_return_if_reached ();
+
+    new_path = gtk_tree_path_new_from_indices (indices[0] - 1, -1);
+    gtk_tree_model_get_iter (model, &swap_with, new_path);
+    gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &swap_with);
+    set_from_model (xml, model, new_path);
+
+    gtk_tree_path_free (new_path);
+    gtk_tree_path_free (path);
+}
+
+
+static void
+button_down (MooGladeXML *xml)
+{
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    GtkTreeIter swap_with;
+    GtkTreePath *path, *new_path;
+    int *indices;
+    int n_children;
+
+    if (!get_selected (xml, &model, &iter))
+        g_return_if_reached ();
+
+    path = gtk_tree_model_get_path (model, &iter);
+    indices = gtk_tree_path_get_indices (path);
+    n_children = gtk_tree_model_iter_n_children (model, NULL);
+
+    if (indices[0] == n_children - 1)
+        g_return_if_reached ();
+
+    new_path = gtk_tree_path_new_from_indices (indices[0] + 1, -1);
+    gtk_tree_model_get_iter (model, &swap_with, new_path);
+    gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &swap_with);
+    set_from_model (xml, model, new_path);
+
+    gtk_tree_path_free (new_path);
+    gtk_tree_path_free (path);
+}
+
+
+static void
+pattern_changed (MooGladeXML *xml)
+{
+    GtkEntry *entry;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+    const char *pattern;
+
+    if (!get_selected (xml, &model, &iter))
+        return;
+
+    entry = moo_glade_xml_get_widget (xml, "pattern");
+    pattern = gtk_entry_get_text (entry);
+
+    gtk_list_store_set (GTK_LIST_STORE (model), &iter, COLUMN_PATTERN, pattern, -1);
+}
+
+
+static void
+enabled_changed (MooGladeXML *xml)
+{
+    GtkToggleButton *button;
+    GtkTreeModel *model;
+    GtkTreeIter iter;
+
+    if (!get_selected (xml, &model, &iter))
+        return;
+
+    button = moo_glade_xml_get_widget (xml, "enabled");
+    gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+                        COLUMN_ENABLED, gtk_toggle_button_get_active (button), -1);
+}
+
+
+static void
+prefs_page_destroy (MooGladeXML *xml)
+{
+    GtkTreeSelection *selection;
+
+    selection = gtk_tree_view_get_selection (moo_glade_xml_get_widget (xml, "treeview"));
+    g_signal_handlers_disconnect_by_func (selection, (gpointer) selection_changed, xml);
+
+    g_signal_handlers_disconnect_by_func (moo_glade_xml_get_widget (xml, "new"),
+                                          (gpointer) button_new, xml);
+    g_signal_handlers_disconnect_by_func (moo_glade_xml_get_widget (xml, "delete"),
+                                          (gpointer) button_delete, xml);
+    g_signal_handlers_disconnect_by_func (moo_glade_xml_get_widget (xml, "up"),
+                                          (gpointer) button_up, xml);
+    g_signal_handlers_disconnect_by_func (moo_glade_xml_get_widget (xml, "down"),
+                                          (gpointer) button_down, xml);
+    g_signal_handlers_disconnect_by_func (moo_glade_xml_get_widget (xml, "pattern"),
+                                          (gpointer) pattern_changed, xml);
+    g_signal_handlers_disconnect_by_func (moo_glade_xml_get_widget (xml, "enabled"),
+                                          (gpointer) enabled_changed, xml);
+}

moo/mooedit/plugins/activestrings/as-plugin-script.c

+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
+ *
+ *   as-plugin-script.c
+ *
+ *   Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
+ *
+ *   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 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   See COPYING file that comes with this distribution.
+ */
+
+#include "as-plugin-script.h"
+
+
+static void as_plugin_context_init_api  (ASPluginContext    *ctx);
+
+
+G_DEFINE_TYPE (ASPluginContext, _as_plugin_context, MS_TYPE_CONTEXT)
+
+
+static void
+_as_plugin_context_init (ASPluginContext *ctx)
+{
+    as_plugin_context_init_api (ctx);
+}
+
+
+static void
+_as_plugin_context_class_init (G_GNUC_UNUSED ASPluginContextClass *klass)
+{
+}
+
+
+MSContext *
+_as_plugin_context_new (void)
+{
+    return g_object_new (AS_TYPE_PLUGIN_CONTEXT, NULL);
+}
+
+
+static void
+as_plugin_context_setup (ASPluginContext *ctx,
+                         MooEdit         *doc,
+                         char            *match,
+                         char           **parens,
+                         guint            n_parens)
+{
+    guint i;
+    MSValue *val;
+
+    val = ms_value_string (match);
+    ms_context_assign_positional (MS_CONTEXT (ctx), 0, val);
+    ms_value_unref (val);
+
+    for (i = 0; i < n_parens; ++i)
+    {
+        val = ms_value_string (parens[i]);
+        ms_context_assign_positional (MS_CONTEXT (ctx), i + 1, val);
+        ms_value_unref (val);
+    }
+
+    ctx->doc = g_object_ref (doc);
+}
+
+
+static void
+as_plugin_context_clear (ASPluginContext *ctx,
+                         guint            n_parens)
+{
+    guint i;
+
+    for (i = 0; i < n_parens + 1; ++i)
+        ms_context_assign_positional (MS_CONTEXT (ctx), i, NULL);
+
+    g_object_ref (ctx->doc);
+    ctx->doc = NULL;
+}
+
+
+gboolean
+_as_plugin_context_exec (MSContext      *ctx,
+                         MSNode         *script,
+                         MooEdit        *doc,
+                         GtkTextIter    *insert,
+                         char           *match,
+                         char          **parens,
+                         guint           n_parens)
+{
+    MSValue *val;
+    gboolean success;
+
+    g_return_val_if_fail (AS_IS_PLUGIN_CONTEXT (ctx), FALSE);
+    g_return_val_if_fail (MS_IS_NODE (script), FALSE);
+    g_return_val_if_fail (MOO_IS_EDIT (doc), FALSE);
+    g_return_val_if_fail (insert != NULL, FALSE);
+    g_return_val_if_fail (match != NULL, FALSE);
+    g_return_val_if_fail (!n_parens || parens, FALSE);
+
+    as_plugin_context_setup (AS_PLUGIN_CONTEXT (ctx),
+                             doc, match, parens, n_parens);
+
+    val = ms_node_eval (script, ctx);
+    success = val != NULL;
+
+    if (val)
+        ms_value_unref (val);
+
+    if (!success)
+    {
+        g_print ("%s\n", ms_context_get_error_msg (ctx));
+        ms_context_clear_error (ctx);
+    }
+
+    as_plugin_context_clear (AS_PLUGIN_CONTEXT (ctx), n_parens);
+
+    return success;
+}
+
+
+enum {
+    FUNC_BS,
+    FUNC_DEL,
+    FUNC_INS,
+    FUNC_UP,
+    FUNC_DOWN,
+    FUNC_LEFT,
+    FUNC_RIGHT,
+    FUNC_SEL,
+    N_BUILTIN_FUNCS
+};
+
+
+static const char *builtin_func_names[N_BUILTIN_FUNCS] = {
+    "Bs", "Del", "Ins", "Up", "Down", "Left", "Right", "Sel"
+};
+
+static MSFunc *builtin_funcs[N_BUILTIN_FUNCS];
+
+
+static gboolean
+check_one_arg (MSValue          **args,
+               guint              n_args,
+               ASPluginContext   *ctx,
+               gboolean           nonnegative,
+               int               *dest,
+               int                default_val)
+{
+    int val;
+
+    if (n_args > 1)
+    {
+        ms_context_set_error (MS_CONTEXT (ctx), MS_ERROR_TYPE,
+                              "number of args must be zero or one");
+        return FALSE;
+    }
+
+    if (!n_args)
+    {
+        *dest = default_val;
+        return TRUE;
+    }
+
+    if (!ms_value_get_int (args[0], &val))
+    {
+        ms_context_set_error (MS_CONTEXT (ctx), MS_ERROR_TYPE,
+                              "argument must be integer");
+        return FALSE;
+    }
+
+    if (nonnegative && val < 0)
+    {
+        ms_context_set_error (MS_CONTEXT (ctx), MS_ERROR_VALUE,
+                              "argument must be non-negative");
+        return FALSE;
+    }
+
+    *dest = val;
+    return TRUE;
+}
+
+
+static MSValue *
+cfunc_bs (MSValue          **args,
+          guint              n_args,
+          ASPluginContext   *ctx)
+{
+    int n;
+    GtkTextIter start, end;
+    GtkTextBuffer *buffer;
+
+    if (!check_one_arg (args, n_args, ctx, TRUE, &n, 1))
+        return NULL;
+
+    if (!n)
+        return ms_value_none ();
+
+    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (ctx->doc));
+
+    if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+    {
+        gtk_text_buffer_delete (buffer, &start, &end);
+        n--;
+    }
+
+    if (n)
+    {
+        gtk_text_iter_backward_chars (&start, n);
+        gtk_text_buffer_delete (buffer, &start, &end);
+    }
+
+    return ms_value_none ();
+}
+
+
+static MSValue *
+cfunc_del (MSValue          **args,
+           guint              n_args,
+           ASPluginContext   *ctx)
+{
+    int n;
+    GtkTextIter start, end;
+    GtkTextBuffer *buffer;
+
+    if (!check_one_arg (args, n_args, ctx, TRUE, &n, 1))
+        return NULL;
+
+    if (!n)
+        return ms_value_none ();
+
+    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (ctx->doc));
+
+    if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+    {
+        gtk_text_buffer_delete (buffer, &start, &end);
+        n--;
+    }
+
+    if (n)
+    {
+        gtk_text_iter_forward_chars (&end, n);
+        gtk_text_buffer_delete (buffer, &start, &end);
+    }
+
+    return ms_value_none ();
+}
+
+
+static void
+get_cursor (MooEdit *doc,
+            int     *line,
+            int     *col)
+{
+    GtkTextBuffer *buffer;
+    GtkTextIter iter;
+
+    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (doc));
+    gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+                                      gtk_text_buffer_get_insert (buffer));
+
+    *line = gtk_text_iter_get_line (&iter);
+    *col = gtk_text_iter_get_line_offset (&iter);
+}
+
+
+static MSValue *
+cfunc_up (MSValue          **args,
+          guint              n_args,
+          ASPluginContext   *ctx)
+{
+    int line, col, n;
+
+    if (!check_one_arg (args, n_args, ctx, FALSE, &n, 1))
+        return NULL;
+
+    if (!n)
+        return ms_value_none ();
+
+    get_cursor (ctx->doc, &line, &col);
+
+    if (line > 0)
+        moo_text_view_move_cursor (MOO_TEXT_VIEW (ctx->doc),
+                                   MAX (line - n, 0), col,
+                                   FALSE);
+
+    return ms_value_none ();
+}
+
+
+static MSValue *
+cfunc_down (MSValue          **args,
+            guint              n_args,
+            ASPluginContext   *ctx)
+{
+    int line, col, n, line_count;
+    GtkTextBuffer *buffer;
+
+    if (!check_one_arg (args, n_args, ctx, FALSE, &n, 1))
+        return NULL;
+
+    if (!n)
+        return ms_value_none ();
+
+    get_cursor (ctx->doc, &line, &col);
+    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (ctx->doc));
+    line_count = gtk_text_buffer_get_line_count (buffer);
+
+    moo_text_view_move_cursor (MOO_TEXT_VIEW (ctx->doc),
+                               MIN (line + n, line_count - 1), col,
+                               FALSE);
+
+    return ms_value_none ();
+}
+
+
+static MSValue *
+cfunc_sel (MSValue           *arg,
+           ASPluginContext   *ctx)
+{
+    int n;
+    GtkTextBuffer *buffer;
+    GtkTextIter start, end;
+
+    if (!ms_value_get_int (arg, &n))
+        return ms_context_set_error (MS_CONTEXT (ctx), MS_ERROR_TYPE,
+                                     "argument must be integer");
+
+    if (!n)
+        return ms_context_set_error (MS_CONTEXT (ctx), MS_ERROR_TYPE,
+                                     "argument must be non zero integer");
+
+    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (ctx->doc));
+    gtk_text_buffer_get_iter_at_mark (buffer, &start,
+                                      gtk_text_buffer_get_insert (buffer));
+    end = start;
+    gtk_text_iter_forward_chars (&end, n);
+
+    gtk_text_buffer_select_range (buffer, &end, &start);
+
+    return ms_value_none ();
+}
+
+
+static void
+move_cursor (MooEdit *doc,
+             int      howmany)
+{
+    GtkTextBuffer *buffer;
+    GtkTextIter iter;
+
+    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (doc));
+    gtk_text_buffer_get_iter_at_mark (buffer, &iter,
+                                      gtk_text_buffer_get_insert (buffer));
+
+    gtk_text_iter_forward_chars (&iter, howmany);
+    gtk_text_buffer_place_cursor (buffer, &iter);
+}
+
+
+static MSValue *
+cfunc_left (MSValue          **args,
+            guint              n_args,
+            ASPluginContext   *ctx)
+{
+    int n;
+
+    if (!check_one_arg (args, n_args, ctx, FALSE, &n, 1))
+        return NULL;
+
+    move_cursor (ctx->doc, -n);
+    return ms_value_none ();
+}
+
+
+static MSValue *
+cfunc_right (MSValue          **args,
+             guint              n_args,
+             ASPluginContext   *ctx)
+{
+    int n;
+
+    if (!check_one_arg (args, n_args, ctx, FALSE, &n, 1))
+        return NULL;
+
+    move_cursor (ctx->doc, n);
+    return ms_value_none ();
+}
+
+
+static MSValue *
+cfunc_ins (MSValue          **args,
+           guint              n_args,
+           ASPluginContext   *ctx)
+{
+    guint i;
+    GtkTextIter start, end;
+    GtkTextBuffer *buffer;
+
+    if (!n_args)
+        return ms_value_none ();
+
+    buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (ctx->doc));
+
+    if (gtk_text_buffer_get_selection_bounds (buffer, &start, &end))
+        gtk_text_buffer_delete (buffer, &start, &end);
+
+    for (i = 0; i < n_args; ++i)
+    {
+        char *s = ms_value_print (args[i]);
+        gtk_text_buffer_insert (buffer, &start, s, -1);
+        g_free (s);
+    }
+
+    return ms_value_none ();
+}
+
+
+static void
+init_api (void)
+{
+    if (builtin_funcs[0])
+        return;
+
+    builtin_funcs[FUNC_BS] = ms_cfunc_new_var ((MSCFunc_Var) cfunc_bs);
+    builtin_funcs[FUNC_DEL] = ms_cfunc_new_var ((MSCFunc_Var) cfunc_del);
+    builtin_funcs[FUNC_INS] = ms_cfunc_new_var ((MSCFunc_Var) cfunc_ins);
+    builtin_funcs[FUNC_UP] = ms_cfunc_new_var ((MSCFunc_Var) cfunc_up);
+    builtin_funcs[FUNC_DOWN] = ms_cfunc_new_var ((MSCFunc_Var) cfunc_down);
+    builtin_funcs[FUNC_LEFT] = ms_cfunc_new_var ((MSCFunc_Var) cfunc_left);
+    builtin_funcs[FUNC_RIGHT] = ms_cfunc_new_var ((MSCFunc_Var) cfunc_right);
+    builtin_funcs[FUNC_SEL] = ms_cfunc_new_1 ((MSCFunc_1) cfunc_sel);
+}
+
+
+static void
+as_plugin_context_init_api (ASPluginContext *ctx)
+{
+    guint i;
+
+    init_api ();
+
+    for (i = 0; i < N_BUILTIN_FUNCS; ++i)
+        ms_context_set_func (MS_CONTEXT (ctx),
+                             builtin_func_names[i],
+                             builtin_funcs[i]);
+}

moo/mooedit/plugins/activestrings/as-plugin-script.h

+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
+ *
+ *   as-plugin-script.h
+ *
+ *   Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
+ *
+ *   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 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   See COPYING file that comes with this distribution.
+ */
+
+#ifndef __AS_PLUGIN_SCRIPT_H__
+#define __AS_PLUGIN_SCRIPT_H__
+
+#include "mooutils/mooscript/mooscript-context.h"
+#include "mooutils/mooscript/mooscript-node.h"
+#include "mooedit/mooedit.h"
+
+G_BEGIN_DECLS
+
+
+#define AS_TYPE_PLUGIN_CONTEXT              (_as_plugin_context_get_type ())
+#define AS_PLUGIN_CONTEXT(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), AS_TYPE_PLUGIN_CONTEXT, ASPluginContext))
+#define AS_PLUGIN_CONTEXT_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), AS_TYPE_PLUGIN_CONTEXT, ASPluginContextClass))
+#define AS_IS_PLUGIN_CONTEXT(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), AS_TYPE_PLUGIN_CONTEXT))
+#define AS_IS_PLUGIN_CONTEXT_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), AS_TYPE_PLUGIN_CONTEXT))
+#define AS_PLUGIN_CONTEXT_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), AS_TYPE_PLUGIN_CONTEXT, ASPluginContextClass))
+
+typedef struct _ASPluginContext ASPluginContext;
+typedef struct _ASPluginContextClass ASPluginContextClass;
+
+
+struct _ASPluginContext {
+    MSContext context;
+    MooEdit *doc;
+};
+
+struct _ASPluginContextClass {
+    MSContextClass context_class;
+};
+
+
+GType        _as_plugin_context_get_type    (void) G_GNUC_CONST;
+
+MSContext   *_as_plugin_context_new         (void);
+
+gboolean     _as_plugin_context_exec        (MSContext      *ctx,
+                                             MSNode         *script,
+                                             MooEdit        *doc,
+                                             GtkTextIter    *insert,
+                                             char           *match,
+                                             char          **parens,
+                                             guint           n_parens);
+
+
+G_END_DECLS
+
+#endif /* __AS_PLUGIN_SCRIPT_H__ */

moo/mooedit/plugins/activestrings/as-plugin.c

+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*-
+ *
+ *   as-plugin.c
+ *
+ *   Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
+ *
+ *   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 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   See COPYING file that comes with this distribution.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#ifndef MOO_VERSION
+#define MOO_VERSION NULL
+#endif
+
+#include "mooedit/mooplugin-macro.h"
+#include "mooedit/mootextview.h"
+#include "mooedit/plugins/mooeditplugins.h"
+#include "mooutils/eggregex.h"
+#include "as-plugin-script.h"
+#include "as-plugin.h"
+#include "mooutils/mooscript/mooscript-parser.h"
+#include <string.h>
+
+#define AS_PLUGIN_ID "ActiveStrings"
+#define AS_DATA "moo-active-strings"
+#define AS_WILDCARD '?'
+
+#define PREFS_ROOT MOO_PLUGIN_PREFS_ROOT "/" AS_PLUGIN_ID
+#define FILE_PREFS_KEY PREFS_ROOT "/file"
+
+
+typedef struct _ASPlugin ASPlugin;
+typedef struct _ASSet ASSet;
+typedef struct _ASString ASString;
+typedef struct _ASStringInfo ASStringInfo;
+typedef struct _ASMatch ASMatch;
+typedef struct _ASInfo ASInfo;
+
+
+struct _ASInfo {
+    char *pattern;
+    char *script;
+    char *lang;
+    guint enabled : 1;
+};
+
+struct _ASPlugin {
+    MooPlugin parent;
+    MSContext *ctx;
+    GHashTable *lang_sets;
+    ASSet *any_lang;
+};
+
+struct _ASStringInfo {
+    guint n_wildcards;
+    char *pattern;
+    guint pattern_len;
+    gunichar last_char;
+    char *script;
+};
+
+struct _ASString {
+    guint whole;
+    guint *parens;
+    guint n_parens;
+};
+
+struct _ASSet {
+    ASString **strings;
+    char **scripts;
+    guint n_strings;
+    EggRegex *regex;
+    guint ref_count;
+    gunichar min_last;
+    gunichar max_last;
+    gunichar *last_chars;
+    guint n_last_chars;
+};
+
+struct _ASMatch {
+    guint string_no;
+    /* all offsets are in chars here */
+    guint start_offset;
+    guint end_offset;
+    guint *parens;
+    guint n_parens;
+};
+
+typedef enum {
+    AS_WORD_BOUNDARY        = 1 << 0,
+    AS_NOT_WORD_BOUNDARY    = 1 << 1
+} ASOptions;
+
+
+static gboolean as_plugin_init      (ASPlugin       *plugin);
+static void     as_plugin_deinit    (ASPlugin       *plugin);
+static void     as_plugin_attach    (ASPlugin       *plugin,
+                                     MooEdit        *doc);
+static void     as_plugin_detach    (ASPlugin       *plugin,
+                                     MooEdit        *doc);
+
+static void     as_plugin_do_action (ASPlugin       *plugin,
+                                     MooEdit        *doc,
+                                     GtkTextIter    *insert,
+                                     ASSet          *set,
+                                     ASMatch        *match,
+                                     char           *full_text,
+                                     char          **parens_text);
+
+static gboolean char_inserted_cb    (MooEdit        *doc,
+                                     GtkTextIter    *where,
+                                     guint           character,
+                                     GSList         *sets);
+static void     process_match       (MooEdit        *doc,
+                                     GtkTextIter    *end,
+                                     ASSet          *set,
+                                     ASMatch        *match);
+
+static ASSet   *as_set_new          (ASStringInfo  **strings,
+                                     guint           n_strings);
+static void     as_set_unref        (ASSet          *set);
+static ASSet   *as_set_ref          (ASSet          *set);
+static gboolean as_set_check_last   (ASSet          *set,
+                                     gunichar        c);
+static gboolean as_set_match        (ASSet          *set,
+                                     const char     *text,
+                                     ASMatch        *match);
+
+static void     as_string_info_set_script (ASStringInfo *info,
+                                     const char     *script);
+static void     as_string_info_free (ASStringInfo   *info);
+
+static void     as_match_destroy    (ASMatch        *match);
+static ASString *as_string_new      (guint           n_wildcards);
+static void     as_string_free      (ASString       *s);
+
+
+static ASInfo *
+as_info_new (const char *pattern,
+             const char *script,
+             const char *lang,
+             gboolean    enabled)
+{
+    ASInfo *info;
+
+    g_return_val_if_fail (pattern && pattern[0], NULL);
+
+    info = g_new0 (ASInfo, 1);
+    info->pattern = (pattern && pattern[0]) ? g_strdup (pattern) : NULL;
+    info->script = (script && script[0]) ? g_strdup (script) : NULL;
+    info->lang = (lang && lang[0]) ? g_ascii_strdown (lang, -1) : NULL;
+    info->enabled = enabled != 0;
+    return info;
+}
+
+
+static void
+as_info_free (ASInfo *info)
+{
+    if (info)
+    {
+        g_free (info->pattern);
+        g_free (info->script);
+        g_free (info->lang);
+        g_free (info);
+    }
+}
+
+
+static int
+cmp_ints (gunichar *c1, gunichar *c2)
+{
+    return *c1 < *c2 ? -1 : (*c1 > *c2 ? 1 : 0);
+}
+
+
+static void
+append_options (GString  *pattern,
+                ASOptions opts)
+{
+    if (opts & AS_WORD_BOUNDARY)
+        g_string_append (pattern, "\\b");
+    else if (opts & AS_NOT_WORD_BOUNDARY)
+        g_string_append (pattern, "\\B");
+}
+
+static void
+append_escaped (GString    *pattern,
+                const char *string,
+                int         len)
+{
+    if (len < 0)
+        len = strlen (string);
+
+    if (!egg_regex_escape (string, len, pattern))
+        g_string_append_len (pattern, string, len);
+}
+
+
+static ASStringInfo *
+as_string_get_info (const char *string,
+                    ASOptions   start_opts,
+                    ASOptions   end_opts,
+                    guint       string_no)
+{
+    char *wc, *p;
+    guint rev_len;
+    ASStringInfo *info = NULL;
+    char *rev = NULL;
+    GString *pattern = NULL;
+
+    if (!string || !string[0])
+    {
+        g_critical ("%s: empty string", G_STRLOC);
+        goto error;
+    }
+
+    rev = g_utf8_strreverse (string, -1);
+    rev_len = strlen (rev);
+    info = g_new0 (ASStringInfo, 1);
+
+    if (string[0] == AS_WILDCARD && string[1] != AS_WILDCARD)
+    {
+        g_critical ("%s: leading '%c' symbol", G_STRLOC, AS_WILDCARD);
+        goto error;
+    }
+
+    if (rev[0] == AS_WILDCARD && rev[1] != AS_WILDCARD)
+    {
+        g_critical ("%s: trailing '%c' symbol", G_STRLOC, AS_WILDCARD);
+        goto error;
+    }
+
+    info->last_char = g_utf8_get_char (rev);
+    pattern = g_string_sized_new (rev_len + 16);
+
+    if (end_opts)
+        append_options (pattern, end_opts);
+
+    g_string_append_printf (pattern, "(?P<%d>", string_no);
+
+    for (p = rev; *p; )
+    {
+        wc = strchr (p, AS_WILDCARD);
+
+        if (!wc)
+        {
+            append_escaped (pattern, p, -1);
+            break;
+        }
+
+        if (wc[1] == AS_WILDCARD)
+        {
+            append_escaped (pattern, p, wc - p + 1);
+            p = wc + 2;
+        }
+        else
+        {
+            append_escaped (pattern, p, wc - p);
+            g_string_append_printf (pattern, "(?P<%d_%d>.*)",
+                                    string_no, info->n_wildcards);
+            info->n_wildcards++;
+            p = wc + 1;
+        }
+    }
+
+    g_string_append (pattern, ")");
+
+    if (start_opts)
+        append_options (pattern, start_opts);
+
+    info->pattern_len = pattern->len;
+    info->pattern = g_string_free (pattern, FALSE);
+    g_free (rev);
+    return info;
+
+error:
+    as_string_info_free (info);
+    g_free (rev);
+    if (pattern)
+        g_string_free (pattern, TRUE);
+    return NULL;
+}
+
+
+static void
+as_string_info_free (ASStringInfo *info)
+{
+    if (info)
+    {
+        g_free (info->pattern);
+        g_free (info->script);
+        g_free (info);
+    }
+}
+
+
+static void
+as_string_info_set_script (ASStringInfo *info,
+                           const char   *script)
+{
+    g_return_if_fail (info != NULL);
+    g_free (info->script);
+    info->script = g_strdup (script);
+}
+
+
+static ASString *
+as_string_new (guint n_wildcards)
+{
+    ASString *s = g_new0 (ASString, 1);
+
+    if (n_wildcards)
+    {
+        s->n_parens = n_wildcards;
+        s->parens = g_new0 (guint, s->n_parens);
+    }
+
+    return s;
+}
+
+
+static void
+as_string_free (ASString *s)
+{
+    if (s)
+    {
+        g_free (s->parens);
+        g_free (s);
+    }
+}
+
+
+static ASSet*
+as_set_new (ASStringInfo **strings,
+            guint          n_strings)
+{
+    ASSet *set = NULL;
+    GString *pattern;
+    guint i, len;
+    GError *error = NULL;
+    gunichar *last_chars;
+    gunichar min_last = 0, max_last = 0;
+    gboolean has_wildcard = FALSE;
+
+    g_return_val_if_fail (strings != NULL, NULL);
+    g_return_val_if_fail (n_strings != 0, NULL);
+
+    set = g_new0 (ASSet, 1);
+    set->ref_count = 1;
+    set->n_strings = n_strings;
+    set->strings = g_new (ASString*, n_strings);
+
+    len = n_strings + 2; /* (||||) */
+    last_chars = g_new (gunichar, n_strings);
+
+    for (i = 0; i < n_strings; ++i)
+    {
+        last_chars[i] = strings[i]->last_char;
+        min_last = MIN (min_last, last_chars[i]);
+        max_last = MAX (max_last, last_chars[i]);
+
+        len += strings[i]->pattern_len;
+
+        set->strings[i] = as_string_new (strings[i]->n_wildcards);
+
+        if (strings[i]->n_wildcards)
+            has_wildcard = TRUE;
+    }
+
+    set->min_last = min_last;
+    set->max_last = max_last;
+
+    pattern = g_string_sized_new (len);
+
+    for (i = 0; i < n_strings; ++i)
+    {
+        if (i)
+            g_string_append_c (pattern, '|');
+
+        g_string_append_len (pattern, strings[i]->pattern, strings[i]->pattern_len);
+    }
+
+    set->regex = egg_regex_new (pattern->str, EGG_REGEX_ANCHORED, 0, &error);
+    g_print ("pattern: %s\n", pattern->str);
+
+    if (error)
+    {
+        g_critical ("%s: %s", G_STRLOC, error->message);
+        g_error_free (error);
+        goto error;
+    }
+
+    egg_regex_optimize (set->regex, &error);
+
+    if (error)
+    {
+        g_critical ("%s: %s", G_STRLOC, error->message);
+        g_error_free (error);
+    }
+
+    set->scripts = g_new0 (char*, n_strings);
+
+    for (i = 0; i < n_strings; ++i)
+    {
+        const char *script = strings[i]->script;
+
+        if (script && script[0])
+            set->scripts[i] = g_strdup (script);
+    }
+
+    g_qsort_with_data (last_chars, n_strings, sizeof (gunichar),
+                       (GCompareDataFunc) cmp_ints, NULL);
+
+    for (i = 1, len = 1; i < n_strings; ++i)
+        if (last_chars[i] != last_chars[len-1])
+            if (len++ != i)
+                last_chars[len-1] = last_chars[i];
+
+    set->last_chars = g_memdup (last_chars, len * sizeof (gunichar));
+    set->n_last_chars = len;
+
+    g_print ("last chars:");
+    for (i = 0; i < set->n_last_chars; ++i)
+        g_print (" %c", set->last_chars[i]);
+    g_print ("\n");
+
+    for (i = 0; i < n_strings; ++i)
+    {
+        int no;
+        guint j, n_parens;
+        char name[32];
+
+        g_snprintf (name, 32, "%d", i);
+        no = egg_regex_get_string_number (set->regex, name);
+        g_assert (no > 0);
+
+        set->strings[i]->whole = no;
+
+        n_parens = set->strings[i]->n_parens;
+        /* they are inverted */
+        for (j = 0; j < n_parens; ++j)
+        {
+            g_snprintf (name, 32, "%d_%d", i, j);
+            no = egg_regex_get_string_number (set->regex, name);
+            g_assert (no > 0);
+            set->strings[i]->parens[n_parens - j - 1] = no;
+        }
+    }
+
+    g_free (last_chars);
+    g_string_free (pattern, TRUE);
+
+    return set;
+
+error:
+    g_free (last_chars);
+    g_string_free (pattern, TRUE);
+    as_set_unref (set);
+    return NULL;
+}
+
+
+static void
+as_match_destroy (ASMatch *match)
+{
+    if (match->n_parens)
+        g_free (match->parens);
+}
+
+
+static gboolean
+as_set_match (ASSet      *set,
+              const char *text,
+              ASMatch    *match)
+{
+    char *reversed;
+    int start_pos, end_pos;
+    gboolean found = FALSE;
+    guint i;
+
+    g_return_val_if_fail (text != NULL, -1);
+
+    reversed = g_utf8_strreverse (text, -1);
+    egg_regex_clear (set->regex);
+
+    if (egg_regex_match (set->regex, reversed, -1, 0) <= 0)
+        goto out;
+
+    for (i = 0; i < set->n_strings; ++i)
+    {
+        egg_regex_fetch_pos (set->regex, reversed,
+                             set->strings[i]->whole,
+                             &start_pos, &end_pos);
+
+        if (start_pos >= 0)
+        {
+            g_assert (start_pos == 0);
+            found = TRUE;
+            break;
+        }
+    }
+
+    if (found)
+    {
+        match->string_no = i;
+        match->start_offset = 0;
+        match->end_offset = g_utf8_pointer_to_offset (reversed, reversed + end_pos);
+
+        if (set->strings[i]->n_parens)
+        {
+            guint j;
+
+            match->n_parens = set->strings[i]->n_parens;
+            match->parens = g_new (guint, 2*match->n_parens);
+
+            for (j = 0; j < match->n_parens; ++j)
+            {
+                egg_regex_fetch_pos (set->regex, reversed,
+                                     set->strings[i]->parens[j],
+                                     &start_pos, &end_pos);
+                g_assert (start_pos >= 0);
+                g_assert (end_pos >= 0);
+
+                match->parens[2*j] =
+                        g_utf8_pointer_to_offset (reversed, reversed + start_pos);
+                match->parens[2*j + 1] = match->parens[2*j] +
+                        g_utf8_pointer_to_offset (reversed + start_pos, reversed + end_pos);
+            }
+        }
+        else
+        {
+            match->n_parens = 0;
+            match->parens = NULL;
+        }
+    }
+
+out:
+    g_free (reversed);
+    return found;
+}
+
+
+static gboolean
+as_set_check_last (ASSet    *set,
+                   gunichar  c)
+{
+    guint i;
+
+    if (c < set->min_last || c > set->max_last)
+        return FALSE;
+
+    for (i = 0; i < set->n_last_chars; ++i)
+        if (c == set->last_chars[i])
+            return TRUE;
+
+    return FALSE;
+}
+
+
+static void
+as_set_unref (ASSet *set)
+{
+    if (set && !--set->ref_count)
+    {
+        guint i;
+
+        for (i = 0; i < set->n_strings; ++i)
+        {
+            as_string_free (set->strings[i]);
+            g_free (set->scripts[i]);
+        }
+
+        g_free (set->strings);
+        g_free (set->scripts);
+        egg_regex_unref (set->regex);
+        g_free (set->last_chars);
+        g_free (set);
+    }
+}
+
+
+static ASSet *as_set_ref (ASSet *set)
+{
+    g_return_val_if_fail (set != NULL, NULL);
+    set->ref_count++;
+    return set;
+}
+
+
+static void
+as_string_info_array_free (GPtrArray *ar)
+{
+    g_ptr_array_foreach (ar, (GFunc) as_string_info_free, NULL);
+    g_ptr_array_free (ar, TRUE);
+}
+
+
+static void
+add_lang_sets (const char *lang,
+               GPtrArray  *strings,
+               ASPlugin   *plugin)
+{
+    ASSet *set;
+
+    g_return_if_fail (strings && strings->len);
+
+    set = as_set_new ((ASStringInfo**) strings->pdata, strings->len);
+
+    if (set)
+        g_hash_table_insert (plugin->lang_sets, g_strdup (lang), set);
+}
+
+
+static gboolean
+is_nonblank_string (const char *string)
+{
+    if (!string)
+        return FALSE;
+
+    while (*string)
+    {
+        if (!g_ascii_isspace (*string))
+            return TRUE;
+        string++;
+    }
+
+    return FALSE;
+}
+
+
+static void
+as_plugin_load_info (ASPlugin *plugin,
+                     GSList   *list)
+{
+    GPtrArray *any_lang;
+    GHashTable *lang_strings;
+    GSList *l;
+
+    if (!list)
+        return;
+
+    any_lang = g_ptr_array_new ();
+    lang_strings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+                                          (GDestroyNotify) as_string_info_array_free);
+
+    for (l = list; l != NULL; l = l->next)
+    {
+        GPtrArray *ar;
+        ASStringInfo *sinfo;
+        ASInfo *info = l->data;
+
+        if (info->lang)
+        {
+            ar = g_hash_table_lookup (lang_strings, info->lang);
+
+            if (!ar)
+            {
+                ar = g_ptr_array_new ();
+                g_hash_table_insert (lang_strings, g_strdup (info->lang), ar);
+            }
+        }
+        else
+        {
+            ar = any_lang;
+        }
+
+        sinfo = as_string_get_info (info->pattern, 0, 0, ar->len);
+
+        if (!sinfo)
+        {
+            g_warning ("%s: invalid pattern '%s'", G_STRLOC, info->pattern);
+            continue;
+        }
+
+        if (is_nonblank_string (info->script))
+        {
+            MSNode *script = ms_script_parse (info->script);
+
+            if (script)
+            {
+                as_string_info_set_script (sinfo, info->script);
+                g_object_unref (script);
+            }
+        }
+
+        g_ptr_array_add (ar, sinfo);
+    }
+
+    if (any_lang->len)
+        plugin->any_lang = as_set_new ((ASStringInfo**) any_lang->pdata,
+                                        any_lang->len);
+
+    g_hash_table_foreach (lang_strings, (GHFunc) add_lang_sets, plugin);
+
+    as_string_info_array_free (any_lang);
+    g_hash_table_destroy (lang_strings);
+}
+
+
+static void
+append_item (const char   *pattern,
+             const char   *script,
+             const char   *lang,
+             gboolean      enabled,
+             GSList      **list)
+{
+    ASInfo *info;
+
+    info = as_info_new (pattern, script, lang, enabled);
+
+    if (info)
+        *list = g_slist_prepend (*list, info);
+}
+
+static void
+as_plugin_load (ASPlugin *plugin)
+{
+    GSList *info = NULL;
+
+    _as_plugin_load (MOO_PLUGIN (plugin),
+                     (ASLoadFunc) append_item,
+                     &info);
+    info = g_slist_reverse (info);
+
+    if (info)
+        as_plugin_load_info (plugin, info);
+
+    g_slist_foreach (info, (GFunc) as_info_free, NULL);
+    g_slist_free (info);
+}
+
+
+static gboolean
+as_plugin_init (ASPlugin *plugin)
+{
+    plugin->lang_sets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+                                               (GDestroyNotify) as_set_unref);
+    plugin->ctx = _as_plugin_context_new ();
+