gem-graph-client/libide/sourceview/ide-source-view.c

8182 lines
256 KiB
C

/* ide-source-view.c
*
* Copyright 2015-2019 Christian Hergert <christian@hergert.me>
*
* 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/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#define G_LOG_DOMAIN "ide-source-view"
#include "config.h"
#include <cairo-gobject.h>
#include <dazzle.h>
#include <glib/gi18n.h>
#include <libide-code.h>
#include <libide-plugins.h>
#include <libide-threading.h>
#include <stdlib.h>
#include <string.h>
#include "ide-buffer-private.h"
#include "ide-completion-private.h"
#include "ide-completion.h"
#include "ide-cursor.h"
#include "ide-hover-private.h"
#include "ide-indenter.h"
#include "ide-snippet-chunk.h"
#include "ide-snippet-context.h"
#include "ide-snippet-private.h"
#include "ide-snippet.h"
#include "ide-source-view-capture.h"
#include "ide-source-view-mode.h"
#include "ide-source-view-movements.h"
#include "ide-source-view-private.h"
#include "ide-source-view.h"
#include "ide-source-view-enums.h"
#include "ide-text-util.h"
#define INCLUDE_STATEMENTS "^#include[\\s]+[\\\"\\<][^\\s\\\"\\\'\\<\\>[:cntrl:]]+[\\\"\\>]"
#define DEFAULT_FONT_DESC "Monospace 11"
#define ANIMATION_X_GROW 50
#define ANIMATION_Y_GROW 30
#define SMALL_SCROLL_DURATION_MSEC 100
#define LARGE_SCROLL_DURATION_MSEC 250
#define FIXIT_LABEL_LEN_MAX 30
#define SCROLL_REPLAY_DELAY 1000
#define DEFAULT_OVERSCROLL_NUM_LINES 1
#define TAG_DEFINITION "action::hover-definition"
#define DEFINITION_HIGHLIGHT_MODIFIER GDK_CONTROL_MASK
#define ALL_ACCELS_MASK (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK)
#define _GDK_RECTANGLE_X2(rect) dzl_cairo_rectangle_x2(rect)
#define _GDK_RECTANGLE_Y2(rect) dzl_cairo_rectangle_y2(rect)
#define _GDK_RECTANGLE_CONTAINS(rect,other) dzl_cairo_rectangle_contains_rectangle(rect,other)
#define _GDK_RECTANGLE_CENTER_X(rect) dzl_cairo_rectangle_center(rect)
#define _GDK_RECTANGLE_CENTER_Y(rect) dzl_cairo_rectangle_middle(rect)
#define TRACE_RECTANGLE(name, rect) \
IDE_TRACE_MSG ("%s = Rectangle(x=%d, y=%d, width=%d, height=%d)", \
name, (rect)->x, (rect)->y, (rect)->width, (rect)->height)
#define SCROLL_X(align) \
(((align) == IDE_SOURCE_SCROLL_BOTH) || ((align) == IDE_SOURCE_SCROLL_X))
#define SCROLL_Y(align) \
(((align) == IDE_SOURCE_SCROLL_BOTH) || ((align) == IDE_SOURCE_SCROLL_Y))
typedef struct
{
IdeBuffer *buffer;
GtkCssProvider *css_provider;
PangoFontDescription *font_desc;
IdeExtensionAdapter *indenter_adapter;
IdeSourceViewCapture *capture;
gchar *display_name;
IdeSourceViewMode *mode;
GtkTextMark *scroll_mark;
GQueue *selections;
GQueue *snippets;
DzlAnimation *hadj_animation;
DzlAnimation *vadj_animation;
IdeGutter *gutter;
IdeCompletion *completion;
IdeHover *hover;
DzlBindingGroup *file_setting_bindings;
DzlSignalGroup *buffer_signals;
guint change_sequence;
guint target_line_column;
GString *command_str;
gunichar command;
gunichar modifier;
gunichar search_char;
gint count;
gunichar inner_left;
gunichar inner_right;
guint scroll_offset;
gint cached_char_height;
gint cached_char_width;
guint saved_line;
guint saved_line_column;
guint saved_selection_line;
guint saved_selection_line_column;
GdkRGBA snippet_area_background_rgba;
guint font_scale;
gint overscroll_num_lines;
guint delay_size_allocate_chainup;
GtkAllocation delay_size_allocation;
IdeLocation *definition_src_location;
GtkTextMark *definition_highlight_start_mark;
GtkTextMark *definition_highlight_end_mark;
GRegex *include_regex;
IdeCursor *cursor;
guint in_key_press;
guint auto_indent : 1;
guint completion_blocked : 1;
guint did_ctrl_opacity : 1;
guint highlight_current_line : 1;
guint in_replay_macro : 1;
guint insert_mark_cleared : 1;
guint insert_matching_brace : 1;
guint interactive_completion : 1;
guint overwrite_braces : 1;
guint recording_macro : 1;
guint scrolling_to_scroll_mark : 1;
guint show_grid_lines : 1;
guint snippet_completion : 1;
guint waiting_for_capture : 1;
guint waiting_for_symbol : 1;
guint show_line_changes : 1;
guint show_line_diagnostics : 1;
guint show_line_numbers : 1;
guint show_relative_line_numbers : 1;
} IdeSourceViewPrivate;
typedef struct
{
IdeSourceView *self;
GtkTextMark *word_start_mark;
GtkTextMark *word_end_mark;
} DefinitionHighlightData;
typedef struct
{
GPtrArray *resolvers;
IdeLocation *location;
} FindReferencesTaskData;
G_DEFINE_TYPE_WITH_PRIVATE (IdeSourceView, ide_source_view, GTK_SOURCE_TYPE_VIEW)
DZL_DEFINE_COUNTER (instances, "IdeSourceView", "Instances", "Number of IdeSourceView instances")
enum {
PROP_0,
PROP_COMPLETION_N_ROWS,
PROP_COUNT,
PROP_FILE_SETTINGS,
PROP_FONT_NAME,
PROP_FONT_DESC,
PROP_INDENTER,
PROP_INDENT_STYLE,
PROP_INSERT_MATCHING_BRACE,
PROP_INTERACTIVE_COMPLETION,
PROP_MODE_DISPLAY_NAME,
PROP_OVERWRITE_BRACES,
PROP_SCROLL_OFFSET,
PROP_SHOW_GRID_LINES,
PROP_SHOW_LINE_CHANGES,
PROP_SHOW_LINE_DIAGNOSTICS,
PROP_SHOW_RELATIVE_LINE_NUMBERS,
PROP_OVERSCROLL,
LAST_PROP,
/* These are overridden */
PROP_AUTO_INDENT,
PROP_HIGHLIGHT_CURRENT_LINE,
PROP_OVERWRITE,
PROP_SHOW_LINE_NUMBERS,
};
enum {
ACTION,
ADD_CURSOR,
APPEND_TO_COUNT,
AUTO_INDENT,
BEGIN_MACRO,
BEGIN_RENAME,
BEGIN_USER_ACTION,
CAPTURE_MODIFIER,
CLEAR_COUNT,
CLEAR_MODIFIER,
CLEAR_SEARCH,
CLEAR_SELECTION,
CLEAR_SNIPPETS,
COPY_CLIPBOARD_EXTENDED,
CYCLE_COMPLETION,
DECREASE_FONT_SIZE,
DELETE_SELECTION,
DRAW_BUBBLES,
DUPLICATE_ENTIRE_LINE,
END_MACRO,
END_USER_ACTION,
FOCUS_LOCATION,
FORMAT_SELECTION,
QUERY_CODE_ACTION,
FIND_REFERENCES,
GOTO_DEFINITION,
HIDE_COMPLETION,
INCREASE_FONT_SIZE,
INDENT_SELECTION,
INSERT_AT_CURSOR_AND_INDENT,
INSERT_MODIFIER,
JUMP,
MOVEMENT,
MOVE_ERROR,
MOVE_SEARCH,
PASTE_CLIPBOARD_EXTENDED,
POP_SELECTION,
POP_SNIPPET,
PUSH_SELECTION,
PUSH_SNIPPET,
REBUILD_HIGHLIGHT,
REINDENT,
REMOVE_CURSORS,
REPLAY_MACRO,
REQUEST_DOCUMENTATION,
RESET,
RESET_FONT_SIZE,
RESTORE_INSERT_MARK,
SAVE_COMMAND,
SAVE_INSERT_MARK,
SAVE_SEARCH_CHAR,
SELECT_INNER,
SELECT_TAG,
SELECTION_THEATRIC,
SET_MODE,
SET_OVERWRITE,
SET_SEARCH_TEXT,
SORT,
SWAP_SELECTION_BOUNDS,
LAST_SIGNAL
};
enum {
FONT_SCALE_XX_SMALL,
FONT_SCALE_X_SMALL,
FONT_SCALE_SMALL,
FONT_SCALE_NORMAL,
FONT_SCALE_LARGE,
FONT_SCALE_X_LARGE,
FONT_SCALE_XX_LARGE,
FONT_SCALE_XXX_LARGE,
LAST_FONT_SCALE
};
static GParamSpec *properties [LAST_PROP];
static guint signals [LAST_SIGNAL];
static gdouble fontScale [LAST_FONT_SCALE] = {
0.57870, 0.69444, 0.83333,
1.0,
1.2, 1.44, 1.728, 2.48832,
};
static gboolean ide_source_view_do_size_allocate_hack_cb (gpointer data);
static void ide_source_view_real_save_insert_mark (IdeSourceView *self);
static void ide_source_view_real_restore_insert_mark (IdeSourceView *self);
static void ide_source_view_real_set_mode (IdeSourceView *self,
const gchar *name,
IdeSourceViewModeType type);
static void ide_source_view_save_column (IdeSourceView *self);
static void ide_source_view_maybe_overwrite (IdeSourceView *self,
GtkTextIter *iter,
const gchar *text,
gint len);
static gpointer
get_selection_owner (IdeSourceView *self)
{
return g_object_get_data (G_OBJECT (gtk_widget_get_toplevel (GTK_WIDGET (self))),
"IDE_SOURCE_VIEW_SELECTION_OWNER");
}
static void
set_selection_owner (IdeSourceView *self,
gpointer tag)
{
g_object_set_data (G_OBJECT (gtk_widget_get_toplevel (GTK_WIDGET (self))),
"IDE_SOURCE_VIEW_SELECTION_OWNER", tag);
}
static void
find_references_task_data_free (FindReferencesTaskData *data)
{
g_clear_pointer (&data->resolvers, g_ptr_array_unref);
g_clear_object (&data->location);
g_slice_free (FindReferencesTaskData, data);
}
static void
block_interactive (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->completion)
ide_completion_block_interactive (priv->completion);
}
static void
unblock_interactive (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->completion != NULL)
ide_completion_unblock_interactive (priv->completion);
}
static void
ide_source_view_set_interactive_completion (IdeSourceView *self,
gboolean interactive_completion)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
interactive_completion = !!interactive_completion;
if (interactive_completion != priv->interactive_completion)
{
priv->interactive_completion = interactive_completion;
if (interactive_completion)
unblock_interactive (self);
else
block_interactive (self);
}
}
static void
definition_highlight_data_free (DefinitionHighlightData *data)
{
if (data != NULL)
{
GtkTextBuffer *buffer;
buffer = gtk_text_mark_get_buffer (data->word_start_mark);
gtk_text_buffer_delete_mark (buffer, data->word_start_mark);
gtk_text_buffer_delete_mark (buffer, data->word_end_mark);
g_clear_object (&data->self);
g_clear_object (&data->word_start_mark);
g_clear_object (&data->word_end_mark);
g_slice_free (DefinitionHighlightData, data);
}
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (DefinitionHighlightData, definition_highlight_data_free)
static gboolean
ide_source_view_can_animate (IdeSourceView *self)
{
GtkSettings *settings;
GdkScreen *screen;
gboolean can_animate = FALSE;
g_assert (IDE_IS_SOURCE_VIEW (self));
screen = gtk_widget_get_screen (GTK_WIDGET (self));
settings = gtk_settings_get_for_screen (screen);
g_object_get (settings, "gtk-enable-animations", &can_animate, NULL);
return can_animate;
}
void
_ide_source_view_set_count (IdeSourceView *self,
gint count)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
priv->count = count;
}
void
_ide_source_view_set_modifier (IdeSourceView *self,
gunichar modifier)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
priv->modifier = modifier;
if (priv->recording_macro && !priv->in_replay_macro)
ide_source_view_capture_record_modifier (priv->capture, modifier);
}
static IdeIndenter *
ide_source_view_get_indenter (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->indenter_adapter != NULL)
return ide_extension_adapter_get_extension (priv->indenter_adapter);
return NULL;
}
static void
ide_source_view_block_handlers (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
dzl_signal_group_block (priv->buffer_signals);
}
static void
ide_source_view_unblock_handlers (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
dzl_signal_group_unblock (priv->buffer_signals);
}
static void
get_rect_for_iters (GtkTextView *text_view,
const GtkTextIter *iter1,
const GtkTextIter *iter2,
GdkRectangle *rect,
GtkTextWindowType window_type)
{
GdkRectangle area;
GdkRectangle tmp;
GtkTextIter begin;
GtkTextIter end;
GtkTextIter iter;
g_assert (GTK_IS_TEXT_VIEW (text_view));
g_assert (iter1 != NULL);
g_assert (iter2 != NULL);
g_assert (rect != NULL);
g_assert (gtk_text_iter_get_buffer (iter1) == gtk_text_iter_get_buffer (iter2));
g_assert (gtk_text_view_get_buffer (text_view) == gtk_text_iter_get_buffer (iter1));
begin = *iter1;
end = *iter2;
if (gtk_text_iter_equal (&begin, &end))
{
gtk_text_view_get_iter_location (text_view, &begin, &area);
goto finish;
}
gtk_text_iter_order (&begin, &end);
if (gtk_text_iter_get_line (&begin) == gtk_text_iter_get_line (&end))
{
gtk_text_view_get_iter_location (text_view, &begin, &area);
gtk_text_view_get_iter_location (text_view, &end, &tmp);
gdk_rectangle_union (&area, &tmp, &area);
goto finish;
}
gtk_text_view_get_iter_location (text_view, &begin, &area);
iter = begin;
do
{
/* skip trailing newline */
if ((gtk_text_iter_starts_line (&iter) && gtk_text_iter_equal (&iter, &end)))
break;
gtk_text_view_get_iter_location (text_view, &iter, &tmp);
gdk_rectangle_union (&area, &tmp, &area);
gtk_text_iter_forward_to_line_end (&iter);
gtk_text_view_get_iter_location (text_view, &iter, &tmp);
gdk_rectangle_union (&area, &tmp, &area);
if (!gtk_text_iter_forward_char (&iter))
break;
}
while (gtk_text_iter_compare (&iter, &end) <= 0);
finish:
gtk_text_view_buffer_to_window_coords (text_view, window_type, area.x, area.y, &area.x, &area.y);
*rect = area;
}
static void
animate_expand (IdeSourceView *self,
const GtkTextIter *begin,
const GtkTextIter *end)
{
DzlBoxTheatric *theatric;
GtkAllocation alloc;
GdkRectangle rect = { 0 };
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (begin != NULL);
g_assert (end != NULL);
get_rect_for_iters (GTK_TEXT_VIEW (self), begin, end, &rect, GTK_TEXT_WINDOW_WIDGET);
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
rect.height = MIN (rect.height, alloc.height - rect.y);
theatric = g_object_new (DZL_TYPE_BOX_THEATRIC,
"alpha", 0.3,
"background", "#729fcf",
"height", rect.height,
"target", self,
"width", rect.width,
"x", rect.x,
"y", rect.y,
NULL);
dzl_object_animate_full (theatric,
DZL_ANIMATION_EASE_IN_CUBIC,
250,
gtk_widget_get_frame_clock (GTK_WIDGET (self)),
g_object_unref,
theatric,
"x", rect.x - ANIMATION_X_GROW,
"width", rect.width + (ANIMATION_X_GROW * 2),
"y", rect.y - ANIMATION_Y_GROW,
"height", rect.height + (ANIMATION_Y_GROW * 2),
"alpha", 0.0,
NULL);
}
static void
animate_shrink (IdeSourceView *self,
const GtkTextIter *begin,
const GtkTextIter *end)
{
DzlBoxTheatric *theatric;
GtkAllocation alloc;
GdkRectangle rect = { 0 };
GdkRectangle char_rect = { 0 };
GtkTextIter copy_begin;
GtkTextIter copy_end;
gboolean is_whole_line;
gboolean is_single_line;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (begin);
g_assert (end);
get_rect_for_iters (GTK_TEXT_VIEW (self), begin, begin, &char_rect, GTK_TEXT_WINDOW_WIDGET);
get_rect_for_iters (GTK_TEXT_VIEW (self), begin, end, &rect, GTK_TEXT_WINDOW_WIDGET);
gtk_widget_get_allocation (GTK_WIDGET (self), &alloc);
rect.height = MIN (rect.height, alloc.height - rect.y);
copy_begin = *begin;
copy_end = *end;
gtk_text_iter_order (&copy_begin, &copy_end);
is_single_line = (gtk_text_iter_get_line (&copy_begin) == gtk_text_iter_get_line (&copy_end));
is_whole_line = ((gtk_text_iter_get_line (&copy_begin) + 1 ==
gtk_text_iter_get_line (&copy_end)) &&
(gtk_text_iter_starts_line (&copy_begin) &&
gtk_text_iter_starts_line (&copy_end)));
theatric = g_object_new (DZL_TYPE_BOX_THEATRIC,
"alpha", 0.3,
"background", "#729fcf",
"height", rect.height,
"target", self,
"width", rect.width,
"x", rect.x,
"y", rect.y,
NULL);
if (is_whole_line)
dzl_object_animate_full (theatric,
DZL_ANIMATION_EASE_OUT_QUAD,
150,
gtk_widget_get_frame_clock (GTK_WIDGET (self)),
g_object_unref,
theatric,
"x", rect.x,
"width", rect.width,
"y", rect.y,
"height", 0,
"alpha", 0.3,
NULL);
else if (is_single_line)
dzl_object_animate_full (theatric,
DZL_ANIMATION_EASE_OUT_QUAD,
150,
gtk_widget_get_frame_clock (GTK_WIDGET (self)),
g_object_unref,
theatric,
"x", rect.x,
"width", 0,
"y", rect.y,
"height", rect.height,
"alpha", 0.3,
NULL);
else
dzl_object_animate_full (theatric,
DZL_ANIMATION_EASE_OUT_QUAD,
150,
gtk_widget_get_frame_clock (GTK_WIDGET (self)),
g_object_unref,
theatric,
"x", rect.x,
"width", 0,
"y", rect.y,
"height", char_rect.height,
"alpha", 0.3,
NULL);
}
void
ide_source_view_scroll_to_insert (IdeSourceView *self)
{
GtkTextBuffer *buffer;
GtkTextMark *mark;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
_ide_buffer_cancel_cursor_restore (IDE_BUFFER (buffer));
mark = gtk_text_buffer_get_insert (buffer);
ide_source_view_scroll_mark_onscreen (self, mark, TRUE, 0.5, 1.0);
IDE_EXIT;
}
static void
ide_source_view_invalidate_window (IdeSourceView *self)
{
GdkWindow *window;
g_assert (IDE_IS_SOURCE_VIEW (self));
if ((window = gtk_text_view_get_window (GTK_TEXT_VIEW (self), GTK_TEXT_WINDOW_WIDGET)))
{
gdk_window_invalidate_rect (window, NULL, TRUE);
gtk_widget_queue_draw (GTK_WIDGET (self));
}
}
static gchar *
text_iter_get_line_prefix (const GtkTextIter *iter)
{
GtkTextIter begin;
GString *str;
g_assert (iter);
gtk_text_iter_assign (&begin, iter);
gtk_text_iter_set_line_offset (&begin, 0);
str = g_string_new (NULL);
if (gtk_text_iter_compare (&begin, iter) != 0)
{
do
{
gunichar c;
c = gtk_text_iter_get_char (&begin);
switch (c)
{
case '\t':
case ' ':
g_string_append_unichar (str, c);
break;
default:
g_string_append_c (str, ' ');
break;
}
}
while (gtk_text_iter_forward_char (&begin) &&
(gtk_text_iter_compare (&begin, iter) < 0));
}
return g_string_free (str, FALSE);
}
static void
ide_source_view_update_auto_indent_override (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkSourceLanguage *language;
const gchar *lang_id = NULL;
IdeIndenter *indenter;
g_assert (IDE_IS_SOURCE_VIEW (self));
/* Update the indenter if necessary */
if (priv->auto_indent &&
priv->indenter_adapter != NULL &&
priv->buffer != NULL &&
NULL != (language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (priv->buffer))))
lang_id = gtk_source_language_get_id (language);
if (priv->indenter_adapter != NULL)
ide_extension_adapter_set_value (priv->indenter_adapter, lang_id);
/* Fetch our indenter */
indenter = ide_source_view_get_indenter (self);
/*
* Updates our override of auto-indent from the GtkSourceView underneath us.
* Since we do our own mimicing of GtkSourceView, we always disable it. Also
* updates our mode which needs to know if we have an indenter to provide
* different CSS selectors.
*/
gtk_source_view_set_auto_indent (GTK_SOURCE_VIEW (self), FALSE);
if (priv->mode != NULL)
ide_source_view_mode_set_has_indenter (priv->mode, !!indenter);
}
static void
ide_source_view_set_file_settings (IdeSourceView *self,
IdeFileSettings *file_settings)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (!file_settings || IDE_IS_FILE_SETTINGS (file_settings));
if (file_settings != ide_source_view_get_file_settings (self))
{
dzl_binding_group_set_source (priv->file_setting_bindings, file_settings);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE_SETTINGS]);
}
}
static void
ide_source_view__buffer_notify_file_settings_cb (IdeSourceView *self,
GParamSpec *pspec,
IdeBuffer *buffer)
{
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
ide_source_view_set_file_settings (self, ide_buffer_get_file_settings (buffer));
}
static void
ide_source_view__buffer_notify_language_cb (IdeSourceView *self,
GParamSpec *pspec,
IdeBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
const gchar *lang_id;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
lang_id = ide_buffer_get_language_id (buffer);
/* Update the indenter, which is provided by a plugin. */
if (priv->indenter_adapter != NULL)
ide_extension_adapter_set_value (priv->indenter_adapter, lang_id);
ide_source_view_update_auto_indent_override (self);
/* Reload hover providers by language */
_ide_hover_set_language (priv->hover, lang_id);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_INDENTER]);
}
static void
ide_source_view__buffer_notify_style_scheme_cb (IdeSourceView *self,
GParamSpec *pspec,
IdeBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkSourceStyleScheme *scheme = NULL;
GtkSourceStyle *snippet_area_style = NULL;
g_autofree gchar *snippet_background = NULL;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer));
if (scheme != NULL)
snippet_area_style = gtk_source_style_scheme_get_style (scheme, "snippet::area");
if (snippet_area_style != NULL)
g_object_get (snippet_area_style, "background", &snippet_background, NULL);
if (snippet_background == NULL ||
!gdk_rgba_parse (&priv->snippet_area_background_rgba, snippet_background))
{
gdk_rgba_parse (&priv->snippet_area_background_rgba, "#204a87");
priv->snippet_area_background_rgba.alpha = 0.1;
}
}
static void
ide_source_view__buffer_request_scroll_to_insert_cb (IdeSourceView *self,
IdeBuffer *buffer)
{
GtkTextMark *mark;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
gtk_text_view_scroll_mark_onscreen (GTK_TEXT_VIEW (self), mark);
}
static void
ide_source_view__buffer_changed_cb (IdeSourceView *self,
IdeBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
priv->change_sequence++;
}
static void
ide_source_view_rebuild_css (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
if (!priv->css_provider)
{
GtkStyleContext *style_context;
priv->css_provider = gtk_css_provider_new ();
style_context = gtk_widget_get_style_context (GTK_WIDGET (self));
gtk_style_context_add_provider (style_context,
GTK_STYLE_PROVIDER (priv->css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}
if (priv->font_desc)
{
g_autofree gchar *str = NULL;
g_autofree gchar *css = NULL;
const PangoFontDescription *font_desc = priv->font_desc;
PangoFontDescription *copy = NULL;
if (priv->font_scale != FONT_SCALE_NORMAL)
font_desc = copy = ide_source_view_get_scaled_font_desc (self);
str = dzl_pango_font_description_to_css (font_desc);
css = g_strdup_printf ("textview { %s }", str ?: "");
gtk_css_provider_load_from_data (priv->css_provider, css, -1, NULL);
if (priv->gutter != NULL)
ide_gutter_style_changed (priv->gutter);
if (priv->completion != NULL)
_ide_completion_set_font_description (priv->completion, font_desc);
g_clear_pointer (&copy, pango_font_description_free);
}
}
static void
ide_source_view_invalidate_range_mark (IdeSourceView *self,
GtkTextMark *mark_begin,
GtkTextMark *mark_end)
{
GtkTextBuffer *buffer;
GdkRectangle rect;
GtkTextIter begin;
GtkTextIter end;
GdkWindow *window;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_TEXT_MARK (mark_begin));
g_assert (GTK_IS_TEXT_MARK (mark_end));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_get_iter_at_mark (buffer, &begin, mark_begin);
gtk_text_buffer_get_iter_at_mark (buffer, &end, mark_end);
get_rect_for_iters (GTK_TEXT_VIEW (self), &begin, &end, &rect, GTK_TEXT_WINDOW_TEXT);
window = gtk_text_view_get_window (GTK_TEXT_VIEW (self), GTK_TEXT_WINDOW_TEXT);
gdk_window_invalidate_rect (window, &rect, FALSE);
}
static void
ide_source_view__buffer_insert_text_cb (IdeSourceView *self,
GtkTextIter *iter,
gchar *text,
gint len,
GtkTextBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSnippet *snippet;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (iter != NULL);
g_assert (text != NULL);
g_assert (IDE_IS_BUFFER (buffer));
if (ide_buffer_get_loading (IDE_BUFFER (buffer)))
return;
gtk_text_buffer_begin_user_action (buffer);
if (NULL != (snippet = g_queue_peek_head (priv->snippets)))
{
ide_source_view_block_handlers (self);
ide_snippet_before_insert_text (snippet, buffer, iter, text, len);
ide_source_view_unblock_handlers (self);
}
}
static void
ide_source_view__buffer_insert_text_after_cb (IdeSourceView *self,
GtkTextIter *iter,
gchar *text,
gint len,
GtkTextBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSnippet *snippet;
GtkTextIter insert;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (iter != NULL);
g_assert (text != NULL);
g_assert (IDE_IS_BUFFER (buffer));
if (ide_buffer_get_loading (IDE_BUFFER (buffer)))
return;
if (NULL != (snippet = g_queue_peek_head (priv->snippets)))
{
GtkTextMark *begin;
GtkTextMark *end;
ide_source_view_block_handlers (self);
ide_snippet_after_insert_text (snippet, buffer, iter, text, len);
ide_source_view_unblock_handlers (self);
begin = ide_snippet_get_mark_begin (snippet);
end = ide_snippet_get_mark_end (snippet);
ide_source_view_invalidate_range_mark (self, begin, end);
}
if (priv->in_key_press)
{
/*
* If we are handling the key-press-event, we might have just inserted
* a character that indicates we should overwrite the next character.
* However, due to GtkIMContext constraints, we need to allow it to be
* inserted and then handle it here.
*/
ide_source_view_maybe_overwrite (self, iter, text, len);
}
/* Ignore multiple cursors unless we have focus */
if (gtk_widget_has_focus (GTK_WIDGET (self)))
{
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);
}
static void
ide_source_view__buffer_delete_range_cb (IdeSourceView *self,
GtkTextIter *begin,
GtkTextIter *end,
GtkTextBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSnippet *snippet;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_TEXT_BUFFER (buffer));
if (NULL != (snippet = g_queue_peek_head (priv->snippets)))
{
GtkTextMark *begin_mark;
GtkTextMark *end_mark;
ide_source_view_block_handlers (self);
ide_snippet_before_delete_range (snippet, buffer, begin, end);
ide_source_view_unblock_handlers (self);
begin_mark = ide_snippet_get_mark_begin (snippet);
end_mark = ide_snippet_get_mark_end (snippet);
ide_source_view_invalidate_range_mark (self, begin_mark, end_mark);
}
IDE_EXIT;
}
static void
ide_source_view__buffer_delete_range_after_cb (IdeSourceView *self,
GtkTextIter *begin,
GtkTextIter *end,
GtkTextBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSnippet *snippet;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_TEXT_BUFFER (buffer));
ide_source_view_block_handlers (self);
if (NULL != (snippet = g_queue_peek_head (priv->snippets)))
ide_snippet_after_delete_range (snippet, buffer, begin, end);
ide_source_view_unblock_handlers (self);
IDE_EXIT;
}
static void
ide_source_view__buffer_mark_set_cb (IdeSourceView *self,
GtkTextIter *iter,
GtkTextMark *mark,
GtkTextBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSnippet *snippet;
GtkTextMark *insert;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (iter != NULL);
g_assert (GTK_IS_TEXT_MARK (mark));
g_assert (GTK_IS_TEXT_BUFFER (buffer));
insert = gtk_text_buffer_get_insert (buffer);
if (mark == insert)
{
ide_source_view_block_handlers (self);
while (NULL != (snippet = g_queue_peek_head (priv->snippets)) &&
!ide_snippet_insert_set (snippet, mark))
ide_source_view_pop_snippet (self);
ide_source_view_unblock_handlers (self);
}
#ifdef IDE_ENABLE_TRACE
if (mark == insert || mark == gtk_text_buffer_get_selection_bound (buffer))
{
GtkTextIter begin;
GtkTextIter end;
if (gtk_text_buffer_get_selection_bounds (buffer, &begin, &end))
{
gtk_text_iter_order (&begin, &end);
IDE_TRACE_MSG ("Selection is now %d:%d to %d:%d",
gtk_text_iter_get_line (&begin),
gtk_text_iter_get_line_offset (&begin),
gtk_text_iter_get_line (&end),
gtk_text_iter_get_line_offset (&end));
}
}
#endif
}
static void
ide_source_view__buffer_notify_has_selection_cb (IdeSourceView *self,
GParamSpec *pspec,
IdeBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
gboolean has_selection;
has_selection = gtk_text_buffer_get_has_selection (GTK_TEXT_BUFFER (buffer));
ide_source_view_mode_set_has_selection (priv->mode, has_selection);
if (has_selection)
set_selection_owner (self, G_OBJECT (self));
else if (get_selection_owner (self) == G_OBJECT (self))
set_selection_owner (self, NULL);
}
static void
ide_source_view__buffer_line_flags_changed_cb (IdeSourceView *self,
IdeBuffer *buffer)
{
GtkSourceGutter *gutter;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
gutter = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (self), GTK_TEXT_WINDOW_LEFT);
gtk_source_gutter_queue_draw (gutter);
IDE_EXIT;
}
static void
ide_source_view__buffer_loaded_cb (IdeSourceView *self,
IdeBuffer *buffer)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextMark *insert;
GtkTextIter iter;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
if (priv->completion_blocked)
{
unblock_interactive (self);
priv->completion_blocked = FALSE;
}
/* Store the line column (visual offset) so movements are correct. */
insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, insert);
priv->target_line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self), &iter);
IDE_EXIT;
}
static void
ide_source_view_set_cursor_from_name (IdeSourceView *self,
const gchar *cursor_name)
{
GdkDisplay *display;
GdkCursor *cursor;
GdkWindow *window = gtk_text_view_get_window (GTK_TEXT_VIEW (self),
GTK_TEXT_WINDOW_TEXT);
if (!window)
return;
display = gdk_window_get_display (window);
cursor = gdk_cursor_new_from_name (display, cursor_name);
gdk_window_set_cursor (window, cursor);
}
static void
ide_source_view_reset_definition_highlight (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->definition_src_location)
g_clear_object (&priv->definition_src_location);
if (priv->buffer != NULL)
{
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (priv->buffer), &begin, &end);
gtk_text_buffer_remove_tag_by_name (GTK_TEXT_BUFFER (priv->buffer), TAG_DEFINITION, &begin, &end);
}
ide_source_view_set_cursor_from_name (self, "text");
}
static void
ide_source_view__buffer__notify_can_redo (IdeSourceView *self,
GParamSpec *pspec,
IdeBuffer *buffer)
{
GActionGroup *group;
gboolean can_redo;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
g_object_get (buffer,
"can-redo", &can_redo,
NULL);
group = gtk_widget_get_action_group (GTK_WIDGET (self), "sourceview");
dzl_widget_action_group_set_action_enabled (DZL_WIDGET_ACTION_GROUP (group), "redo", can_redo);
}
static void
ide_source_view__buffer__notify_can_undo (IdeSourceView *self,
GParamSpec *pspec,
IdeBuffer *buffer)
{
GActionGroup *group;
gboolean can_undo;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
g_object_get (buffer,
"can-undo", &can_undo,
NULL);
group = gtk_widget_get_action_group (GTK_WIDGET (self), "sourceview");
dzl_widget_action_group_set_action_enabled (DZL_WIDGET_ACTION_GROUP (group), "undo", can_undo);
}
static void
ide_source_view_bind_buffer (IdeSourceView *self,
IdeBuffer *buffer,
DzlSignalGroup *group)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_autoptr(IdeContext) context = NULL;
GtkTextMark *insert;
IdeObjectBox *box;
GtkTextIter iter;
IDE_ENTRY;
g_assert (IDE_IS_MAIN_THREAD ());
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_BUFFER (buffer));
g_assert (DZL_IS_SIGNAL_GROUP (group));
priv->buffer = buffer;
ide_source_view_reset_definition_highlight (self);
ide_buffer_hold (buffer);
if (ide_buffer_get_loading (buffer))
{
block_interactive (self);
priv->completion_blocked = TRUE;
}
context = ide_buffer_ref_context (buffer);
_ide_hover_set_context (priv->hover, context);
box = ide_object_box_from_object (G_OBJECT (buffer));
priv->indenter_adapter = ide_extension_adapter_new (IDE_OBJECT (box),
peas_engine_get_default (),
IDE_TYPE_INDENTER,
"Indenter-Languages",
NULL);
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);
/* Marks used for definition highlights */
priv->definition_highlight_start_mark =
gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer), NULL, &iter, TRUE);
priv->definition_highlight_end_mark =
gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (buffer), NULL, &iter, TRUE);
g_object_ref (priv->definition_highlight_start_mark);
g_object_ref (priv->definition_highlight_end_mark);
ide_source_view__buffer_notify_language_cb (self, NULL, buffer);
ide_source_view__buffer_notify_file_settings_cb (self, NULL, buffer);
ide_source_view__buffer_notify_style_scheme_cb (self, NULL, buffer);
ide_source_view__buffer__notify_can_redo (self, NULL, buffer);
ide_source_view__buffer__notify_can_undo (self, NULL, buffer);
ide_source_view_real_set_mode (self, NULL, IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT);
insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
ide_source_view_scroll_mark_onscreen (self, insert, TRUE, 0.5, 0.5);
IDE_EXIT;
}
static void
ide_source_view_unbind_buffer (IdeSourceView *self,
DzlSignalGroup *group)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (DZL_IS_SIGNAL_GROUP (group));
if (priv->buffer == NULL)
IDE_EXIT;
priv->scroll_mark = NULL;
if (priv->completion_blocked)
{
unblock_interactive (self);
priv->completion_blocked = FALSE;
}
if (priv->cursor != NULL)
{
g_object_run_dispose (G_OBJECT (priv->cursor));
g_clear_object (&priv->cursor);
}
ide_clear_and_destroy_object (&priv->indenter_adapter);
g_clear_object (&priv->definition_highlight_start_mark);
g_clear_object (&priv->definition_highlight_end_mark);
ide_buffer_release (priv->buffer);
IDE_EXIT;
}
static gboolean
is_opening_char (gunichar ch)
{
switch (ch)
{
case '{':
case '(':
case '"':
case '\'':
case '[':
return TRUE;
default:
return FALSE;
}
}
static guint
count_chars_on_line (IdeSourceView *view,
gunichar expected_char,
const GtkTextIter *iter)
{
GtkTextIter cur;
guint count = 0;
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (view), 0);
g_return_val_if_fail (iter, 0);
cur = *iter;
gtk_text_iter_set_line_offset (&cur, 0);
while (!gtk_text_iter_ends_line (&cur))
{
gunichar ch;
ch = gtk_text_iter_get_char (&cur);
if (ch == '\\')
{
gtk_text_iter_forward_chars (&cur, 2);
continue;
}
count += (ch == expected_char);
gtk_text_iter_forward_char (&cur);
}
return count;
}
static gboolean
is_xmlish (const gchar *lang_id)
{
return (g_strcmp0 (lang_id, "xml") == 0) ||
(g_strcmp0 (lang_id, "html") == 0);
}
static void
ide_source_view_maybe_overwrite (IdeSourceView *self,
GtkTextIter *iter,
const gchar *text,
gint len)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextIter insert;
GtkTextIter next;
gunichar ch;
gunichar next_ch;
gunichar match;
guint count_open;
guint count_close;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (iter != NULL);
g_assert (text != NULL);
g_assert (len > 0);
/*
* Some auto-indenters will perform triggers on certain key-press that we
* would hijack by otherwise "doing nothing" during this key-press. So to
* avoid that, we actually delete the previous value and then allow this
* key-press event to continue.
*/
if (!priv->overwrite_braces)
return;
/*
* WORKAROUND:
*
* If we are inside of a snippet, then let's not do anything. It really
* messes with the position tracking. Once we can better integrate these
* things, go ahead and remove this.
*/
if (priv->snippets->length)
return;
/*
* Ignore this if it wasn't a single character insertion.
*/
if (len != 1)
return;
/*
* Short circuit if there is already a selection.
*/
buffer = gtk_text_iter_get_buffer (iter);
if (gtk_text_buffer_get_has_selection (buffer))
return;
/*
* @iter is pointing at the location we just inserted text. Since we
* know we only inserted one character, lets move past it and compare
* to see if we want to overwrite.
*/
gtk_text_buffer_get_iter_at_mark (buffer, &insert, gtk_text_buffer_get_insert (buffer));
ch = g_utf8_get_char (text);
next_ch = gtk_text_iter_get_char (&insert);
switch (ch)
{
case ')': case ']': case '}': case '"': case '\'': case ';':
if (ch == next_ch)
{
if (ch == '"' || ch == '\'')
break;
switch (ch)
{
case ']': match = '['; break;
case '}': match = '{'; break;
case ')': match = '('; break;
case '>': match = '<'; break;
default: match = 0; break;
}
count_open = count_chars_on_line (self, match, iter);
count_close = count_chars_on_line (self, ch, iter);
if (count_close != count_open)
break;
}
/* fall through */
default:
return;
}
next = insert;
gtk_text_iter_forward_char (&next);
gtk_text_buffer_delete (buffer, &insert, &next);
*iter = insert;
}
static gboolean
ide_source_view_maybe_insert_match (IdeSourceView *self,
GdkEventKey *event)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkSourceBuffer *sbuf;
GtkTextBuffer *buffer;
GtkTextMark *insert;
const gchar *lang_id;
GtkTextIter iter;
GtkTextIter prev_iter;
gunichar next_ch = 0;
gchar ch[2] = { 0 };
/*
* TODO: I think we should put this into a base class for auto
* indenters. It would make some things a lot more convenient, like
* changing which characters we won't add matching characters for.
*/
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (event);
if (priv->cursor != NULL && ide_cursor_is_enabled (priv->cursor))
return FALSE;
/*
* If we are disabled, then do nothing.
*/
if (!priv->insert_matching_brace)
return FALSE;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
sbuf = GTK_SOURCE_BUFFER (buffer);
insert = gtk_text_buffer_get_insert (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
next_ch = gtk_text_iter_get_char (&iter);
prev_iter = iter;
gtk_text_iter_backward_chars (&prev_iter, 2);
/*
* If the source language has marked this region as a string or comment,
* then do nothing.
*/
if (gtk_source_buffer_iter_has_context_class (sbuf, &prev_iter, "string") ||
gtk_source_buffer_iter_has_context_class (sbuf, &prev_iter, "comment"))
return FALSE;
switch (event->keyval)
{
case GDK_KEY_braceleft:
ch[0] = '}';
break;
case GDK_KEY_parenleft:
ch[0] = ')';
break;
case GDK_KEY_bracketleft:
ch[0] = ']';
break;
case GDK_KEY_quotedbl:
ch[0] = '"';
break;
case GDK_KEY_apostrophe:
ch[0] = '\'';
break;
case GDK_KEY_less:
if (!(lang_id = ide_buffer_get_language_id (IDE_BUFFER (buffer))) || !is_xmlish (lang_id))
return FALSE;
ch[0] = '>';
break;
#if 0
/*
* TODO: We should avoid this when we are in comments, etc. That will
* require some communication with the syntax engine.
*/
case GDK_KEY_quoteleft:
case GDK_KEY_quoteright:
ch = '\'';
break;
#endif
default:
return FALSE;
}
/*
* Insert the match if one of the following is true:
*
* - We are at EOF
* - The next character is whitespace
* - The next character is punctuation
* - The next character is not a opening brace.
* - If the char is ", then there must be an even number already on
* the current line.
*/
if (!next_ch || g_unichar_isspace (next_ch) || (g_unichar_ispunct (next_ch) && !is_opening_char (next_ch)))
{
/*
* Special case for working with double quotes.
*
* Ignore double quote if we just added enough to make there be an
* even number on this line. However, if it was the first quote on
* the line, we still need to include a second.
*/
if (ch[0] == '"' || ch[0] == '\'')
{
guint count;
count = count_chars_on_line (self, ch[0], &iter);
if ((count > 1) && ((count % 2) == 0))
return FALSE;
}
gtk_text_buffer_insert_at_cursor (buffer, ch, 1);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
gtk_text_iter_backward_char (&iter);
gtk_text_buffer_select_range (buffer, &iter, &iter);
return TRUE;
}
return FALSE;
}
static gboolean
ide_source_view_maybe_delete_match (IdeSourceView *self,
GdkEventKey *event)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextIter iter;
GtkTextIter prev;
gunichar ch;
gunichar match;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (event);
g_assert (event->keyval == GDK_KEY_BackSpace);
if (!priv->insert_matching_brace)
return FALSE;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
insert = gtk_text_buffer_get_insert (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
prev = iter;
if (!gtk_text_iter_backward_char (&prev))
return FALSE;
ch = gtk_text_iter_get_char (&prev);
switch (ch)
{
case '[': match = ']'; break;
case '{': match = '}'; break;
case '(': match = ')'; break;
case '"': match = '"'; break;
case '\'': match = '\''; break;
case '<': match = '>'; break;
default: match = 0; break;
}
if (match && (gtk_text_iter_get_char (&iter) == match))
{
gtk_text_iter_forward_char (&iter);
gtk_text_buffer_delete (buffer, &prev, &iter);
return TRUE;
}
return FALSE;
}
static void
ide_source_view_do_indent (IdeSourceView *self,
GdkEventKey *event,
IdeIndenter *indenter)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkWidget *widget = (GtkWidget *)self;
GtkTextView *text_view = (GtkTextView *)self;
GtkTextBuffer *buffer;
GtkTextMark *insert;
g_autofree gchar *indent = NULL;
GtkTextIter begin;
GtkTextIter end;
gint cursor_offset = 0;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (priv->auto_indent == TRUE);
g_assert (event);
g_assert (!indenter || IDE_IS_INDENTER (indenter));
buffer = gtk_text_view_get_buffer (text_view);
/*
* Insert into the buffer so the auto-indenter can see it. If
* GtkSourceView:auto-indent is set, then we will end up with very
* unpredictable results.
*/
GTK_WIDGET_CLASS (ide_source_view_parent_class)->key_press_event (widget, event);
/*
* Set begin and end to the position of the new insertion point.
*/
insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (priv->buffer));
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer), &begin, insert);
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer), &end, insert);
/*
* Let the formatter potentially set the replacement text. If we don't have a
* formatter, use our simple formatter which tries to mimic GtkSourceView.
*/
indent = ide_indenter_format (indenter, text_view, &begin, &end, &cursor_offset, event);
if (indent != NULL)
{
/*
* Insert the indention text.
*/
gtk_text_buffer_begin_user_action (buffer);
if (!gtk_text_iter_equal (&begin, &end))
gtk_text_buffer_delete (buffer, &begin, &end);
gtk_text_buffer_insert (buffer, &begin, indent, -1);
gtk_text_buffer_end_user_action (buffer);
/*
* Make sure we stay in the visible rect.
*/
ide_source_view_scroll_mark_onscreen (self, insert, FALSE, 0, 0);
/*
* Place the cursor, as it could be somewhere within our indent text.
*/
gtk_text_buffer_get_iter_at_mark (buffer, &begin, insert);
if (cursor_offset > 0)
gtk_text_iter_forward_chars (&begin, cursor_offset);
else if (cursor_offset < 0)
gtk_text_iter_backward_chars (&begin, ABS (cursor_offset));
gtk_text_buffer_select_range (buffer, &begin, &begin);
}
IDE_EXIT;
}
static inline gboolean
compare_keys (GdkKeymap *keymap,
GdkEventKey *event,
GtkBindingEntry *binding_entry,
guint *new_keyval,
GdkModifierType *state_consumed)
{
gdk_keymap_translate_keyboard_state (keymap,
event->hardware_keycode, event->state, event->group,
new_keyval, NULL, NULL, state_consumed);
if (g_ascii_isupper (*new_keyval))
{
*new_keyval = gdk_keyval_to_lower (*new_keyval);
*state_consumed &= ~GDK_SHIFT_MASK;
}
return (*new_keyval == binding_entry->keyval &&
(event->state & ~(*state_consumed) & ALL_ACCELS_MASK) == (binding_entry->modifiers & ALL_ACCELS_MASK));
}
static gboolean
is_key_vim_binded (GtkWidget *widget,
GdkEventKey *event,
guint *new_keyval,
GdkModifierType *state_consumed)
{
GdkKeymap *keymap;
GtkBindingSet *binding_set;
GtkBindingEntry *binding_entry;
GtkStyleContext *context;
GtkStateFlags state;
GPtrArray *binding_set_array;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (IDE_SOURCE_VIEW (widget));
context = gtk_widget_get_style_context (GTK_WIDGET (priv->mode));
keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
state = gtk_widget_get_state_flags (GTK_WIDGET (priv->mode));
gtk_style_context_get (context, state, "gtk-key-bindings", &binding_set_array, NULL);
if (binding_set_array)
{
for (guint i = 0; i < binding_set_array->len; i++)
{
binding_set = g_ptr_array_index (binding_set_array, i);
if (g_str_has_prefix (binding_set->set_name, "builder-vim"))
{
binding_entry = binding_set->entries;
while (binding_entry)
{
if (compare_keys (keymap, event, binding_entry, new_keyval, state_consumed))
{
g_ptr_array_unref (binding_set_array);
return TRUE;
}
binding_entry = binding_entry->set_next;
}
}
}
g_ptr_array_unref (binding_set_array);
}
return FALSE;
}
static void
command_string_append_to (GString *command_str,
guint keyval,
GdkModifierType state)
{
if (state & GDK_CONTROL_MASK)
g_string_append (command_str, "<ctrl>");
if (state & GDK_SHIFT_MASK)
g_string_append (command_str, "<shift>");
if (state & GDK_MOD1_MASK)
g_string_append (command_str, "<alt>");
if ((keyval >= '!' && keyval <= '~' ) && keyval != GDK_KEY_bracketleft && keyval != GDK_KEY_bracketright)
g_string_append_c (command_str, keyval);
else if (keyval >= GDK_KEY_KP_0 && keyval <= GDK_KEY_KP_9)
g_string_append_c (command_str, keyval - GDK_KEY_KP_0 + '0');
else
{
g_string_append_c (command_str, '[');
g_string_append (command_str, gdk_keyval_name (keyval));
g_string_append_c (command_str, ']');
}
}
static gboolean
ide_source_view_do_mode (IdeSourceView *self,
GdkEventKey *event)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_autofree gchar *suggested_default = NULL;
guint new_keyval;
GdkModifierType state;
GdkModifierType state_consumed;
gboolean ret = FALSE;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->mode)
{
IdeSourceViewMode *mode;
gboolean handled;
gboolean remove = FALSE;
#ifdef IDE_ENABLE_TRACE
{
gunichar ch = 0;
gchar *name = NULL;
g_object_get (priv->mode, "name", &name, NULL);
if (event->string)
ch = g_utf8_get_char (event->string);
IDE_TRACE_MSG ("dispatching to mode \"%s\": (%s)",
name, g_unichar_isprint (ch) ? event->string : "");
g_free (name);
}
#endif
/* hold a reference incase binding changes mode */
mode = g_object_ref (priv->mode);
if (is_key_vim_binded (GTK_WIDGET (self), event, &new_keyval, &state_consumed))
{
state = event->state & ~(state_consumed);
command_string_append_to (priv->command_str, new_keyval, state);
}
/* lookup what this mode thinks our next default should be */
suggested_default = g_strdup (ide_source_view_mode_get_default_mode (priv->mode));
handled = _ide_source_view_mode_do_event (priv->mode, event, &remove);
if (remove)
{
/* only remove mode if it is still active */
if (priv->mode == mode)
g_clear_object (&priv->mode);
}
g_object_unref (mode);
if (handled)
ret = TRUE;
}
if (priv->mode == NULL)
ide_source_view_real_set_mode (self, suggested_default, IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT);
g_assert (priv->mode != NULL);
if (ide_source_view_mode_get_mode_type (priv->mode) == IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT)
g_string_erase (priv->command_str, 0, -1);
if (ide_source_view_mode_get_keep_mark_on_char (priv->mode))
{
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextMark *selection;
GtkTextIter insert_iter;
GtkTextIter selection_iter;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
insert = gtk_text_buffer_get_insert (buffer);
selection = gtk_text_buffer_get_selection_bound (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, insert);
gtk_text_buffer_get_iter_at_mark (buffer, &selection_iter, selection);
if (gtk_text_iter_ends_line (&insert_iter) && !gtk_text_iter_starts_line (&insert_iter))
{
gtk_text_iter_backward_char (&insert_iter);
if (gtk_text_buffer_get_has_selection (buffer))
gtk_text_buffer_select_range (buffer, &insert_iter, &selection_iter);
else
gtk_text_buffer_select_range (buffer, &insert_iter, &insert_iter);
}
}
gtk_text_view_reset_cursor_blink (GTK_TEXT_VIEW (self));
return ret;
}
static gboolean
is_modifier_key (GdkEventKey *event)
{
static const guint modifier_keyvals[] = {
GDK_KEY_Shift_L, GDK_KEY_Shift_R, GDK_KEY_Shift_Lock,
GDK_KEY_Caps_Lock, GDK_KEY_ISO_Lock, GDK_KEY_Control_L,
GDK_KEY_Control_R, GDK_KEY_Meta_L, GDK_KEY_Meta_R,
GDK_KEY_Alt_L, GDK_KEY_Alt_R, GDK_KEY_Super_L, GDK_KEY_Super_R,
GDK_KEY_Hyper_L, GDK_KEY_Hyper_R, GDK_KEY_ISO_Level3_Shift,
GDK_KEY_ISO_Next_Group, GDK_KEY_ISO_Prev_Group,
GDK_KEY_ISO_First_Group, GDK_KEY_ISO_Last_Group,
GDK_KEY_Mode_switch, GDK_KEY_Num_Lock, GDK_KEY_Multi_key,
GDK_KEY_Scroll_Lock,
0
};
const guint *ac_val;
ac_val = modifier_keyvals;
while (*ac_val)
{
if (event->keyval == *ac_val++)
return TRUE;
}
return FALSE;
}
static gboolean
ide_source_view_key_press_event (GtkWidget *widget,
GdkEventKey *event)
{
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextMark *insert;
IdeSnippet *snippet;
gboolean ret = FALSE;
guint change_sequence;
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
insert = gtk_text_buffer_get_insert (buffer);
/*
* If we are waiting for input for a modifier key, dispatch it now.
*/
if (priv->waiting_for_capture)
{
if (!is_modifier_key (event))
{
guint new_keyval;
GdkModifierType state_consumed;
GdkKeymap *keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
_ide_source_view_set_modifier (self, gdk_keyval_to_unicode (event->keyval));
gdk_keymap_translate_keyboard_state (keymap,
event->hardware_keycode, event->state, event->group,
&new_keyval, NULL, NULL, &state_consumed);
command_string_append_to (priv->command_str, new_keyval, event->state & ~(state_consumed));
}
return TRUE;
}
/*
* If we got Control alone, with no key, and the completion window is
* visible, then request that it make itself less visible.
*/
if (event->keyval == GDK_KEY_Control_L &&
event->state == 0 &&
ide_completion_is_visible (priv->completion))
{
priv->did_ctrl_opacity = TRUE;
return GDK_EVENT_STOP;
}
priv->did_ctrl_opacity = FALSE;
/*
* Are we currently recording a macro? If so lets stash the event for later.
*/
if (priv->recording_macro)
ide_source_view_capture_record_event (priv->capture, (GdkEvent *)event,
priv->count, priv->modifier);
/*
* Check our current change sequence. If the buffer has changed during the
* key-press handler, we'll refocus our selves at the insert caret.
*/
change_sequence = priv->change_sequence;
priv->in_key_press++;
/*
* If we are in a non-default mode, dispatch the event to the mode. This allows custom
* keybindings like Emacs and Vim to be implemented using gtk-bindings CSS.
*/
if (ide_source_view_do_mode (self, event))
{
ret = TRUE;
goto cleanup;
}
/*
* Handle movement through the tab stops of the current snippet if needed.
*/
if (NULL != (snippet = g_queue_peek_head (priv->snippets)))
{
switch ((gint) event->keyval)
{
case GDK_KEY_Escape:
ide_source_view_block_handlers (self);
ide_source_view_pop_snippet (self);
ide_source_view_scroll_to_insert (self);
ide_source_view_unblock_handlers (self);
ret = TRUE;
goto cleanup;
case GDK_KEY_KP_Tab:
case GDK_KEY_Tab:
if ((event->state & GDK_SHIFT_MASK) == 0)
{
ide_source_view_block_handlers (self);
if (!ide_snippet_move_next (snippet))
ide_source_view_pop_snippet (self);
ide_completion_cancel (priv->completion);
/* TODO: ask snippet if we should auto-display completion options? */
ide_source_view_scroll_to_insert (self);
ide_source_view_unblock_handlers (self);
ret = TRUE;
goto cleanup;
}
/* Fallthrough */
case GDK_KEY_ISO_Left_Tab:
ide_source_view_block_handlers (self);
ide_snippet_move_previous (snippet);
ide_source_view_scroll_to_insert (self);
ide_source_view_unblock_handlers (self);
ret = TRUE;
goto cleanup;
default:
break;
}
}
/*
* If we are backspacing, and the next character is the matching brace,
* then we might want to delete it too.
*/
if ((event->keyval == GDK_KEY_BackSpace) && !gtk_text_buffer_get_has_selection (buffer))
{
if (ide_source_view_maybe_delete_match (self, event))
{
ret = TRUE;
goto cleanup;
}
}
/*
* If we have an auto-indenter and the event is for a trigger key, then we
* chain up to the parent class to insert the character, and then let the
* auto-indenter fix things up.
*/
if (priv->buffer != NULL &&
priv->auto_indent &&
(priv->cursor == NULL || !ide_cursor_is_enabled (priv->cursor)))
{
IdeIndenter *indenter = ide_source_view_get_indenter (self);
/*
* Indenter may be NULL and that is okay, the IdeIdenter API
* knows how to deal with that situation by emulating GtkSourceView
* indentation style.
*/
if (ide_indenter_is_trigger (indenter, event))
{
ide_source_view_do_indent (self, event, indenter);
ret = TRUE;
goto cleanup;
}
}
/*
* If repeat-with-count is set, we need to repeat the insertion multiple times.
*/
if (priv->count &&
priv->mode &&
ide_source_view_mode_get_repeat_insert_with_count (priv->mode))
{
for (gint i = MAX (1, priv->count); i > 0; i--)
ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->key_press_event (widget, event);
priv->count = 0;
}
else
{
ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->key_press_event (widget, event);
}
/*
* If we just inserted ({["', we might want to insert a matching close.
*/
if (ret)
ide_source_view_maybe_insert_match (self, event);
/*
* Only scroll to the insert mark if we made a change.
*/
if (priv->change_sequence != change_sequence)
ide_source_view_scroll_mark_onscreen (self, insert, FALSE, 0, 0);
cleanup:
priv->in_key_press--;
return ret;
}
static gboolean
ide_source_view_key_release_event (GtkWidget *widget,
GdkEventKey *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)->key_release_event (widget, event);
if (priv->did_ctrl_opacity)
{
IdeCompletionDisplay *display = ide_completion_get_display (priv->completion);
if (event->keyval == GDK_KEY_Control_L &&
event->state == GDK_CONTROL_MASK &&
ide_completion_is_visible (priv->completion))
{
if (gtk_widget_get_opacity (GTK_WIDGET (display)) == 1.0)
dzl_object_animate (display, DZL_ANIMATION_LINEAR, 250, NULL, "opacity", 0.1, NULL);
else
dzl_object_animate (display, DZL_ANIMATION_LINEAR, 250, NULL, "opacity", 1.0, NULL);
}
priv->did_ctrl_opacity = FALSE;
}
if (priv->definition_src_location)
ide_source_view_reset_definition_highlight (self);
return ret;
}
static gboolean
ide_source_view_process_press_on_definition (IdeSourceView *self,
GdkEventButton *event)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextView *text_view = (GtkTextView *)self;
GtkTextIter iter;
GtkTextWindowType window_type;
gint buffer_x;
gint buffer_y;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (event != NULL);
window_type = gtk_text_view_get_window_type (text_view, event->window);
gtk_text_view_window_to_buffer_coords (text_view,
window_type,
event->x,
event->y,
&buffer_x,
&buffer_y);
gtk_text_view_get_iter_at_location (text_view,
&iter,
buffer_x,
buffer_y);
if (priv->definition_src_location != NULL)
{
GtkTextIter definition_highlight_start;
GtkTextIter definition_highlight_end;
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer),
&definition_highlight_start,
priv->definition_highlight_start_mark);
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer),
&definition_highlight_end,
priv->definition_highlight_end_mark);
if (gtk_text_iter_in_range (&iter, &definition_highlight_start, &definition_highlight_end))
{
g_autoptr(IdeLocation) src_location = NULL;
src_location = g_object_ref (priv->definition_src_location);
ide_source_view_reset_definition_highlight (self);
g_signal_emit (self, signals [FOCUS_LOCATION], 0, src_location);
}
ide_source_view_reset_definition_highlight (self);
return TRUE;
}
return FALSE;
}
static gboolean
ide_source_view_real_button_press_event (GtkWidget *widget,
GdkEventButton *event)
{
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextView *text_view = (GtkTextView *)widget;
gboolean ret;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_TEXT_VIEW (text_view));
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);
/*
* Keep mark on the last character if the sourceviewmode dictates such.
*/
if (gtk_widget_has_focus (widget) &&
priv->mode &&
ide_source_view_mode_get_keep_mark_on_char (priv->mode))
{
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextMark *selection;
GtkTextIter iter;
GtkTextIter iter2;
buffer = gtk_text_view_get_buffer (text_view);
insert = gtk_text_buffer_get_insert (buffer);
selection = gtk_text_buffer_get_selection_bound (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
gtk_text_buffer_get_iter_at_mark (buffer, &iter2, selection);
if (gtk_text_iter_ends_line (&iter) && !gtk_text_iter_starts_line (&iter))
{
GtkTextIter prev = iter;
gtk_text_iter_backward_char (&prev);
if (gtk_text_iter_equal (&iter, &iter2))
gtk_text_buffer_select_range (buffer, &prev, &prev);
}
}
/*
* Update our target column so movements don't cause us to revert
* to the previous column.
*/
ide_source_view_save_column (self);
return ret;
}
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)
{
/* Just using forward/backward to word start/end is not enough
* because _ break words when using those functions while they
* are commonly used in the same word in code */
*word_start = *iter;
*word_end = *iter;
do
{
const gunichar c = gtk_text_iter_get_char (word_end);
if (!(g_unichar_isalnum (c) || c == '_'))
break;
}
while (gtk_text_iter_forward_char (word_end));
if (gtk_text_iter_equal (word_start, word_end))
{
/* Iter is not inside a word */
return FALSE;
}
while (gtk_text_iter_backward_char (word_start))
{
const gunichar c = gtk_text_iter_get_char (word_start);
if (!(g_unichar_isalnum (c) || c == '_'))
{
gtk_text_iter_forward_char (word_start);
break;
}
}
return (!gtk_text_iter_equal (word_start, word_end));
}
static void
ide_source_view_get_definition_on_mouse_over_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(DefinitionHighlightData) data = user_data;
IdeSourceViewPrivate *priv;
IdeBuffer *buffer = (IdeBuffer *)object;
g_autoptr(IdeSymbol) symbol = NULL;
g_autoptr(GError) error = NULL;
IdeLocation *srcloc;
IdeSymbolKind kind;
IDE_ENTRY;
g_assert (data != NULL);
g_assert (IDE_IS_BUFFER (buffer));
g_assert (IDE_IS_SOURCE_VIEW (data->self));
priv = ide_source_view_get_instance_private (data->self);
priv->waiting_for_symbol = FALSE;
symbol = ide_buffer_get_symbol_at_location_finish (buffer, result, &error);
if (symbol == NULL)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
g_warning ("%s", error->message);
IDE_EXIT;
}
/* Short circuit if the async operation completed after we closed */
if (priv->buffer == NULL)
IDE_EXIT;
kind = ide_symbol_get_kind (symbol);
srcloc = ide_symbol_get_location (symbol);
if (srcloc == NULL)
srcloc = ide_symbol_get_header_location (symbol);
if (srcloc != NULL)
{
GtkTextIter word_start;
GtkTextIter word_end;
if (priv->definition_src_location != NULL && priv->definition_src_location != srcloc)
g_clear_object (&priv->definition_src_location);
if (priv->definition_src_location == NULL)
priv->definition_src_location = g_object_ref (srcloc);
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
&word_start, data->word_start_mark);
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer),
&word_end, data->word_end_mark);
if (kind == IDE_SYMBOL_KIND_HEADER)
{
GtkTextIter line_start = word_start;
GtkTextIter line_end = word_end;
g_autofree gchar *line_text = NULL;
g_autoptr (GMatchInfo) matchInfo = NULL;
gtk_text_iter_set_line_offset (&line_start, 0);
gtk_text_iter_forward_to_line_end (&line_end);
line_text = gtk_text_iter_get_visible_text (&line_start,&line_end);
g_regex_match (priv->include_regex, line_text, 0, &matchInfo);
if (g_match_info_matches (matchInfo))
{
gint start_pos;
gint end_pos;
g_match_info_fetch_pos (matchInfo,
0,
&start_pos,
&end_pos);
word_start = line_start;
word_end = line_start;
gtk_text_iter_set_line_index (&word_start, start_pos);
gtk_text_iter_set_line_index (&word_end, end_pos);
}
}
gtk_text_buffer_apply_tag_by_name (GTK_TEXT_BUFFER (priv->buffer),
TAG_DEFINITION, &word_start, &word_end);
if (priv->definition_highlight_start_mark != NULL)
gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (priv->buffer),
priv->definition_highlight_start_mark,
&word_start);
if (priv->definition_highlight_end_mark != NULL)
gtk_text_buffer_move_mark (GTK_TEXT_BUFFER (priv->buffer),
priv->definition_highlight_end_mark,
&word_end);
ide_source_view_set_cursor_from_name (data->self, "pointer");
}
else
ide_source_view_reset_definition_highlight (data->self);
IDE_EXIT;
}
static gboolean
ide_source_view_real_motion_notify_event (GtkWidget *widget,
GdkEventMotion *event)
{
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextIter iter;
GtkTextIter start_iter;
GtkTextIter line_start_iter;
GtkTextIter end_iter;
gunichar ch;
gint buffer_x;
gint buffer_y;
GtkTextWindowType window_type;
DefinitionHighlightData *data;
gboolean word_found = FALSE;
gboolean ret;
g_assert (IDE_IS_SOURCE_VIEW (self));
ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->motion_notify_event (widget, event);
if ((event->state & ALL_ACCELS_MASK) != DEFINITION_HIGHLIGHT_MODIFIER)
{
if (priv->definition_src_location)
ide_source_view_reset_definition_highlight (self);
return ret;
}
window_type = gtk_text_view_get_window_type (GTK_TEXT_VIEW (self), event->window);
gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (self),
window_type,
event->x,
event->y,
&buffer_x,
&buffer_y);
gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (self),
&iter,
buffer_x,
buffer_y);
/* Workaround about a Clang bug where <> includes are not correctly reported */
line_start_iter = iter;
gtk_text_iter_set_line_offset (&line_start_iter, 0);
if (gtk_text_iter_ends_line (&line_start_iter))
goto cleanup;
while ((ch = gtk_text_iter_get_char (&line_start_iter)) &&
g_unichar_isspace (ch) &&
gtk_text_iter_forward_char (&line_start_iter))
;
if (ch == '#')
{
g_autofree gchar *str = NULL;
GtkTextIter sharp_iter = line_start_iter;
GtkTextIter line_end_iter = iter;
gtk_text_iter_forward_char (&line_start_iter);
gtk_text_iter_forward_to_line_end (&line_end_iter);
str = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (priv->buffer), &line_start_iter, &line_end_iter, FALSE);
g_strchug (str);
if (g_str_has_prefix (str, "include"))
{
iter = start_iter = sharp_iter;
end_iter = line_end_iter;
word_found = TRUE;
}
}
if (!word_found && !ide_source_get_word_from_iter (&iter, &start_iter, &end_iter))
goto cleanup;
if (priv->definition_src_location)
{
GtkTextIter definition_highlight_start;
GtkTextIter definition_highlight_end;
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer),
&definition_highlight_start,
priv->definition_highlight_start_mark);
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer),
&definition_highlight_end,
priv->definition_highlight_end_mark);
if (gtk_text_iter_equal (&definition_highlight_start, &start_iter) &&
gtk_text_iter_equal (&definition_highlight_end, &end_iter))
return ret;
ide_source_view_reset_definition_highlight (self);
}
/* Skip work if we're already active */
if (priv->waiting_for_symbol)
return ret;
priv->waiting_for_symbol = TRUE;
data = g_slice_new0 (DefinitionHighlightData);
data->self = self;
data->word_start_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (priv->buffer),
NULL, &start_iter, TRUE);
data->word_end_mark = gtk_text_buffer_create_mark (GTK_TEXT_BUFFER (priv->buffer),
NULL, &end_iter, TRUE);
g_object_ref (data->self);
g_object_ref (data->word_start_mark);
g_object_ref (data->word_end_mark);
ide_buffer_get_symbol_at_location_async (priv->buffer,
&iter,
NULL,
ide_source_view_get_definition_on_mouse_over_cb,
data);
return ret;
cleanup:
ide_source_view_reset_definition_highlight (self);
return ret;
}
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;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
PangoContext *context;
PangoLayout *layout;
g_assert (IDE_IS_SOURCE_VIEW (self));
GTK_WIDGET_CLASS (ide_source_view_parent_class)->style_updated (widget);
context = gtk_widget_get_pango_context (widget);
layout = pango_layout_new (context);
pango_layout_set_text (layout, "X", 1);
pango_layout_get_pixel_size (layout, &priv->cached_char_width, &priv->cached_char_height);
g_object_unref (layout);
}
static void
ide_source_view_real_append_to_count (IdeSourceView *self,
gint digit)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
g_return_if_fail (digit >= 0);
g_return_if_fail (digit <= 9);
priv->count = (priv->count * 10) + digit;
}
static void
ide_source_view_real_capture_modifier (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
priv->waiting_for_capture = TRUE;
while ((priv->modifier == 0) && gtk_widget_has_focus (GTK_WIDGET (self)))
gtk_main_iteration ();
priv->waiting_for_capture = FALSE;
}
static void
ide_source_view_real_change_case (IdeSourceView *self,
GtkSourceChangeCaseType type)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextIter begin;
GtkTextIter end;
g_assert (IDE_IS_SOURCE_VIEW (self));
ide_cursor_remove_cursors (priv->cursor);
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
if (GTK_SOURCE_IS_BUFFER (buffer))
gtk_source_buffer_change_case (GTK_SOURCE_BUFFER (buffer), type, &begin, &end);
}
static void
ide_source_view_real_clear_count (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
priv->count = 0;
}
static void
ide_source_view_real_clear_modifier (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
priv->modifier = 0;
}
static void
ide_source_view_real_clear_selection (IdeSourceView *self)
{
GtkTextView *text_view = (GtkTextView *)self;
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextIter iter;
g_assert (GTK_IS_TEXT_VIEW (text_view));
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);
gtk_text_buffer_select_range (buffer, &iter, &iter);
ide_source_view_scroll_mark_onscreen (self, insert, FALSE, 0, 0);
}
static void
ide_source_view_real_cycle_completion (IdeSourceView *self,
GtkDirectionType direction)
{
IdeCompletion *completion;
g_assert (IDE_IS_SOURCE_VIEW (self));
completion = ide_source_view_get_completion (self);
if (!ide_completion_is_visible (completion))
{
ide_completion_show (completion);
return;
}
switch (direction)
{
case GTK_DIR_TAB_FORWARD:
case GTK_DIR_DOWN:
ide_completion_move_cursor (completion, GTK_MOVEMENT_DISPLAY_LINES, 1);
break;
case GTK_DIR_TAB_BACKWARD:
case GTK_DIR_UP:
ide_completion_move_cursor (completion, GTK_MOVEMENT_DISPLAY_LINES, -1);
break;
case GTK_DIR_LEFT:
case GTK_DIR_RIGHT:
default:
break;
}
}
static void
ide_source_view_real_delete_selection (IdeSourceView *self)
{
GtkTextView *text_view = (GtkTextView *)self;
GtkTextBuffer *buffer;
GtkTextIter begin;
GtkTextIter end;
gboolean editable;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_TEXT_VIEW (text_view));
buffer = gtk_text_view_get_buffer (text_view);
editable = gtk_text_view_get_editable (text_view);
if (!editable)
return;
gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
gtk_text_iter_order (&begin, &end);
if (gtk_text_iter_is_end (&end) && gtk_text_iter_starts_line (&begin))
{
gtk_text_buffer_begin_user_action (buffer);
gtk_text_iter_backward_char (&begin);
gtk_text_buffer_delete (buffer, &begin, &end);
gtk_text_buffer_end_user_action (buffer);
}
else
{
gtk_text_buffer_delete_selection (buffer, TRUE, editable);
}
ide_source_view_save_column (self);
}
static void
ide_source_view_real_indent_selection (IdeSourceView *self,
gint level)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkSourceView *source_view = (GtkSourceView *)self;
GtkTextBuffer *buffer;
GtkTextIter iter;
GtkTextIter selection;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
/*
* Use count to increase direction.
*/
if (priv->count && level)
level *= (gint)priv->count;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
if (level < 0)
{
for (; level < 0; level++)
{
if (gtk_text_buffer_get_selection_bounds (buffer, &iter, &selection))
gtk_source_view_unindent_lines (source_view, &iter, &selection);
}
}
else
{
for (; level > 0; level--)
{
if (gtk_text_buffer_get_selection_bounds (buffer, &iter, &selection))
gtk_source_view_indent_lines (source_view, &iter, &selection);
}
}
}
static void
ide_source_view_real_insert_modifier (IdeSourceView *self,
gboolean use_count)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
gchar str[8] = { 0 };
gint count = 1;
gint len;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (!priv->modifier)
return;
if (use_count)
count = MAX (1, priv->count);
len = g_unichar_to_utf8 (priv->modifier, str);
str [len] = '\0';
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_begin_user_action (buffer);
for (gint i = 0; i < count; i++)
gtk_text_buffer_insert_at_cursor (buffer, str, len);
gtk_text_buffer_end_user_action (buffer);
}
static void
ide_source_view_real_duplicate_entire_line (IdeSourceView *self)
{
GtkTextView *text_view = (GtkTextView *)self;
GtkTextIter begin, end;
gboolean selected;
g_autofree gchar *text = NULL;
g_autofree gchar *duplicate_line = NULL;
GtkTextMark *cursor;
GtkTextBuffer *buffer;
g_assert (GTK_IS_TEXT_VIEW (text_view));
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (text_view);
cursor = gtk_text_buffer_get_insert (buffer);
gtk_text_buffer_begin_user_action (buffer);
selected = gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
if (selected)
{
duplicate_line = gtk_text_iter_get_text (&begin, &end);
gtk_text_buffer_insert (buffer, &begin, duplicate_line, -1);
}
else
{
gtk_text_buffer_get_iter_at_mark (buffer, &begin, cursor);
end = begin;
gtk_text_iter_set_line_offset (&begin, 0);
if (!gtk_text_iter_ends_line (&end))
gtk_text_iter_forward_to_line_end (&end);
if (gtk_text_iter_get_line (&begin) == gtk_text_iter_get_line (&end))
{
text = gtk_text_iter_get_text (&begin, &end);
duplicate_line = g_strconcat (text, "\n", NULL);
gtk_text_buffer_insert (buffer, &begin, duplicate_line, -1);
}
}
gtk_text_buffer_end_user_action (buffer);
}
static void
ide_source_view_real_join_lines (IdeSourceView *self)
{
GtkTextBuffer *buffer;
GtkTextMark *mark;
GtkTextIter begin;
GtkTextIter end;
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
if (!GTK_SOURCE_IS_BUFFER (buffer))
return;
gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
gtk_text_iter_order (&begin, &end);
/*
* We want to leave the cursor inbetween the joined lines, so lets create an
* insert mark and delete it later after we reposition the cursor.
*/
mark = gtk_text_buffer_create_mark (buffer, NULL, &end, TRUE);
/* join lines and restore the insert mark inbetween joined lines. */
gtk_text_buffer_begin_user_action (buffer);
gtk_source_buffer_join_lines (GTK_SOURCE_BUFFER (buffer), &begin, &end);
gtk_text_buffer_get_iter_at_mark (buffer, &end, mark);
gtk_text_buffer_select_range (buffer, &end, &end);
gtk_text_buffer_end_user_action (buffer);
/* Remove our temporary mark. */
gtk_text_buffer_delete_mark (buffer, mark);
}
static void
ide_source_view_real_copy_clipboard_extended (IdeSourceView *self)
{
GtkTextView *text_view = (GtkTextView *)self;
GtkClipboard *clipboard;
GtkTextBuffer *buffer;
GtkTextIter begin, end;
g_autofree gchar *text = NULL;
g_autofree gchar *new_text = NULL;
gsize len;
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self), GDK_SELECTION_CLIPBOARD);
buffer = gtk_text_view_get_buffer (text_view);
gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
if (gtk_text_iter_is_end (&end))
{
text = gtk_text_buffer_get_text (buffer, &begin, &end, FALSE);
len = strlen (text);
new_text = g_malloc (len + 1);
memcpy (new_text, text, len);
new_text[len] = '\n';
gtk_clipboard_set_text (clipboard, new_text, len + 1);
}
else
gtk_text_buffer_copy_clipboard (buffer, clipboard);
}
static void
ide_source_view_real_paste_clipboard_extended (IdeSourceView *self,
gboolean smart_lines,
gboolean after_cursor,
gboolean place_cursor_at_original)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextView *text_view = (GtkTextView *)self;
g_autofree gchar *text = NULL;
GtkClipboard *clipboard;
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextIter iter;
guint target_line;
guint target_line_column;
/*
* NOTE:
*
* In this function, we try to improve how pasting works in GtkTextView. There are some
* semantics that make things easier by tracking the paste of an entire line versus small
* snippets of text.
*
* Basically, we are implementing something close to Vim. However that is not a strict
* requirement, just what we are starting with. In fact, the rest of the handling to be like vim
* is handled within vim.css (for example, what character to leave the insert mark on).
*/
g_assert (GTK_IS_TEXT_VIEW (text_view));
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (text_view);
insert = gtk_text_buffer_get_insert (buffer);
clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self), GDK_SELECTION_CLIPBOARD);
text = gtk_clipboard_wait_for_text (clipboard);
/* Possible mismatch between the clipboard content and the utf-8 converted text
* so we set back the utf-8 text in the clipboard to be sure.
*/
gtk_clipboard_set_text (clipboard, text, -1);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
target_line = gtk_text_iter_get_line (&iter);
target_line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self), &iter);
if (priv->count == 0)
priv->count = 1;
gtk_text_buffer_begin_user_action (buffer);
for (; priv->count > 0; priv->count--)
{
/*
* If we are pasting an entire line, we don't want to paste it at the current location. We want
* to insert a new line after the current line, and then paste it there (so move the insert mark
* first).
*/
if (smart_lines && text && g_str_has_suffix (text, "\n"))
{
g_autofree gchar *trimmed = NULL;
/*
* WORKAROUND:
*
* This is a hack so that we can continue to use the paste code from within GtkTextBuffer.
*
* We needed to keep the trailing \n in the text so that we know when we are selecting whole
* lines. We also need to insert a new line manually based on the context. Furthermore, we
* need to remove the trailing line since we already added one.
*
* Terribly annoying, but the result is something that feels very nice, similar to Vim.
*/
trimmed = g_strndup (text, strlen (text) - 1);
if (after_cursor)
{
if (!gtk_text_iter_ends_line (&iter))
gtk_text_iter_forward_to_line_end (&iter);
gtk_text_buffer_select_range (buffer, &iter, &iter);
g_signal_emit_by_name (self, "insert-at-cursor", "\n");
}
else
{
gtk_text_iter_set_line_offset (&iter, 0);
gtk_text_buffer_select_range (buffer, &iter, &iter);
g_signal_emit_by_name (self, "insert-at-cursor", "\n");
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
gtk_text_iter_backward_line (&iter);
gtk_text_buffer_select_range (buffer, &iter, &iter);
}
if (!place_cursor_at_original)
{
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
target_line = gtk_text_iter_get_line (&iter);
target_line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self),
&iter);
}
gtk_clipboard_set_text (clipboard, trimmed, -1);
GTK_TEXT_VIEW_CLASS (ide_source_view_parent_class)->paste_clipboard (text_view);
gtk_clipboard_set_text (clipboard, text, -1);
}
else
{
if (after_cursor)
{
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
if (!gtk_text_iter_ends_line (&iter))
gtk_text_iter_forward_char (&iter);
gtk_text_buffer_select_range (buffer, &iter, &iter);
}
GTK_TEXT_VIEW_CLASS (ide_source_view_parent_class)->paste_clipboard (text_view);
if (!place_cursor_at_original)
{
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
target_line = gtk_text_iter_get_line (&iter);
target_line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self),
&iter);
}
}
/* Revalidate the position on our next attempt through the paste */
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);
if (priv->cursor != NULL)
ide_cursor_clear_highlight (priv->cursor);
}
static void
ide_source_view_real_selection_theatric (IdeSourceView *self,
IdeSourceViewTheatric theatric)
{
GtkTextBuffer *buffer;
GtkTextIter begin;
GtkTextIter end;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert ((theatric == IDE_SOURCE_VIEW_THEATRIC_EXPAND) ||
(theatric == IDE_SOURCE_VIEW_THEATRIC_SHRINK));
if (!ide_source_view_can_animate (self))
return;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
gtk_text_iter_order (&begin, &end);
if (gtk_text_iter_equal (&begin, &end))
return;
if (gtk_text_iter_starts_line (&end))
gtk_text_iter_backward_char (&end);
switch (theatric)
{
case IDE_SOURCE_VIEW_THEATRIC_EXPAND:
animate_expand (self, &begin, &end);
break;
case IDE_SOURCE_VIEW_THEATRIC_SHRINK:
animate_shrink (self, &begin, &end);
break;
default:
break;
}
}
static void
ide_source_view_save_column (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextView *text_view = (GtkTextView *)self;
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextIter iter;
g_assert (IDE_IS_SOURCE_VIEW (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_column = ide_source_view_get_visual_column (self, &iter);
}
static void
ide_source_view_update_display_name (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
const gchar *display_name = NULL;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->mode != NULL)
display_name = ide_source_view_mode_get_display_name (priv->mode);
if (g_strcmp0 (display_name, priv->display_name) != 0)
{
g_free (priv->display_name);
priv->display_name = g_strdup (display_name);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_MODE_DISPLAY_NAME]);
}
}
static void
ide_source_view_real_set_mode (IdeSourceView *self,
const gchar *mode,
IdeSourceViewModeType type)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_autofree gchar *suggested_default = NULL;
gboolean overwrite;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (!priv->buffer)
IDE_EXIT;
#ifdef IDE_ENABLE_TRACE
{
const gchar *old_mode = "null";
if (priv->mode)
old_mode = ide_source_view_mode_get_name (priv->mode);
IDE_TRACE_MSG ("transition from mode (%s) to (%s)", old_mode, mode ?: "<default>");
}
#endif
ide_source_view_save_column (self);
if (priv->mode)
{
IdeSourceViewMode *old_mode = g_object_ref (priv->mode);
const gchar *str;
/* see if this mode suggested a default next mode */
str = ide_source_view_mode_get_default_mode (old_mode);
suggested_default = g_strdup (str);
g_clear_object (&priv->mode);
g_object_unref (old_mode);
}
if (mode == NULL)
{
mode = suggested_default ?: "default";
type = IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT;
}
/* reset the count when switching to permanent mode */
if (type == IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT)
priv->count = 0;
priv->mode = _ide_source_view_mode_new (GTK_WIDGET (self), mode, type);
overwrite = ide_source_view_mode_get_block_cursor (priv->mode);
if (overwrite != gtk_text_view_get_overwrite (GTK_TEXT_VIEW (self)))
gtk_text_view_set_overwrite (GTK_TEXT_VIEW (self), overwrite);
g_object_notify (G_OBJECT (self), "overwrite");
ide_source_view_update_auto_indent_override (self);
ide_source_view_update_display_name (self);
IDE_EXIT;
}
static void
ide_source_view_real_set_overwrite (IdeSourceView *self,
gboolean overwrite)
{
g_assert (IDE_IS_SOURCE_VIEW (self));
IDE_TRACE_MSG ("Setting overwrite to %s", overwrite ? "TRUE" : "FALSE");
gtk_text_view_set_overwrite (GTK_TEXT_VIEW (self), overwrite);
}
static void
ide_source_view_real_swap_selection_bounds (IdeSourceView *self)
{
GtkTextBuffer *buffer;
GtkTextIter insert;
GtkTextIter selection_bound;
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_get_selection_bounds (buffer, &insert, &selection_bound);
gtk_text_buffer_select_range (buffer, &selection_bound, &insert);
}
static void
ide_source_view_real_movement (IdeSourceView *self,
IdeSourceViewMovement movement,
gboolean extend_selection,
gboolean exclusive,
gboolean apply_count)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
gint count = -1;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (apply_count)
count = priv->count;
if (priv->scrolling_to_scroll_mark)
priv->scrolling_to_scroll_mark = FALSE;
_ide_source_view_apply_movement (self,
movement,
extend_selection,
exclusive,
count,
priv->command_str,
priv->command,
priv->modifier,
priv->search_char,
&priv->target_line_column);
}
static void
ide_source_view_real_move_error (IdeSourceView *self,
GtkDirectionType dir)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeDiagnostics *diagnostics;
GtkTextBuffer *buffer;
GtkTextMark *insert;
GFile *file;
GtkTextIter iter;
gboolean wrap_around = TRUE;
gboolean (*movement) (GtkTextIter *) = NULL;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (!priv->buffer)
return;
if (!(diagnostics = ide_buffer_get_diagnostics (priv->buffer)))
return;
file = ide_buffer_get_file (priv->buffer);
if (dir == GTK_DIR_RIGHT)
dir = GTK_DIR_DOWN;
else if (dir == GTK_DIR_LEFT)
dir = GTK_DIR_UP;
/*
* TODO: This is not particularly very efficient. But I didn't feel like
* plumbing access to the diagnostics set and duplicating most of
* the code for getting a diagnostic at a line. Once the diagnostics
* get support for fast lookups (bloom filter or something) then
* we should change to that.
*/
if (dir == GTK_DIR_DOWN)
movement = gtk_text_iter_forward_line;
else
movement = gtk_text_iter_backward_line;
buffer = GTK_TEXT_BUFFER (priv->buffer);
insert = gtk_text_buffer_get_insert (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
wrapped:
while (movement (&iter))
{
IdeDiagnostic *diag;
guint line = gtk_text_iter_get_line (&iter);
if ((diag = ide_diagnostics_get_diagnostic_at_line (diagnostics, file, line)))
{
IdeLocation *location;
location = ide_diagnostic_get_location (diag);
if (location)
{
guint line_offset;
line_offset = ide_location_get_line_offset (location);
gtk_text_iter_set_line_offset (&iter, 0);
for (; line_offset; line_offset--)
if (gtk_text_iter_ends_line (&iter) || !gtk_text_iter_forward_char (&iter))
break;
gtk_text_buffer_select_range (buffer, &iter, &iter);
ide_source_view_scroll_mark_onscreen (self, insert, TRUE, 0.5, 0.5);
return;
}
break;
}
}
if (wrap_around)
{
if (dir == GTK_DIR_DOWN)
gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (priv->buffer), &iter);
else
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (priv->buffer), &iter);
wrap_around = FALSE;
goto wrapped;
}
}
static gboolean
is_same_range (GtkTextIter *new_start,
GtkTextIter *old_start,
GtkTextIter *new_sel,
GtkTextIter *old_sel)
{
if (gtk_text_iter_equal (new_start, old_start))
return gtk_text_iter_equal (old_sel, new_sel);
if (gtk_text_iter_equal (new_start, old_sel))
return gtk_text_iter_equal (old_start, new_sel);
return FALSE;
}
static void
ide_source_view_real_restore_insert_mark_full (IdeSourceView *self,
gboolean move_mark)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextIter iter;
GtkTextIter selection;
GtkTextIter old_iter;
GtkTextIter old_selection;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->insert_mark_cleared)
{
priv->insert_mark_cleared = FALSE;
return;
}
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
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,
0);
ide_source_view_get_iter_at_visual_column (self,
priv->saved_selection_line_column,
&selection);
gtk_text_buffer_get_selection_bounds (buffer, &old_iter, &old_selection);
if (!is_same_range (&iter, &old_iter, &selection, &old_selection))
gtk_text_buffer_select_range (buffer, &iter, &selection);
if (move_mark)
{
GtkTextMark *insert;
insert = gtk_text_buffer_get_insert (buffer);
ide_source_view_scroll_mark_onscreen (self, insert, FALSE, 0, 0);
}
}
static void
ide_source_view_real_restore_insert_mark (IdeSourceView *self)
{
ide_source_view_real_restore_insert_mark_full (self, TRUE);
}
void
_ide_source_view_clear_saved_mark (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
priv->insert_mark_cleared = TRUE;
}
static void
ide_source_view_real_save_insert_mark (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextMark *selection_bound;
GtkTextIter iter;
GtkTextIter selection;
g_assert (IDE_IS_SOURCE_VIEW (self));
priv->insert_mark_cleared = FALSE;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
insert = gtk_text_buffer_get_insert (buffer);
selection_bound = gtk_text_buffer_get_selection_bound (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
gtk_text_buffer_get_iter_at_mark (buffer, &selection, selection_bound);
priv->saved_line = gtk_text_iter_get_line (&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_column = ide_source_view_get_visual_column (self, &selection);
priv->target_line_column = priv->saved_line_column;
}
static void
ide_source_view_real_save_command (IdeSourceView *self)
{
GdkEvent *event;
guint keyval;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
event = gtk_get_current_event ();
if (event && gdk_event_get_keyval (event, &keyval))
priv->command = (gunichar)keyval;
}
static void
ide_source_view_real_save_search_char (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->modifier)
priv->search_char = priv->modifier;
}
/* In string mode, the search act only on the current line,
* search a string to the right if we are not already in one,
* and only inner_left is used ( inner_right is set to it )
*/
static void
ide_source_view_real_select_inner (IdeSourceView *self,
const gchar *inner_left,
const gchar *inner_right,
gboolean exclusive,
gboolean string_mode)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
gunichar unichar_inner_left;
gunichar unichar_inner_right;
g_assert (IDE_IS_SOURCE_VIEW (self));
unichar_inner_left = g_utf8_get_char (inner_left);
unichar_inner_right = g_utf8_get_char (inner_right);
_ide_source_view_select_inner (self,
unichar_inner_left,
unichar_inner_right,
priv->count,
exclusive,
string_mode);
}
static void
ide_source_view_real_select_tag (IdeSourceView *self,
gboolean exclusive)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
_ide_source_view_select_tag (self, priv->count, exclusive);
}
static void
ide_source_view_real_pop_selection (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextMark *selection_bound;
GtkTextIter insert_iter;
GtkTextIter selection_bound_iter;
gpointer *data;
g_assert (IDE_IS_SOURCE_VIEW (self));
data = g_queue_pop_head (priv->selections);
if (!data)
{
g_warning ("request to pop selection that does not exist!");
return;
}
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
insert = gtk_text_buffer_get_insert (buffer);
selection_bound = gtk_text_buffer_get_selection_bound (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, data [0]);
gtk_text_buffer_get_iter_at_mark (buffer, &selection_bound_iter, data [1]);
gtk_text_buffer_move_mark (buffer, insert, &insert_iter);
gtk_text_buffer_move_mark (buffer, selection_bound, &selection_bound_iter);
gtk_text_buffer_delete_mark (buffer, data [0]);
gtk_text_buffer_delete_mark (buffer, data [1]);
g_object_unref (data [0]);
g_object_unref (data [1]);
g_free (data);
}
static void
ide_source_view_real_push_selection (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextMark *selection_bound;
GtkTextIter insert_iter;
GtkTextIter selection_bound_iter;
gpointer *data;
gboolean left_gravity;
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
insert = gtk_text_buffer_get_insert (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &insert_iter, insert);
selection_bound = gtk_text_buffer_get_selection_bound (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &selection_bound_iter, selection_bound);
left_gravity = (gtk_text_iter_compare (&insert_iter, &selection_bound_iter) <= 0);
insert = gtk_text_buffer_create_mark (buffer, NULL, &insert_iter, left_gravity);
left_gravity = (gtk_text_iter_compare (&selection_bound_iter, &insert_iter) < 0);
selection_bound = gtk_text_buffer_create_mark (buffer, NULL, &selection_bound_iter, left_gravity);
data = g_new0 (gpointer, 2);
data [0] = g_object_ref (insert);
data [1] = g_object_ref (selection_bound);
g_queue_push_head (priv->selections, data);
}
static void
ide_source_view_real_push_snippet (IdeSourceView *self,
IdeSnippet *snippet,
const GtkTextIter *location)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_autoptr(GFile) gparentfile = NULL;
g_autoptr(IdeContext) ide_context = NULL;
IdeSnippetContext *context;
GFile *file = NULL;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (IDE_IS_SNIPPET (snippet));
g_assert (location != NULL);
context = ide_snippet_get_context (snippet);
if (priv->buffer != NULL)
{
if ((file = ide_buffer_get_file (priv->buffer)))
{
g_autofree gchar *name = NULL;
g_autofree gchar *path = NULL;
g_autofree gchar *dirname = NULL;
name = g_file_get_basename (file);
gparentfile = g_file_get_parent (file);
dirname = g_file_get_path (gparentfile);
path = g_file_get_path (file);
ide_snippet_context_add_variable (context, "filename", name);
ide_snippet_context_add_variable (context, "dirname", dirname);
ide_snippet_context_add_variable (context, "path", path);
}
if ((ide_context = ide_buffer_ref_context (priv->buffer)))
{
g_autoptr(GFile) workdir = NULL;
workdir = ide_context_ref_workdir (ide_context);
if (workdir && file)
{
g_autofree gchar *relative_path = NULL;
relative_path = g_file_get_relative_path (workdir, file);
ide_snippet_context_add_variable (context, "relative_path", relative_path);
}
if (workdir && gparentfile)
{
g_autofree gchar *relative_dirname = NULL;
relative_dirname = g_file_get_relative_path (workdir, gparentfile);
ide_snippet_context_add_variable (context, "relative_dirname", relative_dirname);
}
}
}
}
static void
ide_source_view_real_reindent (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
IdeIndenter *indenter;
GtkTextIter begin;
GtkTextIter end;
GdkWindow *window;
GtkTextIter iter;
guint i;
guint first_line;
g_autoptr(GPtrArray) lines = NULL;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->buffer == NULL)
return;
/* indenter may be NULL and that is okay */
indenter = ide_source_view_get_indenter (self);
buffer = GTK_TEXT_BUFFER (priv->buffer);
window = gtk_text_view_get_window (GTK_TEXT_VIEW (self), GTK_TEXT_WINDOW_TEXT);
gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
gtk_text_iter_order (&begin, &end);
gtk_text_iter_set_line_offset (&begin, 0);
first_line = gtk_text_iter_get_line (&begin);
/* if the end position is at index 0 of the next line (common with
* line mode in vim), then move it back to the end of the previous
* line, since we don't really care about that next line.
*/
if (gtk_text_iter_starts_line (&end) &&
gtk_text_iter_get_line (&begin) != gtk_text_iter_get_line (&end))
gtk_text_iter_backward_char (&end);
if (!gtk_text_iter_ends_line (&end))
gtk_text_iter_forward_to_line_end (&end);
lines = g_ptr_array_new_with_free_func (g_free);
if (gtk_text_iter_compare (&begin, &end) == 0)
g_ptr_array_add (lines, g_strdup (""));
else
for (iter = begin;
gtk_text_iter_compare (&iter, &end) < 0;
gtk_text_iter_forward_line (&iter))
{
GtkTextIter line_end = iter;
gchar *line;
if (!gtk_text_iter_ends_line (&line_end))
gtk_text_iter_forward_to_line_end (&line_end);
line = gtk_text_iter_get_slice (&iter, &line_end);
g_ptr_array_add (lines, g_strstrip (line));
}
gtk_text_buffer_begin_user_action (buffer);
gtk_text_buffer_delete (buffer, &begin, &end);
for (i = 0; i < lines->len; i++)
{
g_autofree gchar *indent = NULL;
const gchar *line;
GdkEventKey *event;
gint cursor_offset;
line = g_ptr_array_index (lines, i);
event = dzl_gdk_synthesize_event_key (window, '\n');
indent = ide_indenter_format (indenter, GTK_TEXT_VIEW (self), &begin, &end, &cursor_offset, event);
gdk_event_free ((GdkEvent *)event);
if (indent != NULL)
{
if (!gtk_text_iter_equal (&begin, &end))
gtk_text_buffer_delete (buffer, &begin, &end);
gtk_text_buffer_insert (buffer, &begin, indent, -1);
gtk_text_buffer_insert (buffer, &begin, line, -1);
if (i != lines->len - 1)
gtk_text_buffer_insert (buffer, &begin, "\n", -1);
}
end = begin;
}
gtk_text_buffer_end_user_action (buffer);
/* Advance to first non-whitespace */
gtk_text_iter_set_line (&begin, first_line);
while (!gtk_text_iter_ends_line (&begin) &&
g_unichar_isspace (gtk_text_iter_get_char (&begin)))
gtk_text_iter_forward_char (&begin);
gtk_text_buffer_select_range (buffer, &begin, &begin);
}
static void
ide_source_view_set_overscroll_num_lines (IdeSourceView *self,
gint num_lines)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
gint height;
gint new_margin;
g_assert (IDE_IS_SOURCE_VIEW (self));
priv->overscroll_num_lines = num_lines;
/* Do nothing if there is no height yet */
if (!(height = gtk_widget_get_allocated_height (GTK_WIDGET (self))))
return;
new_margin = priv->overscroll_num_lines * priv->cached_char_height;
if (new_margin < 0)
{
new_margin = height + new_margin;
if (new_margin < 0)
new_margin = height - priv->cached_char_height;
}
new_margin = CLAMP (new_margin, 0, MAX (height - priv->cached_char_height, 0));
/* ensure enough space for the overlay scrollbars and their interactive
* trough when scrolled to the end.
*/
if (new_margin < 16)
new_margin = 16;
g_object_set (self,
"bottom-margin", new_margin,
NULL);
}
static void
ide_source_view_constructed (GObject *object)
{
IdeSourceView *self = (IdeSourceView *)object;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
G_OBJECT_CLASS (ide_source_view_parent_class)->constructed (object);
_ide_source_view_init_shortcuts (self);
ide_source_view_real_set_mode (self, NULL, IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT);
priv->definition_src_location = NULL;
ide_source_view_reset_definition_highlight (self);
priv->completion = _ide_completion_new (GTK_SOURCE_VIEW (self));
/* Disable sourceview completion always */
gtk_source_completion_block_interactive (gtk_source_view_get_completion (GTK_SOURCE_VIEW (self)));
/* Disable completion until focus-in-event */
block_interactive (self);
}
static void
ide_source_view_real_insert_at_cursor (GtkTextView *text_view,
const gchar *str)
{
IdeSourceView *self = (IdeSourceView *)text_view;
GtkTextBuffer *buffer;
GtkTextMark *insert;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_TEXT_VIEW (text_view));
g_assert (str);
GTK_TEXT_VIEW_CLASS (ide_source_view_parent_class)->insert_at_cursor (text_view, str);
buffer = gtk_text_view_get_buffer (text_view);
insert = gtk_text_buffer_get_insert (buffer);
ide_source_view_scroll_mark_onscreen (self, insert, FALSE, 0, 0);
}
static void
ide_source_view_real_sort (IdeSourceView *self,
gboolean ignore_case,
gboolean reverse)
{
GtkTextView *text_view = (GtkTextView *)self;
GtkTextBuffer *buffer;
GtkTextIter begin;
GtkTextIter end;
GtkSourceSortFlags sort_flags = GTK_SOURCE_SORT_FLAGS_NONE;
g_assert (GTK_TEXT_VIEW (self));
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (text_view);
gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
if (gtk_text_iter_equal (&begin, &end))
gtk_text_buffer_get_bounds (buffer, &begin, &end);
if (!ignore_case)
sort_flags |= GTK_SOURCE_SORT_FLAGS_CASE_SENSITIVE;
if (reverse)
sort_flags |= GTK_SOURCE_SORT_FLAGS_REVERSE_ORDER;
gtk_source_buffer_sort_lines (GTK_SOURCE_BUFFER (buffer),
&begin,
&end,
sort_flags,
0);
}
static void
ide_source_view_draw_snippet_background (IdeSourceView *self,
cairo_t *cr,
IdeSnippet *snippet,
gint width)
{
GtkTextBuffer *buffer;
GtkTextView *text_view = (GtkTextView *)self;
GtkTextIter begin;
GtkTextIter end;
GtkTextMark *mark_begin;
GtkTextMark *mark_end;
GdkRectangle r;
g_assert (GTK_IS_TEXT_VIEW (text_view));
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (cr);
g_assert (IDE_IS_SNIPPET (snippet));
buffer = gtk_text_view_get_buffer (text_view);
mark_begin = ide_snippet_get_mark_begin (snippet);
mark_end = ide_snippet_get_mark_end (snippet);
if (!mark_begin || !mark_end)
return;
gtk_text_buffer_get_iter_at_mark (buffer, &begin, mark_begin);
gtk_text_buffer_get_iter_at_mark (buffer, &end, mark_end);
get_rect_for_iters (text_view, &begin, &end, &r, GTK_TEXT_WINDOW_TEXT);
gtk_text_view_window_to_buffer_coords (text_view, GTK_TEXT_WINDOW_TEXT, r.x, r.y, &r.x, &r.y);
dzl_cairo_rounded_rectangle (cr, &r, 5, 5);
cairo_fill (cr);
}
static void
ide_source_view_draw_snippets_background (IdeSourceView *self,
cairo_t *cr)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextView *text_view = GTK_TEXT_VIEW (self);
GdkWindow *window;
gint width;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (cr);
window = gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT);
width = gdk_window_get_width (window);
cairo_save (cr);
gdk_cairo_set_source_rgba (cr, &priv->snippet_area_background_rgba);
for (guint i = 0; i < priv->snippets->length; i++)
{
IdeSnippet *snippet = g_queue_peek_nth (priv->snippets, i);
ide_source_view_draw_snippet_background (self,
cr,
snippet,
width - ((priv->snippets->length - i) * 10));
}
cairo_restore (cr);
}
static void
ide_source_view_real_draw_layer (GtkTextView *text_view,
GtkTextViewLayer layer,
cairo_t *cr)
{
IdeSourceView *self = (IdeSourceView *)text_view;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (GTK_IS_TEXT_VIEW (text_view));
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (cr);
GTK_TEXT_VIEW_CLASS (ide_source_view_parent_class)->draw_layer (text_view, layer, cr);
if (layer == GTK_TEXT_VIEW_LAYER_BELOW_TEXT)
{
if (priv->snippets->length)
ide_source_view_draw_snippets_background (self, cr);
if (g_signal_has_handler_pending (self, signals [DRAW_BUBBLES], 0, FALSE))
{
GdkRectangle rect;
gtk_text_view_get_visible_rect (text_view, &rect);
cairo_save (cr);
cairo_translate (cr, rect.x, rect.y);
g_signal_emit (self, signals [DRAW_BUBBLES], 0, cr);
cairo_restore (cr);
}
}
}
static gboolean
ide_source_view_focus_in_event (GtkWidget *widget,
GdkEventFocus *event)
{
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
gboolean ret;
g_assert (IDE_IS_SOURCE_VIEW (self));
/* Restore the completion window now that we have regained focus. */
unblock_interactive (self);
/* Force size allocation immediately if we have something queued. */
if (priv->delay_size_allocate_chainup)
{
g_clear_handle_id (&priv->delay_size_allocate_chainup, g_source_remove);
ide_source_view_do_size_allocate_hack_cb (self);
}
/*
* Restore the insert mark, but ignore selections (since we cant ensure they
* will stay looking selected, as the other frame could be a view into our
* own buffer).
*/
if (get_selection_owner (self) != self)
{
priv->saved_selection_line = priv->saved_line;
priv->saved_selection_line_column = priv->saved_line_column;
}
ide_source_view_real_restore_insert_mark_full (self, FALSE);
/* restore line highlight if enabled */
if (priv->highlight_current_line)
gtk_source_view_set_highlight_current_line (GTK_SOURCE_VIEW (self), TRUE);
ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->focus_in_event (widget, event);
return ret;
}
static gboolean
ide_source_view_focus_out_event (GtkWidget *widget,
GdkEventFocus *event)
{
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
gboolean ret;
g_assert (IDE_IS_SOURCE_VIEW (self));
/* save our insert mark for when we focus back in. it could have moved if
* another view into the same buffer has caused the insert mark to jump.
*/
ide_source_view_real_save_insert_mark (self);
ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->focus_out_event (widget, event);
/*
* Block the completion window while we are not focused. It confuses text
* insertion and such.
*/
block_interactive (self);
/* We don't want highlight-current-line unless the widget is in focus, so
* disable it until we get re-focused.
*/
gtk_source_view_set_highlight_current_line (GTK_SOURCE_VIEW (self), FALSE);
if (priv->cursor != NULL)
ide_cursor_remove_cursors (priv->cursor);
return ret;
}
static void
ide_source_view_real_begin_macro (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSourceViewModeType mode_type;
GdkEvent *event;
const gchar *mode_name;
gunichar modifier;
gint count;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->in_replay_macro)
IDE_GOTO (in_replay);
priv->recording_macro = TRUE;
mode_type = ide_source_view_mode_get_mode_type (priv->mode);
mode_name = ide_source_view_mode_get_name (priv->mode);
modifier = priv->modifier;
count = priv->count;
event = gtk_get_current_event ();
g_clear_object (&priv->capture);
priv->capture = ide_source_view_capture_new (self, mode_name, mode_type, count, modifier);
ide_source_view_capture_record_event (priv->capture, event, count, modifier);
gdk_event_free (event);
in_replay:
IDE_EXIT;
}
static void
ide_source_view_real_end_macro (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->in_replay_macro)
IDE_GOTO (in_replay);
priv->recording_macro = FALSE;
in_replay:
IDE_EXIT;
}
static void
ide_source_view_goto_definition_symbol_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(IdeSourceView) self = user_data;
g_autoptr(IdeSymbol) symbol = NULL;
IdeBuffer *buffer = (IdeBuffer *)object;
g_autoptr(GError) error = NULL;
IdeLocation *srcloc;
IDE_ENTRY;
g_assert (IDE_IS_BUFFER (buffer));
g_assert (IDE_IS_SOURCE_VIEW (self));
symbol = ide_buffer_get_symbol_at_location_finish (buffer, result, &error);
if (symbol == NULL)
{
g_warning ("%s", error->message);
IDE_EXIT;
}
srcloc = ide_symbol_get_location (symbol);
if (srcloc == NULL)
srcloc = ide_symbol_get_header_location (symbol);
if (srcloc != NULL)
{
guint line = ide_location_get_line (srcloc);
guint line_offset = ide_location_get_line_offset (srcloc);
GFile *file = ide_location_get_file (srcloc);
GFile *our_file = ide_buffer_get_file (buffer);
#ifdef IDE_ENABLE_TRACE
const gchar *filename = g_file_peek_path (file);
IDE_TRACE_MSG ("%s => %s +%u:%u",
ide_symbol_get_name (symbol),
filename, line+1, line_offset+1);
#endif
/* Stash our current position for jump-back */
ide_source_view_jump (self, NULL, NULL);
/*
* If we are navigating within this file, just stay captive instead of
* potentially allowing jumping to the file in another editor.
*/
if (g_file_equal (file, our_file))
{
GtkTextIter iter;
gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (buffer),
&iter, line, line_offset);
gtk_text_buffer_select_range (GTK_TEXT_BUFFER (buffer), &iter, &iter);
ide_source_view_scroll_to_insert (self);
IDE_EXIT;
}
g_signal_emit (self, signals [FOCUS_LOCATION], 0, srcloc);
}
IDE_EXIT;
}
static void
ide_source_view_real_goto_definition (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->buffer != NULL)
{
GtkTextMark *insert;
GtkTextIter iter;
insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (priv->buffer));
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer), &iter, insert);
ide_buffer_get_symbol_at_location_async (priv->buffer,
&iter,
NULL,
ide_source_view_goto_definition_symbol_cb,
g_object_ref (self));
}
}
static void
ide_source_view_real_hide_completion (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
ide_completion_hide (priv->completion);
}
static void
ide_source_view_real_replay_macro (IdeSourceView *self,
gboolean use_count)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSourceViewCapture *capture;
gint count = 1;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->recording_macro)
{
g_warning ("Cannot playback macro while recording.");
IDE_EXIT;
}
if (priv->in_replay_macro)
{
g_warning ("Cannot playback macro while playing back macro.");
IDE_EXIT;
}
if (priv->capture == NULL)
return;
if (use_count)
count = MAX (1, priv->count);
IDE_TRACE_MSG ("Replaying capture %d times.", count);
priv->in_replay_macro = TRUE;
capture = priv->capture, priv->capture = NULL;
for (gint i = 0; i < count; i++)
ide_source_view_capture_replay (capture);
g_clear_object (&priv->capture);
priv->capture = capture, capture = NULL;
priv->in_replay_macro = FALSE;
IDE_EXIT;
}
static void
ide_source_view_begin_user_action (IdeSourceView *self)
{
GtkTextBuffer *buffer;
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_begin_user_action (buffer);
}
static void
ide_source_view_end_user_action (IdeSourceView *self)
{
GtkTextBuffer *buffer;
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_end_user_action (buffer);
}
gboolean
ide_source_view_get_overwrite (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
if (gtk_text_view_get_overwrite (GTK_TEXT_VIEW (self)))
{
if (!priv->mode || !ide_source_view_mode_get_block_cursor (priv->mode))
return TRUE;
}
return FALSE;
}
static gchar *
ide_source_view_get_fixit_label (IdeSourceView *self,
IdeTextEdit *fixit)
{
IdeLocation *begin_loc;
IdeLocation *end_loc;
IdeRange *range;
GtkTextBuffer *buffer;
GtkTextIter begin;
GtkTextIter end;
gchar *old_text = NULL;
gchar *new_text = NULL;
gchar *tmp;
gchar *ret = NULL;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (fixit != NULL);
range = ide_text_edit_get_range (fixit);
if (range == NULL)
goto cleanup;
new_text = g_strdup (ide_text_edit_get_text (fixit));
if (new_text == NULL)
goto cleanup;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
if (!IDE_IS_BUFFER (buffer))
goto cleanup;
begin_loc = ide_range_get_begin (range);
end_loc = ide_range_get_end (range);
ide_buffer_get_iter_at_location (IDE_BUFFER (buffer), &begin, begin_loc);
ide_buffer_get_iter_at_location (IDE_BUFFER (buffer), &end, end_loc);
old_text = gtk_text_iter_get_slice (&begin, &end);
if (strlen (old_text) > FIXIT_LABEL_LEN_MAX)
{
tmp = old_text;
old_text = g_strndup (tmp, FIXIT_LABEL_LEN_MAX);
g_free (tmp);
}
if (strlen (new_text) > FIXIT_LABEL_LEN_MAX)
{
tmp = new_text;
new_text = g_strndup (tmp, FIXIT_LABEL_LEN_MAX);
g_free (tmp);
}
tmp = old_text;
old_text = g_markup_escape_text (old_text, -1);
g_free (tmp);
tmp = new_text;
new_text = g_markup_escape_text (new_text, -1);
g_free (tmp);
if (old_text [0] == '\0')
ret = g_strdup_printf (_("Insert “%s”"), new_text);
else
ret = g_strdup_printf (_("Replace “%s” with “%s”"), old_text, new_text);
cleanup:
g_free (old_text);
g_free (new_text);
return ret;
}
static void
ide_source_view__fixit_activate (IdeSourceView *self,
GtkMenuItem *menu_item)
{
IdeTextEdit *fixit;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_MENU_ITEM (menu_item));
fixit = g_object_get_data (G_OBJECT (menu_item), "IDE_FIXIT");
if (fixit != NULL)
{
IdeLocation *srcloc;
IdeRange *range;
GtkTextBuffer *buffer;
const gchar *text;
GtkTextIter begin;
GtkTextIter end;
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
if (!IDE_IS_BUFFER (buffer))
return;
text = ide_text_edit_get_text (fixit);
range = ide_text_edit_get_range (fixit);
srcloc = ide_range_get_begin (range);
ide_buffer_get_iter_at_location (IDE_BUFFER (buffer), &begin, srcloc);
srcloc = ide_range_get_end (range);
ide_buffer_get_iter_at_location (IDE_BUFFER (buffer), &end, srcloc);
gtk_text_buffer_begin_user_action (buffer);
gtk_text_buffer_delete (buffer, &begin, &end);
gtk_text_buffer_insert (buffer, &begin, text, -1);
gtk_text_buffer_end_user_action (buffer);
}
}
static void
ide_source_view_real_populate_popup (GtkTextView *text_view,
GtkWidget *popup)
{
IdeSourceView *self = (IdeSourceView *)text_view;
g_autoptr(GPtrArray) line_diags = NULL;
GtkSeparatorMenuItem *sep;
IdeDiagnostics *diagnostics;
GtkTextBuffer *buffer;
GtkMenuItem *menu_item;
GtkTextMark *insert;
GtkTextIter iter;
GtkTextIter begin;
GtkTextIter end;
GMenu *model;
g_assert (GTK_IS_TEXT_VIEW (text_view));
g_assert (GTK_IS_WIDGET (popup));
GTK_TEXT_VIEW_CLASS (ide_source_view_parent_class)->populate_popup (text_view, popup);
if (!GTK_IS_MENU (popup))
return;
buffer = gtk_text_view_get_buffer (text_view);
if (!IDE_IS_BUFFER (buffer))
return;
model = dzl_application_get_menu_by_id (DZL_APPLICATION_DEFAULT, "ide-source-view-popup-menu");
gtk_menu_shell_bind_model (GTK_MENU_SHELL (popup), G_MENU_MODEL (model), NULL, TRUE);
gtk_text_buffer_get_selection_bounds (buffer, &begin, &end);
/*
* TODO: I'm pretty sure we don't want to use the insert mark, but the
* location of the button-press-event (if there was one).
*/
insert = gtk_text_buffer_get_insert (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
/*
* Check if we have a diagnostic at this position and if there are fixits associated with it.
* If so, display the "Apply TextEdit" menu item with available fixits.
*/
if ((diagnostics = ide_buffer_get_diagnostics (IDE_BUFFER (buffer))))
{
g_assert (IDE_IS_DIAGNOSTICS (diagnostics));
line_diags = ide_diagnostics_get_diagnostics_at_line (diagnostics,
ide_buffer_get_file (IDE_BUFFER (buffer)),
gtk_text_iter_get_line (&iter));
IDE_PTR_ARRAY_SET_FREE_FUNC (line_diags, g_object_unref);
}
if (line_diags != NULL)
{
for (guint j = 0; j < line_diags->len; j++)
{
IdeDiagnostic *diag = g_ptr_array_index (line_diags, j);
guint num_fixits;
num_fixits = ide_diagnostic_get_n_fixits (diag);
if (num_fixits > 0)
{
GtkWidget *parent;
GtkWidget *submenu;
guint i;
sep = g_object_new (GTK_TYPE_SEPARATOR_MENU_ITEM,
"visible", TRUE,
NULL);
gtk_menu_shell_prepend (GTK_MENU_SHELL (popup), GTK_WIDGET (sep));
submenu = gtk_menu_new ();
parent = g_object_new (GTK_TYPE_MENU_ITEM,
"label", _("Apply Fix-It"),
"submenu", submenu,
"visible", TRUE,
NULL);
gtk_menu_shell_prepend (GTK_MENU_SHELL (popup), parent);
for (i = 0; i < num_fixits; i++)
{
IdeTextEdit *fixit;
gchar *label;
fixit = ide_diagnostic_get_fixit (diag, i);
label = ide_source_view_get_fixit_label (self, fixit);
menu_item = g_object_new (GTK_TYPE_MENU_ITEM,
"label", label,
"visible", TRUE,
NULL);
gtk_menu_shell_append (GTK_MENU_SHELL (submenu), GTK_WIDGET (menu_item));
g_object_set_data_full (G_OBJECT (menu_item),
"IDE_FIXIT",
g_object_ref (fixit),
(GDestroyNotify)g_object_unref);
g_signal_connect_object (menu_item,
"activate",
G_CALLBACK (ide_source_view__fixit_activate),
self,
G_CONNECT_SWAPPED);
}
}
}
}
}
static void
ide_source_view_real_rebuild_highlight (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->buffer != NULL)
ide_buffer_rehighlight (priv->buffer);
IDE_EXIT;
}
static gboolean
ignore_invalid_buffers (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data)
{
if (G_VALUE_HOLDS (from_value, GTK_TYPE_TEXT_BUFFER))
{
GtkTextBuffer *buffer;
buffer = g_value_get_object (from_value);
if (IDE_IS_BUFFER (buffer))
{
g_value_set_object (to_value, buffer);
return TRUE;
}
}
g_value_set_object (to_value, NULL);
return TRUE;
}
static void
ide_source_view_set_indent_style (IdeSourceView *self,
IdeIndentStyle indent_style)
{
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert ((indent_style == IDE_INDENT_STYLE_SPACES) ||
(indent_style == IDE_INDENT_STYLE_TABS));
if (indent_style == IDE_INDENT_STYLE_SPACES)
gtk_source_view_set_insert_spaces_instead_of_tabs (GTK_SOURCE_VIEW (self), TRUE);
else
gtk_source_view_set_insert_spaces_instead_of_tabs (GTK_SOURCE_VIEW (self), FALSE);
}
static gboolean
ide_source_view_do_size_allocate_hack_cb (gpointer data)
{
IdeSourceView *self = data;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkAllocation alloc = priv->delay_size_allocation;
g_assert (IDE_IS_SOURCE_VIEW (self));
priv->delay_size_allocate_chainup = 0;
GTK_WIDGET_CLASS (ide_source_view_parent_class)->size_allocate (GTK_WIDGET (self), &alloc);
ide_source_view_set_overscroll_num_lines (self, priv->overscroll_num_lines);
return G_SOURCE_REMOVE;
}
/*
* HACK:
*
* We really want the panels in Builder to be as smooth as possible when
* animating in and out of the scene. However, since these are not floating
* panels, we have the challenge of trying to go through the entire relayout,
* pixelcache, draw cycle many times per-second. Most systems are simply not
* up to the task.
*
* We can, however, take a shortcut when shrinking the allocation. We can
* simply defer the allocation request that would normally be chained up
* to GtkTextView and finish that work after the animation has completed.
* We use a simple heuristic to determine this, simply "missing" a size
* allocation from the typical frame clock cycle.
*/
static gboolean
ide_source_view_do_size_allocate_hack (IdeSourceView *self,
GtkAllocation *allocation)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkWidget *widget = (GtkWidget *)self;
GtkAllocation old;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (allocation != NULL);
/*
* If we are shrinking the allocation, we can go forward with the hack.
* If not, we will abort our request and do the normal chainup cycle.
*/
gtk_widget_get_allocation (widget, &old);
if ((old.width < allocation->width) || (old.height < allocation->height))
return FALSE;
/*
* Save the allocation for later. We'll need it to apply after our timeout
* which will occur just after the last frame (or sooner if we stall the
* drawing pipeline).
*/
priv->delay_size_allocation = *allocation;
/*
* Register our timeout to occur just after a normal frame interval.
* If we are animating at 60 FPS, we should get another size-allocate within
* the frame cycle, typically 17 msec.
*/
g_clear_handle_id (&priv->delay_size_allocate_chainup, g_source_remove);
priv->delay_size_allocate_chainup = g_timeout_add (30,
ide_source_view_do_size_allocate_hack_cb,
self);
return TRUE;
}
static void
ide_source_view_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (GTK_IS_WIDGET (widget));
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (allocation != NULL);
if (!ide_source_view_do_size_allocate_hack (self, allocation))
{
GTK_WIDGET_CLASS (ide_source_view_parent_class)->size_allocate (GTK_WIDGET (self), allocation);
ide_source_view_set_overscroll_num_lines (self, priv->overscroll_num_lines);
}
}
static gboolean
ide_source_view_scroll_event (GtkWidget *widget,
GdkEventScroll *event)
{
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
gboolean ret = GDK_EVENT_PROPAGATE;
g_assert (IDE_IS_SOURCE_VIEW (self));
/*
* If the user started a manual scroll while we were attempting to scroll to
* the target position, just abort our delayed scroll.
*/
priv->scrolling_to_scroll_mark = FALSE;
/*
* Be forward-portable against changes underneath us.
*/
if (GTK_WIDGET_CLASS (ide_source_view_parent_class)->scroll_event)
ret = GTK_WIDGET_CLASS (ide_source_view_parent_class)->scroll_event (widget, event);
return ret;
}
static void
ide_source_view_real_reset_font_size (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->font_scale != FONT_SCALE_NORMAL)
{
priv->font_scale = FONT_SCALE_NORMAL;
ide_source_view_rebuild_css (self);
}
}
static void
ide_source_view_real_increase_font_size (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->font_scale < LAST_FONT_SCALE - 1)
{
priv->font_scale++;
ide_source_view_rebuild_css (self);
}
}
static void
ide_source_view_real_decrease_font_size (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->font_scale > 0)
{
priv->font_scale--;
ide_source_view_rebuild_css (self);
}
}
static void
ide_source_view_real_delete_from_cursor (GtkTextView *text_view,
GtkDeleteType delete_type,
gint count)
{
if (delete_type == GTK_DELETE_PARAGRAPHS)
ide_text_util_delete_line (text_view, count);
else
GTK_TEXT_VIEW_CLASS (ide_source_view_parent_class)->delete_from_cursor (text_view,
delete_type,
count);
}
static void
ide_source_view_real_select_all (IdeSourceView *self,
gboolean select_)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
g_signal_chain_from_overridden_handler (self, select_);
priv->insert_mark_cleared = TRUE;
}
static void
ide_source_view_rename_changed (IdeSourceView *self,
DzlSimplePopover *popover)
{
const gchar *text;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (DZL_IS_SIMPLE_POPOVER (popover));
text = dzl_simple_popover_get_text (popover);
dzl_simple_popover_set_ready (popover, text != NULL);
IDE_EXIT;
}
static void
ide_source_view_rename_edits_applied (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
g_autoptr(IdeSourceView) self = user_data;
IDE_ENTRY;
g_assert (IDE_IS_BUFFER_MANAGER (object));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_SOURCE_VIEW (self));
/*
* The completion window can sometimes popup when performing the replacements
* so we manually hide that window here.
*/
ide_source_view_real_hide_completion (self);
IDE_EXIT;
}
static void
ide_source_view_rename_edits_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeRenameProvider *provider = (IdeRenameProvider *)object;
g_autoptr(IdeSourceView) self = user_data;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_autoptr(GPtrArray) edits = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(IdeContext) context = NULL;
IdeBufferManager *buffer_manager;
IDE_ENTRY;
g_assert (IDE_IS_RENAME_PROVIDER (provider));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_SOURCE_VIEW (self));
if (!ide_rename_provider_rename_finish (provider, result, &edits, &error))
{
/* TODO: Propagate error to UI */
g_warning ("%s", error->message);
IDE_EXIT;
}
g_assert (edits != NULL);
IDE_PTR_ARRAY_SET_FREE_FUNC (edits, g_object_unref);
context = ide_buffer_ref_context (priv->buffer);
buffer_manager = ide_buffer_manager_from_context (context);
ide_buffer_manager_apply_edits_async (buffer_manager,
IDE_PTR_ARRAY_STEAL_FULL (&edits),
NULL,
ide_source_view_rename_edits_applied,
g_steal_pointer (&self));
IDE_EXIT;
}
static void
ide_source_view_rename_activate (IdeSourceView *self,
const gchar *text,
DzlSimplePopover *popover)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_autoptr(IdeLocation) location = NULL;
IdeRenameProvider *provider;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (text != NULL);
g_assert (DZL_IS_SIMPLE_POPOVER (popover));
if (NULL == (provider = ide_buffer_get_rename_provider (priv->buffer)))
IDE_EXIT;
location = ide_buffer_get_insert_location (priv->buffer);
ide_rename_provider_rename_async (provider,
location,
text,
NULL,
ide_source_view_rename_edits_cb,
g_object_ref (self));
/*
* TODO: We should probably lock all buffers so that we can ensure
* that our edit points are correct by time we get called back.
*/
gtk_popover_popdown (GTK_POPOVER (popover));
IDE_EXIT;
}
static void
ide_source_view_real_begin_rename (IdeSourceView *self)
{
IdeRenameProvider *provider;
DzlSimplePopover *popover;
g_autofree gchar *title = NULL;
GtkTextBuffer *buffer;
GtkTextMark *insert;
GtkTextIter iter;
GtkTextIter word_start;
GtkTextIter word_end;
g_autofree gchar *symbol = NULL;
GdkRectangle loc;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
provider = ide_buffer_get_rename_provider (IDE_BUFFER (buffer));
if (provider == NULL)
{
g_message ("Cannot rename, operation requires an IdeRenameProvider");
return;
}
insert = gtk_text_buffer_get_insert (buffer);
title = ide_buffer_dup_title (IDE_BUFFER (buffer));
gtk_text_buffer_get_iter_at_mark (buffer, &iter, insert);
g_debug ("Renaming symbol from %s +%d:%d",
title,
gtk_text_iter_get_line (&iter) + 1,
gtk_text_iter_get_line_offset (&iter) + 1);
gtk_text_buffer_select_range (buffer, &iter, &iter);
gtk_text_view_get_iter_location (GTK_TEXT_VIEW (self), &iter, &loc);
gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (self),
GTK_TEXT_WINDOW_WIDGET,
loc.x, loc.y, &loc.x, &loc.y);
// get symbol to rename (for the popup)
if (gtk_text_iter_inside_word (&iter) && !gtk_text_iter_starts_word (&iter))
{
word_start = iter;
word_end = iter;
gtk_text_iter_backward_word_start (&word_start);
gtk_text_iter_forward_word_end (&word_end);
}
else if (gtk_text_iter_starts_word (&iter))
{
word_start = iter;
word_end = iter;
gtk_text_iter_forward_word_end (&word_end);
}
else if (gtk_text_iter_ends_word (&iter))
{
word_start = iter;
word_end = iter;
gtk_text_iter_backward_word_start (&word_start);
}
symbol = gtk_text_iter_get_text (&word_start, &word_end);
popover = g_object_new (DZL_TYPE_SIMPLE_POPOVER,
"title", _("Rename symbol"),
"button-text", _("Rename"),
"text", symbol,
"relative-to", self,
"pointing-to", &loc,
NULL);
g_signal_connect_object (popover,
"changed",
G_CALLBACK (ide_source_view_rename_changed),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (popover,
"activate",
G_CALLBACK (ide_source_view_rename_activate),
self,
G_CONNECT_SWAPPED);
gtk_popover_popup (GTK_POPOVER (popover));
IDE_EXIT;
}
static void
ide_source_view_format_selection_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBuffer *buffer = (IdeBuffer *)object;
g_autoptr(IdeSourceView) self = user_data;
g_autoptr(GError) error = NULL;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (G_IS_ASYNC_RESULT (result));
if (!ide_buffer_format_selection_finish (buffer, result, &error))
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
g_warning ("%s", error->message);
}
gtk_text_view_set_editable (GTK_TEXT_VIEW (self), TRUE);
IDE_EXIT;
}
static void
ide_source_view_real_format_selection (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_autoptr(IdeFormatterOptions) options = NULL;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
options = ide_formatter_options_new ();
ide_formatter_options_set_tab_width (options,
gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (self)));
ide_formatter_options_set_insert_spaces (options,
gtk_source_view_get_insert_spaces_instead_of_tabs (GTK_SOURCE_VIEW (self)));
gtk_text_view_set_editable (GTK_TEXT_VIEW (self), FALSE);
ide_buffer_format_selection_async (priv->buffer,
options,
NULL,
ide_source_view_format_selection_cb,
g_object_ref (self));
IDE_EXIT;
}
static void
ide_source_view_code_action_execute_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IDE_TRACE_MSG ("ide_source_view_code_action_execute_cb done");
}
static void
execute_code_action_cb (IdeCodeAction *code_action)
{
ide_code_action_execute_async(code_action,
NULL,
ide_source_view_code_action_execute_cb,
g_object_ref (code_action));
}
static void
popup_menu_detach (GtkWidget *attach_widget,
GtkMenu *menu)
{
}
static void
ide_source_view_code_action_query_cb(GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeBuffer *buffer = (IdeBuffer *)object;
g_autoptr(IdeSourceView) self = user_data;
g_autoptr(GError) error = NULL;
g_autoptr(GPtrArray) code_actions = NULL;
IDE_ENTRY;
g_assert(IDE_IS_SOURCE_VIEW(self));
g_assert(G_IS_ASYNC_RESULT(result));
code_actions = ide_buffer_code_action_query_finish(buffer, result, &error);
if (!code_actions)
{
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
g_warning("%s", error->message);
IDE_EXIT;
}
if (code_actions->len)
{
IdeContext* context;
GtkTextView* text_view;
GtkTextIter iter;
GdkRectangle iter_location;
GdkRectangle visible_rect;
gboolean is_visible;
GtkWidget* popup_menu;
context = ide_buffer_ref_context(buffer);
popup_menu = gtk_menu_new();
gtk_style_context_add_class(gtk_widget_get_style_context(popup_menu),
GTK_STYLE_CLASS_CONTEXT_MENU);
gtk_menu_attach_to_widget(GTK_MENU(popup_menu),
GTK_WIDGET(self),
popup_menu_detach);
for (gsize i = 0; i < code_actions->len; i++)
{
IdeCodeAction* code_action;
GtkWidget* menu_item;
code_action = g_ptr_array_index(code_actions, i);
ide_object_append(IDE_OBJECT(context), IDE_OBJECT(code_action));
menu_item = gtk_menu_item_new_with_label(ide_code_action_get_title(code_action));
g_signal_connect_swapped(menu_item,
"activate",
G_CALLBACK(execute_code_action_cb),
code_action);
gtk_widget_show(menu_item);
gtk_menu_shell_append(GTK_MENU_SHELL(popup_menu), menu_item);
}
gtk_menu_shell_select_first(GTK_MENU_SHELL(popup_menu), FALSE);
text_view = GTK_TEXT_VIEW(self);
gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(text_view),
&iter,
gtk_text_buffer_get_insert(gtk_text_view_get_buffer(text_view)));
gtk_text_view_get_iter_location(text_view, &iter, &iter_location);
gtk_text_view_get_visible_rect(text_view, &visible_rect);
is_visible = (iter_location.x + iter_location.width > visible_rect.x &&
iter_location.x < visible_rect.x + visible_rect.width &&
iter_location.y + iter_location.height > visible_rect.y &&
iter_location.y < visible_rect.y + visible_rect.height);
if (is_visible)
{
gtk_text_view_buffer_to_window_coords(text_view,
GTK_TEXT_WINDOW_WIDGET,
iter_location.x,
iter_location.y,
&iter_location.x,
&iter_location.y);
gtk_menu_popup_at_rect(GTK_MENU(popup_menu),
gtk_widget_get_window(GTK_WIDGET(text_view)),
&iter_location,
GDK_GRAVITY_SOUTH_EAST,
GDK_GRAVITY_NORTH_WEST,
NULL);
}
}
IDE_EXIT;
}
static void
ide_source_view_real_code_action_query (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
ide_buffer_code_action_query_async (priv->buffer,
NULL,
ide_source_view_code_action_query_cb,
g_object_ref (self));
IDE_EXIT;
}
static void
ide_source_view_real_find_references_jump (IdeSourceView *self,
GtkListBoxRow *row,
GtkListBox *list_box)
{
IdeLocation *location;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_LIST_BOX_ROW (row));
g_assert (GTK_IS_LIST_BOX (list_box));
location = g_object_get_data (G_OBJECT (row), "IDE_LOCATION");
if (location != NULL)
g_signal_emit (self, signals [FOCUS_LOCATION], 0, location);
IDE_EXIT;
}
static gboolean
insert_mark_within_range (IdeBuffer *buffer,
IdeRange *range)
{
GtkTextMark *insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (buffer));
IdeLocation *begin = ide_range_get_begin (range);
IdeLocation *end = ide_range_get_end (range);
GtkTextIter iter;
GtkTextIter begin_iter;
GtkTextIter end_iter;
if (!begin || !end)
return FALSE;
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (buffer), &iter, insert);
ide_buffer_get_iter_at_location (buffer, &begin_iter, begin);
ide_buffer_get_iter_at_location (buffer, &end_iter, end);
return gtk_text_iter_compare (&begin_iter, &iter) <= 0 &&
gtk_text_iter_compare (&end_iter, &iter) >= 0;
}
static void
ide_source_view_find_references_cb (GObject *object,
GAsyncResult *result,
gpointer user_data)
{
IdeSymbolResolver *symbol_resolver = (IdeSymbolResolver *)object;
g_autoptr(GPtrArray) references = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(IdeTask) task = user_data;
FindReferencesTaskData *data;
IdeSourceView *self;
IdeSourceViewPrivate *priv;
GtkScrolledWindow *scroller;
GtkPopover *popover;
GtkListBox *list_box;
GtkTextMark *insert;
GtkTextIter iter;
GdkRectangle loc;
IDE_ENTRY;
g_assert (IDE_IS_SYMBOL_RESOLVER (symbol_resolver));
g_assert (G_IS_ASYNC_RESULT (result));
g_assert (IDE_IS_TASK (task));
references = ide_symbol_resolver_find_references_finish (symbol_resolver, result, &error);
IDE_PTR_ARRAY_SET_FREE_FUNC (references, g_object_unref);
self = ide_task_get_source_object (task);
priv = ide_source_view_get_instance_private (self);
data = ide_task_get_task_data (task);
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (data != NULL);
g_assert (data->resolvers != NULL);
g_assert (data->resolvers->len > 0);
g_ptr_array_remove_index (data->resolvers, data->resolvers->len - 1);
/* If references are not found and symbol resolvers are left try those */
if (references == NULL && data->resolvers->len > 0)
{
GCancellable *cancellable;
IdeSymbolResolver *resolver;
cancellable = ide_task_get_cancellable (task);
resolver = g_ptr_array_index (data->resolvers, data->resolvers->len - 1);
ide_symbol_resolver_find_references_async (resolver,
data->location,
ide_buffer_get_language_id (priv->buffer),
cancellable,
ide_source_view_find_references_cb,
g_steal_pointer (&task));
return;
}
/* Ignore popover if we are no longer visible or not top-most */
if (!gtk_widget_get_visible (GTK_WIDGET (self)) ||
!gtk_widget_get_child_visible (GTK_WIDGET (self)))
IDE_EXIT;
insert = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (priv->buffer));
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer), &iter, insert);
gtk_text_buffer_select_range (GTK_TEXT_BUFFER (priv->buffer), &iter, &iter);
gtk_text_view_get_iter_location (GTK_TEXT_VIEW (self), &iter, &loc);
gtk_text_view_buffer_to_window_coords (GTK_TEXT_VIEW (self),
GTK_TEXT_WINDOW_WIDGET,
loc.x, loc.y, &loc.x, &loc.y);
popover = g_object_new (GTK_TYPE_POPOVER,
"modal", TRUE,
"position", GTK_POS_TOP,
"relative-to", self,
"pointing-to", &loc,
NULL);
scroller = g_object_new (GTK_TYPE_SCROLLED_WINDOW,
"min-content-height", 35,
"max-content-height", 200,
"propagate-natural-height", TRUE,
"propagate-natural-width", TRUE,
"visible", TRUE,
NULL);
gtk_container_add (GTK_CONTAINER (popover), GTK_WIDGET (scroller));
list_box = g_object_new (GTK_TYPE_LIST_BOX,
"visible", TRUE,
NULL);
gtk_container_add (GTK_CONTAINER (scroller), GTK_WIDGET (list_box));
if (references != NULL && references->len > 0)
{
g_autoptr(IdeContext) context = ide_buffer_ref_context (priv->buffer);
g_autoptr(GFile) workdir = ide_context_ref_workdir (context);
for (guint i = 0; i < references->len; i++)
{
IdeRange *range = g_ptr_array_index (references, i);
IdeLocation *begin = ide_range_get_begin (range);
GFile *file = ide_location_get_file (begin);
guint line = ide_location_get_line (begin);
guint line_offset = ide_location_get_line_offset (begin);
g_autofree gchar *name = NULL;
g_autofree gchar *text = NULL;
GtkListBoxRow *row;
GtkLabel *label;
if (g_file_has_prefix (file, workdir))
name = g_file_get_relative_path (workdir, file);
else if (g_file_is_native (file))
name = g_file_get_path (file);
else
name = g_file_get_uri (file);
/* translators: %s is the filename, then line number, column number. <> are pango markup */
text = g_strdup_printf (_("<b>%s</b> — <small>Line %u, Column %u</small>"),
name, line + 1, line_offset + 1);
label = g_object_new (GTK_TYPE_LABEL,
"xalign", 0.0f,
"label", text,
"use-markup", TRUE,
"visible", TRUE,
NULL);
row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
"child", label,
"visible", TRUE,
NULL);
g_object_set_data_full (G_OBJECT (row),
"IDE_LOCATION",
g_object_ref (begin),
g_object_unref);
gtk_container_add (GTK_CONTAINER (list_box), GTK_WIDGET (row));
if (insert_mark_within_range (priv->buffer, range))
gtk_list_box_select_row (list_box, row);
}
}
else
{
GtkLabel *label = g_object_new (GTK_TYPE_LABEL,
"label", _("No references were found"),
"visible", TRUE,
NULL);
gtk_container_add (GTK_CONTAINER (list_box), GTK_WIDGET (label));
}
g_signal_connect_object (list_box,
"row-activated",
G_CALLBACK (ide_source_view_real_find_references_jump),
self,
G_CONNECT_SWAPPED);
gtk_popover_popup (popover);
g_signal_connect (popover, "hide", G_CALLBACK (gtk_widget_destroy), NULL);
ide_task_return_boolean (task, TRUE);
IDE_EXIT;
}
static void
ide_source_view_real_find_references (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_autoptr(IdeTask) task = NULL;
g_autoptr(GPtrArray) resolvers = NULL;
FindReferencesTaskData *data;
IdeSymbolResolver *resolver;
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
task = ide_task_new (self, NULL, NULL, NULL);
ide_task_set_source_tag (task, ide_source_view_real_find_references);
resolvers = ide_buffer_get_symbol_resolvers (priv->buffer);
IDE_PTR_ARRAY_SET_FREE_FUNC (resolvers, g_object_unref);
if (resolvers->len == 0)
{
g_debug ("No symbol resolver is available");
IDE_EXIT;
}
data = g_slice_new0 (FindReferencesTaskData);
data->resolvers = g_steal_pointer (&resolvers);
data->location = ide_buffer_get_insert_location (priv->buffer);
ide_task_set_task_data (task, data, find_references_task_data_free);
resolver = g_ptr_array_index (data->resolvers, data->resolvers->len - 1);
/* Try each symbol resolver one by one to find references. */
ide_symbol_resolver_find_references_async (resolver,
data->location,
ide_buffer_get_language_id (priv->buffer),
NULL,
ide_source_view_find_references_cb,
g_steal_pointer (&task));
IDE_EXIT;
}
static void
ide_source_view_real_request_documentation (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextIter iter;
g_assert (IDE_IS_SOURCE_VIEW (self));
if (priv->buffer == NULL)
return;
ide_buffer_get_selection_bounds (priv->buffer, &iter, NULL);
_ide_hover_display (priv->hover, &iter);
}
static void
ide_source_view_real_undo (GtkSourceView *view)
{
IdeSourceView *self = (IdeSourceView *)view;
g_assert (IDE_IS_SOURCE_VIEW (self));
/*
* Disable things that could confuse undo. For example, we need to bail on
* any in-flight snippets because they just can't deal with the buffer
* changes correctly given the GtkTextMark vs run-length design.
*/
ide_source_view_clear_snippets (self);
GTK_SOURCE_VIEW_CLASS (ide_source_view_parent_class)->undo (view);
}
static void
ide_source_view_real_reset (IdeSourceView *self)
{
g_assert (IDE_IS_SOURCE_VIEW (self));
g_signal_emit (self, signals [CLEAR_SEARCH], 0);
g_signal_emit (self, signals [CLEAR_MODIFIER], 0);
g_signal_emit (self, signals [CLEAR_SELECTION], 0);
g_signal_emit (self, signals [CLEAR_COUNT], 0);
g_signal_emit (self, signals [CLEAR_SNIPPETS], 0);
g_signal_emit (self, signals [HIDE_COMPLETION], 0);
g_signal_emit (self, signals [REMOVE_CURSORS], 0);
g_signal_emit (self, signals [SET_MODE], 0, NULL, IDE_SOURCE_VIEW_MODE_TYPE_PERMANENT);
}
static void
ide_source_view_destroy (GtkWidget *widget)
{
IdeSourceView *self = (IdeSourceView *)widget;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_assert (IDE_IS_SOURCE_VIEW (self));
/* Ensure we release the buffer immediately */
if (priv->buffer_signals != NULL)
dzl_signal_group_set_target (priv->buffer_signals, NULL);
GTK_WIDGET_CLASS (ide_source_view_parent_class)->destroy (widget);
}
static void
ide_source_view_dispose (GObject *object)
{
IdeSourceView *self = (IdeSourceView *)object;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
if (priv->hadj_animation)
{
dzl_animation_stop (priv->hadj_animation);
g_clear_weak_pointer (&priv->hadj_animation);
}
if (priv->vadj_animation)
{
dzl_animation_stop (priv->vadj_animation);
g_clear_weak_pointer (&priv->vadj_animation);
}
ide_source_view_clear_snippets (self);
g_clear_handle_id (&priv->delay_size_allocate_chainup, g_source_remove);
g_clear_object (&priv->hover);
g_clear_object (&priv->completion);
g_clear_object (&priv->capture);
ide_clear_and_destroy_object (&priv->indenter_adapter);
g_clear_object (&priv->css_provider);
g_clear_object (&priv->mode);
g_clear_object (&priv->buffer_signals);
g_clear_object (&priv->file_setting_bindings);
g_clear_object (&priv->gutter);
if (priv->command_str != NULL)
{
g_string_free (priv->command_str, TRUE);
priv->command_str = NULL;
}
G_OBJECT_CLASS (ide_source_view_parent_class)->dispose (object);
}
static void
ide_source_view_finalize (GObject *object)
{
IdeSourceView *self = (IdeSourceView *)object;
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_clear_pointer (&priv->display_name, g_free);
g_clear_pointer (&priv->font_desc, pango_font_description_free);
g_clear_pointer (&priv->selections, g_queue_free);
g_clear_pointer (&priv->snippets, g_queue_free);
g_clear_pointer (&priv->include_regex, g_regex_unref);
DZL_COUNTER_DEC (instances);
G_OBJECT_CLASS (ide_source_view_parent_class)->finalize (object);
}
static void
ide_source_view_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeSourceView *self = IDE_SOURCE_VIEW (object);
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
switch (prop_id)
{
case PROP_AUTO_INDENT:
g_value_set_boolean (value, priv->auto_indent);
break;
case PROP_COMPLETION_N_ROWS:
g_value_set_uint (value, ide_completion_get_n_rows (priv->completion));
break;
case PROP_COUNT:
g_value_set_int (value, ide_source_view_get_count (self));
break;
case PROP_FILE_SETTINGS:
g_value_set_object (value, ide_source_view_get_file_settings (self));
break;
case PROP_FONT_DESC:
g_value_set_boxed (value, ide_source_view_get_font_desc (self));
break;
case PROP_HIGHLIGHT_CURRENT_LINE:
g_value_set_boolean (value, ide_source_view_get_highlight_current_line (self));
break;
case PROP_INDENTER:
g_value_set_object (value, ide_source_view_get_indenter (self));
break;
case PROP_INSERT_MATCHING_BRACE:
g_value_set_boolean (value, ide_source_view_get_insert_matching_brace (self));
break;
case PROP_INTERACTIVE_COMPLETION:
g_value_set_boolean (value, priv->interactive_completion);
break;
case PROP_MODE_DISPLAY_NAME:
g_value_set_string (value, ide_source_view_get_mode_display_name (self));
break;
case PROP_OVERWRITE:
g_value_set_boolean (value, ide_source_view_get_overwrite (self));
break;
case PROP_OVERWRITE_BRACES:
g_value_set_boolean (value, ide_source_view_get_overwrite_braces (self));
break;
case PROP_SCROLL_OFFSET:
g_value_set_uint (value, ide_source_view_get_scroll_offset (self));
break;
case PROP_SHOW_GRID_LINES:
g_value_set_boolean (value, ide_source_view_get_show_grid_lines (self));
break;
case PROP_SHOW_LINE_CHANGES:
g_value_set_boolean (value, ide_source_view_get_show_line_changes (self));
break;
case PROP_SHOW_LINE_DIAGNOSTICS:
g_value_set_boolean (value, ide_source_view_get_show_line_diagnostics (self));
break;
case PROP_SHOW_LINE_NUMBERS:
g_value_set_boolean (value, ide_source_view_get_show_line_numbers (self));
break;
case PROP_SHOW_RELATIVE_LINE_NUMBERS:
g_value_set_boolean (value, ide_source_view_get_show_relative_line_numbers (self));
break;
case PROP_OVERSCROLL:
g_value_set_int (value, priv->overscroll_num_lines);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_source_view_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeSourceView *self = IDE_SOURCE_VIEW (object);
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
switch (prop_id)
{
case PROP_AUTO_INDENT:
priv->auto_indent = !!g_value_get_boolean (value);
ide_source_view_update_auto_indent_override (self);
break;
case PROP_COMPLETION_N_ROWS:
if (priv->completion != NULL)
ide_completion_set_n_rows (priv->completion, g_value_get_uint (value));
break;
case PROP_COUNT:
ide_source_view_set_count (self, g_value_get_int (value));
break;
case PROP_FONT_NAME:
ide_source_view_set_font_name (self, g_value_get_string (value));
break;
case PROP_FONT_DESC:
ide_source_view_set_font_desc (self, g_value_get_boxed (value));
break;
case PROP_HIGHLIGHT_CURRENT_LINE:
ide_source_view_set_highlight_current_line (self, g_value_get_boolean (value));
break;
case PROP_INDENT_STYLE:
ide_source_view_set_indent_style (self, g_value_get_enum (value));
break;
case PROP_INSERT_MATCHING_BRACE:
ide_source_view_set_insert_matching_brace (self, g_value_get_boolean (value));
break;
case PROP_INTERACTIVE_COMPLETION:
ide_source_view_set_interactive_completion (self, g_value_get_boolean (value));
break;
case PROP_OVERWRITE:
gtk_text_view_set_overwrite (GTK_TEXT_VIEW (self), g_value_get_boolean (value));
break;
case PROP_OVERWRITE_BRACES:
ide_source_view_set_overwrite_braces (self, g_value_get_boolean (value));
break;
case PROP_SCROLL_OFFSET:
ide_source_view_set_scroll_offset (self, g_value_get_uint (value));
break;
case PROP_SHOW_GRID_LINES:
ide_source_view_set_show_grid_lines (self, g_value_get_boolean (value));
break;
case PROP_SHOW_LINE_CHANGES:
ide_source_view_set_show_line_changes (self, g_value_get_boolean (value));
break;
case PROP_SHOW_LINE_DIAGNOSTICS:
ide_source_view_set_show_line_diagnostics (self, g_value_get_boolean (value));
break;
case PROP_SHOW_LINE_NUMBERS:
ide_source_view_set_show_line_numbers (self, g_value_get_boolean (value));
break;
case PROP_SHOW_RELATIVE_LINE_NUMBERS:
ide_source_view_set_show_relative_line_numbers (self, g_value_get_boolean (value));
break;
case PROP_OVERSCROLL:
ide_source_view_set_overscroll_num_lines (self, g_value_get_int (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_source_view_class_init (IdeSourceViewClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkTextViewClass *text_view_class = GTK_TEXT_VIEW_CLASS (klass);
GtkSourceViewClass *gsv_class = GTK_SOURCE_VIEW_CLASS (klass);
GtkBindingSet *binding_set;
object_class->constructed = ide_source_view_constructed;
object_class->dispose = ide_source_view_dispose;
object_class->finalize = ide_source_view_finalize;
object_class->get_property = ide_source_view_get_property;
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->focus_in_event = ide_source_view_focus_in_event;
widget_class->focus_out_event = ide_source_view_focus_out_event;
widget_class->key_press_event = ide_source_view_key_press_event;
widget_class->key_release_event = ide_source_view_key_release_event;
widget_class->scroll_event = ide_source_view_scroll_event;
widget_class->size_allocate = ide_source_view_size_allocate;
widget_class->style_updated = ide_source_view_real_style_updated;
widget_class->destroy = ide_source_view_destroy;
text_view_class->delete_from_cursor = ide_source_view_real_delete_from_cursor;
text_view_class->draw_layer = ide_source_view_real_draw_layer;
text_view_class->insert_at_cursor = ide_source_view_real_insert_at_cursor;
text_view_class->populate_popup = ide_source_view_real_populate_popup;
gsv_class->undo = ide_source_view_real_undo;
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;
klass->capture_modifier = ide_source_view_real_capture_modifier;
klass->clear_count = ide_source_view_real_clear_count;
klass->clear_modifier = ide_source_view_real_clear_modifier;
klass->clear_selection = ide_source_view_real_clear_selection;
klass->clear_snippets = ide_source_view_clear_snippets;
klass->copy_clipboard_extended = ide_source_view_real_copy_clipboard_extended;
klass->cycle_completion = ide_source_view_real_cycle_completion;
klass->decrease_font_size = ide_source_view_real_decrease_font_size;
klass->delete_selection = ide_source_view_real_delete_selection;
klass->end_macro = ide_source_view_real_end_macro;
klass->goto_definition = ide_source_view_real_goto_definition;
klass->hide_completion = ide_source_view_real_hide_completion;
klass->increase_font_size = ide_source_view_real_increase_font_size;
klass->indent_selection = ide_source_view_real_indent_selection;
klass->insert_modifier = ide_source_view_real_insert_modifier;
klass->move_error = ide_source_view_real_move_error;
klass->movement = ide_source_view_real_movement;
klass->paste_clipboard_extended = ide_source_view_real_paste_clipboard_extended;
klass->pop_selection = ide_source_view_real_pop_selection;
klass->push_selection = ide_source_view_real_push_selection;
klass->rebuild_highlight = ide_source_view_real_rebuild_highlight;
klass->replay_macro = ide_source_view_real_replay_macro;
klass->request_documentation = ide_source_view_real_request_documentation;
klass->reset_font_size = ide_source_view_real_reset_font_size;
klass->restore_insert_mark = ide_source_view_real_restore_insert_mark;
klass->save_command = ide_source_view_real_save_command;
klass->save_insert_mark = ide_source_view_real_save_insert_mark;
klass->save_search_char = ide_source_view_real_save_search_char;
klass->select_inner = ide_source_view_real_select_inner;
klass->select_tag = ide_source_view_real_select_tag;
klass->selection_theatric = ide_source_view_real_selection_theatric;
klass->set_mode = ide_source_view_real_set_mode;
klass->set_overwrite = ide_source_view_real_set_overwrite;
klass->sort = ide_source_view_real_sort;
klass->swap_selection_bounds = ide_source_view_real_swap_selection_bounds;
g_object_class_override_property (object_class, PROP_AUTO_INDENT, "auto-indent");
properties [PROP_COMPLETION_N_ROWS] =
g_param_spec_uint ("completion-n-rows",
"Completion N Rows",
"The number of completion rows to display to the user",
1, 32, 5,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_COUNT] =
g_param_spec_int ("count",
"Count",
"The count for movements.",
-1,
G_MAXINT,
0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_FILE_SETTINGS] =
g_param_spec_object ("file-settings",
"File Settings",
"The file settings that have been loaded for the file.",
IDE_TYPE_FILE_SETTINGS,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_FONT_DESC] =
g_param_spec_boxed ("font-desc",
"Font Description",
"The Pango font description to use for rendering source.",
PANGO_TYPE_FONT_DESCRIPTION,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_FONT_NAME] =
g_param_spec_string ("font-name",
"Font Name",
"The Pango font name to use for rendering source.",
"Monospace",
(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
g_object_class_override_property (object_class,
PROP_HIGHLIGHT_CURRENT_LINE,
"highlight-current-line");
properties [PROP_INDENTER] =
g_param_spec_object ("indenter",
"Indenter",
"Indenter",
IDE_TYPE_INDENTER,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_INDENT_STYLE] =
g_param_spec_enum ("indent-style",
"Indent Style",
"Indent Style",
IDE_TYPE_INDENT_STYLE,
IDE_INDENT_STYLE_TABS,
(G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_INTERACTIVE_COMPLETION] =
g_param_spec_boolean ("interactive-completion",
"Interactive Completion",
"If completion should be completed interactively",
TRUE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_INSERT_MATCHING_BRACE] =
g_param_spec_boolean ("insert-matching-brace",
"Insert Matching Brace",
"Insert a matching brace/bracket/quotation/parenthesis.",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_override_property (object_class, PROP_OVERWRITE, "overwrite");
properties [PROP_MODE_DISPLAY_NAME] =
g_param_spec_string ("mode-display-name",
"Mode Display Name",
"The display name of the keybinding mode.",
NULL,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
properties [PROP_OVERWRITE_BRACES] =
g_param_spec_boolean ("overwrite-braces",
"Overwrite Braces",
"Overwrite a matching brace/bracket/quotation/parenthesis.",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_SCROLL_OFFSET] =
g_param_spec_uint ("scroll-offset",
"Scroll Offset",
"The number of lines between the insertion cursor and screen boundary.",
0,
G_MAXUINT,
0,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_SHOW_GRID_LINES] =
g_param_spec_boolean ("show-grid-lines",
"Show Grid Lines",
"If the background grid should be shown.",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_SHOW_LINE_CHANGES] =
g_param_spec_boolean ("show-line-changes",
"Show Line Changes",
"If line changes should be shown in the left gutter.",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* IdeSourceView:show-line-diagnostics:
*
* If the diagnostics gutter should be visible.
*
* This also requires that IdeBuffer:highlight-diagnostics is set to %TRUE
* to generate diagnostics.
*
* Since: 3.32
*/
properties [PROP_SHOW_LINE_DIAGNOSTICS] =
g_param_spec_boolean ("show-line-diagnostics",
"Show Line Diagnostics",
"If line changes diagnostics should be shown in the left gutter.",
TRUE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_override_property (object_class, PROP_SHOW_LINE_NUMBERS, "show-line-numbers");
properties [PROP_SHOW_RELATIVE_LINE_NUMBERS] =
g_param_spec_boolean ("show-relative-line-numbers",
"Show Relative Line Numbers",
"Show line numbers relative to the cursor line",
FALSE,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
properties [PROP_OVERSCROLL] =
g_param_spec_int ("overscroll",
"Overscroll",
"The number of lines to scroll beyond the end of the "
"buffer. A negative number of lines will scroll until "
"only that number of lines is visible",
G_MININT,
G_MAXINT,
DEFAULT_OVERSCROLL_NUM_LINES,
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, LAST_PROP, properties);
signals [ACTION] =
g_signal_new_class_handler ("action",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (dzl_gtk_widget_action_with_string),
NULL, NULL, NULL,
G_TYPE_NONE,
3,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_STRING);
signals [APPEND_TO_COUNT] =
g_signal_new ("append-to-count",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, append_to_count),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
G_TYPE_INT);
/**
* IdeSourceView::begin-macro:
*
* This signal will begin recording input to the #IdeSourceView. This includes the current
* #IdeSourceViewMode, #IdeSourceView:count and #IdeSourceView:modifier which will be used
* to replay the sequence starting from the correct state.
*
* Pair this with an emission of #IdeSourceView::end-macro to complete the sequence.
*
* Since: 3.32
*/
signals [BEGIN_MACRO] =
g_signal_new ("begin-macro",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, begin_macro),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
/**
* IdeSourceView::begin-rename:
*
* This signal is emitted when the source view should begin a rename
* operation using the #IdeRenameProvider from the underlying buffer. The
* cursor position will be used as the location when sending the request to
* the provider.
*
* Since: 3.32
*/
signals [BEGIN_RENAME] =
g_signal_new ("begin-rename",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, begin_rename),
NULL, NULL, NULL, G_TYPE_NONE, 0);
signals [BEGIN_USER_ACTION] =
g_signal_new_class_handler ("begin-user-action",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (ide_source_view_begin_user_action),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [SAVE_COMMAND] =
g_signal_new ("save-command",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, save_command),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals [SAVE_SEARCH_CHAR] =
g_signal_new ("save-search-char",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, save_search_char),
NULL, NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
/**
* IdeSourceView::capture-modifier:
*
* This signal will block the main loop in a similar fashion to how
* gtk_dialog_run() performs until a key-press has occurred that can be
* captured for use in movements.
*
* Pressing Escape or unfocusing the widget will break from this loop.
*
* Use of this signal is not recommended except in very specific cases.
*
* Since: 3.32
*/
signals [CAPTURE_MODIFIER] =
g_signal_new ("capture-modifier",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, capture_modifier),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
g_signal_override_class_handler ("change-case",
G_TYPE_FROM_CLASS (klass),
G_CALLBACK (ide_source_view_real_change_case));
signals [CLEAR_COUNT] =
g_signal_new ("clear-count",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, clear_count),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [CLEAR_MODIFIER] =
g_signal_new ("clear-modifier",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, clear_modifier),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [CLEAR_SEARCH] =
g_signal_new ("clear-search",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, clear_search),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [CLEAR_SELECTION] =
g_signal_new ("clear-selection",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, clear_selection),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [CLEAR_SNIPPETS] =
g_signal_new ("clear-snippets",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, clear_snippets),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [COPY_CLIPBOARD_EXTENDED] =
g_signal_new ("copy-clipboard-extended",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, copy_clipboard_extended),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [CYCLE_COMPLETION] =
g_signal_new ("cycle-completion",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, cycle_completion),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
GTK_TYPE_DIRECTION_TYPE);
signals [DECREASE_FONT_SIZE] =
g_signal_new ("decrease-font-size",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, decrease_font_size),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [DELETE_SELECTION] =
g_signal_new ("delete-selection",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, delete_selection),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [DRAW_BUBBLES] =
g_signal_new ("draw-bubbles",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
0,
NULL, NULL,
g_cclosure_marshal_VOID__BOXED,
G_TYPE_NONE,
1,
CAIRO_GOBJECT_TYPE_CONTEXT);
g_signal_set_va_marshaller (signals [DRAW_BUBBLES],
G_TYPE_FROM_CLASS (klass),
g_cclosure_marshal_VOID__BOXEDv);
/**
* IdeSourceView::end-macro:
*
* You should call #IdeSourceView::begin-macro before emitting this signal.
*
* Complete a macro recording sequence. This may be called more times than is necessary,
* since #IdeSourceView will only keep the most recent macro recording. This can be
* helpful when implementing recording sequences such as in Vim.
*
* Since: 3.32
*/
signals [END_MACRO] =
g_signal_new ("end-macro",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, end_macro),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [END_USER_ACTION] =
g_signal_new_class_handler ("end-user-action",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (ide_source_view_end_user_action),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [FIND_REFERENCES] =
g_signal_new_class_handler ("find-references",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (ide_source_view_real_find_references),
NULL, NULL, NULL, G_TYPE_NONE, 0);
signals [FOCUS_LOCATION] =
g_signal_new ("focus-location",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (IdeSourceViewClass, focus_location),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
IDE_TYPE_LOCATION);
signals [FORMAT_SELECTION] =
g_signal_new_class_handler ("format-selection",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (ide_source_view_real_format_selection),
NULL, NULL, NULL, G_TYPE_NONE, 0);
signals [QUERY_CODE_ACTION] =
g_signal_new_class_handler ("query-code-action",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (ide_source_view_real_code_action_query),
NULL, NULL, NULL, G_TYPE_NONE, 0);
signals [GOTO_DEFINITION] =
g_signal_new ("goto-definition",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, goto_definition),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [HIDE_COMPLETION] =
g_signal_new ("hide-completion",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, hide_completion),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [INCREASE_FONT_SIZE] =
g_signal_new ("increase-font-size",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, increase_font_size),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [INDENT_SELECTION] =
g_signal_new ("indent-selection",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, indent_selection),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
G_TYPE_INT);
/**
* IdeSourceView::insert-modifier:
* @self: An #IdeSourceView
* @use_count: If the count property should be used to repeat.
*
* Inserts the current modifier character at the insert mark in the buffer.
* If @use_count is %TRUE, then the character will be inserted
* #IdeSourceView:count times.
*
* Since: 3.32
*/
signals [INSERT_MODIFIER] =
g_signal_new ("insert-modifier",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, insert_modifier),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
g_signal_override_class_handler ("join-lines",
G_TYPE_FROM_CLASS (klass),
G_CALLBACK (ide_source_view_real_join_lines));
signals [JUMP] =
g_signal_new ("jump",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (IdeSourceViewClass, jump),
NULL, NULL, NULL,
G_TYPE_NONE,
2,
GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE,
GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
signals [MOVEMENT] =
g_signal_new ("movement",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, movement),
NULL, NULL, NULL,
G_TYPE_NONE,
4,
IDE_TYPE_SOURCE_VIEW_MOVEMENT,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN);
/**
* IdeSourceView::move-error:
* @self: An #IdeSourceView.
* @dir: The direction to move.
*
* Moves to the next search result either forwards or backwards.
*
* Since: 3.32
*/
signals [MOVE_ERROR] =
g_signal_new ("move-error",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, move_error),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
GTK_TYPE_DIRECTION_TYPE);
signals [MOVE_SEARCH] =
g_signal_new ("move-search",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, move_search),
NULL, NULL, NULL,
G_TYPE_NONE,
6,
GTK_TYPE_DIRECTION_TYPE,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN,
G_TYPE_INT);
signals [PASTE_CLIPBOARD_EXTENDED] =
g_signal_new ("paste-clipboard-extended",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, paste_clipboard_extended),
NULL, NULL, NULL,
G_TYPE_NONE,
3,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN);
/**
* IdeSourceView::pop-selection:
*
* Reselects a previousl selected range of text that was saved using
* IdeSourceView::push-selection.
*
* Since: 3.32
*/
signals [POP_SELECTION] =
g_signal_new ("pop-selection",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, pop_selection),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
/**
* IdeSourceView::pop-snippet:
* @self: An #IdeSourceView
* @snippet: An #IdeSnippet.
*
* Pops the current snippet from the sourceview if there is one.
*
* Since: 3.32
*/
signals [POP_SNIPPET] =
g_signal_new_class_handler ("pop-snippet",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
NULL,
NULL, NULL, NULL,
G_TYPE_NONE,
0);
/**
* IdeSourceView::push-selection:
*
* Saves the current selection away to be restored by a call to
* IdeSourceView::pop-selection. You must pop the selection to keep
* the selection stack in consistent order.
*
* Since: 3.32
*/
signals [PUSH_SELECTION] =
g_signal_new ("push-selection",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, push_selection),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
/**
* IdeSourceView::push-snippet:
* @self: An #IdeSourceView
* @snippet: An #IdeSnippet.
* @iter: (allow-none): The location for the snippet, or %NULL.
*
* Pushes @snippet onto the snippet stack at either @iter or the insertion
* mark if @iter is not provided.
*
* Since: 3.32
*/
signals [PUSH_SNIPPET] =
g_signal_new_class_handler ("push-snippet",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_CALLBACK (ide_source_view_real_push_snippet),
NULL, NULL, NULL,
G_TYPE_NONE,
2,
IDE_TYPE_SNIPPET,
GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
signals [REBUILD_HIGHLIGHT] =
g_signal_new ("rebuild-highlight",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, rebuild_highlight),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [DUPLICATE_ENTIRE_LINE] =
g_signal_new_class_handler ("duplicate-entire-line",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (ide_source_view_real_duplicate_entire_line),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [REINDENT] =
g_signal_new_class_handler ("reindent",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (ide_source_view_real_reindent),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
/**
* IdeSourceView:replay-macro:
* @self: an #IdeSourceView.
*
* Replays the last series of captured events that were captured between calls
* to #IdeSourceView::begin-macro and #IdeSourceView::end-macro.
*
* Since: 3.32
*/
signals [REPLAY_MACRO] =
g_signal_new ("replay-macro",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, replay_macro),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
signals [REQUEST_DOCUMENTATION] =
g_signal_new ("request-documentation",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, request_documentation),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
/**
* IdeSourceView::reset:
*
* This is a helper signal that will try to reset keyboard input
* and various stateful settings of the sourceview. This is a good
* signal to map to the "Escape" key.
*
* Since: 3.32
*/
signals [RESET] =
g_signal_new_class_handler ("reset",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (ide_source_view_real_reset),
NULL, NULL, NULL, G_TYPE_NONE, 0);
signals [RESET_FONT_SIZE] =
g_signal_new ("reset-font-size",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, reset_font_size),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [RESTORE_INSERT_MARK] =
g_signal_new ("restore-insert-mark",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, restore_insert_mark),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
signals [SAVE_INSERT_MARK] =
g_signal_new ("save-insert-mark",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, save_insert_mark),
NULL, NULL, NULL,
G_TYPE_NONE,
0);
g_signal_override_class_handler ("select-all",
G_TYPE_FROM_CLASS (klass),
G_CALLBACK (ide_source_view_real_select_all));
signals [SELECT_INNER] =
g_signal_new ("select-inner",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, select_inner),
NULL, NULL, NULL,
G_TYPE_NONE,
4,
G_TYPE_STRING,
G_TYPE_STRING,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN);
signals [SELECT_TAG] =
g_signal_new ("select-tag",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, select_tag),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
signals [SELECTION_THEATRIC] =
g_signal_new ("selection-theatric",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, selection_theatric),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
IDE_TYPE_SOURCE_VIEW_THEATRIC);
signals [SET_MODE] =
g_signal_new ("set-mode",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, set_mode),
NULL, NULL, NULL,
G_TYPE_NONE,
2,
G_TYPE_STRING,
IDE_TYPE_SOURCE_VIEW_MODE_TYPE);
signals [SET_OVERWRITE] =
g_signal_new ("set-overwrite",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, set_overwrite),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN);
signals [SET_SEARCH_TEXT] =
g_signal_new ("set-search-text",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, set_search_text),
NULL, NULL, NULL,
G_TYPE_NONE,
2,
G_TYPE_STRING,
G_TYPE_BOOLEAN);
/**
* IdeSourceView::sort:
* @self: an #IdeSourceView.
* @ignore_case: If character case should be ignored.
* @reverse: If the lines should be sorted in reverse order
*
* This signal is meant to be activated from keybindings to sort the currently selected lines.
* The lines are sorted using qsort() and either strcmp() or strcasecmp().
*
* Since: 3.32
*/
signals [SORT] =
g_signal_new ("sort",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, sort),
NULL, NULL, NULL,
G_TYPE_NONE,
2,
G_TYPE_BOOLEAN,
G_TYPE_BOOLEAN);
signals [SWAP_SELECTION_BOUNDS] =
g_signal_new ("swap-selection-bounds",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (IdeSourceViewClass, swap_selection_bounds),
NULL, NULL, NULL,
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,
GDK_KEY_r,
GDK_CONTROL_MASK | GDK_SHIFT_MASK,
"begin-rename", 0);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_q,
GDK_CONTROL_MASK | GDK_SHIFT_MASK,
"query-code-action", 0);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_space,
GDK_CONTROL_MASK | GDK_SHIFT_MASK,
"find-references", 0);
/* Override "Home" and "<Shift>Home" to use our movements
* instead of the smart home feature of GtkSourceView.
*/
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Home,
0,
"movement", 4,
IDE_TYPE_SOURCE_VIEW_MOVEMENT, IDE_SOURCE_VIEW_MOVEMENT_SMART_HOME,
G_TYPE_BOOLEAN, FALSE,
G_TYPE_BOOLEAN, TRUE,
G_TYPE_BOOLEAN, FALSE);
gtk_binding_entry_add_signal (binding_set,
GDK_KEY_Home,
GDK_MOD1_MASK | GDK_SHIFT_MASK,
"movement", 4,
IDE_TYPE_SOURCE_VIEW_MOVEMENT, IDE_SOURCE_VIEW_MOVEMENT_SMART_HOME,
G_TYPE_BOOLEAN, TRUE,
G_TYPE_BOOLEAN, TRUE,
G_TYPE_BOOLEAN, FALSE);
/* Remove Emoji from GtkTextView, we'll add back in various keybinding modes */
binding_set = gtk_binding_set_by_class (g_type_class_peek (GTK_TYPE_TEXT_VIEW));
gtk_binding_entry_remove (binding_set, GDK_KEY_period, GDK_CONTROL_MASK);
gtk_binding_entry_remove (binding_set, GDK_KEY_semicolon, GDK_CONTROL_MASK);
}
static void
ide_source_view_init (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
DZL_COUNTER_INC (instances);
gtk_widget_add_events (GTK_WIDGET (self), GDK_ENTER_NOTIFY_MASK);
gtk_widget_set_has_tooltip (GTK_WIDGET (self), FALSE);
priv->include_regex = g_regex_new (INCLUDE_STATEMENTS,
G_REGEX_OPTIMIZE,
0,
NULL);
priv->show_line_numbers = TRUE;
priv->show_line_changes = TRUE;
priv->show_line_diagnostics = TRUE;
priv->interactive_completion = TRUE;
priv->target_line_column = 0;
priv->snippets = g_queue_new ();
priv->selections = g_queue_new ();
priv->font_scale = FONT_SCALE_NORMAL;
priv->command_str = g_string_sized_new (32);
priv->overscroll_num_lines = DEFAULT_OVERSCROLL_NUM_LINES;
priv->hover = _ide_hover_new (self);
priv->file_setting_bindings = dzl_binding_group_new ();
dzl_binding_group_bind (priv->file_setting_bindings, "auto-indent",
self, "auto-indent", G_BINDING_SYNC_CREATE);
dzl_binding_group_bind (priv->file_setting_bindings, "indent-width",
self, "indent-width", G_BINDING_SYNC_CREATE);
dzl_binding_group_bind (priv->file_setting_bindings, "tab-width",
self, "tab-width", G_BINDING_SYNC_CREATE);
dzl_binding_group_bind (priv->file_setting_bindings, "right-margin-position",
self, "right-margin-position", G_BINDING_SYNC_CREATE);
dzl_binding_group_bind (priv->file_setting_bindings, "indent-style",
self, "indent-style", G_BINDING_SYNC_CREATE);
dzl_binding_group_bind (priv->file_setting_bindings, "show-right-margin",
self, "show-right-margin", G_BINDING_SYNC_CREATE);
dzl_binding_group_bind (priv->file_setting_bindings, "insert-matching-brace",
self, "insert-matching-brace", G_BINDING_SYNC_CREATE);
dzl_binding_group_bind (priv->file_setting_bindings, "overwrite-braces",
self, "overwrite-braces", G_BINDING_SYNC_CREATE);
priv->buffer_signals = dzl_signal_group_new (IDE_TYPE_BUFFER);
dzl_signal_group_connect_object (priv->buffer_signals,
"changed",
G_CALLBACK (ide_source_view__buffer_changed_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"request-scroll-to-insert",
G_CALLBACK (ide_source_view__buffer_request_scroll_to_insert_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"line-flags-changed",
G_CALLBACK (ide_source_view__buffer_line_flags_changed_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"notify::can-redo",
G_CALLBACK (ide_source_view__buffer__notify_can_redo),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"notify::can-undo",
G_CALLBACK (ide_source_view__buffer__notify_can_undo),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"notify::file-settings",
G_CALLBACK (ide_source_view__buffer_notify_file_settings_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"notify::language",
G_CALLBACK (ide_source_view__buffer_notify_language_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"notify::style-scheme",
G_CALLBACK (ide_source_view__buffer_notify_style_scheme_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"insert-text",
G_CALLBACK (ide_source_view__buffer_insert_text_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"insert-text",
G_CALLBACK (ide_source_view__buffer_insert_text_after_cb),
self,
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
dzl_signal_group_connect_object (priv->buffer_signals,
"delete-range",
G_CALLBACK (ide_source_view__buffer_delete_range_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"delete-range",
G_CALLBACK (ide_source_view__buffer_delete_range_after_cb),
self,
G_CONNECT_SWAPPED | G_CONNECT_AFTER);
dzl_signal_group_connect_object (priv->buffer_signals,
"mark-set",
G_CALLBACK (ide_source_view__buffer_mark_set_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"loaded",
G_CALLBACK (ide_source_view__buffer_loaded_cb),
self,
G_CONNECT_SWAPPED);
dzl_signal_group_connect_object (priv->buffer_signals,
"notify::has-selection",
G_CALLBACK (ide_source_view__buffer_notify_has_selection_cb),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->buffer_signals,
"bind",
G_CALLBACK (ide_source_view_bind_buffer),
self,
G_CONNECT_SWAPPED);
g_signal_connect_object (priv->buffer_signals,
"unbind",
G_CALLBACK (ide_source_view_unbind_buffer),
self,
G_CONNECT_SWAPPED);
g_object_bind_property_full (self, "buffer", priv->buffer_signals, "target", 0,
ignore_invalid_buffers, NULL, NULL, NULL);
dzl_widget_action_group_attach (self, "sourceview");
}
const PangoFontDescription *
ide_source_view_get_font_desc (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), NULL);
return priv->font_desc;
}
/**
* ide_source_view_get_scaled_font_desc:
* @self: a #IdeSourceView
*
* Like ide_source_view_get_font_desc() but takes the editor zoom into
* account. You must free the result with pango_font_description_free().
*
* Returns: (transfer full): a #PangoFontDescription
*
* Since: 3.32
*/
PangoFontDescription *
ide_source_view_get_scaled_font_desc (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
PangoFontDescription *copy;
gdouble font_scale;
guint font_size;
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), NULL);
copy = pango_font_description_copy (priv->font_desc);
font_size = pango_font_description_get_size (priv->font_desc);
font_scale = fontScale [priv->font_scale];
pango_font_description_set_size (copy, font_size * font_scale);
return g_steal_pointer (&copy);
}
void
ide_source_view_set_font_desc (IdeSourceView *self,
const PangoFontDescription *font_desc)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
if (font_desc != priv->font_desc)
{
g_clear_pointer (&priv->font_desc, pango_font_description_free);
if (font_desc)
priv->font_desc = pango_font_description_copy (font_desc);
else
priv->font_desc = pango_font_description_from_string (DEFAULT_FONT_DESC);
priv->font_scale = FONT_SCALE_NORMAL;
ide_source_view_rebuild_css (self);
}
}
void
ide_source_view_set_font_name (IdeSourceView *self,
const gchar *font_name)
{
PangoFontDescription *font_desc = NULL;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
if (font_name)
font_desc = pango_font_description_from_string (font_name);
ide_source_view_set_font_desc (self, font_desc);
if (font_desc)
pango_font_description_free (font_desc);
}
gboolean
ide_source_view_get_show_line_changes (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->show_line_changes;
}
void
ide_source_view_set_show_line_changes (IdeSourceView *self,
gboolean show_line_changes)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
priv->show_line_changes = !!show_line_changes;
if (priv->gutter)
{
ide_gutter_set_show_line_changes (priv->gutter, show_line_changes);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_LINE_CHANGES]);
}
}
gboolean
ide_source_view_get_show_line_diagnostics (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->show_line_diagnostics;
}
void
ide_source_view_set_show_line_diagnostics (IdeSourceView *self,
gboolean show_line_diagnostics)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
priv->show_line_diagnostics = !!show_line_diagnostics;
if (priv->gutter)
{
ide_gutter_set_show_line_diagnostics (priv->gutter, show_line_diagnostics);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_LINE_DIAGNOSTICS]);
}
}
gboolean
ide_source_view_get_show_grid_lines (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->show_grid_lines;
}
void
ide_source_view_set_show_grid_lines (IdeSourceView *self,
gboolean show_grid_lines)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
show_grid_lines = !!show_grid_lines;
if (show_grid_lines != priv->show_grid_lines)
{
priv->show_grid_lines = show_grid_lines;
if (show_grid_lines)
gtk_source_view_set_background_pattern (GTK_SOURCE_VIEW (self),
GTK_SOURCE_BACKGROUND_PATTERN_TYPE_GRID);
else
gtk_source_view_set_background_pattern (GTK_SOURCE_VIEW (self),
GTK_SOURCE_BACKGROUND_PATTERN_TYPE_NONE);
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SHOW_GRID_LINES]);
}
}
gboolean
ide_source_view_get_insert_matching_brace (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
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)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), NULL);
if (priv->mode)
return ide_source_view_mode_get_name (priv->mode);
return NULL;
}
const gchar *
ide_source_view_get_mode_display_name (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), NULL);
return priv->display_name;
}
gboolean
ide_source_view_get_overwrite_braces (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->overwrite_braces;
}
void
ide_source_view_set_insert_matching_brace (IdeSourceView *self,
gboolean insert_matching_brace)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
insert_matching_brace = !!insert_matching_brace;
if (insert_matching_brace != priv->insert_matching_brace)
{
priv->insert_matching_brace = insert_matching_brace;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_INSERT_MATCHING_BRACE]);
}
}
void
ide_source_view_set_overwrite_braces (IdeSourceView *self,
gboolean overwrite_braces)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
overwrite_braces = !!overwrite_braces;
if (overwrite_braces != priv->overwrite_braces)
{
priv->overwrite_braces = overwrite_braces;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_OVERWRITE_BRACES]);
}
}
void
ide_source_view_pop_snippet (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSnippet *snippet;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
if ((snippet = g_queue_pop_head (priv->snippets)))
{
g_autofree gchar *new_text = NULL;
new_text = ide_snippet_get_full_text (snippet);
ide_snippet_finish (snippet);
g_signal_emit (self, signals [POP_SNIPPET], 0, snippet);
g_object_unref (snippet);
if ((snippet = g_queue_peek_head (priv->snippets)))
{
ide_snippet_replace_current_chunk_text (snippet, new_text);
ide_snippet_unpause (snippet);
ide_snippet_move_next (snippet);
}
}
ide_source_view_invalidate_window (self);
}
void
ide_source_view_clear_snippets (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
while (priv->snippets->length)
ide_source_view_pop_snippet (self);
}
/**
* ide_source_view_push_snippet:
* @self: An #IdeSourceView
* @snippet: An #IdeSnippet.
* @location: (allow-none): A location for the snippet or %NULL.
*
* Pushes a new snippet onto the source view.
*
* Since: 3.32
*/
void
ide_source_view_push_snippet (IdeSourceView *self,
IdeSnippet *snippet,
const GtkTextIter *location)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IdeSnippetContext *context;
IdeSnippet *previous;
GtkTextBuffer *buffer;
GtkTextIter iter;
gboolean has_more_tab_stops;
gboolean insert_spaces;
gchar *line_prefix;
guint tab_width;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
g_return_if_fail (IDE_IS_SNIPPET (snippet));
g_return_if_fail (!location ||
(gtk_text_iter_get_buffer (location) == (void*)priv->buffer));
if ((previous = g_queue_peek_head (priv->snippets)))
ide_snippet_pause (previous);
g_queue_push_head (priv->snippets, g_object_ref (snippet));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
if (location != NULL)
iter = *location;
else
gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer));
context = ide_snippet_get_context (snippet);
insert_spaces = gtk_source_view_get_insert_spaces_instead_of_tabs (GTK_SOURCE_VIEW (self));
ide_snippet_context_set_use_spaces (context, insert_spaces);
tab_width = gtk_source_view_get_tab_width (GTK_SOURCE_VIEW (self));
ide_snippet_context_set_tab_width (context, tab_width);
line_prefix = text_iter_get_line_prefix (&iter);
ide_snippet_context_set_line_prefix (context, line_prefix);
g_free (line_prefix);
g_signal_emit (self, signals [PUSH_SNIPPET], 0, snippet, &iter);
gtk_text_buffer_begin_user_action (buffer);
ide_source_view_block_handlers (self);
has_more_tab_stops = ide_snippet_begin (snippet, buffer, &iter);
ide_source_view_scroll_to_insert (self);
ide_source_view_unblock_handlers (self);
gtk_text_buffer_end_user_action (buffer);
if (!ide_source_view_can_animate (self))
{
GtkTextMark *mark_begin;
GtkTextMark *mark_end;
mark_begin = ide_snippet_get_mark_begin (snippet);
mark_end = ide_snippet_get_mark_end (snippet);
if (mark_begin != NULL && mark_end != NULL)
{
GtkTextIter begin;
GtkTextIter end;
gtk_text_buffer_get_iter_at_mark (buffer, &begin, mark_begin);
gtk_text_buffer_get_iter_at_mark (buffer, &end, mark_end);
/*
* HACK:
*
* We need to let the GtkTextView catch up with us so that we can get
* a realistic area back for the location of the end iter. Without
* pumping the main loop, GtkTextView will clamp the result to the
* height of the insert line.
*/
while (gtk_events_pending ())
gtk_main_iteration ();
animate_expand (self, &begin, &end);
}
}
if (!has_more_tab_stops)
ide_source_view_pop_snippet (self);
ide_source_view_invalidate_window (self);
}
void
ide_source_view_jump (IdeSourceView *self,
const GtkTextIter *from,
const GtkTextIter *to)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IDE_ENTRY;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
if (priv->buffer != NULL &&
!ide_buffer_get_loading (priv->buffer))
{
GtkTextIter dummy_from;
GtkTextIter dummy_to;
if (from == NULL)
{
GtkTextMark *mark;
mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (priv->buffer));
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (priv->buffer), &dummy_from, mark);
from = &dummy_from;
}
if (to == NULL)
{
dummy_to = *from;
to = &dummy_to;
}
g_signal_emit (self, signals [JUMP], 0, from, to);
}
IDE_EXIT;
}
/**
* ide_source_view_get_scroll_offset:
*
* Gets the #IdeSourceView:scroll-offset property. This property contains the number of lines
* that should be kept above or below the line containing the insertion cursor relative to the
* top and bottom of the visible text window.
*
* Since: 3.32
*/
guint
ide_source_view_get_scroll_offset (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), 0);
return priv->scroll_offset;
}
/**
* ide_source_view_set_scroll_offset:
*
* Sets the #IdeSourceView:scroll-offset property. See ide_source_view_get_scroll_offset() for
* more information. Set to 0 to unset this property.
*
* Since: 3.32
*/
void
ide_source_view_set_scroll_offset (IdeSourceView *self,
guint scroll_offset)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
if (scroll_offset != priv->scroll_offset)
{
priv->scroll_offset = scroll_offset;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_SCROLL_OFFSET]);
}
}
/**
* ide_source_view_get_visible_rect:
* @self: An #IdeSourceView.
* @visible_rect: (out): a #GdkRectangle.
*
* Gets the visible region in buffer coordinates that is the visible area of the buffer. This
* is similar to gtk_text_view_get_visible_area() except that it takes into account the
* #IdeSourceView:scroll-offset property to ensure there is space above and below the
* visible_rect.
*
* Since: 3.32
*/
void
ide_source_view_get_visible_rect (IdeSourceView *self,
GdkRectangle *visible_rect)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextView *text_view = (GtkTextView *)self;
GdkRectangle area;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
g_return_if_fail (visible_rect);
gtk_text_view_get_visible_rect (text_view, &area);
/*
* If we don't have valid line height, not much we can do now. We can just adjust things
* later once it becomes available.
*/
if (priv->cached_char_height)
{
gint max_scroll_offset;
gint scroll_offset;
gint visible_lines;
gint scroll_offset_height;
visible_lines = area.height / priv->cached_char_height;
max_scroll_offset = (visible_lines - 1) / 2;
scroll_offset = MIN ((gint)priv->scroll_offset, max_scroll_offset);
scroll_offset_height = priv->cached_char_height * scroll_offset;
area.y += scroll_offset_height;
area.height -= (2 * scroll_offset_height);
/*
* If we have an even number of visible lines and scrolloffset is less than our
* desired scrolloffset, we need to remove an extra line so we don't have two
* visible lines.
*/
if ((scroll_offset < (gint)priv->scroll_offset) && (visible_lines & 1) == 0)
area.height -= priv->cached_char_height;
/*
* Use a multiple of the line height so we don't jump around when
* focusing the last line (due to Y2 not fitting in the visible area).
*/
area.height = (area.height / priv->cached_char_height) * priv->cached_char_height;
}
*visible_rect = area;
}
void
ide_source_view_scroll_mark_onscreen (IdeSourceView *self,
GtkTextMark *mark,
IdeSourceScrollAlign use_align,
gdouble alignx,
gdouble aligny)
{
GtkTextView *text_view = (GtkTextView *)self;
GtkTextBuffer *buffer;
GdkRectangle visible_rect;
GdkRectangle mark_rect;
GtkTextIter iter;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
ide_source_view_get_visible_rect (self, &visible_rect);
buffer = gtk_text_view_get_buffer (text_view);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
gtk_text_view_get_iter_location (text_view, &iter, &mark_rect);
if (!_GDK_RECTANGLE_CONTAINS (&visible_rect, &mark_rect))
ide_source_view_scroll_to_mark (self, mark, 0.0, use_align, alignx, aligny, TRUE);
IDE_EXIT;
}
gboolean
ide_source_view_move_mark_onscreen (IdeSourceView *self,
GtkTextMark *mark)
{
GtkTextView *text_view = (GtkTextView *)self;
GtkTextBuffer *buffer;
GtkTextIter iter;
GtkTextIter end;
GdkRectangle visible_rect;
GdkRectangle iter_rect;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
g_return_val_if_fail (GTK_IS_TEXT_MARK (mark), FALSE);
buffer = gtk_text_view_get_buffer (text_view);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
gtk_text_buffer_get_end_iter (buffer, &end);
ide_source_view_get_visible_rect (self, &visible_rect);
gtk_text_view_get_iter_location (text_view, &iter, &iter_rect);
if (_GDK_RECTANGLE_CONTAINS (&visible_rect, &iter_rect))
IDE_RETURN (FALSE);
if (_GDK_RECTANGLE_Y2 (&iter_rect) > _GDK_RECTANGLE_Y2 (&visible_rect))
gtk_text_view_get_iter_at_location (text_view, &iter,
_GDK_RECTANGLE_X2 (&visible_rect),
_GDK_RECTANGLE_Y2 (&visible_rect));
else if (iter_rect.y < visible_rect.y)
gtk_text_view_get_iter_at_location (text_view, &iter, visible_rect.x, visible_rect.y);
else
return gtk_text_view_move_mark_onscreen (text_view, mark);
gtk_text_buffer_move_mark (buffer, mark, &iter);
IDE_RETURN (TRUE);
}
static gboolean
ide_source_view_mark_is_onscreen (IdeSourceView *self,
GtkTextMark *mark)
{
GtkTextBuffer *buffer;
GdkRectangle visible_rect;
GdkRectangle mark_rect;
GtkTextIter iter;
g_assert (IDE_IS_SOURCE_VIEW (self));
g_assert (GTK_IS_TEXT_MARK (mark));
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
ide_source_view_get_visible_rect (self, &visible_rect);
gtk_text_view_get_iter_location (GTK_TEXT_VIEW (self), &iter, &mark_rect);
return (_GDK_RECTANGLE_CONTAINS (&visible_rect, &mark_rect));
}
static void
ide_source_view__vadj_animation_completed (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
IDE_ENTRY;
g_assert (IDE_IS_SOURCE_VIEW (self));
/*
* If the mark we were scrolling to is not yet on screen, then just wait for another size
* allocate so that we can continue making progress.
*/
if (!ide_source_view_mark_is_onscreen (self, priv->scroll_mark))
IDE_EXIT;
priv->scrolling_to_scroll_mark = FALSE;
IDE_EXIT;
}
/*
* Many parts of this function were taken from gtk_text_view_scroll_to_iter ()
* https://developer.gnome.org/gtk3/stable/GtkTextView.html#gtk-text-view-scroll-to-iter
*/
void
ide_source_view_scroll_to_iter (IdeSourceView *self,
const GtkTextIter *iter,
gdouble within_margin,
IdeSourceScrollAlign use_align,
gdouble xalign,
gdouble yalign,
gboolean animate_scroll)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextView *text_view = (GtkTextView *)self;
GtkTextBuffer *buffer;
GdkRectangle rect;
GdkRectangle screen;
gint xvalue = 0;
gint yvalue = 0;
gint scroll_dest;
gint screen_bottom;
gint screen_right;
gint screen_xoffset;
gint screen_yoffset;
gint current_x_scroll;
gint current_y_scroll;
GtkAdjustment *hadj;
GtkAdjustment *vadj;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
g_return_if_fail (iter != NULL);
g_return_if_fail (within_margin >= 0.0 && within_margin <= 0.5);
g_return_if_fail (xalign >= 0.0 && xalign <= 1.0);
g_return_if_fail (yalign >= 0.0 && yalign <= 1.0);
if (!ide_source_view_can_animate (self))
animate_scroll = FALSE;
buffer = gtk_text_view_get_buffer (text_view);
gtk_text_buffer_move_mark (buffer, priv->scroll_mark, iter);
hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE (self));
vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (self));
gtk_text_view_get_iter_location (text_view,
iter,
&rect);
gtk_text_view_get_visible_rect (text_view, &screen);
current_x_scroll = screen.x;
current_y_scroll = screen.y;
screen_xoffset = screen.width * within_margin;
screen_yoffset = screen.height * within_margin;
screen.x += screen_xoffset;
screen.y += screen_yoffset;
screen.width -= screen_xoffset * 2;
screen.height -= screen_yoffset * 2;
/* paranoia check */
if (screen.width < 1)
screen.width = 1;
if (screen.height < 1)
screen.height = 1;
/* The -1 here ensures that we leave enough space to draw the cursor
* when this function is used for horizontal scrolling.
*/
screen_right = screen.x + screen.width - 1;
screen_bottom = screen.y + screen.height;
/* The alignment affects the point in the target character that we
* choose to align. If we're doing right/bottom alignment, we align
* the right/bottom edge of the character the mark is at; if we're
* doing left/top we align the left/top edge of the character; if
* we're doing center alignment we align the center of the
* character.
*/
/* Vertical alignment */
scroll_dest = current_y_scroll;
if (SCROLL_Y (use_align))
{
scroll_dest = rect.y + (rect.height * yalign) - (screen.height * yalign);
/* if scroll_dest < screen.y, we move a negative increment (up),
* else a positive increment (down)
*/
yvalue = scroll_dest - screen.y + screen_yoffset;
}
else
{
/* move minimum to get onscreen */
if (rect.y < screen.y)
{
scroll_dest = rect.y;
yvalue = scroll_dest - screen.y - screen_yoffset;
}
else if ((rect.y + rect.height) > screen_bottom)
{
scroll_dest = rect.y + rect.height;
yvalue = scroll_dest - screen_bottom + screen_yoffset;
}
}
yvalue += current_y_scroll;
/* Scroll offset adjustment */
if (priv->cached_char_height)
{
gint max_scroll_offset;
gint visible_lines;
gint scroll_offset;
gint scroll_offset_height;
visible_lines = screen.height / priv->cached_char_height;
max_scroll_offset = (visible_lines - 1) / 2;
scroll_offset = MIN ((gint)priv->scroll_offset, max_scroll_offset);
scroll_offset_height = priv->cached_char_height * scroll_offset;
if (scroll_offset_height > 0)
{
if (rect.y - scroll_offset_height < yvalue)
yvalue -= (scroll_offset_height - (rect.y - yvalue));
else if (_GDK_RECTANGLE_Y2 (&rect) + scroll_offset_height > yvalue + screen.height)
yvalue += (_GDK_RECTANGLE_Y2 (&rect) + scroll_offset_height) - (yvalue + screen.height);
}
}
/* Horizontal alignment */
scroll_dest = current_x_scroll;
if (SCROLL_X (use_align))
{
scroll_dest = rect.x + (rect.width * xalign) - (screen.width * xalign);
/* if scroll_dest < screen.y, we move a negative increment (left),
* else a positive increment (right)
*/
xvalue = scroll_dest - screen.x + screen_xoffset;
}
else
{
/* move minimum to get onscreen */
if (rect.x < screen.x)
{
scroll_dest = rect.x;
xvalue = scroll_dest - screen.x - screen_xoffset;
}
else if ((rect.x + rect.width) > screen_right)
{
scroll_dest = rect.x + rect.width;
xvalue = scroll_dest - screen_right + screen_xoffset;
}
}
xvalue += current_x_scroll;
if (animate_scroll)
{
GdkFrameClock *frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self));
guint duration_msec = LARGE_SCROLL_DURATION_MSEC;
gdouble difference;
gdouble page_size;
gdouble current;
current = gtk_adjustment_get_value (vadj);
page_size = gtk_adjustment_get_page_size (vadj);
difference = ABS (current - yvalue);
/*
* Ignore animations if we are scrolling less than two full lines. This
* helps when pressing up/down for key repeat. Also, if it's a partial
* page scroll (less than page size), use less time to animate, so it
* isn't so annoying.
*/
if (difference < (priv->cached_char_height * 2))
goto ignore_animation;
else if (difference <= page_size)
duration_msec = SMALL_SCROLL_DURATION_MSEC;
priv->scrolling_to_scroll_mark = TRUE;
if (priv->hadj_animation != NULL)
{
dzl_animation_stop (priv->hadj_animation);
g_clear_weak_pointer (&priv->hadj_animation);
}
priv->hadj_animation =
dzl_object_animate (hadj,
DZL_ANIMATION_EASE_OUT_CUBIC,
duration_msec,
frame_clock,
"value", (double)xvalue,
NULL);
g_object_add_weak_pointer (G_OBJECT (priv->hadj_animation),
(gpointer *)&priv->hadj_animation);
if (priv->vadj_animation != NULL)
{
dzl_animation_stop (priv->vadj_animation);
g_clear_weak_pointer (&priv->vadj_animation);
}
priv->vadj_animation =
dzl_object_animate_full (vadj,
DZL_ANIMATION_EASE_OUT_CUBIC,
duration_msec,
frame_clock,
(GDestroyNotify)ide_source_view__vadj_animation_completed,
self,
"value", (double)yvalue,
NULL);
g_object_add_weak_pointer (G_OBJECT (priv->vadj_animation),
(gpointer *)&priv->vadj_animation);
}
else
{
ignore_animation:
gtk_adjustment_set_value (hadj, xvalue);
gtk_adjustment_set_value (vadj, yvalue);
}
IDE_EXIT;
}
void
ide_source_view_scroll_to_mark (IdeSourceView *self,
GtkTextMark *mark,
gdouble within_margin,
IdeSourceScrollAlign use_align,
gdouble xalign,
gdouble yalign,
gboolean animate_scroll)
{
GtkTextBuffer *buffer;
GtkTextIter iter;
IDE_ENTRY;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
g_return_if_fail (GTK_IS_TEXT_MARK (mark));
g_return_if_fail (xalign >= 0.0);
g_return_if_fail (xalign <= 1.0);
g_return_if_fail (yalign >= 0.0);
g_return_if_fail (yalign <= 1.0);
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
ide_source_view_scroll_to_iter (self, &iter, within_margin, use_align, xalign, yalign,
animate_scroll);
#ifdef IDE_ENABLE_TRACE
{
const gchar *name = gtk_text_mark_get_name (mark);
IDE_TRACE_MSG ("Scrolling to mark \"%s\" at %d:%d",
name ? name : "unnamed",
gtk_text_iter_get_line (&iter),
gtk_text_iter_get_line_offset (&iter));
}
#endif
IDE_EXIT;
}
gboolean
ide_source_view_place_cursor_onscreen (IdeSourceView *self)
{
GtkTextBuffer *buffer;
GtkTextMark *insert;
gboolean ret;
IDE_ENTRY;
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self));
insert = gtk_text_buffer_get_insert (buffer);
ret = ide_source_view_move_mark_onscreen (self, insert);
IDE_RETURN (ret);
}
/**
* ide_source_view_get_file_settings:
* @self: an #IdeSourceView.
*
* Gets the #IdeSourceView:file-settings property. This contains various
* settings for how the file should be rendered in the view, and preferences
* such as spaces vs tabs.
*
* Returns: (transfer none) (nullable): An #IdeFileSettings or %NULL.
*
* Since: 3.32
*/
IdeFileSettings *
ide_source_view_get_file_settings (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), NULL);
return (IdeFileSettings *)dzl_binding_group_get_source (priv->file_setting_bindings);
}
gboolean
ide_source_view_get_highlight_current_line (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->highlight_current_line;
}
void
ide_source_view_set_highlight_current_line (IdeSourceView *self,
gboolean highlight_current_line)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
/*
* This overrides the default GtkSourceView::highlight-current-line so that
* we can turn off the line highlight when the IdeSourceView is not in focus.
* See ide_source_view_real_focus_in_event() and
* ide_source_view_real_focus_out_event() for the machinery.
*/
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
highlight_current_line = !!highlight_current_line;
if (highlight_current_line != priv->highlight_current_line)
{
priv->highlight_current_line = highlight_current_line;
g_object_notify (G_OBJECT (self), "highlight-current-line");
}
}
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 *line_column)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkTextBuffer *buffer;
GtkTextIter iter;
g_return_if_fail (IDE_IS_SOURCE_VIEW (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, 0);
ide_source_view_get_iter_at_visual_column (self, priv->saved_line_column, &iter);
}
else
{
GtkTextMark *mark;
mark = gtk_text_buffer_get_insert (buffer);
gtk_text_buffer_get_iter_at_mark (buffer, &iter, mark);
}
if (line)
*line = gtk_text_iter_get_line (&iter);
if (line_column)
*line_column = gtk_source_view_get_visual_column (GTK_SOURCE_VIEW (self), &iter);
}
gint
ide_source_view_get_count (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), 0);
return priv->count;
}
void
ide_source_view_set_count (IdeSourceView *self,
gint count)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
if (count < 0)
count = 0;
if (count != priv->count)
{
priv->count = count;
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_COUNT]);
}
}
GtkTextMark *
_ide_source_view_get_scroll_mark (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), NULL);
return priv->scroll_mark;
}
/**
* ide_source_view_get_current_snippet:
*
* Gets the current snippet if there is one, otherwise %NULL.
*
* Returns: (transfer none) (nullable): An #IdeSnippet or %NULL.
*
* Since: 3.32
*/
IdeSnippet *
ide_source_view_get_current_snippet (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), NULL);
return g_queue_peek_head (priv->snippets);
}
gboolean
ide_source_view_get_show_line_numbers (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->show_line_numbers;
}
void
ide_source_view_set_show_line_numbers (IdeSourceView *self,
gboolean show_line_numbers)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
priv->show_line_numbers = !!show_line_numbers;
if (priv->gutter)
{
ide_gutter_set_show_line_numbers (priv->gutter, show_line_numbers);
g_object_notify (G_OBJECT (self), "show-line-numbers");
}
}
gboolean
ide_source_view_get_show_relative_line_numbers (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->show_relative_line_numbers;
}
void
ide_source_view_set_show_relative_line_numbers (IdeSourceView *self,
gboolean show_relative_line_numbers)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
priv->show_relative_line_numbers = !!show_relative_line_numbers;
if (priv->gutter)
{
ide_gutter_set_show_relative_line_numbers (priv->gutter, show_relative_line_numbers);
g_object_notify (G_OBJECT (self), "show-relative-line-numbers");
}
}
gboolean
ide_source_view_is_processing_key (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->in_key_press > 0;
}
/**
* ide_source_view_get_completion:
* @self: a #IdeSourceView
*
* Get the completion for the #IdeSourceView
*
* Returns: (transfer none): an #IdeCompletion
*
* Since: 3.32
*/
IdeCompletion *
ide_source_view_get_completion (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), NULL);
return priv->completion;
}
/**
* ide_source_view_has_snippet:
* @self: a #IdeSourceView
*
* Checks if there is an active snippet.
*
* Returns: %TRUE if there is an active snippet.
*
* Since: 3.32
*/
gboolean
ide_source_view_has_snippet (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->snippets->length > 0;
}
/**
* ide_source_view_set_gutter:
* @self: a #IdeSourceView
* @gutter: an #IdeGutter
*
* Allows setting the gutter for the sourceview.
*
* Generally, this will always be #IdeOmniGutterRenderer. However, to avoid
* circular dependencies, an interface is used to allow plugins to set
* this object.
*
* Since: 3.32
*/
void
ide_source_view_set_gutter (IdeSourceView *self,
IdeGutter *gutter)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
GtkSourceGutter *left_gutter;
g_return_if_fail (IDE_IS_SOURCE_VIEW (self));
g_return_if_fail (!gutter || IDE_IS_GUTTER (gutter));
g_return_if_fail (!gutter || GTK_SOURCE_IS_GUTTER_RENDERER (gutter));
if (gutter == priv->gutter)
return;
left_gutter = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (self),
GTK_TEXT_WINDOW_LEFT);
if (priv->gutter)
{
gtk_source_gutter_remove (left_gutter, GTK_SOURCE_GUTTER_RENDERER (priv->gutter));
g_clear_object (&priv->gutter);
}
if (gutter)
{
priv->gutter = g_object_ref_sink (gutter);
gtk_source_gutter_insert (left_gutter,
GTK_SOURCE_GUTTER_RENDERER (gutter),
0);
ide_gutter_set_show_line_numbers (priv->gutter, priv->show_line_numbers);
ide_gutter_set_show_relative_line_numbers (priv->gutter, priv->show_relative_line_numbers);
ide_gutter_set_show_line_changes (priv->gutter, priv->show_line_changes);
ide_gutter_set_show_line_diagnostics (priv->gutter, priv->show_line_diagnostics);
ide_gutter_style_changed (gutter);
}
g_object_notify (G_OBJECT (self), "show-line-changes");
g_object_notify (G_OBJECT (self), "show-line-diagnostics");
g_object_notify (G_OBJECT (self), "show-line-numbers");
g_object_notify (G_OBJECT (self), "show-relative-line-numbers");
}
gboolean
_ide_source_view_has_cursors (IdeSourceView *self)
{
IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
g_return_val_if_fail (IDE_IS_SOURCE_VIEW (self), FALSE);
return priv->cursor != NULL && ide_cursor_is_enabled (priv->cursor);
}