Branch: refs/heads/master Author: Thomas Martitz kugel@rockbox.org Committer: elextr elextr@gmail.com Date: Fri, 21 Jul 2017 06:42:58 UTC Commit: b2668dae67dcca97740d5f4adc4200519cdde8a0 https://github.com/geany/geany/commit/b2668dae67dcca97740d5f4adc4200519cdde8...
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).