Index: src/utils.c
===================================================================
--- src/utils.c	(Revision 3443)
+++ src/utils.c	(Arbeitskopie)
@@ -1354,21 +1354,14 @@
 }


-/**
- * Replaces all occurrences of @c needle in @c haystack with @c replace.
- * As of Geany 0.16, @a replace can match @a needle, so the following will work:
- * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
- *
- * @param haystack The input string to operate on. This string is modified in place.
- * @param needle The string which should be replaced.
- * @param replace The replacement for @c needle.
- *
- * @return @a TRUE if @c needle was found, else @a FALSE.
- **/
-gboolean utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
+static guint utils_string_replace_helper(GString *haystack,
+											const gchar *needle,
+											const gchar *replace,
+											const guint max_replaces)
 {
 	const gchar *stack, *match;
 	gssize pos = -1;
+	guint replace_count = 0;

 	if (haystack->len == 0)
 		return FALSE;
@@ -1378,7 +1371,7 @@
 	while (1)
 	{
 		match = strstr(stack, needle);
-		if (match == NULL)
+		if (match == NULL || (max_replaces > 0 && replace_count++ >= max_replaces))
 			break;
 		else
 		{
@@ -1396,10 +1389,33 @@
 			}
 		}
 	}
-	return (pos != -1);
+	return replace_count;
 }


+/**
+ * Replaces all occurrences of @c needle in @c haystack with @c replace.
+ * As of Geany 0.16, @a replace can match @a needle, so the following will work:
+ * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
+ *
+ * @param haystack The input string to operate on. This string is modified in place.
+ * @param needle The string which should be replaced.
+ * @param replace The replacement for @c needle.
+ *
+ * @return @a amount of replacements done
+ **/
+guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
+{
+	return utils_string_replace_helper(haystack, needle, replace, 0);
+}
+
+
+guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
+{
+	return utils_string_replace_helper(haystack, needle, replace, 1);
+}
+
+
 /* Get project or default startup directory (if set), or NULL. */
 const gchar *utils_get_default_dir_utf8(void)
 {
Index: src/utils.h
===================================================================
--- src/utils.h	(Revision 3443)
+++ src/utils.h	(Arbeitskopie)
@@ -82,8 +82,10 @@

 gchar *utils_get_hostname(void);

-gboolean utils_string_replace_all(GString *str, const gchar *needle, const gchar *replace);
+guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace);

+guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace);
+
 gchar *utils_str_replace(gchar *haystack, const gchar *needle, const gchar *replacement);

 gint utils_strpos(const gchar* haystack, const gchar *needle);
Index: src/keybindings.c
===================================================================
--- src/keybindings.c	(Revision 3443)
+++ src/keybindings.c	(Arbeitskopie)
@@ -242,6 +242,8 @@
 		GDK_Down, GDK_MOD1_MASK, "edit_scrolllinedown", _("Scroll down the view by one line"), NULL);
 	keybindings_set_item(group, GEANY_KEYS_EDITOR_COMPLETESNIPPET, NULL,	/* handled specially in check_snippet_completion() */
 		GDK_Tab, 0, "edit_completesnippet", _("Complete snippet"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_EDITOR_SNIPPET_NEXT_CURSOR, cb_func_editor_action,
+		0, 0, "move_snippetnextcursor", _("Move cursor in snippet"), NULL);
 	keybindings_set_item(group, GEANY_KEYS_EDITOR_SUPPRESSSNIPPETCOMPLETION, cb_func_editor_action,
 		0, 0, "edit_suppresssnippetcompletion", _("Suppress snippet completion"), NULL);
 	keybindings_set_item(group, GEANY_KEYS_EDITOR_CONTEXTACTION, cb_func_editor_action,
@@ -1725,6 +1727,9 @@
 		case GEANY_KEYS_EDITOR_DUPLICATELINE:
 			duplicate_lines(doc->editor);
 			break;
+		case GEANY_KEYS_EDITOR_SNIPPET_NEXT_CURSOR:
+			editor_snippet_next_cursor(doc->editor->sci, sci_get_current_position(doc->editor->sci));
+			break;
 		case GEANY_KEYS_EDITOR_DELETELINE:
 			delete_lines(doc->editor);
 			break;
Index: src/keybindings.h
===================================================================
--- src/keybindings.h	(Revision 3443)
+++ src/keybindings.h	(Arbeitskopie)
@@ -139,6 +139,7 @@
 	GEANY_KEYS_EDITOR_AUTOCOMPLETE,
 	GEANY_KEYS_EDITOR_CALLTIP,
 	GEANY_KEYS_EDITOR_MACROLIST,
+	GEANY_KEYS_EDITOR_SNIPPET_NEXT_CURSOR,
 	GEANY_KEYS_EDITOR_COUNT
 };

Index: src/editor.c
===================================================================
--- src/editor.c	(Revision 3443)
+++ src/editor.c	(Arbeitskopie)
@@ -64,6 +64,16 @@
 #define SSM(s, m, w, l) scintilla_send_message(s, m, w, l)


+#define MAX_CURSOR_IN_SNIPPET 10
+#define MAX_INDENT_COUNT 10
+struct snippet_cursor_info {
+	gint cur_idx;
+	gint current;
+	gint positions[MAX_CURSOR_IN_SNIPPET];
+	gboolean enabled;
+};
+static struct snippet_cursor_info snippet_cursor;
+
 /* holds word under the mouse or keyboard cursor */
 static gchar current_word[GEANY_MAX_WORD_LENGTH];

@@ -1862,19 +1872,41 @@
 }


