[gnome-builder] libide: port gbsourcevim line selection into movements



commit 87d70ad3af9da51366aa270b14b9d890a9c9b4a5
Author: Christian Hergert <christian hergert me>
Date:   Fri Mar 6 19:53:02 2015 -0800

    libide: port gbsourcevim line selection into movements
    
    This code handled all of our weird corner cases a lot better, so just
    port it to this and move on to other problems.

 data/keybindings/vim.css           |    6 +-
 libide/ide-source-view-movements.c |  358 +++++++++++++++++++++++++++---------
 libide/ide-source-view-movements.h |    3 +-
 libide/ide-source-view.c           |   15 +-
 4 files changed, 279 insertions(+), 103 deletions(-)
---
diff --git a/data/keybindings/vim.css b/data/keybindings/vim.css
index 8d63210..e5baeb4 100644
--- a/data/keybindings/vim.css
+++ b/data/keybindings/vim.css
@@ -324,10 +324,10 @@
                     "restore-insert-mark" () };
 
   /* visual mode transition */
-  bind "v" { "movement" (next-char, 1, 0, 1)
+  bind "v" { "movement" (next-char, 1, 1, 1)
              "set-mode" ("vim-visual", permanent) };
-  bind "<shift>v" { "movement" (first-char, 0, 0, 0)
-                    "movement" (last-char, 1, 0, 0)
+  bind "<shift>v" { "movement" (first-char, 0, 1, 0)
+                    "movement" (next-line, 1, 0, 1)
                     "set-mode" ("vim-visual-line", permanent) };
   bind "<ctrl>v" { "set-mode" ("vim-visual-block", permanent) };
 }
diff --git a/libide/ide-source-view-movements.c b/libide/ide-source-view-movements.c
index 1b78e55..dfdc8a8 100644
--- a/libide/ide-source-view-movements.c
+++ b/libide/ide-source-view-movements.c
@@ -24,16 +24,30 @@
 #include "ide-source-view-movements.h"
 #include "ide-vim-iter.h"
 
+#define ANCHOR_BEGIN "SELECTION_ANCHOR_BEGIN"
+#define ANCHOR_END   "SELECTION_ANCHOR_END"
+
+#define TRACE_ITER(iter) \
+  IDE_TRACE_MSG("%d:%d", gtk_text_iter_get_line(iter), \
+                gtk_text_iter_get_line_offset(iter))
+
 typedef struct
 {
   IdeSourceView         *self;
-  IdeSourceViewMovement  type;                 /* Type of movement */
-  GtkTextIter            insert;               /* Current insert cursor location */
-  GtkTextIter            selection;            /* Current selection cursor location */
-  gint                   count;                /* Repeat count for movement */
-  guint                  extend_selection : 1; /* If selection should be extended */
-  guint                  exclusive : 1;        /* See ":help exclusive" in vim */
-  guint                  ignore_select : 1;    /* Don't update selection after movement */
+  /* The target_offset contains the ideal character line_offset. This can
+   * sometimes be further forward than designed when the line does not have
+   * enough characters to get back to the original position. -1 indicates
+   * no preference.
+   */
+  gint                  *target_offset;
+  IdeSourceViewMovement  type;                     /* Type of movement */
+  GtkTextIter            insert;                   /* Current insert cursor location */
+  GtkTextIter            selection;                /* Current selection cursor location */
+  gint                   count;                    /* Repeat count for movement */
+  guint                  extend_selection : 1;     /* If selection should be extended */
+  guint                  exclusive : 1;            /* See ":help exclusive" in vim */
+  guint                  ignore_select : 1;        /* Don't update selection after movement */
+  guint                  ignore_target_offset : 1; /* Don't propagate new line offset */
 } Movement;
 
 typedef struct
@@ -44,6 +58,124 @@ typedef struct
 } MatchingBracketState;
 
 static gboolean
