[gnome-builder] Multiple Cursor support in editor



commit 057b61912d882b015d3dd0ffbe6c5616858083fc
Author: Anoop Chandu <anoopchandu96 gmail com>
Date:   Thu Apr 27 15:47:25 2017 +0530

    Multiple Cursor support in editor
    
    New cursors can be added in 3 ways:
    -By selcting text and pressing <ctrl><shift>e a new cursor in every row of
     selected text in same column will be created.
    -By holding <ctrl> and clicking any where in editor.
    -By selecting text and presing <ctrl><shift>d a new cursor will be created at
     next occurence of selected text.
    
    All cursors can be removed by pressing Escape.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=777488

 data/keybindings/default.css        |    5 +-
 data/keybindings/emacs.css          |    5 +-
 data/keybindings/vim.css            |    8 +-
 libide/Makefile.am                  |    3 +
 libide/ide-enums.c.in               |    1 +
 libide/sourceview/ide-cursor.c      |  853 +++++++++++++++++++++++++++++++++++
 libide/sourceview/ide-cursor.h      |   48 ++
 libide/sourceview/ide-source-view.c |  111 +++++
 libide/sourceview/ide-source-view.h |    3 +
 9 files changed, 1033 insertions(+), 4 deletions(-)
---
diff --git a/data/keybindings/default.css b/data/keybindings/default.css
index c064ef8..32895e7 100644
--- a/data/keybindings/default.css
+++ b/data/keybindings/default.css
@@ -7,9 +7,12 @@
                   "clear-selection" ()
                   "clear-count" ()
                   "clear-snippets" ()
-                  "hide-completion" () };
+                  "hide-completion" ()
+                  "remove-cursors" () };
   bind "<shift>F7" { "action" ("frame", "show-spellcheck", "1") };
   bind "<ctrl>f" { "action" ("frame", "find", "3") };
+  bind "<ctrl><shift>e" { "add-cursor" (column) };
+  bind "<ctrl><shift>d" { "add-cursor" (match) };
   bind "<ctrl>h" { "action" ("frame", "find-replace", "3") };
   bind "<ctrl>o" { "action" ("win", "open-with-dialog", "") };
   bind "<ctrl>s" { "action" ("view", "save", "") };
diff --git a/data/keybindings/emacs.css b/data/keybindings/emacs.css
index f0fea7c..9107119 100644
--- a/data/keybindings/emacs.css
+++ b/data/keybindings/emacs.css
@@ -53,7 +53,10 @@
                   "clear-selection" ()
                   "clear-count" ()
                   "clear-snippets" ()
-                  "hide-completion" () };
+                  "hide-completion" ()
+                  "remove-cursors" () };
+  bind "<ctrl><shift>e" { "add-cursor" (column) };
+  bind "<ctrl><shift>d" { "add-cursor" (match) };
   bind "<ctrl>x" { "set-mode" ("emacs-x", transient) };
   bind "<ctrl>c" { "set-mode" ("emacs-c", transient) };
   bind "<ctrl>underscore" { "undo" () };
diff --git a/data/keybindings/vim.css b/data/keybindings/vim.css
index 008e2df..652a3c2 100644
--- a/data/keybindings/vim.css
+++ b/data/keybindings/vim.css
@@ -84,7 +84,8 @@
                   "clear-selection" ()
                   "clear-snippets" ()
                   "hide-completion" ()
