Hi all.

I attach three patches:
1. Keybindings to insert new line after/before current
2. Snippet keybindings support
3. Automatically insert additional indentation in XML/HTML files if
previous line ends with an opening tag

They must be unrelated and can be applied independently of each other.

Some of the patches have drawbacks.  Before "polishing" them, I want to
know whether they are useful and may go to trunk.

The drawback of the first patch is that I doubt it's so useful.  I know
Vi[m] provides shortcuts for these actions ('o' and 'O' in normal
mode), but their necessity is questionable for me.  I implemented this
to ease XML editing with tag autocompletion turned on.  Suppose you
need to write to consequent <li>'s.  Without this patch you need to
press End+Enter after writing the first <li> to place the cursor where
you need to start the second.  With my patch, you only need to press
one shortcut you can assign in Preferences > Keybindings :)

The drawback of the third patch is that it's not completed.  If user
likes to leave HTML tags like <br> "unclosed", she would be disturbed by
automatic indentation caused by my patch, so a check box in Preferences
is desirable.  I'll code it as soon as we decide this patch can go to
trunk.

Speaking about the second patch, here is an example of assigning a
keybinding (I currently use it in my user snippets.conf):

  # special group to define keybindings
  # keybindings' format resembles the one used in Preferences >
  #   Keybindings tab
  [Keybindings]
  block_cursor=<Alt>bracketleft

Keybindings may be assigned either in user or global snippets.conf (I'm
not sure if the latter is useful).  Another decision I made is to allow
keybindings for snippets in [Special] section (like "block_cursor" used
above).  While "block_cursor" snippet is not very useful when you have
to type its name, it is useful when you just have to press a shortcut.

And, as usual for me, all of these patches lack user documentation…

Best regards,
Eugene.
diff --git a/src/editor.c b/src/editor.c
index 052d6c7..74129c7 100644
--- a/src/editor.c
+++ b/src/editor.c
@@ -3912,7 +3912,7 @@ void editor_select_paragraph(GeanyEditor *editor)
 
 
 /* simple indentation to indent the current line with the same indent as the previous one */
