[gnome-builder/wip/libide] libide: move xml indenter into libide
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/libide] libide: move xml indenter into libide
- Date: Wed, 18 Feb 2015 07:22:36 +0000 (UTC)
commit 464beb420bc0fc5885480ed4a1994d824aa21574
Author: Christian Hergert <christian hergert me>
Date: Tue Feb 17 23:22:28 2015 -0800
libide: move xml indenter into libide
libide/Makefile.am | 2 +
libide/xml/ide-xml-indenter.c | 438 +++++++++++++++++++++++++++++++++++++++++
libide/xml/ide-xml-indenter.h | 32 +++
3 files changed, 472 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 7a717cb..e65bfa3 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -138,6 +138,8 @@ libide_1_0_la_public_sources = \
libide/ide.h \
libide/local/ide-local-device.c \
libide/local/ide-local-device.h \
+ libide/xml/ide-xml-indenter.c \
+ libide/xml/ide-xml-indenter.h \
$(NULL)
libide_1_0_la_SOURCES = \
diff --git a/libide/xml/ide-xml-indenter.c b/libide/xml/ide-xml-indenter.c
new file mode 100644
index 0000000..88ae18d
--- /dev/null
+++ b/libide/xml/ide-xml-indenter.c
@@ -0,0 +1,438 @@
+/* gb-source-auto-indenter-xml.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-xml-indenter"
+
+#include <gtksourceview/gtksource.h>
+#include <string.h>
+
+#include "ide-xml-indenter.h"
+
+#define ENTRY
+#define EXIT return
+#define GOTO(_l) goto _l
+#define RETURN(_v) return _v
+
+struct _IdeXmlIndenter
+{
+ IdeIndenter parent_class;
+};
+
+/*
+ * TODO:
+ *
+ * This is very naive. But let's see if it gets the job done enough to not
+ * be super annoying. Things like indent_width belong as fields in a private
+ * structure.
+ *
+ * Anywho, if you want to own this module, go for it.
+ */
+
+#define INDENT_WIDTH 2
+
+G_DEFINE_TYPE (IdeXmlIndenter, ide_xml_indenter, IDE_TYPE_INDENTER)
+
+static gunichar
+text_iter_peek_next_char (const GtkTextIter *location)
+{
+ GtkTextIter iter = *location;
+
+ if (gtk_text_iter_forward_char (&iter))
+ return gtk_text_iter_get_char (&iter);
+
+ return 0;
+}
+
+static gunichar
+text_iter_peek_prev_char (const GtkTextIter *location)
+{
+ GtkTextIter iter = *location;
+
+ if (gtk_text_iter_backward_char (&iter))
+ return gtk_text_iter_get_char (&iter);
+
+ return 0;
+}
+
+static void
+build_indent (IdeXmlIndenter *xml,
+ guint line_offset,
+ GtkTextIter *matching_line,
+ GString *str)
+{
+ GtkTextIter iter;
+ gunichar ch;
+
+ if (!line_offset)
+ return;
+
+ gtk_text_buffer_get_iter_at_line (gtk_text_iter_get_buffer (matching_line),
+ &iter,
+ gtk_text_iter_get_line (matching_line));
+
+ do {
+ ch = gtk_text_iter_get_char (&iter);
+
+ switch (ch)
+ {
+ case '\t':
+ case ' ':
+ g_string_append_unichar (str, ch);
+ break;
+
+ default:
+ g_string_append_c (str, ' ');
+ break;
+ }
+ } while (gtk_text_iter_forward_char (&iter) &&
+ (gtk_text_iter_compare (&iter, matching_line) <= 0) &&
+ (str->len < line_offset));
+
+ while (str->len < line_offset)
+ g_string_append_c (str, ' ');
+}
+
+static gboolean
+text_iter_in_cdata (const GtkTextIter *location)
+{
+ GtkTextIter iter = *location;
+ gboolean ret = FALSE;
+
+ if (gtk_text_iter_backward_search (&iter, "<![CDATA[",
+ GTK_TEXT_SEARCH_TEXT_ONLY,
+ NULL, &iter, NULL))
+ {
+ if (!gtk_text_iter_forward_search (&iter, "]]>",
+ GTK_TEXT_SEARCH_TEXT_ONLY,
+ NULL, NULL, location))
+ {
+ ret = TRUE;
+ GOTO (cleanup);
+ }
+ }
+
+cleanup:
+ return ret;
+}
+
+static gboolean
+text_iter_backward_to_element_start (const GtkTextIter *iter,
+ GtkTextIter *match_begin)
+{
+ GtkTextIter tmp = *iter;
+ gboolean ret = FALSE;
+ gint depth = 0;
+
+ g_return_val_if_fail (iter, FALSE);
+ g_return_val_if_fail (match_begin, FALSE);
+
+ while (gtk_text_iter_backward_char (&tmp))
+ {
+ gunichar ch;
+
+ ch = gtk_text_iter_get_char (&tmp);
+
+ if ((ch == '/') && (text_iter_peek_prev_char (&tmp) == '<'))
+ {
+ gtk_text_iter_backward_char (&tmp);
+ depth++;
+ }
+ else if ((ch == '/') && (text_iter_peek_next_char (&tmp) == '>'))
+ {
+ depth++;
+ }
+ else if ((ch == '<') && (text_iter_peek_next_char (&tmp) != '!'))
+ {
+ depth--;
+ if (depth < 0)
+ {
+ *match_begin = tmp;
+ ret = TRUE;
+ GOTO (cleanup);
+ }
+ }
+ }
+
+cleanup:
+ return ret;
+}
+
+static gchar *
+ide_xml_indenter_indent (IdeXmlIndenter *xml,
+ GtkTextIter *begin,
+ GtkTextIter *end,
+ gint *cursor_offset,
+ guint tab_width)
+{
+ GtkTextIter match_begin;
+ GString *str;
+ guint offset;
+
+ g_return_val_if_fail (IDE_IS_XML_INDENTER (xml), NULL);
+ g_return_val_if_fail (begin, NULL);
+ g_return_val_if_fail (end, NULL);
+
+ str = g_string_new (NULL);
+
+ if (text_iter_backward_to_element_start (begin, &match_begin))
+ {
+ offset = gtk_text_iter_get_line_offset (&match_begin);
+ build_indent (xml, offset + INDENT_WIDTH, &match_begin, str);
+
+ /*
+ * If immediately after our cursor is a closing tag, we will move it to
+ * a line after our indent line.
+ */
+ if ('<' == gtk_text_iter_get_char (end) &&
+ '/' == text_iter_peek_next_char (end))
+ {
+ GString *str2;
+
+ str2 = g_string_new (NULL);
+ build_indent (xml, offset, &match_begin, str2);
+
+ g_string_append (str, "\n");
+ g_string_append (str, str2->str);
+
+ *cursor_offset = -str2->len - 1;
+
+ g_string_free (str2, TRUE);
+ }
+
+ GOTO (cleanup);
+ }
+
+ /* do nothing */
+
+cleanup:
+ return g_string_free (str, (str->len == 0));
+}
+
+static gchar *
+ide_xml_indenter_maybe_unindent (IdeXmlIndenter *xml,
+ GtkTextIter *begin,
+ GtkTextIter *end)
+{
+ GtkTextIter tmp;
+ gunichar ch;
+
+ g_return_val_if_fail (IDE_IS_XML_INDENTER (xml), NULL);
+ g_return_val_if_fail (begin, NULL);
+ g_return_val_if_fail (end, NULL);
+
+ tmp = *begin;
+
+ if (!gtk_text_iter_backward_char (&tmp))
+ return NULL;
+
+ if (('/' == gtk_text_iter_get_char (&tmp)) &&
+ gtk_text_iter_backward_char (&tmp) &&
+ ('<' == gtk_text_iter_get_char (&tmp)) &&
+ (ch = text_iter_peek_prev_char (&tmp)) &&
+ ((ch == ' ') || (ch == '\t')))
+ {
+ if (ch == '\t')
+ {
+ gtk_text_iter_backward_char (&tmp);
+ *begin = tmp;
+ return g_strdup ("</");
+ }
+ else
+ {
+ gint count = INDENT_WIDTH;
+
+ while (count)
+ {
+ if (!gtk_text_iter_backward_char (&tmp) ||
+ !(ch = gtk_text_iter_get_char (&tmp)) ||
+ (ch != ' '))
+ return NULL;
+ count--;
+ if (count == 0)
+ GOTO (success);
+ }
+ }
+ }
+
+ return NULL;
+
+success:
+ *begin = tmp;
+ return g_strdup ("</");
+}
+
+static gboolean
+find_end (gunichar ch,
+ gpointer user_data)
+{
+ return (ch == '>' || g_unichar_isspace (ch));
+}
+
+static gchar *
+ide_xml_indenter_maybe_add_closing (IdeXmlIndenter *xml,
+ GtkTextIter *begin,
+ GtkTextIter *end,
+ gint *cursor_offset)
+{
+ GtkTextIter match_begin;
+ GtkTextIter match_end;
+ GtkTextIter copy;
+
+ g_return_val_if_fail (IDE_IS_XML_INDENTER (xml), NULL);
+ g_return_val_if_fail (begin, NULL);
+ g_return_val_if_fail (end, NULL);
+
+ copy = *begin;
+
+ gtk_text_iter_backward_char (©);
+ gtk_text_iter_backward_char (©);
+
+ if (gtk_text_iter_get_char (©) == '/')
+ return NULL;
+
+ copy = *begin;
+
+ if (gtk_text_iter_backward_search (©, "<", GTK_TEXT_SEARCH_TEXT_ONLY,
+ &match_begin, &match_end, NULL))
+ {
+ gchar *text;
+
+ /* avoid closing elements on spurious > */
+ gtk_text_iter_backward_char (©);
+ text = gtk_text_iter_get_slice (&match_begin, ©);
+ if (strchr (text, '>'))
+ {
+ g_free (text);
+ return NULL;
+ }
+ g_free (text);
+
+ gtk_text_iter_forward_char (&match_begin);
+ if (gtk_text_iter_get_char (&match_begin) == '/')
+ return NULL;
+
+ match_end = match_begin;
+
+ if (gtk_text_iter_forward_find_char (&match_end, find_end, NULL, begin))
+ {
+ gchar *slice;
+ gchar *ret = NULL;
+
+ slice = gtk_text_iter_get_slice (&match_begin, &match_end);
+
+ if (slice && *slice && *slice != '!')
+ {
+ ret = g_strdup_printf ("</%s>", slice);
+ *cursor_offset = -strlen (ret);
+ }
+
+ g_free (slice);
+
+ return ret;
+ }
+ }
+
+ return NULL;
+}
+
+static gboolean
+ide_xml_indenter_is_trigger (IdeIndenter *indenter,
+ GdkEventKey *event)
+{
+ switch (event->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_slash:
+ case GDK_KEY_greater:
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static gchar *
+ide_xml_indenter_format (IdeIndenter *indenter,
+ GtkTextView *view,
+ GtkTextIter *begin,
+ GtkTextIter *end,
+ gint *cursor_offset,
+ GdkEventKey *trigger)
+{
+ IdeXmlIndenter *xml = (IdeXmlIndenter *)indenter;
+ GtkTextBuffer *buffer;
+ guint tab_width = 2;
+ gint indent_width = -1;
+
+ g_return_val_if_fail (IDE_IS_XML_INDENTER (xml), NULL);
+
+ buffer = gtk_text_view_get_buffer (view);
+
+ *cursor_offset = 0;
+
+ if (GTK_SOURCE_IS_VIEW (view))
+ {
+ tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (view));
+ indent_width = gtk_source_view_get_indent_width (GTK_SOURCE_VIEW (view));
+ if (indent_width != -1)
+ tab_width = indent_width;
+ }
+
+ /* do nothing if we are in a cdata section */
+ if (text_iter_in_cdata (begin))
+ return NULL;
+
+ switch (trigger->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ if ((trigger->state & GDK_SHIFT_MASK) == 0)
+ return ide_xml_indenter_indent (xml, begin, end, cursor_offset,
+ tab_width);
+ return NULL;
+
+ case GDK_KEY_slash:
+ return ide_xml_indenter_maybe_unindent (xml, begin, end);
+
+ case GDK_KEY_greater:
+ return ide_xml_indenter_maybe_add_closing (xml, begin, end,
+ cursor_offset);
+
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ return NULL;
+}
+
+static void
+ide_xml_indenter_class_init (IdeXmlIndenterClass *klass)
+{
+ IdeIndenterClass *parent = IDE_INDENTER_CLASS (klass);
+
+ parent->format = ide_xml_indenter_format;
+ parent->is_trigger = ide_xml_indenter_is_trigger;
+}
+
+static void
+ide_xml_indenter_init (IdeXmlIndenter *self)
+{
+}
diff --git a/libide/xml/ide-xml-indenter.h b/libide/xml/ide-xml-indenter.h
new file mode 100644
index 0000000..7886f10
--- /dev/null
+++ b/libide/xml/ide-xml-indenter.h
@@ -0,0 +1,32 @@
+/* gb-source-auto-indenter-xml.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This file is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_XML_INDENTER_H
+#define IDE_XML_INDENTER_H
+
+#include "ide-indenter.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_XML_INDENTER (ide_xml_indenter_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeXmlIndenter, ide_xml_indenter, IDE, XML_INDENTER, IdeIndenter)
+
+G_END_DECLS
+
+#endif /* IDE_XML_INDENTER_H */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]