/* ide-source-view.c * * Copyright 2015-2019 Christian Hergert * * 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 . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-source-view" #include "config.h" #include #include #include #include #include #include #include #include #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 (©_begin, ©_end); is_single_line = (gtk_text_iter_get_line (©_begin) == gtk_text_iter_get_line (©_end)); is_whole_line = ((gtk_text_iter_get_line (©_begin) + 1 == gtk_text_iter_get_line (©_end)) && (gtk_text_iter_starts_line (©_begin) && gtk_text_iter_starts_line (©_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 (©, 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, ""); if (state & GDK_SHIFT_MASK) g_string_append (command_str, ""); if (state & GDK_MOD1_MASK) g_string_append (command_str, ""); 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 ?: ""); } #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 (_("%sLine %u, Column %u"), 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 "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 (©); } 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); }