[gnome-builder/wip/slaf/vim-textobjects] vim: add tag text-object support



commit 24087f24b52d8795f2ecd70d4768196e25f428b8
Author: Sebastien Lafargue <slafargue gnome org>
Date:   Wed Oct 28 12:11:45 2015 +0100

    vim: add tag text-object support
    
    use of a new select-tag signal with exclusive
    as parameter.

 libide/ide-source-view-movements.c |  495 ++++++++++++++++++++++++++++++++++++
 libide/ide-source-view-movements.h |    4 +
 libide/ide-source-view.c           |   23 ++
 libide/ide-source-view.h           |    2 +
 4 files changed, 524 insertions(+), 0 deletions(-)
---
diff --git a/libide/ide-source-view-movements.c b/libide/ide-source-view-movements.c
index 28bfc49..d22b0dc 100644
--- a/libide/ide-source-view-movements.c
+++ b/libide/ide-source-view-movements.c
@@ -18,6 +18,8 @@
 
 #define G_LOG_DOMAIN "ide-source-view"
 
+#include <string.h>
+
 #include "ide-debug.h"
 #include "ide-enums.h"
 #include "ide-internal.h"
@@ -64,6 +66,30 @@ typedef struct
   gboolean         string_mode;
 } MatchingBracketState;
 
+typedef enum
+{
+  HTML_TAG_KIND_ERROR,
+  HTML_TAG_KIND_OPEN,
+  HTML_TAG_KIND_CLOSE,
+  HTML_TAG_KIND_EMPTY,
+  HTML_TAG_KIND_STRAY_END,
+  HTML_TAG_KIND_COMMENT
+} HtmlTagKind;
+
+typedef struct
+{
+  GtkTextIter  begin;
+  GtkTextIter  end;
+  gchar       *name;
+  HtmlTagKind  kind;
+} HtmlTag;
+
+typedef struct
+{
+  HtmlTag *left;
+  HtmlTag *right;
+} HtmlElement;
+
 static gboolean
 is_single_line_selection (const GtkTextIter *begin,
                           const GtkTextIter *end)
@@ -1744,3 +1770,472 @@ _ide_source_view_select_inner (IdeSourceView *self,
 
     }
 }
+
+static gboolean
+html_tag_predicate (GtkTextIter *iter,
+                    gunichar     ch,
+                    gpointer     user_data)
+{
+  GtkTextIter near;
+  gunichar bound = GPOINTER_TO_UINT (user_data);
+
+  if (ch == bound)
+    {
+      if (!gtk_text_iter_starts_line (iter))
+        {
+          near = *iter;
+          gtk_text_iter_backward_char (&near);
+
+          return (gtk_text_iter_get_char (&near) != '\\');
+        }
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/* Iter need to be at the start of the name */
+static gchar *
+get_html_tag_name (GtkTextIter *iter)
+{
+  GtkTextIter start = *iter;
+  gunichar ch;
+
+  do
+    {
+      ch = gtk_text_iter_get_char (iter);
+      if (!(g_unichar_isalnum (ch) || ch == '-' || ch == '_' || ch == '.'))
+        break;
+    } while (gtk_text_iter_forward_char(iter));
+
+  return gtk_text_iter_get_text (&start, iter);
+}
+
+static gboolean
+find_tag_end (GtkTextIter *cursor)
+{
+  gunichar ch;
+  gunichar previous = 0;
+
+  while ((ch = gtk_text_iter_get_char (cursor)))
+    {
+      if (previous == '\\')
+        {
+          previous = 0;
+          gtk_text_iter_forward_char (cursor);
+          continue;
+        }
+
+      if (ch == '>')
+        return TRUE;
+      else if (ch == '<')
+        return FALSE;
+
+      previous = ch;
+      gtk_text_iter_forward_char (cursor);
+    }
+
+  return FALSE;
+}
+
+static gboolean
+find_chars (GtkTextIter *cursor,
+            GtkTextIter *end,
+            const gchar *str,
+            gboolean     only_at_start)
+{
+  const gchar *base_str;
+  const gchar *limit;
+  GtkTextIter base_cursor;
+  gboolean is_buffer_end = FALSE;
+
+  g_return_val_if_fail (!ide_str_empty0 (str), FALSE);
+
+  limit = str + strlen (str);
+  base_str = str;
+  base_cursor = *cursor;
+  do
+    {
+      *cursor = base_cursor;
+      do
+        {
+          if (gtk_text_iter_get_char (cursor) != g_utf8_get_char (str))
+            {
+              if (only_at_start)
+                return FALSE;
+              else
+                break;
+            }
+
+          str = g_utf8_find_next_char (str, limit);
+          if (str == NULL)
+            {
+              *end = *cursor;
+              gtk_text_iter_forward_char (end);
+
+              *cursor = base_cursor;
+              return TRUE;
+            }
+
+        } while ((is_buffer_end = gtk_text_iter_forward_char (cursor)));
+
+      if (is_buffer_end)
+        return FALSE;
+      else
+        str = base_str;
+    } while (gtk_text_iter_forward_char (&base_cursor));
+
+  return FALSE;
+}
+
+/* iter is updated to the left of the tag for a GTK_DIR_LEFT direction or in case
+ * of error in the tag, and to the right of the tag for a GTK_DIR_RIGHT direction.
+ * If no tag can be found, NULL is returned and iter equal the corresponding buffer bound.
+ */
+static HtmlTag *
+find_html_tag (GtkTextIter      *iter,
+               GtkDirectionType  direction)
+{
+  GtkTextIter cursor;
+  GtkTextIter end;
+  HtmlTag *tag;
+  gchar *name;
+  gunichar ch;
+  gboolean ret;
+
+  g_return_val_if_fail (direction == GTK_DIR_LEFT || direction == GTK_DIR_RIGHT, NULL);
+
+  if (direction == GTK_DIR_LEFT)
+    ret = _ide_vim_iter_backward_find_char (iter, html_tag_predicate, GUINT_TO_POINTER ('<'), NULL);
+  else
+    ret = (gtk_text_iter_get_char (iter) == '<') ||
+          _ide_vim_iter_forward_find_char (iter, html_tag_predicate, GUINT_TO_POINTER ('<'), NULL);
+
+  if (!ret)
+    return NULL;
+
+  tag = g_new0 (HtmlTag, 1);
+  tag->kind = HTML_TAG_KIND_OPEN;
+  cursor = tag->begin = tag->end = *iter;
+
+  gtk_text_iter_forward_char (&cursor);
+  if (gtk_text_iter_is_end (&cursor))
+    {
+      tag->kind = HTML_TAG_KIND_ERROR;
+      tag->end = cursor;
+
+      return tag;
+    }
+
+  ch = gtk_text_iter_get_char (&cursor);
+  if (ch == '/')
+    {
+      tag->kind = HTML_TAG_KIND_CLOSE;
+      gtk_text_iter_forward_char (&cursor);
+    }
+  else if (ch == '>')
+    {
+      tag->kind = HTML_TAG_KIND_EMPTY;
+      gtk_text_iter_forward_char (&cursor);
+      if (direction == GTK_DIR_RIGHT)
+        *iter = cursor;
+
+      tag->end = cursor;
+
+      return tag;
+    }
+  else if (find_chars (&cursor, &end, "!--", TRUE))
+    {
+      tag->kind = HTML_TAG_KIND_COMMENT;
+      cursor = end;
+      if (find_chars (&cursor, &end, "-->", FALSE))
+        {
+          tag->end = end;
+          if (direction == GTK_DIR_RIGHT)
+            *iter = tag->end;
+        }
+      else
+        {
+          tag->kind = HTML_TAG_KIND_ERROR;
+          tag->end = cursor;
+        }
+
+      return tag;
+    }
+
+  name = get_html_tag_name (&cursor);
+  if (ide_str_empty0 (name))
+    {
+      g_free (name);
+      tag->kind = HTML_TAG_KIND_ERROR;
+      tag->end = cursor;
+
+      return tag;
+    }
+  else
+    {
+      tag->name = g_utf8_casefold (name, -1);
+      g_free (name);
+    }
+
+  if (!find_tag_end (&cursor))
+    {
+      tag->kind = HTML_TAG_KIND_ERROR;
+      tag->end = cursor;
+
+      return tag;
+    }
+
+  tag->end = cursor;
+  gtk_text_iter_forward_char (&tag->end);
+
+  gtk_text_iter_backward_char (&cursor);
+  if (gtk_text_iter_get_char (&cursor) == '/' && tag->kind != HTML_TAG_KIND_CLOSE)
+    tag->kind = HTML_TAG_KIND_STRAY_END;
+
+  if (direction == GTK_DIR_RIGHT)
+    *iter = tag->end;
+
+  return tag;
+}
+
+static void
+free_html_tag (gpointer data)
+{
+  HtmlTag *tag = (HtmlTag *)data;
+
+  if (tag != NULL)
+    {
+      g_free (tag->name);
+      g_free (tag);
+    }
+}
+
+/* cursor should be at the left of the block cursor */
+static HtmlTag *
+find_non_matching_html_tag_at_left (GtkTextIter *cursor,
+                                    gboolean     block_cursor)
+{
+  GQueue *stack;
+  HtmlTag *tag;
+  HtmlTag *last_closing_tag;
+  GtkTextIter cursor_right;
+
+  stack = g_queue_new ();
+
+  cursor_right = *cursor;
+  if (block_cursor)
+    gtk_text_iter_forward_char (&cursor_right);
+
+  while ((tag = find_html_tag (&cursor_right, GTK_DIR_LEFT)))
+    {
+      if (tag->kind == HTML_TAG_KIND_CLOSE)
+        {
+          if (gtk_text_iter_compare (cursor, &tag->end) >= 0)
+            {
+              g_queue_push_head (stack, tag);
+              continue;
+            }
+          else
+            cursor_right = tag->begin;
+        }
+      else if (tag->kind == HTML_TAG_KIND_OPEN)
+        {
+          last_closing_tag = g_queue_peek_head (stack);
+          if (last_closing_tag != NULL)
+            {
+              if (ide_str_equal0 (tag->name, last_closing_tag->name))
+                {
+                  g_queue_pop_head (stack);
+                  free_html_tag (last_closing_tag);
+                }
+            }
+          else
+            {
+              *cursor = tag->begin;
+              break;
+            }
+        }
+
+      free_html_tag (tag);
+    }
+
+  g_queue_free_full (stack, free_html_tag);
+
+  return tag;
+}
+
+/* cursor should be at the left of the block cursor */
+static HtmlTag *
+find_non_matching_html_tag_at_right (GtkTextIter *cursor,
+                                     gboolean     block_cursor)
+{
+  GQueue *stack;
+  HtmlTag *tag;
+  HtmlTag *last_closing_tag;
+  GtkTextIter cursor_left;
+  GtkTextIter cursor_right;
+
+  stack = g_queue_new ();
+  cursor_left = cursor_right = *cursor;
+
+  if (block_cursor)
+    gtk_text_iter_forward_char (&cursor_right);
+
+  tag = find_html_tag (&cursor_right, GTK_DIR_LEFT);
+  if (tag != NULL && gtk_text_iter_compare (cursor, &tag->end) < 0)
+    {
+      if (tag->kind == HTML_TAG_KIND_CLOSE)
+        cursor_left = tag->begin;
+      else if (tag->kind == HTML_TAG_KIND_OPEN)
+        cursor_left = tag->end;
+    }
+
+  while ((tag = find_html_tag (&cursor_left, GTK_DIR_RIGHT)))
+    {
+      if (tag->kind == HTML_TAG_KIND_OPEN)
+        {
+          g_queue_push_head (stack, tag);
+          continue;
+        }
+      else if (tag->kind == HTML_TAG_KIND_CLOSE)
+        {
+          while ((last_closing_tag = g_queue_pop_head (stack)))
+            {
+              gboolean is_names_equal = ide_str_equal0 (tag->name, last_closing_tag->name);
+
+              free_html_tag (last_closing_tag);
+              if (is_names_equal)
+                break;
+            }
+
+          if (last_closing_tag == NULL)
+            {
+              *cursor = tag->begin;
+              break;
+            }
+        }
+      else if (tag->kind == HTML_TAG_KIND_ERROR)
+        gtk_text_iter_forward_char (&cursor_left);
+
+      free_html_tag (tag);
+    }
+
+  g_queue_free_full (stack, free_html_tag);
+
+  return tag;
+}
+
+static void
+free_html_element (gpointer data)
+{
+  HtmlElement *element = (HtmlElement *)data;
+
+  if (element != NULL)
+    {
+      free_html_tag (element->left);
+      free_html_tag (element->right);
+
+      g_free (element);
+    }
+}
+
+static HtmlElement *
+get_html_element (GtkTextIter cursor_left,
+                  gboolean    block_cursor)
+{
+  HtmlElement *element = NULL;
+  HtmlTag *left_tag;
+  HtmlTag *right_tag;
+
+  right_tag = find_non_matching_html_tag_at_right (&cursor_left, block_cursor);
+  if (right_tag != NULL)
+    {
+      while ((left_tag = find_non_matching_html_tag_at_left (&cursor_left, block_cursor)))
+        {
+          if (!ide_str_equal0 (left_tag->name, right_tag->name))
+            {
+              cursor_left = left_tag->begin;
+              free_html_tag (left_tag);
+              left_tag = NULL;
+
+              if (block_cursor && !gtk_text_iter_backward_char (&cursor_left))
+                break;
+
+            }
+          else
+            break;
+        };
+
+      if (left_tag != NULL)
+        {
+          element = g_new0 (HtmlElement, 1);
+          element->left = left_tag;
+          element->right = right_tag;
+        }
+      else
+        free_html_tag (right_tag);
+    }
+
+  return element;
+}
+
+static HtmlElement *
+get_html_element_parent (HtmlElement *element)
+{
+  g_return_val_if_fail (element != NULL, NULL);
+
+  return get_html_element (element->right->end, FALSE);
+}
+
+void
+_ide_source_view_select_tag (IdeSourceView *self,
+                             guint          count,
+                             gboolean       exclusive)
+{
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert_mark;
+  GtkTextMark *selection_mark;
+  GtkTextIter insert;
+  GtkTextIter selection;
+  GtkTextIter selection_left;
+  HtmlElement *element;
+  HtmlElement *element_parent;
+
+  g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
+  insert_mark = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &insert, insert_mark);
+  selection_mark = gtk_text_buffer_get_selection_bound (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &selection, selection_mark);
+
+  selection_left = selection;
+  if (gtk_text_buffer_get_has_selection (buffer))
+    {
+      /* fix for visual mode selection and fake block cursor */
+      gtk_text_iter_order (&insert, &selection_left);
+      gtk_text_iter_backward_char (&selection_left);
+    }
+
+  element = get_html_element (selection_left, TRUE);
+  while (element != NULL &&
+         (gtk_text_iter_compare (&insert, &element->left->begin) < 0 ||
+          gtk_text_iter_compare (&selection, &element->right->end) > 0))
+    {
+      element_parent = get_html_element_parent (element);
+      free_html_element (element);
+      element = element_parent;
+    }
+
+  if (element != NULL)
+    {
+      if (exclusive)
+        gtk_text_buffer_select_range (buffer, &element->left->end, &element->right->begin);
+      else
+        gtk_text_buffer_select_range (buffer, &element->left->begin, &element->right->end);
+
+      free_html_element (element);
+    }
+}
diff --git a/libide/ide-source-view-movements.h b/libide/ide-source-view-movements.h
index 0024957..1afde8c 100644
--- a/libide/ide-source-view-movements.h
+++ b/libide/ide-source-view-movements.h
@@ -40,6 +40,10 @@ void _ide_source_view_select_inner   (IdeSourceView *self,
                                       gboolean       exclusive,
                                       gboolean       string_mode);
 
+void _ide_source_view_select_tag     (IdeSourceView *self,
+                                      guint          count,
+                                      gboolean       exclusive);
+
 G_END_DECLS
 
 #endif /* IDE_SOURCE_VIEW_HELPER_H */
diff --git a/libide/ide-source-view.c b/libide/ide-source-view.c
index 71351ba..c45aa44 100644
--- a/libide/ide-source-view.c
+++ b/libide/ide-source-view.c
@@ -250,6 +250,7 @@ enum {
   SAVE_INSERT_MARK,
   SAVE_SEARCH_CHAR,
   SELECT_INNER,
+  SELECT_TAG,
   SELECTION_THEATRIC,
   SET_MODE,
   SET_OVERWRITE,
@@ -3475,6 +3476,17 @@ ide_source_view_real_select_inner (IdeSourceView *self,
 }
 
 static void
+ide_source_view_real_select_tag (IdeSourceView *self,
+                                 gboolean       exclusive)
+{
+  IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+  g_assert (IDE_IS_SOURCE_VIEW (self));
+
+  _ide_source_view_select_tag (self, priv->count, exclusive);
+}
+
+static void
 ide_source_view__completion_hide_cb (IdeSourceView       *self,
                                      GtkSourceCompletion *completion)
 {
@@ -5391,6 +5403,7 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
   klass->save_insert_mark = ide_source_view_real_save_insert_mark;
   klass->save_search_char = ide_source_view_real_save_search_char;
   klass->select_inner = ide_source_view_real_select_inner;
+  klass->select_tag = ide_source_view_real_select_tag;
   klass->selection_theatric = ide_source_view_real_selection_theatric;
   klass->set_mode = ide_source_view_real_set_mode;
   klass->set_overwrite = ide_source_view_real_set_overwrite;
@@ -6050,6 +6063,16 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
                   G_TYPE_BOOLEAN,
                   G_TYPE_BOOLEAN);
 
+    gSignals [SELECT_TAG] =
+    g_signal_new ("select-tag",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (IdeSourceViewClass, select_tag),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  G_TYPE_BOOLEAN);
+
   gSignals [SELECTION_THEATRIC] =
     g_signal_new ("selection-theatric",
                   G_TYPE_FROM_CLASS (klass),
diff --git a/libide/ide-source-view.h b/libide/ide-source-view.h
index dfb874c..b9ccf3b 100644
--- a/libide/ide-source-view.h
+++ b/libide/ide-source-view.h
@@ -264,6 +264,8 @@ struct _IdeSourceViewClass
                                        const gchar             *inner_right,
                                        gboolean                 exclusive,
                                        gboolean                 string_mode);
+  void (*select_tag)                  (IdeSourceView           *self,
+                                       gboolean                 exclusive);
   void (*selection_theatric)          (IdeSourceView           *self,
                                        IdeSourceViewTheatric    theatric);
   void (*set_mode)                    (IdeSourceView           *self,


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