+is_single_line_selection (const GtkTextIter *begin,
+                          const GtkTextIter *end)
+{
+  if (gtk_text_iter_compare (begin, end) < 0)
+    return ((gtk_text_iter_get_line_offset (begin) == 0) &&
+            (gtk_text_iter_get_line_offset (end) == 0) &&
+            ((gtk_text_iter_get_line (begin) + 1) ==
+             gtk_text_iter_get_line (end)));
+  else
+    return ((gtk_text_iter_get_line_offset (begin) == 0) &&
+            (gtk_text_iter_get_line_offset (end) == 0) &&
+            ((gtk_text_iter_get_line (end) + 1) ==
+             gtk_text_iter_get_line (begin)));
+}
+
+static gboolean
+is_single_char_selection (const GtkTextIter *begin,
+                          const GtkTextIter *end)
+{
+  GtkTextIter tmp;
+
+  g_assert (begin);
+  g_assert (end);
+
+  tmp = *begin;
+  if (gtk_text_iter_forward_char (&tmp) && gtk_text_iter_equal (&tmp, end))
+    return TRUE;
+
+  tmp = *end;
+  if (gtk_text_iter_forward_char (&tmp) && gtk_text_iter_equal (&tmp, begin))
+    return TRUE;
+
+  return FALSE;
+}
+
+static void
+select_range (Movement    *mv,
+              GtkTextIter *insert_iter,
+              GtkTextIter *selection_iter)
+{
+  GtkTextBuffer *buffer;
+  GtkTextMark *insert;
+  GtkTextMark *selection;
+  gint insert_off;
+  gint selection_off;
+
+  g_assert (insert_iter);
+  g_assert (selection_iter);
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (mv->self));
+  insert = gtk_text_buffer_get_insert (buffer);
+  selection = gtk_text_buffer_get_selection_bound (buffer);
+
+  mv->ignore_select = TRUE;
+
+  /*
+   * If the caller is requesting that we select a single character, we will
+   * keep the iter before that character. This more closely matches the visual
+   * mode in VIM.
+   */
+  insert_off = gtk_text_iter_get_offset (insert_iter);
+  selection_off = gtk_text_iter_get_offset (selection_iter);
+  if ((insert_off - selection_off) == 1)
+    gtk_text_iter_order (insert_iter, selection_iter);
+
+  gtk_text_buffer_move_mark (buffer, insert, insert_iter);
+  gtk_text_buffer_move_mark (buffer, selection, selection_iter);
+}
+
+static void
+ensure_anchor_selected (Movement *mv)
+{
+  GtkTextBuffer *buffer;
+  GtkTextMark *selection_mark;
+  GtkTextMark *insert_mark;
+  GtkTextIter anchor_begin;
+  GtkTextIter anchor_end;
+  GtkTextIter insert_iter;
+  GtkTextIter selection_iter;
+  GtkTextMark *selection_anchor_begin;
+  GtkTextMark *selection_anchor_end;
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (mv->self));
+
+  selection_anchor_begin = gtk_text_buffer_get_mark (buffer, ANCHOR_BEGIN);
+  selection_anchor_end = gtk_text_buffer_get_mark (buffer, ANCHOR_END);
+
+  if (!selection_anchor_begin || !selection_anchor_end)
+    return;
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &anchor_begin, selection_anchor_begin);
+  gtk_text_buffer_get_iter_at_mark (buffer, &anchor_end, selection_anchor_end);
+
+  insert_mark = gtk_text_buffer_get_insert (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, insert_mark);
+
+  selection_mark = gtk_text_buffer_get_selection_bound (buffer);
+  gtk_text_buffer_get_iter_at_mark (buffer, &selection_iter, selection_mark);
+
+  if ((gtk_text_iter_compare (&selection_iter, &anchor_end) < 0) &&
+      (gtk_text_iter_compare (&insert_iter, &anchor_end) < 0))
+    {
+      if (gtk_text_iter_compare (&insert_iter, &selection_iter) < 0)
+        select_range (mv, &insert_iter, &anchor_end);
+      else
+        select_range (mv, &anchor_end, &selection_iter);
+    }
+  else if ((gtk_text_iter_compare (&selection_iter, &anchor_begin) > 0) &&
+           (gtk_text_iter_compare (&insert_iter, &anchor_begin) > 0))
+    {
+      if (gtk_text_iter_compare (&insert_iter, &selection_iter) < 0)
+        select_range (mv, &anchor_begin, &selection_iter);
+      else
+        select_range (mv, &insert_iter, &anchor_begin);
+    }
+}
+
+static gboolean
 text_iter_forward_to_empty_line (GtkTextIter *iter,
                                  GtkTextIter *bounds)
 {
@@ -61,33 +193,6 @@ text_iter_forward_to_empty_line (GtkTextIter *iter,
   return FALSE;
 }
 
-static gboolean
-is_single_line_selection (const GtkTextIter *insert,
-                          const GtkTextIter *selection)
-{
-  return (gtk_text_iter_get_line (insert) == gtk_text_iter_get_line (selection)) &&
-         (gtk_text_iter_starts_line (insert) || gtk_text_iter_starts_line (selection)) &&
-         (gtk_text_iter_ends_line (insert) || gtk_text_iter_ends_line (selection));
-}
-
-static gboolean
-is_line_selection (const GtkTextIter *insert,
-                   const GtkTextIter *selection)
-{
-  gboolean ret = FALSE;
-
-  if (is_single_line_selection (insert, selection))
-    ret = TRUE;
-  else if (gtk_text_iter_starts_line (selection) &&
-      gtk_text_iter_ends_line (insert) &&
-      !gtk_text_iter_equal (selection, insert))
-    ret = TRUE;
-
-  IDE_TRACE_MSG ("is_line_selection=%s", ret ? "TRUE" : "FALSE");
-
-  return ret;
-}
-
 static void
 ide_source_view_movements_get_selection (Movement *mv)
 {
@@ -325,88 +430,143 @@ ide_source_view_movements_last_line (Movement *mv)
 static void
 ide_source_view_movements_next_line (Movement *mv)
 {
-  mv->count = MAX (1, mv->count);
+  GtkTextBuffer *buffer;
+  gboolean has_selection;
+  guint line;
+  guint offset = 0;
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (mv->self));
+
+  /* check for linewise */
+  has_selection = !gtk_text_iter_equal (&mv->insert, &mv->selection) || !mv->exclusive;
+
+  line = gtk_text_iter_get_line (&mv->insert);
+
+  if ((*mv->target_offset) > 0)
+    offset = *mv->target_offset;
 
   /*
-   * Try to use the normal move-cursor helpers if this is a simple movement.
+   * If we have a whole line selected (from say `V`), then we need to swap
+   * the cursor and selection. This feels to me like a slight bit of a hack.
+   * There may be cause to actually have a selection mode and know the type
+   * of selection (line vs individual characters).
    */
-  if (!mv->extend_selection || !is_line_selection (&mv->insert, &mv->selection))
+  if (is_single_line_selection (&mv->insert, &mv->selection))
     {
-      IDE_TRACE_MSG ("next-line simple");
+      guint target_line;
 
-      mv->ignore_select = TRUE;
-      g_signal_emit_by_name (mv->self,
-                             "move-cursor",
-                             GTK_MOVEMENT_DISPLAY_LINES,
-                             mv->count,
-                             mv->extend_selection);
-      return;
-    }
+      if (gtk_text_iter_compare (&mv->insert, &mv->selection) < 0)
+        gtk_text_iter_order (&mv->selection, &mv->insert);
 
-  IDE_TRACE_MSG ("next-line with line-selection");
+      target_line = gtk_text_iter_get_line (&mv->insert) + 1;
+      gtk_text_iter_set_line (&mv->insert, target_line);
 
-  if (gtk_text_iter_equal (&mv->insert, &mv->selection))
-    {
-      gtk_text_iter_forward_lines (&mv->selection, mv->count);
-      gtk_text_iter_set_line_offset (&mv->selection, 0);
+      if (target_line != gtk_text_iter_get_line (&mv->insert))
+        goto select_to_end;
+
+      select_range (mv, &mv->insert, &mv->selection);
+      ensure_anchor_selected (mv);
       return;
     }
 
-  gtk_text_iter_forward_lines (&mv->insert, mv->count);
-
-  /* (!mv->exclusive) == LINEWISE */
+  if (is_single_char_selection (&mv->insert, &mv->selection))
+    {
+      if (gtk_text_iter_compare (&mv->insert, &mv->selection) < 0)
+        *mv->target_offset = ++offset;
+    }
 
-  if (!mv->exclusive &&
-      (gtk_text_iter_get_line (&mv->insert) == gtk_text_iter_get_line (&mv->selection)))
+  gtk_text_buffer_get_iter_at_line (buffer, &mv->insert, line + 1);
+  if ((line + 1) == gtk_text_iter_get_line (&mv->insert))
     {
-      gtk_text_iter_set_line_offset (&mv->insert, 0);
-      gtk_text_iter_set_line_offset (&mv->selection, 0);
-      gtk_text_iter_forward_to_line_end (&mv->selection);
+      for (; offset; offset--)
+        if (!gtk_text_iter_ends_line (&mv->insert))
+          if (!gtk_text_iter_forward_char (&mv->insert))
+            break;
+      if (has_selection)
+        {
+          select_range (mv, &mv->insert, &mv->selection);
+          ensure_anchor_selected (mv);
+        }
+      else
+        gtk_text_buffer_select_range (buffer, &mv->insert, &mv->insert);
+    }
+  else
+    {
+select_to_end:
+      gtk_text_buffer_get_end_iter (buffer, &mv->insert);
+      if (has_selection)
+        {
+          select_range (mv, &mv->insert, &mv->selection);
+          ensure_anchor_selected (mv);
+        }
+      else
+        gtk_text_buffer_select_range (buffer, &mv->insert, &mv->insert);
     }
 }
 
 static void
 ide_source_view_movements_previous_line (Movement *mv)
 {
-  /*
-   * Try to use the normal move-cursor helpers if this is a simple movement.
-   */
-  if (!mv->extend_selection || !is_line_selection (&mv->insert, &mv->selection))
-    {
-      IDE_TRACE_MSG ("previous-line simple");
+  GtkTextBuffer *buffer;
+  gboolean has_selection;
+  guint line;
+  guint offset = 0;
 
-      mv->count = MAX (1, mv->count);
-      mv->ignore_select = TRUE;
-      g_signal_emit_by_name (mv->self,
-                             "move-cursor",
-                             GTK_MOVEMENT_DISPLAY_LINES,
-                             -mv->count,
-                             mv->extend_selection);
-      return;
-    }
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (mv->self));
 
-  IDE_TRACE_MSG ("previous-line with line-selection");
+  /* check for linewise */
+  has_selection = !gtk_text_iter_equal (&mv->insert, &mv->selection) || !mv->exclusive;
 
-  g_assert (mv->extend_selection);
-  g_assert (is_line_selection (&mv->insert, &mv->selection));
+  line = gtk_text_iter_get_line (&mv->insert);
 
-  if (gtk_text_iter_is_start (&mv->insert) || gtk_text_iter_is_start (&mv->selection))
+  if ((*mv->target_offset) > 0)
+    offset = *mv->target_offset;
+
+  if (line == 0)
     return;
 
   /*
-   * if the current line is selected
+   * If we have a whole line selected (from say `V`), then we need to swap the cursor and
+   * selection. This feels to me like a slight bit of a hack.  There may be cause to actually have
+   * a selection mode and know the type of selection (line vs individual characters).
    */
   if (is_single_line_selection (&mv->insert, &mv->selection))
     {
-      gtk_text_iter_order (&mv->insert, &mv->selection);
-      gtk_text_iter_backward_line (&mv->insert);
-      gtk_text_iter_set_line_offset (&mv->insert, 0);
+      if (gtk_text_iter_compare (&mv->insert, &mv->selection) > 0)
+        gtk_text_iter_order (&mv->insert, &mv->selection);
+      gtk_text_iter_set_line (&mv->insert, gtk_text_iter_get_line (&mv->insert) - 1);
+      select_range (mv, &mv->insert, &mv->selection);
+      ensure_anchor_selected (mv);
+      return;
     }
-  else
+
+  if (is_single_char_selection (&mv->insert, &mv->selection))
     {
-      gtk_text_iter_backward_line (&mv->insert);
-      if (!gtk_text_iter_ends_line (&mv->insert))
-        gtk_text_iter_forward_to_line_end (&mv->insert);
+      if (gtk_text_iter_compare (&mv->insert, &mv->selection) > 0)
+        {
+          if (offset)
+            --offset;
+          *mv->target_offset = offset;
+        }
+    }
+
+  gtk_text_buffer_get_iter_at_line (buffer, &mv->insert, line - 1);
+  if ((line - 1) == gtk_text_iter_get_line (&mv->insert))
+    {
+      for (; offset; offset--)
+        if (!gtk_text_iter_ends_line (&mv->insert))
+          if (!gtk_text_iter_forward_char (&mv->insert))
+            break;
+
+      if (has_selection)
+        {
+          if (gtk_text_iter_equal (&mv->insert, &mv->selection))
+            gtk_text_iter_backward_char (&mv->insert);
+          select_range (mv, &mv->insert, &mv->selection);
+          ensure_anchor_selected (mv);
+        }
+      else
+        gtk_text_buffer_select_range (buffer, &mv->insert, &mv->insert);
     }
 }
 
