[geany/geany] b2668d: Better snippets (#1470)

Thomas Martitz git-noreply at xxxxx
Fri Jul 21 06:42:58 UTC 2017


Branch:      refs/heads/master
Author:      Thomas Martitz <kugel at rockbox.org>
Committer:   elextr <elextr at gmail.com>
Date:        Fri, 21 Jul 2017 06:42:58 UTC
Commit:      b2668dae67dcca97740d5f4adc4200519cdde8a0
             https://github.com/geany/geany/commit/b2668dae67dcca97740d5f4adc4200519cdde8a0

Log Message:
-----------
Better snippets (#1470)

* snippets: Allow keybinding overloading of snippet-next-cursor.

This allows to use the same key as for inserting snippets, or plugins to
map something to the same keybinding (e.g. if they implement a similar facility).

* snippets: Remove cursor position at the end of constructs.

This is not consistently done for all languages, and hard to get right
e.g. for python. It's probably not terribly useful either.

* snippets: Use Scintilla indicators for cursor posititons
* api: Increment API version.
* snippets: restore behavior of cursor-less snippets
* snippts: use ascii character for the placeholder.

Do not require documents to be UTF-8 for using snippets.

* snippets: fix start/end detection, when searching for the next cursor

Tested @vfaronov


Modified Paths:
--------------
    data/snippets.conf
    src/editor.c
    src/editor.h
    src/highlighting.c
    src/keybindings.c
    src/plugindata.h

Modified: data/snippets.conf
34 lines changed, 17 insertions(+), 17 deletions(-)
===================================================================
@@ -29,7 +29,7 @@
 brace_open=\n{\n\t
 brace_close=}\n
 block=\n{\n\t%cursor%\n}
-block_cursor=\n{\n\t%cursor%\n}\n%cursor%
+block_cursor=\n{\n\t%cursor%\n}
 #wordchars=_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
 
 # Optional keybindings to insert snippets
@@ -42,70 +42,70 @@ if=if (%cursor%)%block_cursor%
 else=else%block_cursor%
 for=for (i = 0; i < %cursor%; i++)%block_cursor%
 while=while (%cursor%)%block_cursor%
-do=do\n{\n\t%cursor%\n} while (%cursor%)\n%cursor%
-switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%%cursor%
+do=do\n{\n\t%cursor%\n} while (%cursor%)\n
+switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%
 
 [C++]
 if=if (%cursor%)%block_cursor%
 else=else%block_cursor%
 for=for (int i = 0; i < %cursor%; i++)%brace_open%\n%brace_close%
 while=while (%cursor%)%block_cursor%
-do=do\n{\n\t%cursor%\n} while (%cursor%)\n%cursor%
-switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%%cursor%
+do=do\n{\n\t%cursor%\n} while (%cursor%)\n
+switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%
 try=try%block%\ncatch (%cursor%)%block_cursor%
 
 [Java]
 if=if (%cursor%)%block_cursor%
 else=else%block_cursor%
 for=for (int i = 0; i < %cursor%; i++)%brace_open%\n%brace_close%
 while=while (%cursor%)%block_cursor%
-do=do\n{\n\t%cursor%\n} while (%cursor%)\n%cursor%
-switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%%cursor%
+do=do\n{\n\t%cursor%\n} while (%cursor%)\n
+switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%
 try=try%block%\ncatch (%cursor%)%block_cursor%
 
 [PHP]
 if=if (%cursor%)%block_cursor%
 else=else%block_cursor%
 for=for ($i = 0; $i < %cursor%; $i++)%brace_open%\n%brace_close%
 while=while (%cursor%)%block_cursor%
-do=do\n{\n\t%cursor%\n} while (%cursor%)\n%cursor%
-switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%%cursor%
+do=do\n{\n\t%cursor%\n} while (%cursor%)\n
+switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%
 try=try%block%\ncatch (%cursor%)%block_cursor%
 
 [Javascript]
 if=if (%cursor%)%block_cursor%
 else=else%block_cursor%
 for=for (i = 0; i < %cursor%; i++)%block_cursor%
 while=while (%cursor%)%block_cursor%
-do=do\n{\n\t%cursor%\n} while (%cursor%)\n%cursor%
-switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%%cursor%
+do=do\n{\n\t%cursor%\n} while (%cursor%)\n
+switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%
 try=try%block%\ncatch (%cursor%)%block_cursor%
 
 [C#]
 if=if (%cursor%)%block_cursor%
 else=else%block_cursor%
 for=for (i = 0; i < %cursor%; i++)%block_cursor%
 while=while (%cursor%)%block_cursor%
-do=do\n{\n\t%cursor%\n} while (%cursor%)\n%cursor%
-switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%%cursor%
+do=do\n{\n\t%cursor%\n} while (%cursor%)\n
+switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%
 try=try%block%\ncatch (%cursor%)%block_cursor%
 
 [Vala]
 if=if (%cursor%)%block_cursor%
 else=else%block_cursor%
 for=for (i = 0; i < %cursor%; i++)%block_cursor%
 while=while (%cursor%)%block_cursor%
-do=do\n{\n\t%cursor%\n} while (%cursor%)\n%cursor%
-switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%%cursor%
+do=do\n{\n\t%cursor%\n} while (%cursor%)\n
+switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%
 try=try%block%\ncatch (%cursor%)%block_cursor%
 
 [ActionScript]
 if=if (%cursor%)%block_cursor%
 else=else%block_cursor%
 for=for (i = 0; i < %cursor%; i++)%block_cursor%
 while=while (%cursor%)%block_cursor%
-do=do\n{\n\t%cursor%\n} while (%cursor%)\n%cursor%
-switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%%cursor%
+do=do\n{\n\t%cursor%\n} while (%cursor%)\n
+switch=switch (%cursor%)%brace_open%case %cursor%:\n\t\t%cursor%\n\t\tbreak;\n\tdefault:\n\t\t%cursor%\n%brace_close%
 try=try%block%\ncatch (%cursor%)%block_cursor%
 
 [Python]


Modified: src/editor.c
173 lines changed, 95 insertions(+), 78 deletions(-)
===================================================================
@@ -72,8 +72,6 @@
 #define SSM(s, m, w, l) scintilla_send_message(s, m, w, l)
 
 static GHashTable *snippet_hash = NULL;
-static GQueue *snippet_offsets = NULL;
-static gint snippet_cursor_insert_pos;
 static GtkAccelGroup *snippet_accel_group = NULL;
 static gboolean autocomplete_scope_shown = FALSE;
 
@@ -112,15 +110,13 @@ static void read_current_word(GeanyEditor *editor, gint pos, gchar *word, gsize
 static gsize count_indent_size(GeanyEditor *editor, const gchar *base_indent);
 static const gchar *snippets_find_completion_by_name(const gchar *type, const gchar *name);
 static void snippets_make_replacements(GeanyEditor *editor, GString *pattern);
-static gssize replace_cursor_markers(GeanyEditor *editor, GString *pattern);
 static GeanyFiletype *editor_get_filetype_at_line(GeanyEditor *editor, gint line);
 static gboolean sci_is_blank_line(ScintillaObject *sci, gint line);
 
 
 void editor_snippets_free(void)
 {
 	g_hash_table_destroy(snippet_hash);
-	g_queue_free(snippet_offsets);
 	gtk_window_remove_accel_group(GTK_WINDOW(main_widgets.window), snippet_accel_group);
 }
 
@@ -272,8 +268,6 @@ void editor_snippets_init(void)
 	GKeyFile *sysconfig = g_key_file_new();
 	GKeyFile *userconfig = g_key_file_new();
 
-	snippet_offsets = g_queue_new();
-
 	sysconfigfile = g_build_filename(app->datadir, "snippets.conf", NULL);
 	userconfigfile = g_build_filename(app->configdir, "snippets.conf", NULL);
 
@@ -2394,6 +2388,50 @@ static void fix_indentation(GeanyEditor *editor, GString *buf)
 }
 
 
+typedef struct
+{
+	Sci_Position start, len;
+} SelectionRange;
+
+
+#define CURSOR_PLACEHOLDER "_" /* Would rather use … but not all docs are unicode */
+/* Replaces the internal cursor markers with the placeholder suitable for
+ * display. Except for the first cursor if indicator_for_first is FALSE,
+ * which is simply deleted.
+ *
+ * Returns insertion points as SelectionRange list, so that the caller
+ * can use the positions (currently for indicators). */
+static GSList *replace_cursor_markers(GeanyEditor *editor, GString *template,
+									  gboolean indicator_for_first)
+{
+	gssize cur_index = -1;
+	gint i = 0;
+	GSList *temp_list = NULL;
+	gint cursor_steps = 0, old_cursor = 0;
+	SelectionRange *sel;
+
+	while (TRUE)
+	{
+		cursor_steps = utils_string_find(template, cursor_steps, -1, geany_cursor_marker);
+		if (cursor_steps == -1)
+			break;
+
+		sel = g_new0(SelectionRange, 1);
+		sel->start = cursor_steps;
+		g_string_erase(template, cursor_steps, strlen(geany_cursor_marker));
+		if (i > 0 || indicator_for_first)
+		{
+			g_string_insert(template, cursor_steps, CURSOR_PLACEHOLDER);
+			sel->len = sizeof(CURSOR_PLACEHOLDER) - 1;
+		}
+		i += 1;
+		temp_list = g_slist_append(temp_list, sel);
+	}
+
+	return temp_list;
+}
+
+
 /** Inserts text, replacing \\t tab chars (@c 0x9) and \\n newline chars (@c 0xA)
  * accordingly for the document.
  * - Leading tabs are replaced with the correct indentation.
@@ -2421,6 +2459,7 @@ void editor_insert_text_block(GeanyEditor *editor, const gchar *text, gint inser
 	GString *buf;
 	const gchar *eol = editor_get_eol_char(editor);
 	gint idx;
+	GSList *jump_locs, *item;
 
 	g_return_if_fail(text);
 	g_return_if_fail(editor != NULL);
@@ -2461,43 +2500,75 @@ void editor_insert_text_block(GeanyEditor *editor, const gchar *text, gint inser
 
 	fix_indentation(editor, buf);
 
-	idx = replace_cursor_markers(editor, buf);
-	if (idx >= 0)
+	jump_locs = replace_cursor_markers(editor, buf, cursor_index < 0);
+	sci_insert_text(sci, insert_pos, buf->str);
+
+	foreach_list(item, jump_locs)
 	{
-		sci_insert_text(sci, insert_pos, buf->str);
-		sci_set_current_position(sci, insert_pos + idx, FALSE);
+		SelectionRange *sel = item->data;
+		gint start = insert_pos + sel->start;
+		gint end = start + sel->len;
+		editor_indicator_set_on_range(editor, GEANY_INDICATOR_SNIPPET, start, end);
+		/* jump to first cursor position initially */
+		if (item == jump_locs)
+			sci_set_selection(sci, start, end);
 	}
-	else
-		sci_insert_text(sci, insert_pos, buf->str);
 
-	snippet_cursor_insert_pos = sci_get_current_position(sci);
+	/* Set cursor to the requested index, or by default to after the snippet */
+	if (cursor_index >= 0)
+		sci_set_current_position(sci, insert_pos + idx, FALSE);
+	else if (jump_locs == NULL)
+		sci_set_current_position(sci, insert_pos + buf->len, FALSE);
 
+	g_slist_free_full(jump_locs, g_free);
 	g_string_free(buf, TRUE);
 }
 
 
+static gboolean find_next_snippet_indicator(GeanyEditor *editor, SelectionRange *sel)
+{
+	ScintillaObject *sci = editor->sci;
+	gint val;
+	gint pos = sci_get_current_position(sci);
+
+	if (pos == sci_get_length(sci))
+		return FALSE; /* EOF */
+
+	/* Rewind the cursor a bit if we're in the middle (or start) of an indicator,
+	 * and treat that as the next indicator. */
+	while (SSM(sci, SCI_INDICATORVALUEAT, GEANY_INDICATOR_SNIPPET, pos) && pos > 0)
+		pos -= 1;
+
+	/* Be careful at the beginning of the file */
+	if (SSM(sci, SCI_INDICATORVALUEAT, GEANY_INDICATOR_SNIPPET, pos))
+		sel->start = pos;
+	else
+		sel->start = SSM(sci, SCI_INDICATOREND, GEANY_INDICATOR_SNIPPET, pos);
+	sel->len = SSM(sci, SCI_INDICATOREND, GEANY_INDICATOR_SNIPPET, sel->start) - sel->start;
+
+	/* 0 if there is no remaining cursor */
+	return sel->len > 0;
+}
+
+
 /* Move the cursor to the next specified cursor position in an inserted snippet.
  * Can, and should, be optimized to give better results */
-void editor_goto_next_snippet_cursor(GeanyEditor *editor)
+gboolean editor_goto_next_snippet_cursor(GeanyEditor *editor)
 {
 	ScintillaObject *sci = editor->sci;
 	gint current_pos = sci_get_current_position(sci);
+	SelectionRange sel;
 
-	if (snippet_offsets && !g_queue_is_empty(snippet_offsets))
+	if (find_next_snippet_indicator(editor, &sel))
 	{
-		gint offset;
-
-		offset = GPOINTER_TO_INT(g_queue_pop_head(snippet_offsets));
-		if (current_pos > snippet_cursor_insert_pos)
-			snippet_cursor_insert_pos = offset + current_pos;
-		else
-			snippet_cursor_insert_pos += offset;
-
-		sci_set_current_position(sci, snippet_cursor_insert_pos, TRUE);
+		sci_indicator_set(sci, GEANY_INDICATOR_SNIPPET);
+		sci_set_selection(sci, sel.start, sel.start + sel.len);
+		return TRUE;
 	}
 	else
 	{
 		utils_beep();
+		return FALSE;
 	}
 }
 
@@ -2528,60 +2599,6 @@ static void snippets_make_replacements(GeanyEditor *editor, GString *pattern)
 }
 
 
