[gnome-builder] Multiple Cursor support in editor
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] Multiple Cursor support in editor
- Date: Mon, 8 May 2017 21:04:52 +0000 (UTC)
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]