-static void smart_line_indentation(GeanyEditor *editor, gint first_line, gint last_line)
+void editor_smart_indentation_for_lines(GeanyEditor *editor, gint first_line, gint last_line)
 {
 	gint i, sel_start = 0, sel_end = 0;
 
@@ -3964,12 +3964,12 @@ void editor_smart_line_indentation(GeanyEditor *editor, gint pos)
 
 	sci_start_undo_action(sci);
 
-	smart_line_indentation(editor, first_line, last_line);
+	editor_smart_indentation_for_lines(editor, first_line, last_line);
 
 	/* set cursor position if there was no selection */
 	if (first_sel_start == first_sel_end)
 	{
-		gint indent_pos = SSM(sci, SCI_GETLINEINDENTPOSITION, first_line, 0);
+		gint indent_pos = sci_get_indent_position(sci, first_line);
 
 		/* use indent position as user may wish to change indentation afterwards */
 		sci_set_current_position(sci, indent_pos, FALSE);
@@ -4008,7 +4008,7 @@ void editor_indentation_by_one_space(GeanyEditor *editor, gint pos, gboolean dec
 
 	for (i = first_line; i <= last_line; i++)
 	{
-		indentation_end = SSM(editor->sci, SCI_GETLINEINDENTPOSITION, i, 0);
+		indentation_end = sci_get_indent_position(editor->sci, i);
 		if (decrease)
 		{
 			line_start = SSM(editor->sci, SCI_POSITIONFROMLINE, i, 0);
diff --git a/src/editor.h b/src/editor.h
index 70b3c0e..871b12e 100644
--- a/src/editor.h
+++ b/src/editor.h
@@ -212,6 +212,8 @@ void editor_insert_alternative_whitespace(GeanyEditor *editor);
 
 void editor_indent(GeanyEditor *editor, gboolean increase);
 
+void editor_smart_indentation_for_lines(GeanyEditor *editor, gint first_line, gint last_line);
+
 void editor_smart_line_indentation(GeanyEditor *editor, gint pos);
 
 void editor_indentation_by_one_space(GeanyEditor *editor, gint pos, gboolean decrease);
diff --git a/src/keybindings.c b/src/keybindings.c
index 4e68723..4c487cd 100644
--- a/src/keybindings.c
+++ b/src/keybindings.c
@@ -371,6 +371,10 @@ static void init_default_kb(void)
 	keybindings_set_item(group, GEANY_KEYS_FORMAT_REFLOWPARAGRAPH, NULL,
 		GDK_j, GDK_CONTROL_MASK, "format_reflowparagraph", _("_Reflow Lines/Block"),
 		LW(reflow_lines_block1));
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_INSERTLINE_AFTER, NULL,
+		0, 0, "format_insertline_after", _("Insert new line after current"), NULL);
+	keybindings_set_item(group, GEANY_KEYS_FORMAT_INSERTLINE_BEFORE, NULL,
+		0, 0, "format_insertline_before", _("Insert new line before current"), NULL);
 
 	group = ADD_KB_GROUP(INSERT, _("Insert"), cb_func_insert_action);
 
@@ -2402,6 +2406,56 @@ static void reflow_paragraph(GeanyEditor *editor)
 }
 
 
+static void insert_line_after(GeanyEditor *editor)
+{
+	ScintillaObject *sci = editor->sci;
+
+	gchar *eol;
+	gint line = sci_get_current_line(sci);
+
+	switch (sci_get_eol_mode(sci))
+	{
+		case SC_EOL_CRLF: eol = "\r\n"; break;
+		case SC_EOL_CR: eol = "\r"; break;
+		default: eol = "\n"; break;
+	}
+
+	sci_start_undo_action(sci);
+	sci_insert_text(sci, sci_get_line_end_position(sci, line), eol);
+	editor_smart_indentation_for_lines(editor, line+1, line+1);
+	sci_set_current_position(sci, sci_get_line_end_position(sci, line+1), TRUE);
+	sci_end_undo_action(sci);
+}
+
+
+static void insert_line_before(GeanyEditor *editor)
+{
+	ScintillaObject *sci = editor->sci;
+
+	gchar *eol;
+	gint line = sci_get_current_line(sci);
+	gint linestart = sci_get_position_from_line(sci, line);
+
+	gint indentpos = sci_get_indent_position(sci, line);
+	gchar *indentation = sci_get_contents_range(sci, linestart, indentpos);
+
+	switch (sci_get_eol_mode(sci))
+	{
+		case SC_EOL_CRLF: eol = "\r\n"; break;
+		case SC_EOL_CR: eol = "\r"; break;
+		default: eol = "\n"; break;
+	}
+
+	sci_start_undo_action(sci);
+	sci_insert_text(sci, linestart, eol);
+	sci_insert_text(sci, linestart, indentation);
+	sci_set_current_position(sci, indentpos, TRUE);
+	sci_end_undo_action(sci);
+
+	g_free(indentation);
+}
+
+
 /* common function for format keybindings, only valid when scintilla has focus. */
 static gboolean cb_func_format_action(guint key_id)
 {
@@ -2459,6 +2513,12 @@ static gboolean cb_func_format_action(guint key_id)
 		case GEANY_KEYS_FORMAT_REFLOWPARAGRAPH:
 			reflow_paragraph(doc->editor);
 			break;
+		case GEANY_KEYS_FORMAT_INSERTLINE_AFTER:
+			insert_line_after(doc->editor);
+			break;
+		case GEANY_KEYS_FORMAT_INSERTLINE_BEFORE:
+			insert_line_before(doc->editor);
+			break;
 	}
 	return TRUE;
 }
diff --git a/src/keybindings.h b/src/keybindings.h
index b876b61..b16d36f 100644
--- a/src/keybindings.h
+++ b/src/keybindings.h
@@ -191,6 +191,8 @@ enum GeanyKeysFormatID
 	GEANY_KEYS_FORMAT_SENDTOCMD3,				/**< Keybinding. */
 	GEANY_KEYS_FORMAT_SENDTOVTE,				/**< Keybinding. */
 	GEANY_KEYS_FORMAT_REFLOWPARAGRAPH,			/**< Keybinding. */
+	GEANY_KEYS_FORMAT_INSERTLINE_AFTER,			/**< Keybinding. */
+	GEANY_KEYS_FORMAT_INSERTLINE_BEFORE,			/**< Keybinding. */
 	GEANY_KEYS_FORMAT_COUNT
 };
 
@@ -370,4 +372,3 @@ void keybindings_show_shortcuts(void);
 const GeanyKeyBinding *keybindings_check_event(GdkEventKey *ev, gint *group_id, gint *binding_id);
 
 #endif
-
diff --git a/src/sciwrappers.c b/src/sciwrappers.c
index 5371590..c2dc6dc 100644
--- a/src/sciwrappers.c
+++ b/src/sciwrappers.c
@@ -1220,3 +1220,9 @@ gint sci_text_width(ScintillaObject *sci, gint styleNumber, const gchar *text)
 {
 	return SSM(sci, SCI_TEXTWIDTH, styleNumber, (sptr_t) text);
 }
+
+
+gint sci_get_indent_position(ScintillaObject *sci, gint line)
+{
+	return SSM(sci, SCI_GETLINEINDENTPOSITION, line, 0);
+}
diff --git a/src/sciwrappers.h b/src/sciwrappers.h
index 7aa4d2d..81bfc88 100644
--- a/src/sciwrappers.h
+++ b/src/sciwrappers.h
@@ -185,4 +185,6 @@ void				sci_lines_split				(ScintillaObject *sci, gint pixelWidth);
 void				sci_lines_join				(ScintillaObject *sci);
 gint				sci_text_width				(ScintillaObject *sci, gint styleNumber, const gchar *text);
 
+gint				sci_get_indent_position		(ScintillaObject *sci, gint line);
+
 #endif
diff --git a/src/editor.c b/src/editor.c
index 74129c7..a29338e 100644
--- a/src/editor.c
+++ b/src/editor.c
@@ -76,6 +76,7 @@
 static GHashTable *snippet_hash = NULL;
 static GQueue *snippet_offsets = NULL;
 static gint snippet_cursor_insert_pos;
+static GtkAccelGroup *snippet_accel_group = NULL;
 
 /* holds word under the mouse or keyboard cursor */
 static gchar current_word[GEANY_MAX_WORD_LENGTH];
@@ -106,6 +107,12 @@ static enum
 static gchar indent[100];
 
 
+typedef struct {
+	guint key;
+	GdkModifierType mods;
+} Accel;
+
+
 static void on_new_line_added(GeanyEditor *editor);
 static gboolean handle_xml(GeanyEditor *editor, gint pos, gchar ch);
 static void insert_indent_after_line(GeanyEditor *editor, gint line);
@@ -119,12 +126,19 @@ static void editor_highlight_braces(GeanyEditor *editor, gint cur_pos);
 static void read_current_word(GeanyEditor *editor, gint pos, gchar *word, size_t wordlen,
 		const gchar *wc, gboolean stem);
 static gsize count_indent_size(GeanyEditor *editor, const gchar *base_indent);
+static void connect_snippet_keybinding(gchar *key, Accel *accel, gpointer user_data);
+static void on_snippet_keybinding_activate(gchar *key);
+static void gclosurenotify_call_free(gpointer data, GClosure *closure);
+static const gchar *snippets_find_completion_by_name(const gchar *type, const gchar *name);
+static gssize snippets_make_replacements(GeanyEditor *editor, GString *pattern,
+		gsize indent_size);
 
 
 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);
 }
 
 