-static gssize replace_cursor_markers(GeanyEditor *editor, GString *pattern)
-{
-	gssize cur_index = -1;
-	gint i;
-	GList *temp_list = NULL;
-	gint cursor_steps = 0, old_cursor = 0;
-
-	i = 0;
-	while (1)
-	{
-		cursor_steps = utils_string_find(pattern, cursor_steps, -1, geany_cursor_marker);
-		if (cursor_steps == -1)
-			break;
-
-		g_string_erase(pattern, cursor_steps, strlen(geany_cursor_marker));
-
-		if (i++ > 0)
-		{
-			/* save the relative offset to each cursor position */
-			temp_list = g_list_prepend(temp_list, GINT_TO_POINTER(cursor_steps - old_cursor));
-		}
-		else
-		{
-			/* first cursor already includes newline positions */
-			cur_index = cursor_steps;
-		}
-		old_cursor = cursor_steps;
-	}
-
-	/* put the cursor positions for the most recent
-	 * parsed snippet first, followed by any remaining positions */
-	i = 0;
-	if (temp_list)
-	{
-		GList *node;
-
-		temp_list = g_list_reverse(temp_list);
-		foreach_list(node, temp_list)
-			g_queue_push_nth(snippet_offsets, node->data, i++);
-
-		/* limit length of queue */
-		while (g_queue_get_length(snippet_offsets) > 20)
-			g_queue_pop_tail(snippet_offsets);
-
-		g_list_free(temp_list);
-	}
-	/* if there's no first cursor, skip whole snippet */
-	if (cur_index < 0)
-		cur_index = pattern->len;
-
-	return cur_index;
-}
-
-
 static gboolean snippets_complete_constructs(GeanyEditor *editor, gint pos, const gchar *word)
 {
 	ScintillaObject *sci = editor->sci;


Modified: src/editor.h
5 lines changed, 3 insertions(+), 2 deletions(-)
===================================================================
@@ -70,7 +70,8 @@ typedef enum
 	/** Indicator used to highlight search results in the document. This is a
 	 *  rounded box around the text. */
 	/* start container indicator outside of lexer indicators (0..7), see Scintilla docs */
-	GEANY_INDICATOR_SEARCH = 8
+	GEANY_INDICATOR_SEARCH = 8,
+	GEANY_INDICATOR_SNIPPET = 9
 }
 GeanyIndicator;
 
