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

Reply via email to