On Wed, 8 Sep 2010 23:47:28 +0400%
Eugene Arshinov <[email protected]> wrote:
> Hi
>
> On Wed, 8 Sep 2010 16:14:07 +0100%
> Nick Treleaven <[email protected]> wrote:
>
> > On Sun, 29 Aug 2010 21:59:32 +0400
> > Eugene Arshinov <[email protected]> wrote:
> >
> > > > > >> >> If we do want snippet keybindings perhaps it would be
> > > > > >> >> better to integrate with the existing keybindings rather
> > > > > >> >> than storing them separately - then users could
> > > > > >> >> configure them in the normal way.
> >
> > > > Here is the new patch. It seems to be not so simple and clean
> > > > as the previous one, but now keybindings are edited in
> > > > Preferences dialog as (I believe) most users prefer.
> > > >
> > > > Unfortunately, keybindings_set_item was not enough for me to
> > > > implement this. For example, I had to extend GeanyKeyGroup
> > > > struct so that I have a way to tweak loading from / saving to
> > > > keybindings.conf. Anyway, any suggestions about how to improve
> > > > the patch are welcome.
> >
> > OK, I didn't explain before what I had in mind but I think this
> > patch is too complex.
> >
> > What I think we could accept is setting up the snippet keybinding
> > group size and items (like a plugin) when snippets.conf is read,
> > using any defaults found in the file. I don't think we should write
> > to snippets.conf. If the user wants to set the keybinding in
> > snippets.conf, then they must remember that it won't be kept in sync
> > with keybindings.conf. This should make the code simpler.
> >
>
> No, I didn't mean to read/save keybindings in snippets.conf. It is
> very strange that my patch does it, maybe I forgot to remove
> something from the code :) I'll recheck the patch, probably tomorrow.
OK, it does not read keybindings from snippets.conf, only snippet names.
Maybe function names are confusing: if you look at
load_snippet_keybindings_from_snippets, you may think it really loads
keybindings, but it doesn't :) Instead it reads just snippet names and
accordingly updates list of snippet keybindings available in
Preferences.
>
> The patch is complex because the list of snippets is somewhat used in
> two places: snippets.conf and keybindings.conf (now containing snippet
> keybindings). When one of each files is [re]loaded, the list of
> keybindings in Preferences should be updated. Maybe I used too
> complex logic for handling that…
>
> > > > Currently there are two little problems
> > > >
> > > > - All underscores from keybinding names are removed (I wonder
> > > > why), so names of some "special" snippets are displayed
> > > > incorrectly (**)
> >
> > > > ** and users are allowed to assign keybindings for "special"
> > > > snippets
> >
> > This is so a menu item string with underscores for mnemonics can be
> > reused without adding a translation string but without displaying
> > the underscore mnemonics for the keybindings dialog.
> >
> > This could be fixed by adding a keygroup field 'translatable', on by
> > default.
>
> OK, I will implement it after we decide what to do with the
> overcomplexity mentioned above.
>
> >
> > > > - Editing of keybindings in the Preferences dialog isn't
> > > > "caught" completely by Geany. For example, if I remove a
> > > > keybinding using click - BackSpace - Enter and press OK, Geany
> > > > thinks I edited nothing and does not update keybindings.conf.
> > > > Though, as expected, the removed keybinding no longer
> > > > functions.
> >
> > I can't reproduce this as described.
> >
>
> I'll try to reproduce it with trunk.
>
Works fine :) Probably my mistake.
> > > > I finished this patch today, so I hadn't much time for testing.
> > > > As for now, everything except the two issues above seems to work
> > > > fine.
> > > >
> > > > Best regards,
> > > > Eugene.
> > >
> > > Sorry, that patch is a little outdated. Here is the last
> > > version.
> > >
I attach slightly updated version, with 2 code comments updated and 1
unneeded function parameter removed.
Best regards,
Eugene.
diff --git a/src/editor.c b/src/editor.c
index 052d6c7..1463a6e 100644
--- a/src/editor.c
+++ b/src/editor.c
@@ -37,6 +37,7 @@
#include <ctype.h>
+#include <stdlib.h>
#include <string.h>
#include <gdk/gdkkeysyms.h>
@@ -105,6 +106,17 @@ static enum
static gchar indent[100];
+/* snippet keybindings */
+
+typedef struct
+{
+ gchar *name;
+ guint key;
+ GdkModifierType mods;
+} SnippetKeybinding;
+
+static GeanyKeyGroup *kb_group;
+
static void on_new_line_added(GeanyEditor *editor);
static gboolean handle_xml(GeanyEditor *editor, gint pos, gchar ch);
@@ -120,6 +132,15 @@ static void read_current_word(GeanyEditor *editor, gint pos, gchar *word, size_t
const gchar *wc, gboolean stem);
static gsize count_indent_size(GeanyEditor *editor, const gchar *base_indent);
+/* snippet keybindings */
+static void create_snippet_keybindings_group(guint count);
+static void save_snippet_keybindings(GKeyFile *config, const gchar *section, GeanyKeyGroup *group);
+static void load_snippet_keybindings_from_snippets(void);
+static void load_snippet_keybindings_from_keyfile(GKeyFile *config, const gchar *section,
+ GeanyKeyGroup *group);
+static void merge_snippet_keybindings(SnippetKeybinding *kbs, gsize len, gboolean from_snippets);
+static gboolean activate_snippet(guint key_id);
+
void editor_snippets_free(void)
{
@@ -134,7 +155,6 @@ void editor_snippets_init(void)
gchar *sysconfigfile, *userconfigfile;
gchar **groups_user, **groups_sys;
gchar **keys_user, **keys_sys;
- gchar *value;
GKeyFile *sysconfig = g_key_file_new();
GKeyFile *userconfig = g_key_file_new();
GHashTable *tmp;
@@ -162,15 +182,15 @@ void editor_snippets_init(void)
for (i = 0; i < len; i++)
{
keys_sys = g_key_file_get_keys(sysconfig, groups_sys[i], &len_keys, NULL);
+
/* create new hash table for the read section (=> filetype) */
tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
g_hash_table_insert(snippet_hash, g_strdup(groups_sys[i]), tmp);
for (j = 0; j < len_keys; j++)
- {
g_hash_table_insert(tmp, g_strdup(keys_sys[j]),
- utils_get_setting_string(sysconfig, groups_sys[i], keys_sys[j], ""));
- }
+ g_key_file_get_string(sysconfig, groups_sys[i], keys_sys[j], NULL));
+
g_strfreev(keys_sys);
}
@@ -186,23 +206,16 @@ void editor_snippets_init(void)
tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
g_hash_table_insert(snippet_hash, g_strdup(groups_user[i]), tmp);
}
+
for (j = 0; j < len_keys; j++)
- {
- value = g_hash_table_lookup(tmp, keys_user[j]);
- if (value == NULL)
- { /* value = NULL means the key doesn't yet exist, so insert */
- g_hash_table_insert(tmp, g_strdup(keys_user[j]),
- utils_get_setting_string(userconfig, groups_user[i], keys_user[j], ""));
- }
- else
- { /* old key and value will be freed by destroy function (g_free) */
- g_hash_table_replace(tmp, g_strdup(keys_user[j]),
- utils_get_setting_string(userconfig, groups_user[i], keys_user[j], ""));
- }
- }
+ g_hash_table_insert(tmp, g_strdup(keys_user[j]),
+ g_key_file_get_string(userconfig, groups_user[i], keys_user[j], NULL));
+
g_strfreev(keys_user);
}
+ load_snippet_keybindings_from_snippets();
+
g_free(sysconfigfile);
g_free(userconfigfile);
g_strfreev(groups_sys);
@@ -5093,3 +5106,263 @@ void editor_indent(GeanyEditor *editor, gboolean increase)
sci_set_current_line(sci, lstart);
}
}
+
+/* Snippet keybindings */
+
+static void create_snippet_keybindings_group(guint count)
+{
+ kb_group = keybindings_set_group(NULL, "snippets", _("Snippets"), count, activate_snippet);
+ kb_group->load = load_snippet_keybindings_from_keyfile;
+ kb_group->save = save_snippet_keybindings;
+}
+
+
+/* Save snippet keybindings to keybindings.conf omitting empty records */
+static void save_snippet_keybindings(GKeyFile *config, const gchar *section, GeanyKeyGroup *group)
+{
+ gsize i;
+ gchar *accel;
+ GeanyKeyBinding *kb;
+
+ /* clear */
+ g_key_file_remove_group(config, section, NULL);
+
+ /* write */
+ for (i = 0; i < kb_group->count; i++)
+ {
+ kb = &kb_group->keys[i];
+ if (kb->key == 0 && kb->mods == 0)
+ continue;
+
+ accel = gtk_accelerator_name(kb->key, kb->mods);
+ g_key_file_set_string(config, section, kb->name, accel);
+ g_free(accel);
+ }
+}
+
+
+/* Create snippet keybindings for snippets in `snippet_hash' loaded from snippets.conf */
+static void load_snippet_keybindings_from_snippets(void)
+{
+ GArray *kbs;
+ GList *list, *names;
+
+ kbs = g_array_sized_new(FALSE, FALSE, sizeof(SnippetKeybinding), 20);
+ for (list = g_hash_table_get_values(snippet_hash); list; list = list->next)
+ for (names = g_hash_table_get_keys(list->data); names; names = names->next)
+ {
+ SnippetKeybinding kb = { names->data, 0, 0 };
+ g_array_append_val(kbs, kb);
+ }
+ merge_snippet_keybindings((SnippetKeybinding *)kbs->data, kbs->len, TRUE);
+ g_array_free(kbs, TRUE);
+}
+
+
+/* Load snippet keybindings from keybindings.conf */
+static void load_snippet_keybindings_from_keyfile(GKeyFile *config, const gchar *section,
+ GeanyKeyGroup *group)
+{
+ gchar **keys;
+ gchar *accel;
+ gsize len, i;
+ SnippetKeybinding *kbs;
+
+ keys = g_key_file_get_keys(config, section, &len, NULL);
+ if( keys )
+ {
+ kbs = g_new(SnippetKeybinding, len);
+ for (i = 0; i < len; i++)
+ {
+ kbs[i].name = keys[i];
+ accel = g_key_file_get_value(config, section, keys[i], NULL);
+ gtk_accelerator_parse(accel, &kbs[i].key, &kbs[i].mods);
+ g_free(accel);
+ }
+ merge_snippet_keybindings(kbs, len, FALSE);
+ g_free(kbs);
+ g_strfreev(keys);
+ }
+}
+
+
+static int cmp_snippet_keybindings(const void *first, const void *second)
+{
+ gchar *first_name = ((const SnippetKeybinding *)first)->name;
+ gchar *second_name = ((const SnippetKeybinding *)second)->name;
+
+ return strcmp(first_name, second_name);
+}
+
+
+/* Helper function for merge_snippet_keybindings().
+ * Handles the case when a new keybinding should be added to kb_group->keys */
+static void handle_new(const SnippetKeybinding *kb, gsize *i, gboolean from_snippets)
+{
+ gchar *label = NULL;
+
+ if (!from_snippets)
+ label = g_strconcat(kb->name, _(" (missing in snippets.conf)"), NULL);
+
+ keybindings_set_item(kb_group, (*i)++, NULL, kb->key, kb->mods, kb->name,
+ label ? label : kb->name, NULL);
+
+ g_free(label);
+}
+
+
+/* Helper function for merge_snippet_keybindings().
+ * Handles the case when a keybinding existed in kb_group->keys before merge and is still present */
+static void handle_updated(const GeanyKeyBinding *old, const SnippetKeybinding *kb, gsize *i)
+{
+ guint key = kb->key;
+ GdkModifierType mods = kb->mods;
+
+ if (key == 0 && mods == 0)
+ {
+ key = old->key;
+ mods = old->mods;
+ }
+
+ keybindings_set_item(kb_group, (*i)++, NULL, key, mods, old->name, old->name, NULL);
+}
+
+
+/* Helper function for merge_snippet_keybindings().
+ * Handles the case when a keybinding existed in kb_group->keys before merge and now is absent */
+static void handle_removed(const GeanyKeyBinding *kb, gsize *i, gboolean from_snippets)
+{
+ gchar *label = NULL;
+
+ if (from_snippets)
+ {
+ if (kb->key == 0 && kb->mods == 0)
+ return;
+ else
+ label = g_strconcat(kb->name, _(" (missing in snippets.conf)"), NULL);
+ }
+
+ keybindings_set_item(kb_group, (*i)++, NULL, kb->key, kb->mods, kb->name,
+ label ? label : kb->name, NULL);
+
+ g_free(label);
+}
+
+
+/* Update keybindings in kb_group->keys
+ *
+ * @param kbs - array of SnippetKeybinding's
+ * @param len - number of elements in `kbs'
+ * @param from_snippets - TRUE if `kbs' come from snippets.conf, FALSE -- from keybindings.conf
+ */
+static void merge_snippet_keybindings(SnippetKeybinding *kbs, gsize len, gboolean from_snippets)
+{
+ gsize i, j, k, last_count;
+ GeanyKeyBinding *last_keys;
+ gchar *name;
+
+ if (len == 0)
+ return;
+
+ /* sort lexicographically */
+ qsort(kbs, len, sizeof(SnippetKeybinding), cmp_snippet_keybindings);
+
+ /* omit duplicates */
+ len = utils_unique(kbs, len, sizeof(SnippetKeybinding), cmp_snippet_keybindings);
+ if (len == 0)
+ return;
+
+ last_count = 0;
+ last_keys = NULL;
+ if (kb_group)
+ {
+ last_count = kb_group->count;
+ last_keys = kb_group->keys;
+ kb_group->count = len + last_count;
+ kb_group->keys = g_new0(GeanyKeyBinding, len + last_count);
+ }
+ else
+ create_snippet_keybindings_group(len + last_count);
+
+ /* Update kb_group->keys.
+ * - i iterates over kb_group->keys
+ * - j iterates over last_keys
+ * - k iterates over kbs
+ * All of these array are sorted by snippet name */
+
+ for (i = j = k = 0; k < len; k++)
+ {
+ int r;
+ name = kbs[k].name;
+
+ /* 'r' stores 0 if there is an item with same name in 'last_keys' and it's index is 'j';
+ * otherwise stores a positive value */
+ for (r = 1; j < last_count && (r = strcmp(name, last_keys[j].name)) > 0; j++)
+ {
+ keybindings_unregister_kb_accel(&last_keys[j]);
+ handle_removed(&last_keys[j], &i, from_snippets);
+ }
+
+ if (r == 0)
+ {
+ keybindings_unregister_kb_accel(&last_keys[j]);
+ handle_updated(&last_keys[j], &kbs[k], &i);
+ j++;
+ }
+ else
+ handle_new(&kbs[k], &i, from_snippets);
+ }
+
+ for (; j < last_count; j++)
+ {
+ keybindings_unregister_kb_accel(&last_keys[j]);
+ handle_removed(&last_keys[j], &i, from_snippets);
+ }
+
+ kb_group->count = i;
+ keybindings_free_group_keys(last_keys, last_count);
+}
+
+
+/* Called when a snippet keybinding is activated */
+static gboolean activate_snippet(guint key_id)
+{
+ gchar *name = kb_group->keys[key_id].name;
+ GeanyDocument *doc = document_get_current();
+ const gchar *s;
+ GHashTable *specials;
+ GString *pattern;
+ gint pos, line, indent_width, cursor_pos;
+
+ if (!doc)
+ {
+ utils_beep();
+ return FALSE;
+ }
+
+ s = snippets_find_completion_by_name(doc->file_type->name, name);
+ if (!s) /* may be "special" snippet */
+ {
+ specials = g_hash_table_lookup(snippet_hash, "Special");
+ if (G_LIKELY(specials != NULL))
+ s = g_hash_table_lookup(specials, name);
+ }
+ if (!s)
+ {
+ utils_beep();
+ return FALSE;
+ }
+
+ pos = sci_get_current_position(doc->editor->sci);
+ line = sci_get_line_from_position(doc->editor->sci, pos);
+ indent_width = sci_get_line_indentation(doc->editor->sci, line);
+
+ pattern = g_string_new(s);
+ cursor_pos = snippets_make_replacements(doc->editor, pattern, indent_width);
+
+ editor_insert_text_block(doc->editor, pattern->str, pos, cursor_pos, indent_width, FALSE);
+ sci_scroll_caret(doc->editor->sci);
+
+ g_string_free(pattern, TRUE);
+ return TRUE;
+}
diff --git a/src/keybindings.c b/src/keybindings.c
index 4e68723..77031e0 100644
--- a/src/keybindings.c
+++ b/src/keybindings.c
@@ -679,8 +679,20 @@ static void load_user_kb(void)
/* now load user defined keys */
if (g_key_file_load_from_file(config, configfile, G_KEY_FILE_KEEP_COMMENTS, NULL))
{
- keybindings_foreach(load_kb, config);
+ gsize g, i;
+ GeanyKeyGroup *group;
+
+ for (g = 0; g < keybinding_groups->len; g++)
+ {
+ group = g_ptr_array_index(keybinding_groups, g);
+ if (group->load)
+ group->load(config, group->name, group);
+ else
+ for (i = 0; i < group->count; i++)
+ load_kb(group, &group->keys[i], config);
+ }
}
+
g_free(configfile);
g_key_file_free(config);
}
@@ -696,6 +708,13 @@ static void apply_kb_accel(GeanyKeyGroup *group, GeanyKeyBinding *kb, gpointer u
}
+void keybindings_unregister_kb_accel(GeanyKeyBinding *kb)
+{
+ if (kb->key != 0 && kb->menu_item)
+ gtk_widget_remove_accelerator(kb->menu_item, kb_accel_group, kb->key, kb->mods);
+}
+
+
void keybindings_load_keyfile(void)
{
load_user_kb();
@@ -769,6 +788,8 @@ void keybindings_write_to_file(void)
gchar *configfile = g_strconcat(app->configdir, G_DIR_SEPARATOR_S, "keybindings.conf", NULL);
gchar *data;
GKeyFile *config = g_key_file_new();
+ gsize g, i;
+ GeanyKeyGroup *group;
/* add comment if the file is newly created */
if (! g_key_file_load_from_file(config, configfile, G_KEY_FILE_KEEP_COMMENTS, NULL))
@@ -778,7 +799,16 @@ void keybindings_write_to_file(void)
"But you can also change the keys in Geany's preferences dialog.", NULL);
}
- keybindings_foreach(set_keyfile_kb, config);
+ /* save */
+ for (g = 0; g < keybinding_groups->len; g++)
+ {
+ group = g_ptr_array_index(keybinding_groups, g);
+ if (group->save)
+ group->save(config, group->name, group);
+ else
+ for (i = 0; i < group->count; i++)
+ set_keyfile_kb(group, &group->keys[i], config);
+ }
/* write the file */
data = g_key_file_to_data(config, NULL, NULL);
@@ -2641,16 +2671,22 @@ GeanyKeyGroup *keybindings_set_group(GeanyKeyGroup *group, const gchar *section_
/* used for plugins */
void keybindings_free_group(GeanyKeyGroup *group)
{
- GeanyKeyBinding *kb;
-
g_assert(group->plugin);
- foreach_c_array(kb, group->keys, group->count)
+ keybindings_free_group_keys(group->keys, group->count);
+ g_ptr_array_remove_fast(keybinding_groups, group);
+ g_free(group);
+}
+
+
+void keybindings_free_group_keys(GeanyKeyBinding *keys, int count)
+{
+ GeanyKeyBinding *kb;
+
+ foreach_c_array(kb, keys, count)
{
g_free(kb->name);
g_free(kb->label);
}
- g_free(group->keys);
- g_ptr_array_remove_fast(keybinding_groups, group);
- g_free(group);
+ g_free(keys);
}
diff --git a/src/keybindings.h b/src/keybindings.h
index b876b61..c8ac107 100644
--- a/src/keybindings.h
+++ b/src/keybindings.h
@@ -51,6 +51,11 @@ GeanyKeyBinding;
* with the same key combination to handle it). */
typedef gboolean (*GeanyKeyGroupCallback) (guint key_id);
+struct GeanyKeyGroup;
+typedef void (*GeanyKeyGroupLoadCallback) (GKeyFile *config, const gchar *section,
+ struct GeanyKeyGroup *group);
+typedef GeanyKeyGroupLoadCallback GeanyKeyGroupSaveCallback;
+
/** A collection of keybindings grouped together. */
typedef struct GeanyKeyGroup GeanyKeyGroup;
@@ -64,6 +69,8 @@ struct GeanyKeyGroup
GeanyKeyBinding *keys; /* array of GeanyKeyBinding structs */
gboolean plugin; /* used by plugin */
GeanyKeyGroupCallback callback; /* use this or individual keybinding callbacks */
+ GeanyKeyGroupLoadCallback load; /* custom callback to load data from the configuration file */
+ GeanyKeyGroupSaveCallback save; /* custom callback to save data from the configuration file */
};
#endif
@@ -348,6 +355,8 @@ GeanyKeyGroup *keybindings_set_group(GeanyKeyGroup *group, const gchar *section_
void keybindings_free_group(GeanyKeyGroup *group);
+void keybindings_free_group_keys(GeanyKeyBinding *keys, int count);
+
GeanyKeyBinding *keybindings_set_item(GeanyKeyGroup *group, gsize key_id,
GeanyKeyCallback callback, guint key, GdkModifierType mod,
const gchar *name, const gchar *label, GtkWidget *menu_item);
@@ -369,5 +378,7 @@ void keybindings_show_shortcuts(void);
const GeanyKeyBinding *keybindings_check_event(GdkEventKey *ev, gint *group_id, gint *binding_id);
+void keybindings_unregister_kb_accel(GeanyKeyBinding *kb);
+
#endif
diff --git a/src/utils.c b/src/utils.c
index 1d1161f..123007b 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -2007,3 +2007,35 @@ gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_va
}
+/* "Remove" duplicate elements from a sorted array
+ *
+ * Actually, this function moves all copies to the end of the array and returns the number of
+ * unique elements, which will be placed in the beginning.
+ *
+ * Array must be sorted using the same comparator function (@c cmp).
+ *
+ * @param start - start address of the array
+ * @param len - number of elements in the array
+ * @param element_size - size of an element
+ * @cmp - Function to compare elements. Takes two pointers to array elements
+ * @returns number of unique elements
+ */
+gsize utils_unique(void *start, gsize len, gsize element_size, GCompareFunc cmp)
+{
+ gsize i, j;
+
+ for (i = 0, j = 1; j < len; j++)
+ {
+ if (cmp(start + j*element_size, start + i*element_size) != 0)
+ {
+ /* swap(elements[++i], elements[j]) */
+ void *mem = g_alloca(element_size);
+
+ i++;
+ memcpy(mem, start + i*element_size, element_size);
+ memcpy(start + i*element_size, start + j*element_size, element_size);
+ memcpy(start + j*element_size, mem, element_size);
+ }
+ }
+ return i + 1;
+}
diff --git a/src/utils.h b/src/utils.h
index 0e53d56..c16b5cf 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -231,4 +231,6 @@ gchar *utils_str_remove_chars(gchar *string, const gchar *chars);
gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...) G_GNUC_NULL_TERMINATED;
+gsize utils_unique(void *start, gsize len, gsize element_size, GCompareFunc cmp);
+
#endif
_______________________________________________
Geany-devel mailing list
[email protected]
http://lists.uvena.de/cgi-bin/mailman/listinfo/geany-devel