@@ -237,7 +238,7 @@ gboolean editor_start_auto_complete(GeanyEditor *editor, gint pos, gboolean forc
 
 gboolean editor_complete_word_part(GeanyEditor *editor);
 
-void editor_goto_next_snippet_cursor(GeanyEditor *editor);
+gboolean editor_goto_next_snippet_cursor(GeanyEditor *editor);
 
 gboolean editor_complete_snippet(GeanyEditor *editor, gint pos);
 


Modified: src/highlighting.c
5 lines changed, 5 insertions(+), 0 deletions(-)
===================================================================
@@ -660,6 +660,11 @@ static void styleset_common(ScintillaObject *sci, guint ft_id)
 		invert(common_style_set.styling[GCS_MARKER_SEARCH].background));
 	SSM(sci, SCI_INDICSETALPHA, GEANY_INDICATOR_SEARCH, 60);
 
+	/* Snippet cursor indicator, when inserting snippets with multiple
+	 * cursor positions. */
+	SSM(sci, SCI_INDICSETSTYLE, GEANY_INDICATOR_SNIPPET, INDIC_DOTBOX);
+	SSM(sci, SCI_INDICSETALPHA, GEANY_INDICATOR_SNIPPET, 60);
+
 	/* define marker symbols
 	 * 0 -> line marker */
 	SSM(sci, SCI_MARKERDEFINE, 0, SC_MARK_SHORTARROW);


