On Sun, 29 Aug 2010 21:35:35 +0400%
Eugene Arshinov <[email protected]> wrote:
> On Wed, 18 Aug 2010 09:56:45 +1000%
> Lex Trotman <[email protected]> wrote:
>
> > On 18 August 2010 00:35, Nick Treleaven
> > <[email protected]> wrote:
> > > On Tue, 17 Aug 2010 10:08:08 +1000
> > > Lex Trotman <[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.
> > >> >>
> > >> >
> > >> > It's worth thinking.
> > >>
> > >> This could be a bit of work since the number of snippets is
> > >> variable and the keybindings GUI is fixed. I had a quick look a
> > >> while ago with the view of allowing the extra build commands to
> > >> have keybindings but never came up with a simple way of handling
> > >> variable numbers of commands.
> > >>
> > >> If you decide to add a general way of adding variable numbers of
> > >> entries in the keybindings GUI it could then be used elsewhere.
> > >
> > > Plugins can change the number of keybindings in their plugin's key
> > > group*, so maybe this could be done for other things in the core.
> > > It would probably only take some small changes. 'Format->Send
> > > selection to' keybindings could be variable size too.
> > >
> > > *
> > > http://www.geany.org/manual/reference/pluginutils_8h.html#e8eeecc54d81ce05457e04ad98028a68
> >
> > Looks like a good solution, I never thought of looking in the plugin
> > interface.
> >
>
> 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.
>
> 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 (**)
>
> - 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.
>
> ** and users are allowed to assign keybindings for "special" snippets
>
> 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.
diff --git a/src/editor.c b/src/editor.c
index 052d6c7..98f92f4 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 both 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(kb_group, &last_keys[j]);
+ handle_removed(&last_keys[j], &i, from_snippets);
+ }
+
+ if (r == 0)
+ {
+ keybindings_unregister_kb_accel(kb_group, &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(kb_group, &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..6eab379 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(GeanyKeyGroup *group, 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..0eaf2e1 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(GeanyKeyGroup *group, GeanyKeyBinding *kb);
+
#endif
diff --git a/src/utils.c b/src/utils.c
index 1d1161f..c6a5ad6 100644
--- a/src/utils.c
+++ b/src/utils.c
@@ -2007,3 +2007,33 @@ gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_va
}
+/* "Remove" duplicate elements from an 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.
+ *
+ * @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