[gnome-builder] sourceview: move by visual column



commit 08573b8d549fb31f470d4e63b3863502357ecce2
Author: Andreas Brauchli <andreas brauchli sensirion com>
Date:   Thu Apr 6 20:51:24 2017 +0200

    sourceview: move by visual column
    
    Change movements to stick to the visual column instead of the line
    offset. This make movements between lines with different (or buggy)
    indentation more coherent with what the user expects.
    
    Column index 0 is the first column which is shown as column 1 to the
    user (this reflects the previous offset behavior).
    
    * Convert saved line offset instances to columns
    * Make ide_source_view_get_visual_column a wrapper for GtkSourceView's
      gtk_source_view_get_visual_column
    * Use multiples of tab_width as tab stops. This might not universally hold
      in the future but it's a good start.
    * ide_source_view_get_iter_at_visual_column is based on the column
      calculation of GtkSourceView's gtk_source_view_get_visual_column,
      including the FIXME comment about invisible characters.
    
    IdeSourceLocation positions remain in character offsets because
    tab_width of different files might be different.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=781342

 libide/sourceview/ide-source-view-movements.c |   53 ++++++------
 libide/sourceview/ide-source-view-movements.h |    2 +-
 libide/sourceview/ide-source-view.c           |  121 ++++++++++++++++++-------
 libide/sourceview/ide-source-view.h           |    7 ++-
 4 files changed, 121 insertions(+), 62 deletions(-)
---
diff --git a/libide/sourceview/ide-source-view-movements.c b/libide/sourceview/ide-source-view-movements.c
index 892f889..69b3de5 100644
--- a/libide/sourceview/ide-source-view-movements.c
+++ b/libide/sourceview/ide-source-view-movements.c
@@ -39,12 +39,11 @@
 typedef struct
 {
   IdeSourceView         *self;
-  /* 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.
+  /* The target_column contains the ideal character column (visual offset).
+   * This can sometimes be further forward than designed when the line does not
+   * have enough characters to get back to the original position.
    */
-  gint                  *target_offset;
+  guint                 *target_column;
   IdeSourceViewMovement  type;                        /* Type of movement */
   GtkTextIter            insert;                      /* Current insert cursor location */
   GtkTextIter            selection;                   /* Current selection cursor location */
@@ -56,7 +55,7 @@ typedef struct
   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 */
+  guint                  ignore_target_column : 1;    /* Don't propagate new line column */
   guint                  ignore_scroll_to_insert : 1; /* Don't scroll to insert mark */
 } Movement;
 
@@ -500,7 +499,7 @@ ide_source_view_movements_next_line (Movement *mv)
   GtkTextBuffer *buffer;
   gboolean has_selection;
   guint line;
-  guint offset = 0;
+  guint column = *mv->target_column;
 
   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (mv->self));
 
@@ -509,9 +508,6 @@ ide_source_view_movements_next_line (Movement *mv)
 
   line = gtk_text_iter_get_line (&mv->insert);
 