@@ -134,10 +148,14 @@ void editor_snippets_init(void)
 	gchar *sysconfigfile, *userconfigfile;
 	gchar **groups_user, **groups_sys;
 	gchar **keys_user, **keys_sys;
-	gchar *value;
 	GKeyFile *sysconfig = g_key_file_new();
 	GKeyFile *userconfig = g_key_file_new();
 	GHashTable *tmp;
+	GHashTable *snippet_keybindings_hash =
+		g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+	snippet_accel_group = gtk_accel_group_new();
+	gtk_window_add_accel_group(GTK_WINDOW(main_widgets.window), snippet_accel_group);
 
 	snippet_offsets = g_queue_new();
 
@@ -162,15 +180,37 @@ void editor_snippets_init(void)
 	for (i = 0; i < len; i++)
 	{
 		keys_sys = g_key_file_get_keys(sysconfig, groups_sys[i], &len_keys, NULL);
-		/* create new hash table for the read section (=> filetype) */
-		tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
-		g_hash_table_insert(snippet_hash, g_strdup(groups_sys[i]), tmp);
 
-		for (j = 0; j < len_keys; j++)
+		if (strcmp(groups_sys[i], "Keybindings") == 0)
 		{
-			g_hash_table_insert(tmp, g_strdup(keys_sys[j]),
-						utils_get_setting_string(sysconfig, groups_sys[i], keys_sys[j], ""));
+			for (j = 0; j < len_keys; j++)
+			{
+				Accel accel;
+				gchar *accel_string = g_key_file_get_value(sysconfig, groups_sys[i], keys_sys[j], NULL);
+
+				gtk_accelerator_parse(accel_string, &accel.key, &accel.mods);
+				if (accel.key == 0 && accel.mods == 0)
+					g_warning("Can not parse accelerator \"%s\" from system-wide snippets.conf", accel_string);
+				else
+					g_hash_table_insert(snippet_keybindings_hash, g_strdup(keys_sys[j]),
+						g_memdup(&accel, sizeof(Accel)));
+
+				g_free(accel_string);
+			}
 		}
+		else
+		{
+			/* create new hash table for the read section (=> filetype) */
+			tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+			g_hash_table_insert(snippet_hash, g_strdup(groups_sys[i]), tmp);
+
+			for (j = 0; j < len_keys; j++)
+			{
+				g_hash_table_insert(tmp, g_strdup(keys_sys[j]),
+						g_key_file_get_string(sysconfig, groups_sys[i], keys_sys[j], NULL));
+			}
+		}
+
 		g_strfreev(keys_sys);
 	}
 