+/* Move the cursor to the next specified cursor position in an inserted snippet. */
+void editor_snippet_next_cursor(ScintillaObject *sci, gint current_pos)
+{
+	if (!snippet_cursor.enabled)
+		return;
+
+	snippet_cursor.cur_idx++;
+	if (snippet_cursor.cur_idx > 0)
+		snippet_cursor.current = snippet_cursor.positions[snippet_cursor.cur_idx] -
+				snippet_cursor.positions[snippet_cursor.cur_idx - 1];
+
+	if (snippet_cursor.current > 0)
+		sci_set_current_position(sci, snippet_cursor.current + current_pos, FALSE);
+	else
+		snippet_cursor.enabled = FALSE;
+}
+
+
 static gboolean snippets_complete_constructs(GeanyEditor *editor, gint pos, const gchar *word)
 {
 	gchar *str, *whitespace;
 	GString *pattern;
-	gint step, str_len;
+	gint str_len, i;
+	gint cursor_steps, tmp_pos, whitespace_len, indent_count = 0;
 	gsize cur_index;
 	gint ft_id = FILETYPE_ID(editor->document->file_type);
 	GHashTable *specials;
 	ScintillaObject *sci = editor->sci;
+	const GeanyIndentPrefs *iprefs = editor_get_indent_prefs(editor);

 	str = g_strdup(word);
 	g_strstrip(str);

+	snippet_cursor.enabled = FALSE;
+
 	pattern = g_string_new(snippets_find_completion_by_name(filetypes[ft_id]->name, str));
 	if (pattern == NULL || pattern->len == 0)
 	{
@@ -1906,23 +1938,70 @@

 	/* transform other wildcards */
 	utils_string_replace_all(pattern, "%newline%", editor_get_eol_char(editor));
+	utils_string_replace_all(pattern, "\n", editor_get_eol_char(editor));

-	/* use spaces for indentation, will be fixed up */
-	whitespace = g_strnfill(editor_get_indent_prefs(editor)->width, ' ');
-	utils_string_replace_all(pattern, "%ws%", whitespace);
+	/* Find %cursor% wildcards, store their positions and handle whitespace wildcards.
+	 * (This looks dirty, but is needed, since we want to have multiple %cursor% insertions
+	 * working for any indentation type. Spaces and Tabs need different steps for the cursor.
+	 * So, before replacing %cursor% one by one, we replace every %ws% and \t up to the next
+	 * cursor and count them. Later we modify the step size, depending on the configured
+	 * indentation type and counted indentations.) */
+	whitespace = g_strnfill(iprefs->width, ' '); /* use spaces for indentation, will be fixed up */
+	whitespace_len = strlen(whitespace);
+	for (i = 0; i < MAX_CURSOR_IN_SNIPPET; i++)
+	{
+		snippet_cursor.positions[i] = -1;
+		cursor_steps = utils_strpos(pattern->str, "%cursor%");
+		if (cursor_steps != -1)
+		{
+			/* count indentations up to the next %cursor% and fix step number since it was
+			 * counted with %ws% and \t in the pattern, and not the actual whitespace number
+			 * which is going to be */
+			while ((tmp_pos = utils_strpos(pattern->str, "%ws%")) < cursor_steps && tmp_pos != -1)
+			{
+				indent_count++;
+				utils_string_replace_first(pattern, "%ws%", whitespace);
+				cursor_steps -= 4 - whitespace_len; /* 4 is the len of %ws% */
+			}
+			while ((tmp_pos = utils_strpos(pattern->str, "\t")) < cursor_steps && tmp_pos != -1)
+			{
+				indent_count++;
+				utils_string_replace_first(pattern, "\t", whitespace);
+				cursor_steps -= 1 - whitespace_len;
+			}
+			/* finally replace the next %cursor% */
+			utils_string_replace_first(pattern, "%cursor%", "");
+			/* modify cursor_steps to take indentation count and type into account */
+			if (iprefs->type & GEANY_INDENT_TYPE_TABS)
+				cursor_steps -= indent_count * (iprefs->width -1);
+		}
+		else /* no more found */
+			break;
+
+		snippet_cursor.positions[i] = cursor_steps;
+	}
+	/* replace remaining %ws% and \t which may occur after the last %cursor% */
+	utils_string_replace_all(pattern, "\t", whitespace);
+	utils_string_replace_all(pattern, "%ws", whitespace);
 	g_free(whitespace);

-	/* find the %cursor% pos (has to be done after all other operations) */
-	step = utils_strpos(pattern->str, "%cursor%");
-	if (step != -1)
-		utils_string_replace_all(pattern, "%cursor%", "");
+	snippet_cursor.current = snippet_cursor.positions[0];
+	snippet_cursor.cur_idx = 0;
+	if (snippet_cursor.current < 1)
+	{
+		if (snippet_cursor.current >= 0)
+			cur_index = snippet_cursor.current;
+		else
+			cur_index = pattern->len;
+		snippet_cursor.enabled = FALSE;
+	}
+	else
+	{
+		cur_index = snippet_cursor.current;
+		snippet_cursor.enabled = TRUE;
+	}

 	/* finally insert the text and set the cursor */
-	if (step != -1)
-		cur_index = step;
-	else
-		cur_index = pattern->len;
-
 	editor_insert_text_block(editor, pattern->str, pos, cur_index, -1);
 	sci_scroll_caret(sci);

Index: src/editor.h
===================================================================
--- src/editor.h	(Revision 3443)
+++ src/editor.h	(Arbeitskopie)
@@ -167,6 +167,8 @@

 gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean force);

+void editor_snippet_next_cursor(ScintillaObject *sci, gint current_pos);
+
 gboolean editor_complete_snippet(GeanyEditor *editor, gint pos);

 void editor_auto_latex(GeanyEditor *editor, gint pos);
Index: doc/geany.txt
===================================================================
--- doc/geany.txt	(Revision 3443)
+++ doc/geany.txt	(Arbeitskopie)
@@ -806,7 +806,9 @@
                   ('key= value' won't work, use 'key=\\svalue')

 %cursor%          Place the cursor at this position after completion has
-                  been done.
+                  been done. You can define multiple %cursor% wildcards
+                  and use the ``Move cursor in snippet`` to jump to the next
+                  defined cursor position in the completed snippet.

 %...%             "..." means the name of a key in the "Special" section.
                   If you have defined a key "brace_open" in the "Special"
@@ -826,7 +828,8 @@

 Every time you write ``myname`` <TAB> in Geany, it will replace "myname"
 with "Enrico Tröger". The key to start auto completion can be changed
-in the preferences dialog, by default it is TAB.
+in the preferences dialog, by default it is TAB. The corresponding keybinding
+is called ``Complete snippet``.

 Since Geany 0.15 you can also use most of the available templates wildcards
 listed in `Template wildcards`_. All wildcards which are listed as
@@ -2377,6 +2380,10 @@
                                                           argument. See the section called `Context
                                                           actions`_.

+Move cursor in snippet                                    Jumps to the next defined cursor positions in a
+                                                          completed snippets if multiple cursor positions
+                                                          where defined.
+
 **Clipboard**

 Cut                             Ctrl-X                    Cut the current selection to the clipboard.
Index: data/snippets.conf
===================================================================
--- data/snippets.conf	(Revision 3443)
+++ data/snippets.conf	(Arbeitskopie)
@@ -3,9 +3,11 @@
 # use \n or %newline% for a new line (it will be replaced by the used EOL char(s) - LF, CR/LF, CR).
 # use \t or %ws% for an indentation step, it will be replaced according to the current document's indent mode.
 # use \s to force whitespace at beginning or end of a value ('key= value' won't work, use 'key=\svalue').
-# use %cursor% to define where the cursor should be placed after completion.
 # use %key% for all keys defined in the [Special] section.
-# you can define a section for each supported filetype to overwrite default settings, the section
+# use %cursor% to define where the cursor should be placed after completion. You can define multiple
+#     %cursor% wildcards and use the "Move cursor in snippet" to jump to the next defined cursor
+#     position in the completed snippet.
+# You can define a section for each supported filetype to overwrite default settings, the section
 # name must match exactly the internal filetype name, run 'geany --ft-names' for a full list.
 #
 # Additionally, you can use most of the template wildcards like {developer} or {date} in the snippets.