-  if ((*mv->target_offset) > 0)
-    offset = *mv->target_offset;
-
   /*
    * 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.
@@ -542,10 +538,14 @@ ide_source_view_movements_next_line (Movement *mv)
   if (is_single_char_selection (&mv->insert, &mv->selection))
     {
       if (gtk_text_iter_compare (&mv->insert, &mv->selection) < 0)
-        *mv->target_offset = ++offset;
+        *mv->target_column = ++column;
     }
 
-  gtk_text_buffer_get_iter_at_line_offset (buffer, &mv->insert, line + 1, offset);
+  gtk_text_buffer_get_iter_at_line (buffer, &mv->insert, line + 1);
+  if (gtk_text_iter_get_line (&mv->insert) == line + 1)
+    ide_source_view_get_iter_at_visual_column (mv->self, *mv->target_column, &mv->insert);
+  else
+    gtk_text_buffer_get_end_iter (buffer, &mv->insert);
 
 select_to_end:
 
@@ -573,7 +573,7 @@ ide_source_view_movements_previous_line (Movement *mv)
   GtkTextBuffer *buffer;
   gboolean has_selection;
   guint line;
-  guint offset = 0;
+  guint column = 0;
 
   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (mv->self));
 
@@ -582,8 +582,8 @@ ide_source_view_movements_previous_line (Movement *mv)
 
   line = gtk_text_iter_get_line (&mv->insert);
 
-  if ((*mv->target_offset) > 0)
-    offset = *mv->target_offset;
+  if ((*mv->target_column) > 0)
+    column = *mv->target_column;
 
   if (line == 0)
     return FALSE;
@@ -607,16 +607,16 @@ ide_source_view_movements_previous_line (Movement *mv)
     {
       if (gtk_text_iter_compare (&mv->insert, &mv->selection) > 0)
         {
-          if (offset)
-            --offset;
-          *mv->target_offset = offset;
+          if (column)
+            --column;
+          *mv->target_column = column;
         }
     }
 
   gtk_text_buffer_get_iter_at_line (buffer, &mv->insert, line - 1);
   if (line == ((guint)gtk_text_iter_get_line (&mv->insert) + 1))
     {
-      gtk_text_buffer_get_iter_at_line_offset (buffer, &mv->insert, line - 1, offset);
+      ide_source_view_get_iter_at_visual_column (mv->self, column, &mv->insert);
 
       if (has_selection)
         {
@@ -1936,7 +1936,7 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
                                  gunichar               command,
                                  gunichar               modifier,
                                  gunichar               search_char,
-                                 gint                  *target_offset)
+                                 guint                 *target_column)
 {
   Movement mv = { 0 };
   GtkTextBuffer *buffer;
@@ -1945,6 +1945,7 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
   gint min_count = 1;
   gint end_line;
   gsize i;
+  guint line;
 
   g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
 
@@ -1981,13 +1982,13 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
   end_line = gtk_text_iter_get_line (&end_iter);
 
   mv.self = self;
-  mv.target_offset = target_offset;
+  mv.target_column = target_column;
   mv.type = movement;
   mv.extend_selection = extend_selection;
   mv.exclusive = exclusive;
   mv.count = count;
   mv.ignore_select = FALSE;
-  mv.ignore_target_offset = FALSE;
+  mv.ignore_target_column = FALSE;
   mv.command_str = command_str;
   mv.command = command;
   mv.modifier = modifier;
@@ -2115,7 +2116,7 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
       break;
 
     case IDE_SOURCE_VIEW_MOVEMENT_PREVIOUS_LINE:
-      mv.ignore_target_offset = TRUE;
+      mv.ignore_target_column = TRUE;
       mv.ignore_select = TRUE;
       mv.count = MIN (mv.count, end_line);
       /*
@@ -2129,7 +2130,7 @@ _ide_source_view_apply_movement (IdeSourceView         *self,
       break;
 
     case IDE_SOURCE_VIEW_MOVEMENT_NEXT_LINE:
-      mv.ignore_target_offset = TRUE;
+      mv.ignore_target_column = TRUE;
       mv.ignore_select = TRUE;
       mv.count = MIN (mv.count, end_line);
       /*
@@ -2269,8 +2270,8 @@ _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);
+  if (!mv.ignore_target_column)
+    ide_source_view_get_visual_position (mv.self, &line, target_column);
 
   if (!mv.ignore_scroll_to_insert)
     ide_source_view_scroll_mark_onscreen (self, insert, TRUE, 0.5, 0.5);
diff --git a/libide/sourceview/ide-source-view-movements.h b/libide/sourceview/ide-source-view-movements.h
index c867e4f..a24b2dc 100644
--- a/libide/sourceview/ide-source-view-movements.h
+++ b/libide/sourceview/ide-source-view-movements.h
@@ -32,7 +32,7 @@ void _ide_source_view_apply_movement (IdeSourceView         *source_view,
                                       gunichar               command,
                                       gunichar               modifier,
                                       gunichar               search_char,
-                                      gint                  *target_offset);
+                                      guint                 *target_column);
 
 void _ide_source_view_select_inner   (IdeSourceView *self,
                                       gunichar       inner_left,
diff --git a/libide/sourceview/ide-source-view.c b/libide/sourceview/ide-source-view.c
index b5d7c5b..3d7410a 100644
--- a/libide/sourceview/ide-source-view.c
+++ b/libide/sourceview/ide-source-view.c
@@ -131,7 +131,7 @@ typedef struct
 
   guint                        change_sequence;
 
-  gint                         target_line_offset;
+  guint                        target_line_column;
   GString                     *command_str;
   gunichar                     command;
   gunichar                     modifier;
@@ -145,9 +145,9 @@ typedef struct
   gint                         cached_char_width;
 
   guint                        saved_line;
-  guint                        saved_line_offset;
+  guint                        saved_line_column;
   guint                        saved_selection_line;
-  guint                        saved_selection_line_offset;
+  guint                        saved_selection_line_column;
 
   GdkRGBA                      bubble_color1;
   GdkRGBA                      bubble_color2;
@@ -337,7 +337,7 @@ static void ide_source_view_real_restore_insert_mark (IdeSourceView         *sel
 static void ide_source_view_real_set_mode            (IdeSourceView         *self,
                                                       const gchar           *name,
                                                       IdeSourceViewModeType  type);
-static void ide_source_view_save_offset              (IdeSourceView         *self);
+static void ide_source_view_save_column              (IdeSourceView         *self);
 static void ide_source_view_maybe_overwrite          (IdeSourceView         *self,
                                                       GtkTextIter           *iter,
                                                       const gchar           *text,
@@ -1480,9 +1480,10 @@ ide_source_view__buffer_loaded_cb (IdeSourceView *self,
 
   insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
 
-  /* Store the line offset so movements are correct. */
+  /* Store the line column (visual offset) so movements are correct. */
   gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, insert);