-                  "set-mode" ("vim-normal", permanent) };
+                  "set-mode" ("vim-normal", permanent)
+                  "remove-cursors" () };
   bind "<ctrl>bracketleft" { "end-macro" ()
                              "set-overwrite" (0)
                              "clear-count" ()
@@ -108,6 +109,8 @@
   bind "<ctrl>plus" { "increase-font-size" () };
   bind "<ctrl>equal" { "increase-font-size" () };
   bind "<ctrl>0" { "reset-font-size" () };
+  bind "<ctrl><shift>e" { "add-cursor" (column) };
+  bind "<ctrl><shift>d" { "add-cursor" (match) };
 
   bind "F4" { "action" ("view", "find-other-file", "") };
 
@@ -1920,7 +1923,8 @@
                   "clear-snippets" ()
                   "hide-completion" ()
                   "movement" (previous-char, 0, 1, 0)
-                  "set-mode" ("vim-normal", permanent) };
+                  "set-mode" ("vim-normal", permanent) 
+                  "remove-cursors" () };
   bind "<ctrl>bracketleft" { "end-macro" ()
                              "set-overwrite" (0)
                              "clear-count" ()
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 47e8d88..d20adf8 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -138,6 +138,7 @@ libide_1_0_la_public_headers =                                              \
        sourceview/ide-completion-provider.h                                \
        sourceview/ide-completion-results.h                                 \
        sourceview/ide-completion-words.h                                   \
+       sourceview/ide-cursor.h                                             \
        sourceview/ide-indenter.h                                           \
        sourceview/ide-language.h                                           \
        sourceview/ide-source-map.h                                         \
@@ -325,6 +326,7 @@ libide_1_0_la_public_sources =                                              \
        sourceview/ide-completion-provider.c                                \
        sourceview/ide-completion-results.c                                 \
        sourceview/ide-completion-words.c                                   \
+       sourceview/ide-cursor.c                                             \
        sourceview/ide-indenter.c                                           \
        sourceview/ide-language.c                                           \
        sourceview/ide-source-map.c                                         \
@@ -663,6 +665,7 @@ glib_enum_headers =                                                         \
        files/ide-indent-style.h                                            \
        highlighting/ide-highlighter.h                                      \
        runtimes/ide-runtime.h                                              \
+       sourceview/ide-cursor.h                                             \
        sourceview/ide-source-view.h                                        \
        symbols/ide-symbol.h                                                \
        threading/ide-thread-pool.h                                         \
diff --git a/libide/ide-enums.c.in b/libide/ide-enums.c.in
index 069de62..f23475a 100644
--- a/libide/ide-enums.c.in
+++ b/libide/ide-enums.c.in
@@ -13,6 +13,7 @@
 #include "files/ide-indent-style.h"
 #include "highlighting/ide-highlighter.h"
 #include "runtimes/ide-runtime.h"
+#include "sourceview/ide-cursor.h"
 #include "sourceview/ide-source-view.h"
 #include "symbols/ide-symbol.h"
 #include "threading/ide-thread-pool.h"
diff --git a/libide/sourceview/ide-cursor.c b/libide/sourceview/ide-cursor.c
new file mode 100644
index 0000000..f794f95
--- /dev/null
+++ b/libide/sourceview/ide-cursor.c
@@ -0,0 +1,853 @@
+/* ide-cursor.c
+ *
+ * Copyright (C) 2017 Anoop Chandu <anoopchandu96 gmail com>
+ *
+ * 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 "ide-cursor"
+
+#include <egg-signal-group.h>
+
+#include "ide-macros.h"
+
+#include "ide-source-view.h"
+#include "ide-cursor.h"
+#include "ide-text-util.h"
+
+struct _IdeCursor
+{
+  GObject                      parent_instance;
+
+  IdeSourceView               *source_view;
+  GtkSourceSearchContext      *search_context;
+
+  GList                       *cursors;
+
+  GtkTextTag                  *highlight_tag;
+
+  EggSignalGroup              *operations_signals;
+
+  guint                        overwrite : 1;
+};
+
+typedef struct
+{
+  GtkTextMark *selection_bound;
+  GtkTextMark *insert;
+} VirtualCursor;
+
+G_DEFINE_TYPE (IdeCursor, ide_cursor, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_IDE_SOURCE_VIEW,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void ide_cursor_set_visible        (IdeCursor     *self,
+                                           GtkTextBuffer *buffer,
+                                           gboolean       visible);
+
+static void ide_cursor_set_real_cursor    (IdeCursor     *self,
+                                           GtkTextBuffer *buffer,
+                                           VirtualCursor *vc);
+
+static void ide_cursor_set_virtual_cursor (IdeCursor     *self,
+                                           GtkTextBuffer *buffer,
+                                           VirtualCursor *vc);
+
+static void
+ide_cursor_dispose (GObject *object)
+{
+  IdeCursor *self = (IdeCursor *)object;
+  GtkTextBuffer *buffer;
+
+  g_return_if_fail (IDE_IS_CURSOR (self));
+
+  if (self->source_view != NULL)
+    {
+      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+      if (self->highlight_tag != NULL)
+        gtk_text_tag_table_remove (gtk_text_buffer_get_tag_table (buffer),
+                                   self->highlight_tag);
+      ide_clear_weak_pointer (&self->source_view);
+    }
+
+  if (self->operations_signals != NULL)
+    {
+      egg_signal_group_set_target (self->operations_signals, NULL);
+      g_clear_object (&self->operations_signals);
+    }
+
+  g_clear_object (&self->highlight_tag);
+  g_clear_object (&self->search_context);
+
+  if (buffer != NULL && self->cursors != NULL)
+    {
+      for (const GList *iter = self->cursors; iter != NULL; iter = iter->next)
+        {
+          VirtualCursor *vc;
+
+          vc = iter->data;
+
+          if (buffer != NULL)
+            {
+              gtk_text_buffer_delete_mark (buffer, vc->insert);
+              gtk_text_buffer_delete_mark (buffer, vc->selection_bound);
+            }
+
+          g_slice_free (VirtualCursor, vc);
+        }
+
+    }
+
+  g_clear_pointer (&self->cursors, g_list_free);
+}
+
+
+/* toggles the visibility of cursors */
+static void
+ide_cursor_set_visible (IdeCursor      *self,
+                        GtkTextBuffer  *buffer,
+                        gboolean        visible)
+{
+  g_assert (IDE_IS_CURSOR (self));
+  g_assert (GTK_IS_TEXT_BUFFER (buffer));
+
+  if (self->cursors != NULL)
+    {
+      for (const GList *iter = self->cursors; iter != NULL; iter = iter->next)
+        {
+          VirtualCursor *vc;
+          GtkTextIter selection_bound, insert;
+
+          vc = iter->data;
+          gtk_text_buffer_get_iter_at_mark (buffer, &selection_bound, vc->selection_bound);
+          gtk_text_buffer_get_iter_at_mark (buffer, &insert, vc->insert);
+
+          if (gtk_text_iter_equal (&insert, &selection_bound))
+            {
+              if (self->overwrite)
+                {
+                  gtk_text_iter_forward_char (&selection_bound);
+                }
+              else
+                {
+                  gtk_text_mark_set_visible (vc->insert, visible);
+                  continue;
+                }
+            }
+
+          if (visible)
+            gtk_text_buffer_apply_tag (buffer, self->highlight_tag, &selection_bound, &insert);
+          else
+            gtk_text_buffer_remove_tag (buffer, self->highlight_tag, &selection_bound, &insert);
+        }
+    }
+}
+
+/* sets real cursor at virtual cursor position */
+static void
+ide_cursor_set_real_cursor (IdeCursor     *self,
+                            GtkTextBuffer *buffer,
+                            VirtualCursor *vc)
+{
+  GtkTextIter selection_bound, insert;
+
+  g_assert (IDE_IS_CURSOR (self));
+  g_assert (GTK_IS_TEXT_BUFFER (buffer));
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &selection_bound, vc->selection_bound);
+  gtk_text_buffer_get_iter_at_mark (buffer, &insert, vc->insert);
+
+  gtk_text_buffer_select_range (buffer, &insert, &selection_bound);
+}
+
+/* sets virutal cursor at actual cursor position */
+static void
+ide_cursor_set_virtual_cursor (IdeCursor     *self,
+                               GtkTextBuffer *buffer,
+                               VirtualCursor *vc)
+{
+  GtkTextIter selection_bound, insert;
+
+  g_assert (IDE_IS_CURSOR (self));
+  g_assert (GTK_IS_TEXT_BUFFER (buffer));
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &insert, gtk_text_buffer_get_insert (buffer));
+  gtk_text_buffer_get_iter_at_mark (buffer, &selection_bound, gtk_text_buffer_get_selection_bound (buffer));
+
+  gtk_text_buffer_move_mark (buffer, vc->selection_bound, &selection_bound);
+  gtk_text_buffer_move_mark (buffer, vc->insert, &insert);
+}
+
+void
+ide_cursor_remove_cursors (IdeCursor *self)
+{
+  g_return_if_fail (IDE_IS_CURSOR (self));
+
+  if (self->cursors != NULL)
+    {
+      GtkTextBuffer *buffer;
+      VirtualCursor *vc;
+
+      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+      ide_cursor_set_visible (self, buffer, FALSE);
+
+      for (const GList *iter = self->cursors; iter != NULL; iter = iter->next)
+        {
+          vc = iter->data;
+
+          gtk_text_buffer_delete_mark (buffer, vc->insert);
+          gtk_text_buffer_delete_mark (buffer, vc->selection_bound);
+
+          g_slice_free (VirtualCursor, vc);
+        }
+
+      g_clear_pointer (&self->cursors, g_list_free);
+    }
+}
+
+static void
+ide_cursor_add_cursor_by_column (IdeCursor *self)
+{
+  GtkTextBuffer *buffer;
+  GtkTextIter begin, end, temp;
+  gint begin_line, begin_offset, end_line, end_offset, offset;
+  GtkTextIter iter;
+
+  g_assert (IDE_IS_CURSOR (self));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+  if (!gtk_text_buffer_get_selection_bounds (buffer, &begin, &end))
+    return;
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &temp, gtk_text_buffer_get_insert (buffer));
+  offset = gtk_text_iter_get_line_offset (&temp);
+
+  begin_line = gtk_text_iter_get_line (&begin);
+  begin_offset = gtk_text_iter_get_line_offset (&begin);
+  end_line = gtk_text_iter_get_line (&end);
+  end_offset = gtk_text_iter_get_line_offset (&end);
+
+  if (begin_line == end_line)
+    return;
+
+  for (int i = begin_line; i <= end_line; i++)
+    {
+      VirtualCursor *vc;
+
+      if ((i == begin_line && offset < begin_offset) ||
+          (i == end_line && offset > end_offset))
+        continue;
+
+      gtk_text_buffer_get_iter_at_line_offset (buffer, &iter, i, offset);
+
+      vc = g_slice_new0 (VirtualCursor);
+      vc->selection_bound = gtk_text_buffer_create_mark (buffer, NULL, &iter, FALSE);
+
+      vc->insert = gtk_text_buffer_create_mark (buffer, NULL, &iter, FALSE);
+      self->cursors = g_list_prepend (self->cursors, vc);
+
+      if (self->overwrite)
+        {
+          GtkTextIter iter1 = iter;
+
+          gtk_text_iter_forward_char (&iter1);
+          gtk_text_buffer_apply_tag (buffer, self->highlight_tag, &iter, &iter1);
+        }
+      else
+        {
+          gtk_text_mark_set_visible (vc->insert, TRUE);
+        }
+    }
+
+  gtk_text_buffer_select_range (buffer, &iter, &iter);
+}
+
+static void
+ide_cursor_add_cursor_by_position (IdeCursor *self)
+{
+  GtkTextIter insert_iter, selection_bound_iter;
+  VirtualCursor *vc;
+  GtkTextBuffer *buffer;
+
+  g_assert (IDE_IS_CURSOR (self));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+  gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, gtk_text_buffer_get_insert (buffer));
+  gtk_text_buffer_get_iter_at_mark (buffer, &selection_bound_iter, gtk_text_buffer_get_selection_bound 
(buffer));
+
+  vc = g_slice_new0 (VirtualCursor);
+  vc->selection_bound = gtk_text_buffer_create_mark (buffer, NULL, &insert_iter, FALSE);
+  vc->insert = gtk_text_buffer_create_mark (buffer, NULL, &selection_bound_iter, FALSE);
+  self->cursors = g_list_prepend (self->cursors, vc);
+
+  if (gtk_text_iter_equal (&insert_iter, &selection_bound_iter))
+    {
+      if (self->overwrite)
+        {
+          gtk_text_iter_forward_char (&selection_bound_iter);
+          gtk_text_buffer_apply_tag (buffer, self->highlight_tag, &insert_iter, &selection_bound_iter);
+        }
+      else
+        {
+          gtk_text_mark_set_visible (vc->insert, TRUE);
+        }
+    }
+  else
+    {
+      gtk_text_buffer_apply_tag (buffer, self->highlight_tag, &insert_iter, &selection_bound_iter);
+    }
+}
+
+static void
+ide_cursor_add_cursor_by_match (IdeCursor *self)
+{
+  g_autofree gchar *text = NULL;
+  GtkTextIter begin, end, match_begin, match_end;
+  gboolean has_wrapped_around = FALSE;
+  VirtualCursor *vc;
+  GtkSourceSearchContext *search_context;
+  GtkSourceSearchSettings *search_settings;
+  GtkTextBuffer *buffer;
+
+  g_assert (IDE_IS_CURSOR (self));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+  if (!gtk_text_buffer_get_selection_bounds (buffer, &begin, &end))
+    return;
+  gtk_text_iter_order (&begin, &end);
+
+  text = gtk_text_buffer_get_text (buffer, &begin, &end, FALSE);
+
+  search_context = self->search_context;
+  search_settings = gtk_source_search_context_get_settings (search_context);
+
+  if (g_strcmp0 (gtk_source_search_settings_get_search_text (search_settings), text) != 0)
+    gtk_source_search_settings_set_search_text (search_settings, text);
+
+  if (!gtk_source_search_context_forward2 (search_context, &end,
+                                           &match_begin, &match_end, &has_wrapped_around))
+    return;
+
+  if (self->cursors == NULL)
+    {
+      vc = g_slice_new0 (VirtualCursor);
+      vc->selection_bound = gtk_text_buffer_create_mark (buffer, NULL, &begin, FALSE);
+      vc->insert = gtk_text_buffer_create_mark (buffer, NULL, &end, FALSE);
+
+      self->cursors = g_list_prepend (self->cursors, vc);
+
+      gtk_text_buffer_apply_tag (buffer, self->highlight_tag, &begin, &end);
+    }
+
+  vc = g_slice_new0 (VirtualCursor);
+  vc->selection_bound = gtk_text_buffer_create_mark (buffer, NULL, &match_begin, FALSE);
+  vc->insert = gtk_text_buffer_create_mark (buffer, NULL, &match_end, FALSE);
+
+  self->cursors = g_list_prepend (self->cursors, vc);
+
+  gtk_text_buffer_apply_tag (buffer, self->highlight_tag, &match_begin, &match_end);
+
+  gtk_text_buffer_select_range (buffer, &match_begin, &match_end);
+
+  ide_source_view_scroll_mark_onscreen (self->source_view, vc->insert, TRUE, 0.5, 0.5);
+}
+
+void
+ide_cursor_add_cursor (IdeCursor      *self,
+                       IdeCursorType   type)
+{
+  g_return_if_fail (IDE_IS_CURSOR (self));
+  g_return_if_fail (type<=IDE_CURSOR_MATCH);
+
+  if (type == IDE_CURSOR_COLUMN)
+    ide_cursor_add_cursor_by_column (self);
+  else if (type == IDE_CURSOR_SELECT)
+    ide_cursor_add_cursor_by_position (self);
+  else if (type == IDE_CURSOR_MATCH)
+    ide_cursor_add_cursor_by_match (self);
+}
+
+void
+ide_cursor_insert_text (IdeCursor *self,
+                        gchar     *text,
+                        gint       len)
+{
+  g_return_if_fail (IDE_IS_CURSOR (self));
+
+  if (self->cursors != NULL)
+    {
+      GtkTextBuffer *buffer;
+
+      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->source_view));
+
+      ide_cursor_set_visible (self, buffer, FALSE);
+      ide_cursor_set_virtual_cursor (self, buffer, self->cursors->data);
+
+      for (const GList *iter = self->cursors->next; iter != NULL; iter = iter->next)
+        {
+          VirtualCursor *vc = iter->data;
+          GtkTextIter begin, end;
+
+          gtk_text_buffer_get_iter_at_mark (buffer, &begin, vc->insert);
+          gtk_text_buffer_get_iter_at_mark (buffer, &end, vc->selection_bound);
+
+          if (gtk_text_iter_equal (&begin, &end))
+            {
+              if (self->overwrite)
+                {
+                  gtk_text_iter_forward_char (&end);
+                  gtk_text_buffer_delete (buffer, &begin, &end);
+                  gtk_text_buffer_get_iter_at_mark (buffer, &end, vc->insert);
+                }
+              gtk_text_buffer_insert (buffer, &end, text, len);
+            }
+          else
+            {
+              gtk_text_buffer_delete (buffer, &begin, &end);
+              gtk_text_buffer_get_iter_at_mark (buffer, &end, vc->insert);
+              gtk_text_buffer_insert (buffer, &end, text, len);
+            }
+        }
+
+      ide_cursor_set_visible (self, buffer, TRUE);
+    }
+}
+
+static void
+ide_cursor_backspace (GtkTextView *text_view,
+                      IdeCursor   *self)
+{
+  g_assert (GTK_IS_TEXT_VIEW (text_view));
+  g_assert (IDE_IS_CURSOR (self));
+
+  if (self->cursors != NULL)
+    {
+      GtkTextBuffer *buffer;
+
+      buffer = gtk_text_view_get_buffer (text_view);
+
+      ide_cursor_set_visible (self, buffer, FALSE);
+      ide_cursor_set_virtual_cursor (self, buffer, self->cursors->data);
+
+      gtk_text_buffer_begin_user_action (buffer);
+
+      for (const GList *iter = self->cursors->next; iter != NULL; iter = iter->next)
+        {
+          VirtualCursor *vc = iter->data;
+          GtkTextIter begin, end;
+
+          gtk_text_buffer_get_iter_at_mark (buffer, &begin, vc->selection_bound);
+          gtk_text_buffer_get_iter_at_mark (buffer, &end, vc->insert);
+
+          if (gtk_text_iter_equal (&begin, &end))
+            gtk_text_buffer_backspace (buffer, &end, TRUE, gtk_text_view_get_editable (text_view));
+          else
+            gtk_text_buffer_delete (buffer, &begin, &end);
+        }
+
+      gtk_text_buffer_end_user_action (buffer);
+
+      ide_cursor_set_visible (self, buffer, TRUE);
+    }
+}
+
+static void
+ide_cursor_delete_from_cursor (GtkTextView    *text_view,
+                               GtkDeleteType   delete_type,
+                               gint            count,
+                               IdeCursor      *self)
+{
+  g_assert (IDE_IS_SOURCE_VIEW (text_view));
+  g_assert (IDE_IS_CURSOR (self));
+
+  if (self->cursors != NULL)
+    {
+      GtkTextBuffer *buffer;
+      GtkTextIter ins;
+      GtkTextMark *mark;
+
+      buffer = gtk_text_view_get_buffer (text_view);
+
+      gtk_text_buffer_get_iter_at_mark (buffer, &ins, gtk_text_buffer_get_insert(buffer));
+      mark = gtk_text_buffer_create_mark (buffer, NULL, &ins, FALSE);
+
+      ide_cursor_set_visible (self, buffer, FALSE);
+      ide_cursor_set_virtual_cursor (self, buffer, self->cursors->data);
+
+      gtk_text_buffer_begin_user_action (buffer);
+
+      for (const GList *iter = self->cursors->next; iter != NULL; iter = iter->next)
+        {
+          VirtualCursor *vc = iter->data;
+          GtkTextIter begin, end;
+
+          gtk_text_buffer_get_iter_at_mark (buffer, &begin, vc->selection_bound);
+          gtk_text_buffer_get_iter_at_mark (buffer, &end, vc->insert);
+
+          ide_cursor_set_real_cursor (self, buffer, vc);
+
+          if (delete_type == GTK_DELETE_PARAGRAPHS)
+            ide_text_util_delete_line (text_view, count);
+          else
+            GTK_TEXT_VIEW_GET_CLASS (text_view)->delete_from_cursor (text_view,
+                                                                     delete_type,
+                                                                     count);
+          ide_cursor_set_virtual_cursor (self, buffer, vc);
+        }
+
+      gtk_text_buffer_end_user_action (buffer);
+
+      ide_cursor_set_visible (self, buffer, TRUE);
+      gtk_text_buffer_get_iter_at_mark (buffer, &ins, mark);
+      gtk_text_buffer_select_range (buffer, &ins, &ins);
+    }
+}
+
+static void
+ide_cursor_delete_selection (IdeSourceView *source_view,
+                             IdeCursor     *self)
+{
+  GtkTextBuffer *buffer;
+  gboolean editable;
+
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+  g_assert (IDE_IS_CURSOR (self));
+
+  buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view));
+  editable = gtk_text_view_get_editable (GTK_TEXT_VIEW (source_view));
+
+  if (!editable)
+    return;
+
+  if (self->cursors != NULL)
+    {
+      ide_cursor_set_visible (self, buffer, FALSE);
+      ide_cursor_set_virtual_cursor (self, buffer, self->cursors->data);
+
+      gtk_text_buffer_begin_user_action (buffer);
+
+      for (const GList *iter = self->cursors->next; iter != NULL; iter = iter->next)
+        {
+          VirtualCursor *vc = iter->data;
+          GtkTextIter begin, end;
+
+          gtk_text_buffer_get_iter_at_mark (buffer, &begin, vc->selection_bound);
+          gtk_text_buffer_get_iter_at_mark (buffer, &end, vc->insert);
+
+          gtk_text_iter_order (&begin, &end);
+
+          if (gtk_text_iter_is_end (&end) && gtk_text_iter_starts_line (&begin))
+            gtk_text_iter_backward_char (&begin);
+
+          gtk_text_buffer_delete (buffer, &begin, &end);
+        }
+
+      gtk_text_buffer_end_user_action (buffer);
+      ide_cursor_set_visible (self, buffer, TRUE);
+    }
+}
+
+static void
+ide_cursor_move_cursor (GtkTextView    *text_view,
+                        GtkMovementStep step,
+                        gint            count,
+                        gboolean        extend_selection,
+                        IdeCursor      *self)
+{
+  g_assert (GTK_IS_TEXT_VIEW (text_view));
+  g_assert (IDE_IS_CURSOR (self));
+
+  if (self->cursors != NULL)
+    {
+      GtkTextBuffer *buffer;
+      GtkTextIter sel, ins;
+
+      buffer = gtk_text_view_get_buffer (text_view);
+
+      gtk_text_buffer_get_iter_at_mark (buffer, &ins, gtk_text_buffer_get_insert (buffer));
+      gtk_text_buffer_get_iter_at_mark (buffer, &sel, gtk_text_buffer_get_selection_bound (buffer));
+
+      ide_cursor_set_visible (self, buffer, FALSE);
+      ide_cursor_set_virtual_cursor (self, buffer, self->cursors->data);
+
+      for (const GList *iter = self->cursors->next; iter != NULL; iter = iter->next)
+        {
+          ide_cursor_set_real_cursor (self, buffer, iter->data);
+
+          GTK_TEXT_VIEW_GET_CLASS (text_view)->move_cursor (text_view,
+                                                            step,
+                                                            count,
+                                                            extend_selection);
+
+          ide_cursor_set_virtual_cursor (self, buffer, iter->data);
+        }
+
+      ide_cursor_set_visible (self, buffer, TRUE);
+      gtk_text_buffer_select_range (buffer, &ins, &sel);
+
+      gtk_text_view_scroll_mark_onscreen (text_view, gtk_text_buffer_get_insert (buffer));
+    }
+}
+
+static void
+ide_cursor_movement (IdeSourceView         *source_view,
+                     IdeSourceViewMovement  movement,
+                     gboolean               extend_selection,
+                     gboolean               exclusive,
+                     gboolean               apply_count,
+                     IdeCursor             *self)
+{
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+  g_assert (IDE_IS_CURSOR (self));
+
+  if (self->cursors)
+    {
+      GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view));
+      GtkTextIter sel,ins;
+
+      gtk_text_buffer_get_iter_at_mark (buffer, &sel, gtk_text_buffer_get_selection_bound(buffer));
+      gtk_text_buffer_get_iter_at_mark (buffer, &ins, gtk_text_buffer_get_insert(buffer));
+
+      ide_cursor_set_visible (self, buffer, FALSE);
+      ide_cursor_set_virtual_cursor (self, buffer, self->cursors->data);
+
+      for (const GList *iter = self->cursors->next; iter != NULL; iter = iter->next)
+        {
+          ide_cursor_set_real_cursor (self, buffer, iter->data);
+
+          IDE_SOURCE_VIEW_GET_CLASS (source_view)->movement (source_view,
+                                                             movement,
+                                                             extend_selection,
+                                                             exclusive,
+                                                             apply_count);
+
+          ide_cursor_set_virtual_cursor (self, buffer, iter->data);
+        }
+      ide_cursor_set_visible (self, buffer, TRUE);
+      gtk_text_buffer_select_range (buffer, &ins, &sel);
+    }
+}
+
+static void
+ide_cursor_select_inner (IdeSourceView *source_view,
+                         const gchar   *inner_left,
+                         const gchar   *inner_right,
+                         gboolean       exclusive,
+                         gboolean       string_mode,
+                         IdeCursor     *self)
+{
+  g_assert (IDE_IS_SOURCE_VIEW (source_view));
+  g_assert (IDE_IS_CURSOR (self));
+
+  if (self->cursors != NULL)
+    {
+      GtkTextBuffer *buffer;
+      GtkTextIter sel,ins;
+
+      buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (source_view));
+
+      gtk_text_buffer_get_iter_at_mark (buffer, &sel, gtk_text_buffer_get_selection_bound (buffer));
+      gtk_text_buffer_get_iter_at_mark (buffer, &ins, gtk_text_buffer_get_insert (buffer));
+
+      ide_cursor_set_visible (self, buffer, FALSE);
+      ide_cursor_set_virtual_cursor (self, buffer, self->cursors->data);
+
+      for (const GList *iter = self->cursors->next; iter != NULL; iter = iter->next)
+        {
+          ide_cursor_set_real_cursor (self, buffer, iter->data);
+
+          IDE_SOURCE_VIEW_GET_CLASS (source_view)->select_inner (source_view,
+                                                                 inner_left,
+                                                                 inner_right,
+                                                                 exclusive,
+                                                                 string_mode);
+
+          ide_cursor_set_virtual_cursor (self, buffer, iter->data);
+        }
+
+      ide_cursor_set_visible (self, buffer, TRUE);
+      gtk_text_buffer_select_range (buffer, &ins, &sel);
+    }
+}
+
+static void
+ide_cursor_toggle_overwrite (GtkTextView *text_view,
+                             IdeCursor   *self)
+{
+  GtkTextBuffer *buffer;
+
+  g_assert (GTK_IS_TEXT_VIEW (text_view));
+  g_assert (IDE_IS_CURSOR (self));
+
+  buffer = gtk_text_view_get_buffer (text_view);
+
+  ide_cursor_set_visible (self, buffer, FALSE);
+  self->overwrite = gtk_text_view_get_overwrite (text_view);
+  ide_cursor_set_visible (self, buffer, TRUE);
+}
+
+static void
+ide_cursor_constructed (GObject *object)
+{
+  IdeCursor *self = (IdeCursor *)object;
+  GtkTextView *text_view;
+  GtkTextBuffer *buffer;
+  g_autoptr(GtkSourceSearchSettings) search_settings = NULL;
+
+  G_OBJECT_CLASS (ide_cursor_parent_class)->constructed (object);
+
+  text_view = GTK_TEXT_VIEW (self->source_view);
+
+  buffer = gtk_text_view_get_buffer (text_view);
+
+  search_settings = g_object_new (GTK_SOURCE_TYPE_SEARCH_SETTINGS,
+                                  "wrap-around", FALSE,
+                                  "regex-enabled", FALSE,
+                                  "case-sensitive", TRUE,
+                                  NULL);
+  self->search_context = g_object_new (GTK_SOURCE_TYPE_SEARCH_CONTEXT,
+                                       "buffer", buffer,
+                                       "highlight", FALSE,
+                                       "settings", search_settings,
+                                       NULL);
+
+  gtk_text_tag_table_add (gtk_text_buffer_get_tag_table (buffer),
+                          self->highlight_tag);
+
+  self->overwrite = gtk_text_view_get_overwrite (text_view);
+
+  egg_signal_group_set_target (self->operations_signals, self->source_view);
+}
+
+static void
+ide_cursor_get_property (GObject    *object,
+                         guint       prop_id,
+                         GValue     *value,
+                         GParamSpec *pspec)
+{
+  IdeCursor *self = IDE_CURSOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_IDE_SOURCE_VIEW:
+      g_value_set_object (value, self->source_view);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_cursor_set_property (GObject      *object,
+                         guint         prop_id,
+                         const GValue *value,
+                         GParamSpec   *pspec)
+{
+  IdeCursor *self = IDE_CURSOR (object);
+
+  switch (prop_id)
+    {
+    case PROP_IDE_SOURCE_VIEW:
+      ide_set_weak_pointer (&self->source_view, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_cursor_class_init (IdeCursorClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_cursor_constructed;
+  object_class->dispose = ide_cursor_dispose;
+  object_class->get_property = ide_cursor_get_property;
+  object_class->set_property = ide_cursor_set_property;
+
+  properties [PROP_IDE_SOURCE_VIEW] =
+    g_param_spec_object ("ide-source-view",
+                         "IdeSourceView",
+                         "The IdeSourceView on which cursors are there",
+                         IDE_TYPE_SOURCE_VIEW,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_cursor_init (IdeCursor *self)
+{
+  self->highlight_tag = g_object_new (GTK_TYPE_TEXT_TAG,
+                                      "underline", PANGO_UNDERLINE_SINGLE,
+                                      NULL);
+
+  self->operations_signals = egg_signal_group_new (IDE_TYPE_SOURCE_VIEW);
+
+  egg_signal_group_connect_object (self->operations_signals,
+                                   "move-cursor",
+                                   G_CALLBACK (ide_cursor_move_cursor),
+                                   self,
+                                   G_CONNECT_AFTER);
+  egg_signal_group_connect_object (self->operations_signals,
+                                   "delete-from-cursor",
+                                   G_CALLBACK (ide_cursor_delete_from_cursor),
+                                   self,
+                                   G_CONNECT_AFTER);
+  egg_signal_group_connect_object (self->operations_signals,
+                                   "backspace",
+                                   G_CALLBACK (ide_cursor_backspace),
+                                   self,
+                                   G_CONNECT_AFTER);
+  egg_signal_group_connect_object (self->operations_signals,
+                                   "toggle-overwrite",
+                                   G_CALLBACK (ide_cursor_toggle_overwrite),
+                                   self,
+                                   G_CONNECT_AFTER);
+  egg_signal_group_connect_object (self->operations_signals,
+                                   "movement",
+                                   G_CALLBACK (ide_cursor_movement),
+                                   self,
+                                   G_CONNECT_AFTER);
+  egg_signal_group_connect_object (self->operations_signals,
+                                   "select-inner",
+                                   G_CALLBACK (ide_cursor_select_inner),
+                                   self,
+                                   G_CONNECT_AFTER);
+  egg_signal_group_connect_object (self->operations_signals,
+                                   "delete-selection",
+                                   G_CALLBACK (ide_cursor_delete_selection),
+                                   self,
+                                   G_CONNECT_AFTER);
+}
+
+gboolean
+ide_cursor_is_enabled (IdeCursor *self)
+{
+  g_return_val_if_fail (IDE_IS_CURSOR (self), FALSE);
+
+  return (self->cursors != NULL);
+}
diff --git a/libide/sourceview/ide-cursor.h b/libide/sourceview/ide-cursor.h
new file mode 100644
index 0000000..4e377f4
--- /dev/null
+++ b/libide/sourceview/ide-cursor.h
@@ -0,0 +1,48 @@
+/* ide-cursor.h
+ *
+ * Copyright (C) 2017 Anoop Chandu <anoopchandu96 gmail com>
+ *
+ * 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 __IDE_CURSOR_
+
+#define __IDE_CURSOR_
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CURSOR (ide_cursor_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeCursor, ide_cursor, IDE, CURSOR, GObject)
+
+typedef enum
+{
+  IDE_CURSOR_COLUMN,
+  IDE_CURSOR_SELECT,
+  IDE_CURSOR_MATCH
+} IdeCursorType;
+
+void         ide_cursor_add_cursor            (IdeCursor *self,
+                                               guint      type);
+void         ide_cursor_remove_cursors        (IdeCursor *self);
+void         ide_cursor_insert_text           (IdeCursor *self,
+                                               gchar     *text,
+                                               gint       len);
+gboolean     ide_cursor_is_enabled            (IdeCursor *self);
+
+G_END_DECLS
+
+#endif
diff --git a/libide/sourceview/ide-source-view.c b/libide/sourceview/ide-source-view.c
index 6bd5a53..878c44d 100644
--- a/libide/sourceview/ide-source-view.c
+++ b/libide/sourceview/ide-source-view.c
@@ -63,6 +63,7 @@
 #include "sourceview/ide-source-view-movements.h"
 #include "sourceview/ide-source-view.h"
 #include "sourceview/ide-text-util.h"
+#include "sourceview/ide-cursor.h"
 #include "symbols/ide-symbol.h"
 #include "symbols/ide-symbol-resolver.h"
 #include "theatrics/ide-box-theatric.h"
@@ -174,6 +175,8 @@ typedef struct
 
   GRegex                      *include_regex;
 
+  IdeCursor                   *cursor;
+
   guint                        auto_indent : 1;
   guint                        completion_blocked : 1;
   guint                        completion_visible : 1;
@@ -253,6 +256,7 @@ enum {
 
 enum {
   ACTION,
+  ADD_CURSOR,
   APPEND_TO_COUNT,
   AUTO_INDENT,
   BEGIN_MACRO,
@@ -290,6 +294,7 @@ enum {
   PUSH_SNIPPET,
   REBUILD_HIGHLIGHT,
   REINDENT,
+  REMOVE_CURSORS,
   REPLAY_MACRO,
   REQUEST_DOCUMENTATION,
   RESET_FONT_SIZE,
@@ -1251,6 +1256,8 @@ ide_source_view__buffer_insert_text_cb (IdeSourceView *self,
   g_assert (text != NULL);
   g_assert (GTK_IS_TEXT_BUFFER (buffer));
 
+  gtk_text_buffer_begin_user_action (buffer);
+
   if (NULL != (snippet = g_queue_peek_head (priv->snippets)))
     {
       ide_source_view_block_handlers (self);
@@ -1270,6 +1277,7 @@ ide_source_view__buffer_insert_text_after_cb (IdeSourceView *self,
 {
   IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
   IdeSourceSnippet *snippet;
+  GtkTextIter insert;
 
   IDE_ENTRY;
 
@@ -1303,6 +1311,17 @@ ide_source_view__buffer_insert_text_after_cb (IdeSourceView *self,
       ide_source_view_maybe_overwrite (self, iter, text, len);
     }
 
+  gtk_text_buffer_get_iter_at_mark (buffer, &insert, gtk_text_buffer_get_insert(buffer));
+  if (gtk_text_iter_equal (iter, &insert))
+    {
+      ide_source_view_block_handlers (self);
+      ide_cursor_insert_text (priv->cursor, text, len);
+      ide_source_view_unblock_handlers (self);
+      gtk_text_buffer_get_iter_at_mark (buffer, iter, gtk_text_buffer_get_insert (buffer));
+    }
+
+  gtk_text_buffer_end_user_action (buffer);
+
   IDE_EXIT;
 }
 
@@ -1692,6 +1711,10 @@ ide_source_view_bind_buffer (IdeSourceView  *self,
 
   g_clear_object (&search_settings);
 
+  priv->cursor = g_object_new (IDE_TYPE_CURSOR,
+                               "ide-source-view", self,
+                               NULL);
+
   /* Create scroll mark used by movements and our scrolling helper */
   gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (buffer), &iter);
   priv->scroll_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer), NULL, &iter, TRUE);
@@ -1757,6 +1780,12 @@ ide_source_view_unbind_buffer (IdeSourceView  *self,
 
   egg_signal_group_set_target (priv->completion_providers_signals, NULL);
 
+  if (priv->cursor != NULL)
+    {
+      g_object_run_dispose (G_OBJECT (priv->cursor));
+      g_clear_object (&priv->cursor);
+    }
+
   g_clear_object (&priv->search_context);
   g_clear_object (&priv->indenter_adapter);
   g_clear_object (&priv->completion_providers);
@@ -2645,6 +2674,19 @@ ide_source_view_real_button_press_event (GtkWidget      *widget,
   if (ide_source_view_process_press_on_definition (self, event))
     return TRUE;
 
+  if (event->button == GDK_BUTTON_PRIMARY)
+    {
+      if (event->state & GDK_CONTROL_MASK)
+        {
+          if (!ide_cursor_is_enabled (priv->cursor))
+            ide_cursor_add_cursor (priv->cursor, IDE_CURSOR_SELECT);
+        }
+      else if (ide_cursor_is_enabled (priv->cursor))
+        {
+          ide_cursor_remove_cursors (priv->cursor);
+        }
+    }
+
   ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->button_press_event (widget, event);
 
   /*
@@ -2687,6 +2729,24 @@ ide_source_view_real_button_press_event (GtkWidget      *widget,
 }
 
 static gboolean
+ide_source_view_real_button_release_event (GtkWidget      *widget,
+                                           GdkEventButton *event)
+{
+  IdeSourceView *self = (IdeSourceView *)widget;
+  IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+  gboolean ret;
+
+  g_assert (IDE_IS_SOURCE_VIEW (self));
+
+  ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->button_release_event (widget, event);
+
+  if ((event->button == GDK_BUTTON_PRIMARY) && (event->state & GDK_CONTROL_MASK))
+    ide_cursor_add_cursor (priv->cursor, IDE_CURSOR_SELECT);
+
+  return ret;
+}
+
+static gboolean
 ide_source_get_word_from_iter (const GtkTextIter *iter,
                                GtkTextIter *word_start,
                                GtkTextIter *word_end)
@@ -2987,6 +3047,35 @@ ide_source_view_query_tooltip (GtkWidget  *widget,
 }
 
 static void
+ide_source_view_real_add_cursor (IdeSourceView *self,
+                                 IdeCursorType  type)
+{
+  IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_SOURCE_VIEW (self));
+
+  ide_cursor_add_cursor (priv->cursor, type);
+
+  IDE_EXIT;
+}
+
+static void
+ide_source_view_real_remove_cursors (IdeSourceView *self)
+{
+  IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_SOURCE_VIEW (self));
+
+  ide_cursor_remove_cursors (priv->cursor);
+
+  IDE_EXIT;
+}
+
+static void
 ide_source_view_real_style_updated (GtkWidget *widget)
 {
   IdeSourceView *self = (IdeSourceView *)widget;
@@ -6485,6 +6574,7 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
   object_class->set_property = ide_source_view_set_property;
 
   widget_class->button_press_event = ide_source_view_real_button_press_event;
+  widget_class->button_release_event = ide_source_view_real_button_release_event;
   widget_class->motion_notify_event = ide_source_view_real_motion_notify_event;
   widget_class->draw = ide_source_view_real_draw;
   widget_class->focus_in_event = ide_source_view_focus_in_event;
@@ -6501,6 +6591,8 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
   text_view_class->insert_at_cursor = ide_source_view_real_insert_at_cursor;
   text_view_class->populate_popup = ide_source_view_real_populate_popup;
 
+  klass->add_cursor = ide_source_view_real_add_cursor;
+  klass->remove_cursors = ide_source_view_real_remove_cursors;
   klass->append_to_count = ide_source_view_real_append_to_count;
   klass->begin_macro = ide_source_view_real_begin_macro;
   klass->begin_rename = ide_source_view_real_begin_rename;
@@ -7335,6 +7427,25 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
                   G_TYPE_NONE,
                   0);
 
+  signals [ADD_CURSOR] =
+    g_signal_new ("add-cursor",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (IdeSourceViewClass, add_cursor),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  1,
+                  IDE_TYPE_CURSOR_TYPE);
+
+  signals [REMOVE_CURSORS] =
+    g_signal_new ("remove-cursors",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                  G_STRUCT_OFFSET (IdeSourceViewClass, remove_cursors),
+                  NULL, NULL, NULL,
+                  G_TYPE_NONE,
+                  0);
+
   binding_set = gtk_binding_set_by_class (klass);
 
   gtk_binding_entry_add_signal (binding_set,
diff --git a/libide/sourceview/ide-source-view.h b/libide/sourceview/ide-source-view.h
index c898f23..8d9842a 100644
--- a/libide/sourceview/ide-source-view.h
+++ b/libide/sourceview/ide-source-view.h
@@ -298,6 +298,9 @@ struct _IdeSourceViewClass
   void (*decrease_font_size)          (IdeSourceView           *self);
   void (*reset_font_size)             (IdeSourceView           *self);
   void (*begin_rename)                (IdeSourceView           *self);
+  void (*add_cursor)                  (IdeSourceView           *self,
+                                       guint                    type);
+  void (*remove_cursors)              (IdeSourceView           *self);
 
   gpointer _reserved1;
   gpointer _reserved2;



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