Modified: src/keybindings.c
4 lines changed, 2 insertions(+), 2 deletions(-)
===================================================================
@@ -2158,8 +2158,8 @@ static gboolean cb_func_editor_action(guint key_id)
 			duplicate_lines(doc->editor);
 			break;
 		case GEANY_KEYS_EDITOR_SNIPPETNEXTCURSOR:
-			editor_goto_next_snippet_cursor(doc->editor);
-			break;
+			/* allow overloading */
+			return editor_goto_next_snippet_cursor(doc->editor);
 		case GEANY_KEYS_EDITOR_DELETELINE:
 			delete_lines(doc->editor);
 			break;


Modified: src/plugindata.h
2 lines changed, 1 insertions(+), 1 deletions(-)
===================================================================
@@ -59,7 +59,7 @@ G_BEGIN_DECLS
  * @warning You should not test for values below 200 as previously
  * @c GEANY_API_VERSION was defined as an enum value, not a macro.
  */
-#define GEANY_API_VERSION 231
+#define GEANY_API_VERSION 232
 
 /* hack to have a different ABI when built with GTK3 because loading GTK2-linked plugins
  * with GTK3-linked Geany leads to crash */



--------------
This E-Mail was brought to you by github_commit_mail.py (Source: https://github.com/geany/infrastructure).


More information about the Commits mailing list