@@ -853,7 +1013,8 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
                                  IdeSourceViewMovement  movement,
                                  gboolean               extend_selection,
                                  gboolean               exclusive,
-                                 guint                  count)
+                                 guint                  count,
+                                 gint                  *target_offset)
 {
   Movement mv = { 0 };
 
@@ -875,11 +1036,13 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
 #endif
 
   mv.self = self;
+  mv.target_offset = target_offset;
   mv.type = movement;
   mv.extend_selection = extend_selection;
   mv.exclusive = exclusive;
   mv.count = count;
   mv.ignore_select = FALSE;
+  mv.ignore_target_offset = FALSE;
 
   ide_source_view_movements_get_selection (&mv);
 
@@ -970,11 +1133,25 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
       break;
 
     case IDE_SOURCE_VIEW_MOVEMENT_PREVIOUS_LINE:
-      ide_source_view_movements_previous_line (&mv);
+      {
+        gsize i;
+
+        mv.ignore_target_offset = TRUE;
+        mv.ignore_select = TRUE;
+        for (i = 0; i < MAX (1, mv.count); i++)
+          ide_source_view_movements_previous_line (&mv);
+      }
       break;
 
     case IDE_SOURCE_VIEW_MOVEMENT_NEXT_LINE:
-      ide_source_view_movements_next_line (&mv);
+      {
+        gsize i;
+
+        mv.ignore_target_offset = TRUE;
+        mv.ignore_select = TRUE;
+        for (i = 0; i < MAX (1, mv.count); i++)
+          ide_source_view_movements_next_line (&mv);
+      }
       break;
 
     case IDE_SOURCE_VIEW_MOVEMENT_FIRST_LINE:
@@ -1040,4 +1217,7 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
 
   if (!mv.ignore_select)
     ide_source_view_movements_select_range (&mv);
+
+  if (!mv.ignore_target_offset)
+    *target_offset = gtk_text_iter_get_line_offset (&mv.insert);
 }
