medit/moo/mooedit/moocompletionsimple.c

704 lines
21 KiB
C

/*
* moocompletionsimple.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 "mooedit/moocompletionsimple.h"
#include "mooedit/mootextpopup.h"
#include "mooedit/mooedit-script.h"
#include "mooutils/moomarshals.h"
#include "mooutils/eggregex.h"
#include "mooscript/mooscript-parser.h"
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <string.h>
#define MOO_COMPLETION_VAR_MATCH "match"
#define MOO_COMPLETION_VAR_COMPLETION "completion"
enum {
COLUMN_DATA,
COLUMN_GROUP
};
/* same as GCompletionFunc - must not allocate a new string;
must allow two simultaneous calls */
typedef char* (*MooCompletionStringFunc) (gpointer data);
typedef void (*MooCompletionFreeFunc) (gpointer data);
typedef int (*MooCompletionCmpFunc) (gpointer data1,
gpointer data2);
struct _MooCompletionSimplePrivate {
GSList *groups;
GSList *active_groups;
gboolean groups_found;
GList *data;
MooCompletionStringFunc string_func;
MooCompletionFreeFunc free_func;
MooCompletionCmpFunc cmp_func;
};
struct _MooCompletionGroup {
char *name;
EggRegex *regex;
guint *parens;
guint n_parens;
GCompletion *cmpl;
GList *data;
char *suffix;
MSNode *script;
MooCompletionFreeFunc free_func;
};
static void moo_completion_simple_dispose (GObject *object);
static void moo_completion_simple_populate (MooTextCompletion *cmpl,
GtkTreeModel *model,
GtkTextIter *cursor,
const char *text);
static void moo_completion_simple_complete (MooTextCompletion *cmpl,
GtkTreeModel *model,
GtkTreeIter *iter);
static int list_sort_func (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
MooCompletionSimple *cmpl);
static char *completion_text_func (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer data);
static MooCompletionSimple *moo_completion_simple_new (MooCompletionStringFunc string_func,
MooCompletionFreeFunc free_func,
MooCompletionCmpFunc cmp_func);
static MooCompletionGroup *moo_completion_group_new (const char *name,
MooCompletionStringFunc string_func,
MooCompletionFreeFunc free_func);
static void moo_completion_group_free (MooCompletionGroup *group);
static gboolean moo_completion_group_find (MooCompletionGroup *group,
const char *line,
int *start_pos,
int *end_pos);
static GList *moo_completion_group_complete (MooCompletionGroup *group,
const char *text,
char **prefix);
static void moo_completion_simple_finish (MooTextCompletion *cmpl);
G_DEFINE_TYPE (MooCompletionSimple, moo_completion_simple, MOO_TYPE_TEXT_COMPLETION)
static void
moo_completion_simple_dispose (GObject *object)
{
MooCompletionSimple *cmpl = MOO_COMPLETION_SIMPLE (object);
if (cmpl->priv)
{
g_slist_foreach (cmpl->priv->groups, (GFunc) moo_completion_group_free, NULL);
g_slist_free (cmpl->priv->groups);
g_slist_free (cmpl->priv->active_groups);
g_free (cmpl->priv);
cmpl->priv = NULL;
}
G_OBJECT_CLASS(moo_completion_simple_parent_class)->dispose (object);
}
static void
moo_completion_simple_class_init (MooCompletionSimpleClass *klass)
{
MooTextCompletionClass *cmpl_clas = MOO_TEXT_COMPLETION_CLASS(klass);
G_OBJECT_CLASS(klass)->dispose = moo_completion_simple_dispose;
cmpl_clas->populate = moo_completion_simple_populate;
cmpl_clas->finish = moo_completion_simple_finish;
cmpl_clas->complete = moo_completion_simple_complete;
}
static void
moo_completion_simple_init (MooCompletionSimple *cmpl)
{
GtkListStore *store;
cmpl->priv = g_new0 (MooCompletionSimplePrivate, 1);
store = gtk_list_store_new (2, G_TYPE_POINTER, G_TYPE_POINTER);
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (store),
COLUMN_DATA,
(GtkTreeIterCompareFunc) list_sort_func,
cmpl, NULL);
moo_text_completion_set_model (MOO_TEXT_COMPLETION (cmpl),
GTK_TREE_MODEL (store));
moo_text_completion_set_text_func (MOO_TEXT_COMPLETION (cmpl),
completion_text_func, cmpl,
NULL);
g_object_unref (store);
}
static GtkTextBuffer *
get_buffer (MooCompletionSimple *cmpl)
{
return moo_text_completion_get_buffer (MOO_TEXT_COMPLETION (cmpl));
}
static char *
moo_completion_simple_find_groups (MooCompletionSimple *cmpl,
GtkTextIter *cursor,
const char *line)
{
GSList *l;
gboolean found = FALSE;
int start_pos = -1, end_pos = -1;
char *text = NULL;
for (l = cmpl->priv->groups; l != NULL; l = l->next)
{
int start_pos_here, end_pos_here;
MooCompletionGroup *grp = l->data;
if (!moo_completion_group_find (grp, line, &start_pos_here, &end_pos_here))
continue;
if (!found)
{
found = TRUE;
start_pos = start_pos_here;
end_pos = end_pos_here;
}
if (start_pos_here == start_pos && end_pos_here == end_pos)
cmpl->priv->active_groups =
g_slist_prepend (cmpl->priv->active_groups, grp);
}
if (found)
{
GtkTextIter start, end;
cmpl->priv->active_groups = g_slist_reverse (cmpl->priv->active_groups);
start = end = *cursor;
gtk_text_iter_set_line_index (&start, start_pos);
gtk_text_iter_set_line_index (&end, end_pos);
text = gtk_text_iter_get_slice (&start, &end);
moo_text_completion_set_region (MOO_TEXT_COMPLETION (cmpl), &start, &end);
}
return text;
}
static int
list_sort_func (GtkTreeModel *model,
GtkTreeIter *a,
GtkTreeIter *b,
MooCompletionSimple *cmpl)
{
gpointer data1, data2;
g_assert (MOO_IS_COMPLETION_SIMPLE (cmpl));
g_assert (cmpl->priv->cmp_func != NULL);
gtk_tree_model_get (model, a, COLUMN_DATA, &data1, -1);
gtk_tree_model_get (model, b, COLUMN_DATA, &data2, -1);
return cmpl->priv->cmp_func (data1, data2);
}
static void
moo_completion_simple_exec_script (MooCompletionSimple *cmpl,
MooCompletionGroup *group,
GtkTextIter *start,
GtkTextIter *end,
const char *completion)
{
MSContext *ctx;
char *match;
MSValue *result;
GtkTextView *doc;
doc = moo_text_completion_get_doc (MOO_TEXT_COMPLETION (cmpl));
ctx = moo_edit_script_context_new (doc, NULL);
match = gtk_text_iter_get_slice (start, end);
ms_context_assign_string (ctx, MOO_COMPLETION_VAR_MATCH, match);
ms_context_assign_string (ctx, MOO_COMPLETION_VAR_COMPLETION, completion);
gtk_text_buffer_delete (get_buffer (cmpl), start, end);
gtk_text_buffer_place_cursor (get_buffer (cmpl), start);
result = ms_top_node_eval (group->script, ctx);
if (result)
ms_value_unref (result);
else
g_warning ("%s: %s", G_STRLOC,
ms_context_get_error_msg (ctx));
g_free (match);
g_object_unref (ctx);
}
static void
moo_completion_simple_complete (MooTextCompletion *text_cmpl,
GtkTreeModel *model,
GtkTreeIter *iter)
{
char *text, *old_text;
GtkTextIter start, end;
gpointer data = NULL;
MooCompletionGroup *group = NULL;
gboolean set_cursor = FALSE;
MooCompletionSimple *cmpl = MOO_COMPLETION_SIMPLE (text_cmpl);
g_return_if_fail (cmpl->priv->active_groups != NULL);
gtk_tree_model_get (model, iter, COLUMN_DATA, &data, COLUMN_GROUP, &group, -1);
g_assert (group != NULL);
text = cmpl->priv->string_func ? cmpl->priv->string_func (data) : data;
g_return_if_fail (text != NULL);
moo_text_completion_get_region (MOO_TEXT_COMPLETION (cmpl), &start, &end);
old_text = gtk_text_iter_get_slice (&start, &end);
gtk_text_buffer_begin_user_action (get_buffer (cmpl));
if (group->script)
{
moo_completion_simple_exec_script (cmpl, group, &start, &end, text);
}
else
{
if (strcmp (text, old_text))
{
gtk_text_buffer_delete (get_buffer (cmpl), &start, &end);
gtk_text_buffer_insert (get_buffer (cmpl), &start, text, -1);
set_cursor = TRUE;
}
if (group->suffix)
{
gboolean do_insert = TRUE;
if (!gtk_text_iter_ends_line (&start))
{
char *old_suffix;
end = start;
gtk_text_iter_forward_to_line_end (&end);
old_suffix = gtk_text_iter_get_slice (&start, &end);
if (!strncmp (group->suffix, old_suffix, strlen (group->suffix)))
{
do_insert = FALSE;
gtk_text_iter_forward_chars (&start, g_utf8_strlen (group->suffix, -1));
set_cursor = TRUE;
}
g_free (old_suffix);
}
if (do_insert)
{
gtk_text_buffer_insert (get_buffer (cmpl), &start, group->suffix, -1);
set_cursor = TRUE;
}
}
}
if (set_cursor)
gtk_text_buffer_place_cursor (get_buffer (cmpl), &start);
gtk_text_buffer_end_user_action (get_buffer (cmpl));
moo_text_completion_hide (MOO_TEXT_COMPLETION (cmpl));
g_free (old_text);
}
static void
moo_completion_simple_populate (MooTextCompletion *text_cmpl,
GtkTreeModel *model,
GtkTextIter *cursor,
const char *text)
{
GSList *l;
const char *prefix = text;
char *freeme = NULL;
MooCompletionSimple *cmpl = MOO_COMPLETION_SIMPLE (text_cmpl);
if (!cmpl->priv->groups_found)
{
freeme = moo_completion_simple_find_groups (cmpl, cursor, text);
if (!freeme)
return;
cmpl->priv->groups_found = TRUE;
prefix = freeme;
}
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID,
GTK_SORT_ASCENDING);
for (l = cmpl->priv->active_groups; l != NULL; l = l->next)
{
GList *list;
MooCompletionGroup *group;
group = l->data;
list = moo_completion_group_complete (group, prefix, NULL);
while (list)
{
GtkTreeIter iter;
gtk_list_store_append (GTK_LIST_STORE (model), &iter);
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
COLUMN_DATA, list->data,
COLUMN_GROUP, group,
-1);
list = list->next;
}
}
if (cmpl->priv->cmp_func)
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
COLUMN_DATA, GTK_SORT_ASCENDING);
g_free (freeme);
}
static void
moo_completion_simple_finish (MooTextCompletion *text_cmpl)
{
MooCompletionSimple *cmpl = MOO_COMPLETION_SIMPLE (text_cmpl);
g_slist_free (cmpl->priv->active_groups);
cmpl->priv->active_groups = NULL;
cmpl->priv->groups_found = FALSE;
}
static MooCompletionSimple *
moo_completion_simple_new (MooCompletionStringFunc string_func,
MooCompletionFreeFunc free_func,
MooCompletionCmpFunc cmp_func)
{
MooCompletionSimple *cmpl = g_object_new (MOO_TYPE_COMPLETION_SIMPLE, NULL);
cmpl->priv->string_func = string_func;
cmpl->priv->free_func = free_func;
cmpl->priv->cmp_func = cmp_func;
return cmpl;
}
static void
text_cell_data_func (G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
GtkCellRenderer *cell,
GtkTreeModel *tree_model,
GtkTreeIter *iter,
MooCompletionSimple *cmpl)
{
gpointer data = NULL;
char *text;
g_assert (MOO_IS_COMPLETION_SIMPLE (cmpl));
gtk_tree_model_get (tree_model, iter, 0, &data, -1);
text = cmpl->priv->string_func ? cmpl->priv->string_func (data) : data;
g_return_if_fail (text != NULL);
g_object_set (cell, "text", text, NULL);
}
static char *
completion_text_func (GtkTreeModel *model,
GtkTreeIter *iter,
gpointer user_data)
{
MooCompletionSimple *cmpl = user_data;
gpointer data = NULL;
char *text;
gtk_tree_model_get (model, iter, 0, &data, -1);
text = cmpl->priv->string_func ? cmpl->priv->string_func (data) : data;
g_return_val_if_fail (text != NULL, NULL);
return g_strdup (text);
}
MooTextCompletion *
moo_completion_simple_new_text (GList *words)
{
MooCompletionSimple *cmpl;
GtkCellRenderer *cell;
MooTextPopup *popup;
cmpl = moo_completion_simple_new (NULL, g_free, (MooCompletionCmpFunc) strcmp);
if (words)
{
MooCompletionGroup *group = moo_completion_simple_new_group (cmpl, NULL);
moo_completion_group_add_data (group, words);
moo_completion_group_set_pattern (group, "\\w*", NULL, 0);
}
popup = moo_text_completion_get_popup (MOO_TEXT_COMPLETION (cmpl));
cell = gtk_cell_renderer_text_new ();
gtk_cell_layout_clear (GTK_CELL_LAYOUT (popup->column));
gtk_tree_view_column_pack_start (popup->column, cell, TRUE);
gtk_tree_view_column_set_cell_data_func (popup->column, cell,
(GtkTreeCellDataFunc) text_cell_data_func,
g_object_ref (cmpl),
g_object_unref);
return MOO_TEXT_COMPLETION (cmpl);
}
MooCompletionGroup *
moo_completion_simple_new_group (MooCompletionSimple *cmpl,
const char *name)
{
MooCompletionGroup *group;
g_return_val_if_fail (MOO_IS_COMPLETION_SIMPLE (cmpl), NULL);
group = moo_completion_group_new (name,
cmpl->priv->string_func,
cmpl->priv->free_func);
cmpl->priv->groups = g_slist_append (cmpl->priv->groups, group);
return group;
}
/****************************************************************************/
/* MooCompletionGroup
*/
MooCompletionGroup *
moo_completion_group_new (const char *name,
MooCompletionStringFunc string_func,
MooCompletionFreeFunc free_func)
{
MooCompletionGroup *group = g_new0 (MooCompletionGroup, 1);
group->cmpl = g_completion_new (string_func);
group->free_func = free_func;
group->name = g_strdup (name);
return group;
}
void
moo_completion_group_add_data (MooCompletionGroup *group,
GList *data)
{
g_return_if_fail (group != NULL);
if (!data)
return;
g_completion_clear_items (group->cmpl);
group->data = g_list_concat (group->data, data);
g_completion_add_items (group->cmpl, group->data);
}
static gboolean
moo_completion_group_find (MooCompletionGroup *group,
const char *line,
int *start_pos_p,
int *end_pos_p)
{
g_return_val_if_fail (group != NULL, FALSE);
g_return_val_if_fail (line != NULL, FALSE);
g_return_val_if_fail (group->regex != NULL, FALSE);
egg_regex_clear (group->regex);
if (egg_regex_match (group->regex, line, 0) >= 1)
{
guint i;
for (i = 0; i < group->n_parens; ++i)
{
int start_pos = -1, end_pos = -1;
egg_regex_fetch_pos (group->regex, group->parens[i],
&start_pos, &end_pos);
if (start_pos >= 0 && end_pos >= 0)
{
if (start_pos_p)
*start_pos_p = start_pos;
if (end_pos_p)
*end_pos_p = end_pos;
return TRUE;
}
}
}
return FALSE;
}
static GList *
moo_completion_group_complete (MooCompletionGroup *group,
const char *text,
char **prefix)
{
char *dummy = NULL;
GList *list;
if (!prefix)
prefix = &dummy;
/* g_completion_complete_utf8 wants prefix != NULL */
list = g_completion_complete_utf8 (group->cmpl, text, prefix);
g_free (dummy);
return list;
}
void
moo_completion_group_set_pattern (MooCompletionGroup *group,
const char *pattern,
const guint *parens,
guint n_parens)
{
EggRegex *regex;
GError *error = NULL;
char *real_pattern;
g_return_if_fail (group != NULL);
g_return_if_fail (pattern && pattern[0]);
g_return_if_fail (!parens || n_parens);
real_pattern = g_strdup_printf ("%s$", pattern);
regex = egg_regex_new (real_pattern, 0, 0, &error);
if (!regex)
{
g_warning ("%s: %s", G_STRLOC, error->message);
goto err;
}
egg_regex_optimize (regex, &error);
if (error)
{
g_warning ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
}
egg_regex_free (group->regex);
group->regex = regex;
g_free (group->parens);
if (!parens)
{
group->parens = g_new0 (guint, 1);
group->n_parens = 1;
}
else
{
group->parens = g_memdup (parens, n_parens * sizeof (guint));
group->n_parens = n_parens;
}
g_free (real_pattern);
return;
err:
if (error)
g_error_free (error);
g_free (real_pattern);
egg_regex_free (regex);
}
void
moo_completion_group_set_suffix (MooCompletionGroup *group,
const char *suffix)
{
g_return_if_fail (group != NULL);
if (group->suffix != suffix)
{
g_free (group->suffix);
group->suffix = (suffix && suffix[0]) ? g_strdup (suffix) : NULL;
}
}
void
moo_completion_group_set_script (MooCompletionGroup *group,
const char *script)
{
g_return_if_fail (group != NULL);
if (group->script)
{
ms_node_unref (group->script);
group->script = NULL;
}
if (script)
group->script = ms_script_parse (script);
}
static void
moo_completion_group_free (MooCompletionGroup *group)
{
g_return_if_fail (group != NULL);
egg_regex_free (group->regex);
g_free (group->parens);
g_completion_free (group->cmpl);
g_free (group->suffix);
g_free (group->name);
if (group->script)
ms_node_unref (group->script);
if (group->free_func)
g_list_foreach (group->data, (GFunc) group->free_func, NULL);
g_list_free (group->data);
g_free (group);
}