-  priv->target_line_offset = gtk_text_iter_get_line_offset (&iter);
+  priv->target_line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self),
+                                                                &iter);
 
   /* Only scroll if the user hasn't started an intermediate scroll */
   adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
@@ -2672,10 +2673,10 @@ ide_source_view_real_button_press_event (GtkWidget      *widget,
     }
 
   /*
-   * Update our target offset so movements don't cause us to revert
-   * to the previous offset.
+   * Update our target column so movements don't cause us to revert
+   * to the previous column.
    */
-  ide_source_view_save_offset (self);
+  ide_source_view_save_column (self);
 
   return ret;
 }
@@ -3163,7 +3164,7 @@ ide_source_view_real_delete_selection (IdeSourceView *self)
       gtk_text_buffer_delete_selection (buffer, TRUE, editable);
     }
 
-  ide_source_view_save_offset (self);
+  ide_source_view_save_column (self);
 }
 
 static void
@@ -3326,7 +3327,7 @@ ide_source_view_real_jump (IdeSourceView     *self,
   GtkTextBuffer *buffer;
   gchar *fragment;
   guint line;
-  guint line_offset;
+  guint line_column;
 
   IDE_ENTRY;
 
@@ -3334,9 +3335,9 @@ ide_source_view_real_jump (IdeSourceView     *self,
   g_assert (location);
 
   line = gtk_text_iter_get_line (location);
-  line_offset = gtk_text_iter_get_line_offset (location);
+  line_column = ide_source_view_get_visual_column (self, location);
 
-  IDE_TRACE_MSG ("Jump to %d:%d", line + 1, line_offset + 1);
+  IDE_TRACE_MSG ("Jump to %d:%d", line + 1, line_column + 1);
 
   if (priv->back_forward_list == NULL)
     IDE_EXIT;
@@ -3353,7 +3354,7 @@ ide_source_view_real_jump (IdeSourceView     *self,
     IDE_EXIT;
 
   uri = ide_uri_new_from_file (ide_file_get_file (file));
-  fragment = g_strdup_printf ("L%u_%u", line + 1, line_offset + 1);
+  fragment = g_strdup_printf ("L%u_%u", line + 1, line_column + 1);
   ide_uri_set_fragment (uri, fragment);
   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
   mark = gtk_text_buffer_create_mark (buffer, NULL, location, FALSE);
@@ -3381,7 +3382,7 @@ ide_source_view_real_paste_clipboard_extended (IdeSourceView *self,
   GtkTextMark *insert;
   GtkTextIter iter;
   guint target_line;
-  guint target_line_offset;
+  guint target_line_column;
 
   /*
    * NOTE:
@@ -3406,7 +3407,7 @@ ide_source_view_real_paste_clipboard_extended (IdeSourceView *self,
 
   gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
   target_line = gtk_text_iter_get_line (&iter);
-  target_line_offset = gtk_text_iter_get_line_offset (&iter);
+  target_line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self), &iter);
 
   gtk_text_buffer_begin_user_action (buffer);
 
@@ -3453,7 +3454,8 @@ ide_source_view_real_paste_clipboard_extended (IdeSourceView *self,
         {
           gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
           target_line = gtk_text_iter_get_line (&iter);
-          target_line_offset = gtk_text_iter_get_line_offset (&iter);
+          target_line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self),
+                                                                  &iter);
         }
 
       gtk_clipboard_set_text (clipboard, trimmed, -1);
@@ -3476,11 +3478,13 @@ ide_source_view_real_paste_clipboard_extended (IdeSourceView *self,
         {
           gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
           target_line = gtk_text_iter_get_line (&iter);
-          target_line_offset = gtk_text_iter_get_line_offset (&iter);
+          target_line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self),
+                                                                  &iter);
         }
     }
 
-  gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, target_line, target_line_offset);
+  gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, target_line, 0);
+  ide_source_view_get_iter_at_visual_column (self, target_line_column, &iter);
   gtk_text_buffer_select_range (buffer, &iter, &iter);
 
   gtk_text_buffer_end_user_action (buffer);
@@ -3527,7 +3531,7 @@ ide_source_view_real_selection_theatric (IdeSourceView         *self,
 }
 
 static void
-ide_source_view_save_offset (IdeSourceView *self)
+ide_source_view_save_column (IdeSourceView *self)
 {
   IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
   GtkTextView *text_view = (GtkTextView *)self;
@@ -3540,7 +3544,7 @@ ide_source_view_save_offset (IdeSourceView *self)
   buffer = gtk_text_view_get_buffer (text_view);
   insert = gtk_text_buffer_get_insert (buffer);
   gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
-  priv->target_line_offset = gtk_text_iter_get_line_offset (&iter);
+  priv->target_line_column = ide_source_view_get_visual_column (self, &iter);
 }
 
 static void
@@ -3588,7 +3592,7 @@ ide_source_view_real_set_mode (IdeSourceView         *self,
   }
 #endif
 
-  ide_source_view_save_offset (self);
+  ide_source_view_save_column (self);
 
   if (priv->mode)
     {
@@ -3679,7 +3683,7 @@ ide_source_view_real_movement (IdeSourceView         *self,
                                    priv->command,
                                    priv->modifier,
                                    priv->search_char,
-                                   &priv->target_line_offset);
+                                   &priv->target_line_column);
 }
 
 static void
@@ -4036,11 +4040,15 @@ ide_source_view_real_restore_insert_mark_full (IdeSourceView *self,
 
   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
 
-  gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, priv->saved_line, priv->saved_line_offset);
+  gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, priv->saved_line, 0);
+  ide_source_view_get_iter_at_visual_column (self, priv->saved_line_column, &iter);
   gtk_text_buffer_get_iter_at_line_offset (buffer,
                                            &selection,
                                            priv->saved_selection_line,
-                                           priv->saved_selection_line_offset);
+                                           0);
+  ide_source_view_get_iter_at_visual_column (self,
+                                             priv->saved_selection_line_column,
+                                             &selection);
 
   gtk_text_buffer_select_range (buffer, &iter, &selection);
 
@@ -4081,11 +4089,11 @@ ide_source_view_real_save_insert_mark (IdeSourceView *self)
   gtk_text_buffer_get_iter_at_mark (buffer, &selection, selection_bound);
 
   priv->saved_line = gtk_text_iter_get_line (&iter);
-  priv->saved_line_offset = gtk_text_iter_get_line_offset (&iter);
+  priv->saved_line_column = ide_source_view_get_visual_column (self, &iter);
   priv->saved_selection_line = gtk_text_iter_get_line (&selection);
-  priv->saved_selection_line_offset = gtk_text_iter_get_line_offset (&selection);
+  priv->saved_selection_line_column = ide_source_view_get_visual_column (self, &selection);
 
-  priv->target_line_offset = priv->saved_line_offset;
+  priv->target_line_column = priv->saved_line_column;
 }
 
 static void
@@ -4977,7 +4985,7 @@ ide_source_view_focus_in_event (GtkWidget     *widget,
   if (!workbench || ide_workbench_get_selection_owner (workbench) != G_OBJECT (self))
     {
       priv->saved_selection_line = priv->saved_line;
-      priv->saved_selection_line_offset = priv->saved_line_offset;
+      priv->saved_selection_line_column = priv->saved_line_column;
     }
 
   ide_source_view_real_restore_insert_mark_full (self, FALSE);
@@ -7365,7 +7373,7 @@ ide_source_view_init (IdeSourceView *self)
 
   EGG_COUNTER_INC (instances);
 
-  priv->target_line_offset = -1;
+  priv->target_line_column = 0;
   priv->snippets = g_queue_new ();
   priv->selections = g_queue_new ();
   priv->show_line_diagnostics = TRUE;
@@ -7668,6 +7676,39 @@ ide_source_view_get_insert_matching_brace (IdeSourceView *self)
   return priv->insert_matching_brace;
 }
 
+void
+ide_source_view_get_iter_at_visual_column (IdeSourceView *self,
+                                           guint column,
+                                           GtkTextIter *location)
+{
+  gunichar tab_char;
+  guint visual_col = 0;
+  guint tab_width;
+
+  g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
+
+  tab_char = g_utf8_get_char ("\t");
+  tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (self));
+  gtk_text_iter_set_line_offset (location, 0);
+
+  while (!gtk_text_iter_ends_line (location))
+    {
+      if (gtk_text_iter_get_char (location) == tab_char)
+        visual_col += (tab_width - (visual_col % tab_width));
+      else
+        ++visual_col;
+
+      if (visual_col > column)
+        break;
+
+      /* FIXME: this does not handle invisible text correctly, but
+       *       * gtk_text_iter_forward_visible_cursor_position is too
+       *       slow */
+      if (!gtk_text_iter_forward_char (location))
+        break;
+    }
+}
+
 const gchar *
 ide_source_view_get_mode_name (IdeSourceView *self)
 {
@@ -8703,10 +8744,19 @@ ide_source_view_set_highlight_current_line (IdeSourceView *self,
     }
 }
 
+guint
+ide_source_view_get_visual_column (IdeSourceView *self,
+                                   const GtkTextIter *location)
+{
+  g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), 0);
+
+  return gtk_source_view_get_visual_column(GTK_SOURCE_VIEW (self), location);
+}
+
 void
 ide_source_view_get_visual_position (IdeSourceView *self,
                                      guint         *line,
-                                     guint         *column)
+                                     guint         *line_column)
 {
   IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
   GtkTextBuffer *buffer;
@@ -8717,7 +8767,10 @@ ide_source_view_get_visual_position (IdeSourceView *self,
   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
 
   if (!gtk_widget_has_focus (GTK_WIDGET (self)))
-    gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, priv->saved_line, priv->saved_line_offset);
+    {
+      gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, priv->saved_line, 0);
+      ide_source_view_get_iter_at_visual_column (self, priv->saved_line_column, &iter);
+    }
   else
     {
       GtkTextMark *mark;
@@ -8729,8 +8782,8 @@ ide_source_view_get_visual_position (IdeSourceView *self,
   if (line)
     *line = gtk_text_iter_get_line (&iter);
 
-  if (column)
-    *column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self), &iter);
+  if (line_column)
+    *line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self), &iter);
 }
 
 void
