Kaydet (Commit) b2668dae authored tarafından Thomas Martitz's avatar Thomas Martitz Kaydeden (comit) elextr

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 
üst e0dd1ee1
......@@ -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,16 +42,16 @@ 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]
......@@ -59,8 +59,8 @@ 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]
......@@ -68,8 +68,8 @@ 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]
......@@ -77,8 +77,8 @@ 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#]
......@@ -86,8 +86,8 @@ 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]
......@@ -95,8 +95,8 @@ 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]
......@@ -104,8 +104,8 @@ 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]
......
......@@ -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,7 +110,6 @@ 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);
......@@ -120,7 +117,6 @@ 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;
......
......@@ -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);
......
......@@ -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);
......
......@@ -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;
......
......@@ -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 */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment