[gnome-builder/wip/slaf/vim-textobjects] vim: add tag text-object support
- From: Sébastien Lafargue <slafargue src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/slaf/vim-textobjects] vim: add tag text-object support
- Date: Wed, 4 Nov 2015 21:39:28 +0000 (UTC)
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]