diff --git a/libide/ide-source-view-movements.h b/libide/ide-source-view-movements.h
index 1689e1e..4f17fee 100644
--- a/libide/ide-source-view-movements.h
+++ b/libide/ide-source-view-movements.h
@@ -27,7 +27,8 @@ void _ide_source_view_apply_movement (IdeSourceView         *source_view,
                                       IdeSourceViewMovement  movement,
                                       gboolean               extend_selection,
                                       gboolean               exclusive,
-                                      guint                  count);
+                                      guint                  count,
+                                      gint                  *target_offset);
 
 G_END_DECLS
 
diff --git a/libide/ide-source-view.c b/libide/ide-source-view.c
index 640af1e..bd08444 100644
--- a/libide/ide-source-view.c
+++ b/libide/ide-source-view.c
@@ -78,6 +78,7 @@ typedef struct
   gulong                       buffer_notify_highlight_diagnostics_handler;
   gulong                       buffer_notify_language_handler;
 
+  gint                         target_line_offset;
   guint                        count;
   guint                        scroll_offset;
 
@@ -2158,17 +2159,10 @@ ide_source_view_real_movement (IdeSourceView         *self,
   g_assert (IDE_IS_SOURCE_VIEW (self));
 
   if (apply_count)
-    {
-      count = priv->count;
-#if 0
-      /*
-       * TODO: I think we want this in a clear-count action.
-       */
-      priv->count = 0;
-#endif
-    }
+    count = priv->count;
 
-  _ide_source_view_apply_movement (self, movement, extend_selection, exclusive, count);
+  _ide_source_view_apply_movement (self, movement, extend_selection, exclusive,
+                                   count, &priv->target_line_offset);
 }
 
 static void
@@ -2938,6 +2932,7 @@ ide_source_view_init (IdeSourceView *self)
 {
   IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
 
+  priv->target_line_offset = -1;
   priv->snippets = g_queue_new ();
   priv->selections = g_queue_new ();
 


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