diff --git a/libide/sourceview/ide-source-view.h b/libide/sourceview/ide-source-view.h
index 8684bfe..c898f23 100644
--- a/libide/sourceview/ide-source-view.h
+++ b/libide/sourceview/ide-source-view.h
@@ -328,15 +328,20 @@ struct _IdeSourceViewClass
 void                        ide_source_view_clear_snippets            (IdeSourceView              *self);
 IdeSourceSnippet           *ide_source_view_get_current_snippet       (IdeSourceView              *self);
 IdeBackForwardList         *ide_source_view_get_back_forward_list     (IdeSourceView              *self);
+guint                       ide_source_view_get_visual_column         (IdeSourceView              *self,
+                                                                       const GtkTextIter          *location);
 void                        ide_source_view_get_visual_position       (IdeSourceView              *self,
                                                                        guint                      *line,
-                                                                       guint                      
*line_offset);
+                                                                       guint                      
*line_column);
 gint                        ide_source_view_get_count                 (IdeSourceView              *self);
 gboolean                    ide_source_view_get_enable_word_completion(IdeSourceView              *self);
 IdeFileSettings            *ide_source_view_get_file_settings         (IdeSourceView              *self);
 const PangoFontDescription *ide_source_view_get_font_desc             (IdeSourceView              *self);
 gboolean                    ide_source_view_get_highlight_current_line(IdeSourceView              *self);
 gboolean                    ide_source_view_get_insert_matching_brace (IdeSourceView              *self);
+void                        ide_source_view_get_iter_at_visual_column (IdeSourceView              *self,
+                                                                       guint                      column,
+                                                                       GtkTextIter                *location);
 const gchar                *ide_source_view_get_mode_display_name     (IdeSourceView              *self);
 const gchar                *ide_source_view_get_mode_name             (IdeSourceView              *self);
 gboolean                    ide_source_view_get_overwrite_braces      (IdeSourceView              *self);


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