@@ -180,29 +220,46 @@ void editor_snippets_init(void)
 	{
 		keys_user = g_key_file_get_keys(userconfig, groups_user[i], &len_keys, NULL);
 
-		tmp = g_hash_table_lookup(snippet_hash, groups_user[i]);
-		if (tmp == NULL)
-		{	/* new key found, create hash table */
-			tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
-			g_hash_table_insert(snippet_hash, g_strdup(groups_user[i]), tmp);
+		if (strcmp(groups_user[i], "Keybindings") == 0)
+		{
+			for (j = 0; j < len_keys; j++)
+			{
+				Accel accel;
+				gchar *accel_string = g_key_file_get_value(userconfig, groups_user[i], keys_user[j], NULL);
+
+				gtk_accelerator_parse(accel_string, &accel.key, &accel.mods);
+				if (accel.key == 0 && accel.mods == 0)
+					g_warning("Can not parse accelerator \"%s\" from user snippets.conf", accel_string);
+				else
+					g_hash_table_insert(snippet_keybindings_hash, g_strdup(keys_user[j]),
+						g_memdup(&accel, sizeof(Accel)));
+
+				g_free(accel_string);
+			}
 		}
-		for (j = 0; j < len_keys; j++)
+		else
 		{
-			value = g_hash_table_lookup(tmp, keys_user[j]);
-			if (value == NULL)
-			{	/* value = NULL means the key doesn't yet exist, so insert */
-				g_hash_table_insert(tmp, g_strdup(keys_user[j]),
-						utils_get_setting_string(userconfig, groups_user[i], keys_user[j], ""));
+			tmp = g_hash_table_lookup(snippet_hash, groups_user[i]);
+			if (tmp == NULL)
+			{	/* new key found, create hash table */
+				tmp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+				g_hash_table_insert(snippet_hash, g_strdup(groups_user[i]), tmp);
 			}
-			else
-			{	/* old key and value will be freed by destroy function (g_free) */
-				g_hash_table_replace(tmp, g_strdup(keys_user[j]),
-						utils_get_setting_string(userconfig, groups_user[i], keys_user[j], ""));
+
+			for (j = 0; j < len_keys; j++)
+			{
+				g_hash_table_insert(tmp, g_strdup(keys_user[j]),
+						g_key_file_get_string(userconfig, groups_user[i], keys_user[j], NULL));
 			}
 		}
+
 		g_strfreev(keys_user);
 	}
 
+	/* connect snippet keybindings */
+	g_hash_table_foreach(snippet_keybindings_hash, (GHFunc)connect_snippet_keybinding, NULL);
+	g_hash_table_destroy(snippet_keybindings_hash);
+
 	g_free(sysconfigfile);
 	g_free(userconfigfile);
 	g_strfreev(groups_sys);
@@ -212,6 +269,61 @@ void editor_snippets_init(void)
 }
 
 
+static void connect_snippet_keybinding(gchar *key, Accel *accel, gpointer user_data)
+{
+	gtk_accel_group_connect(snippet_accel_group, accel->key, accel->mods, 0,
+		g_cclosure_new_swap((GCallback)on_snippet_keybinding_activate,
+			g_strdup(key), gclosurenotify_call_free));
+}
+
+
+static void gclosurenotify_call_free(gpointer data, GClosure *closure)
+{
+	g_free(data);
+}
+
+
+static void on_snippet_keybinding_activate(gchar *key)
+{
+	GeanyDocument *doc = document_get_current();
+	const gchar *s;
+	GHashTable *specials;
+	GString *pattern;
+	gint pos, line, indent_width, cursor_pos;
+
+	if (!doc)
+	{
+		utils_beep();
+		return;
+	}
+
+	s = snippets_find_completion_by_name(doc->file_type->name, key);
+	if (!s) /* allow user to specify keybindings for "special" snippets */
+	{
+		specials = g_hash_table_lookup(snippet_hash, "Special");
+		if (G_LIKELY(specials != NULL))
+			s = g_hash_table_lookup(specials, key);
+	}
+	if (!s)
+	{
+		utils_beep();
+		return;
+	}
+
+	pos = sci_get_current_position(doc->editor->sci);
+	line = sci_get_line_from_position(doc->editor->sci, pos);
+	indent_width = sci_get_line_indentation(doc->editor->sci, line);
+
+	pattern = g_string_new(s);
+	cursor_pos = snippets_make_replacements(doc->editor, pattern, indent_width);
+
+	editor_insert_text_block(doc->editor, pattern->str, pos, cursor_pos, indent_width, FALSE);
+	sci_scroll_caret(doc->editor->sci);
+
+	g_string_free(pattern, TRUE);
+}
+
+
 static gboolean on_editor_button_press_event(GtkWidget *widget, GdkEventButton *event,
 											 gpointer data)
 {
diff --git a/src/editor.c b/src/editor.c
index a29338e..5e9d6e7 100644
--- a/src/editor.c
+++ b/src/editor.c
@@ -1315,6 +1315,33 @@ static gint get_python_indent(ScintillaObject *sci, gint line)
 }
 
 
+static gint get_xml_indent(ScintillaObject *sci, gint line)
+{
+	gboolean opened_tag = FALSE;
+	gint last_char = sci_get_line_end_position(sci, line) - 1;
+
+	if (sci_get_char_at(sci, last_char) == '>' &&
+		sci_get_char_at(sci, last_char - 1) != '/')
+	{
+		gint style = sci_get_style_at(sci, last_char);
+
+		if (style == SCE_H_TAG || style == SCE_H_TAGUNKNOWN)
+		{
+			gchar *line_contents = sci_get_contents_range(sci,
+				sci_get_position_from_line(sci, line), last_char);
+			gchar *tagopen = strrchr(line_contents, '<');
+
+			if (tagopen && *(tagopen+1) != '/')
+				opened_tag = TRUE;
+
+			g_free(line_contents);
+		}
+	}
+
+	return opened_tag ? 1 : 0;
+}
+
+
 static gint get_indent_size_after_line(GeanyEditor *editor, gint line)
 {
 	ScintillaObject *sci = editor->sci;
@@ -1327,11 +1354,25 @@ static gint get_indent_size_after_line(GeanyEditor *editor, gint line)
 
 	if (iprefs->auto_indent_mode > GEANY_AUTOINDENT_BASIC)
 	{
+		gint additional_indent = 0;
+
 		if (lexer_has_braces(sci))
-			size += iprefs->width * get_brace_indent(sci, line);
+			additional_indent = iprefs->width * get_brace_indent(sci, line);
 		else
 		if (FILETYPE_ID(editor->document->file_type) == GEANY_FILETYPES_PYTHON)
-			size += iprefs->width * get_python_indent(sci, line);
+			additional_indent = iprefs->width * get_python_indent(sci, line);
+
+		/* HTML lexer "has braces" because of PHP and JavaScript.  If get_brace_indent() did not
+		 * recommend us to insert additional indent, we are probably not in PHP/JavaScript chunk and
+		 * should make the XML-related check */
+		if (additional_indent == 0 && (
+			FILETYPE_ID(editor->document->file_type) == GEANY_FILETYPES_HTML ||
+			FILETYPE_ID(editor->document->file_type) == GEANY_FILETYPES_XML))
+		{
+			size += iprefs->width * get_xml_indent(sci, line);
+		}
+
+		size += additional_indent;
 	}
 	return size;
 }
_______________________________________________
Geany-devel mailing list
Geany-devel@uvena.de
http://lists.uvena.de/cgi-bin/mailman/listinfo/geany-devel

Reply via email to