[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