[gnome-builder/wip/xml-indent] auto-indent: add a rudimentary xml indenter



commit 495448f3aa0b4f83b301f8642d5221e26a989585
Author: Christian Hergert <christian hergert me>
Date:   Thu Oct 16 22:20:39 2014 -0700

    auto-indent: add a rudimentary xml indenter
    
    Emphasis on the rudamentary.

 src/auto-indent/gb-source-auto-indenter-xml.c |  324 +++++++++++++++++++++++++
 src/auto-indent/gb-source-auto-indenter-xml.h |   56 +++++
 src/editor/gb-editor-tab.c                    |    3 +
 src/gnome-builder.mk                          |    2 +
 4 files changed, 385 insertions(+), 0 deletions(-)
---
diff --git a/src/auto-indent/gb-source-auto-indenter-xml.c b/src/auto-indent/gb-source-auto-indenter-xml.c
new file mode 100644
index 0000000..0625f53
--- /dev/null
+++ b/src/auto-indent/gb-source-auto-indenter-xml.c
@@ -0,0 +1,324 @@
+/* gb-source-auto-indenter-xml.c
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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 "indent-xml"
+
+#include "gb-log.h"
+#include "gb-source-auto-indenter-xml.h"
+
+/*
+ * 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 (GbSourceAutoIndenterXml,
+               gb_source_auto_indenter_xml,
+               GB_TYPE_SOURCE_AUTO_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 (GbSourceAutoIndenterXml *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, ' ');
+}
+
+GbSourceAutoIndenter *
+gb_source_auto_indenter_xml_new (void)
+{
+  return g_object_new (GB_TYPE_SOURCE_AUTO_INDENTER_XML, NULL);
+}
+
+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 *
+gb_source_auto_indenter_xml_indent (GbSourceAutoIndenterXml *xml,
+                                    GtkTextIter             *begin,
+                                    GtkTextIter             *end,
+                                    gint                    *cursor_offset,
+                                    guint                    tab_width)
+{
+  GtkTextIter match_begin;
+  GString *str;
+  guint offset;
+
+  g_return_val_if_fail (GB_IS_SOURCE_AUTO_INDENTER_XML (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);
+      GOTO (cleanup);
+    }
+
+  /* do nothing */
+
+cleanup:
+  return g_string_free (str, (str->len == 0));
+}
+
+static gchar *
+gb_source_auto_indenter_xml_maybe_unindent (GbSourceAutoIndenterXml *xml,
+                                            GtkTextIter             *begin,
+                                            GtkTextIter             *end)
+{
+  GtkTextIter tmp;
+  gunichar ch;
+
+  g_return_val_if_fail (GB_IS_SOURCE_AUTO_INDENTER_XML (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
+gb_source_auto_indenter_xml_is_trigger (GbSourceAutoIndenter *indenter,
+                                        GdkEventKey          *event)
+{
+  switch (event->keyval)
+    {
+    case GDK_KEY_Return:
+    case GDK_KEY_KP_Enter:
+    case GDK_KEY_slash:
+      return TRUE;
+
+    default:
+      break;
+    }
+
+  return FALSE;
+}
+
+static gchar *
+gb_source_auto_indenter_xml_format (GbSourceAutoIndenter *indenter,
+                                    GtkTextView          *view,
+                                    GtkTextBuffer        *buffer,
+                                    GtkTextIter          *begin,
+                                    GtkTextIter          *end,
+                                    gint                 *cursor_offset,
+                                    GdkEventKey          *trigger)
+{
+  GbSourceAutoIndenterXml *xml = (GbSourceAutoIndenterXml *)indenter;
+  guint tab_width = 2;
+
+  g_return_val_if_fail (GB_IS_SOURCE_AUTO_INDENTER_XML (xml), NULL);
+
+  *cursor_offset = 0;
+
+  if (GTK_SOURCE_IS_VIEW (view))
+    tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (view));
+
+  /* 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:
+      return gb_source_auto_indenter_xml_indent (xml, begin, end, cursor_offset,
+                                                 tab_width);
+
+    case GDK_KEY_slash:
+      return gb_source_auto_indenter_xml_maybe_unindent (xml, begin, end);
+
+    default:
+      g_return_val_if_reached (NULL);
+    }
+
+  return NULL;
+}
+
+static void
+gb_source_auto_indenter_xml_class_init (GbSourceAutoIndenterXmlClass *klass)
+{
+  GbSourceAutoIndenterClass *parent = GB_SOURCE_AUTO_INDENTER_CLASS (klass);
+
+  parent->format = gb_source_auto_indenter_xml_format;
+  parent->is_trigger = gb_source_auto_indenter_xml_is_trigger;
+}
+
+static void
+gb_source_auto_indenter_xml_init (GbSourceAutoIndenterXml *self)
+{
+}
diff --git a/src/auto-indent/gb-source-auto-indenter-xml.h b/src/auto-indent/gb-source-auto-indenter-xml.h
new file mode 100644
index 0000000..96bdeea
--- /dev/null
+++ b/src/auto-indent/gb-source-auto-indenter-xml.h
@@ -0,0 +1,56 @@
+/* gb-source-auto-indenter-xml.h
+ *
+ * Copyright (C) 2014 Christian Hergert <christian hergert me>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 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 GB_SOURCE_AUTO_INDENTER_XML_H
+#define GB_SOURCE_AUTO_INDENTER_XML_H
+
+#include "gb-source-auto-indenter.h"
+
+G_BEGIN_DECLS
+
+#define GB_TYPE_SOURCE_AUTO_INDENTER_XML            (gb_source_auto_indenter_xml_get_type())
+#define GB_SOURCE_AUTO_INDENTER_XML(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_SOURCE_AUTO_INDENTER_XML, GbSourceAutoIndenterXml))
+#define GB_SOURCE_AUTO_INDENTER_XML_CONST(obj)      (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GB_TYPE_SOURCE_AUTO_INDENTER_XML, GbSourceAutoIndenterXml const))
+#define GB_SOURCE_AUTO_INDENTER_XML_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  
GB_TYPE_SOURCE_AUTO_INDENTER_XML, GbSourceAutoIndenterXmlClass))
+#define GB_IS_SOURCE_AUTO_INDENTER_XML(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GB_TYPE_SOURCE_AUTO_INDENTER_XML))
+#define GB_IS_SOURCE_AUTO_INDENTER_XML_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  
GB_TYPE_SOURCE_AUTO_INDENTER_XML))
+#define GB_SOURCE_AUTO_INDENTER_XML_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  
GB_TYPE_SOURCE_AUTO_INDENTER_XML, GbSourceAutoIndenterXmlClass))
+
+typedef struct _GbSourceAutoIndenterXml        GbSourceAutoIndenterXml;
+typedef struct _GbSourceAutoIndenterXmlClass   GbSourceAutoIndenterXmlClass;
+typedef struct _GbSourceAutoIndenterXmlPrivate GbSourceAutoIndenterXmlPrivate;
+
+struct _GbSourceAutoIndenterXml
+{
+  GbSourceAutoIndenter parent;
+
+  /*< private >*/
+  GbSourceAutoIndenterXmlPrivate *priv;
+};
+
+struct _GbSourceAutoIndenterXmlClass
+{
+  GbSourceAutoIndenterClass parent;
+};
+
+GType                 gb_source_auto_indenter_xml_get_type (void);
+GbSourceAutoIndenter *gb_source_auto_indenter_xml_new      (void);
+
+G_END_DECLS
+
+#endif /* GB_SOURCE_AUTO_INDENTER_XML_H */
diff --git a/src/editor/gb-editor-tab.c b/src/editor/gb-editor-tab.c
index 8bc9ee2..cf17c4a 100644
--- a/src/editor/gb-editor-tab.c
+++ b/src/editor/gb-editor-tab.c
@@ -31,6 +31,7 @@
 #include "gb-rgba.h"
 #include "gb-source-auto-indenter.h"
 #include "gb-source-auto-indenter-c.h"
+#include "gb-source-auto-indenter-xml.h"
 #include "gb-source-change-gutter-renderer.h"
 #include "gb-source-highlight-menu.h"
 #include "gb-source-snippet.h"
@@ -438,6 +439,8 @@ gb_editor_tab_language_changed (GbEditorTab      *tab,
 
       if (g_str_equal (lang_id, "c") || g_str_equal (lang_id, "chdr"))
         indenter = gb_source_auto_indenter_c_new ();
+      if (g_str_equal (lang_id, "xml") || g_str_equal (lang_id, "html"))
+        indenter = gb_source_auto_indenter_xml_new ();
     }
 
   gb_source_view_set_auto_indenter (tab->priv->source_view, indenter);
diff --git a/src/gnome-builder.mk b/src/gnome-builder.mk
index 76e1d82..36aa68f 100644
--- a/src/gnome-builder.mk
+++ b/src/gnome-builder.mk
@@ -13,6 +13,8 @@ libgnome_builder_la_SOURCES = \
        src/auto-indent/gb-source-auto-indenter.h \
        src/auto-indent/gb-source-auto-indenter-c.c \
        src/auto-indent/gb-source-auto-indenter-c.h \
+       src/auto-indent/gb-source-auto-indenter-xml.c \
+       src/auto-indent/gb-source-auto-indenter-xml.h \
        src/commands/gb-command.c \
        src/commands/gb-command.h \
        src/commands/gb-command-bar.c \


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]