1445 lines
44 KiB
C
1445 lines
44 KiB
C
/* ide-editor-page.c
|
|
*
|
|
* Copyright 2017-2019 Christian Hergert <chergert@redhat.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "ide-editor-page"
|
|
|
|
#include "config.h"
|
|
|
|
#include <dazzle.h>
|
|
#include <libpeas/peas.h>
|
|
#include <gtksourceview/gtksource.h>
|
|
#include <pango/pangofc-fontmap.h>
|
|
|
|
#include "ide-editor-page.h"
|
|
#include "ide-editor-page-addin.h"
|
|
#include "ide-editor-private.h"
|
|
#include "ide-line-change-gutter-renderer.h"
|
|
|
|
#define AUTO_HIDE_TIMEOUT_SECONDS 5
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_AUTO_HIDE_MAP,
|
|
PROP_BUFFER,
|
|
PROP_BUFFER_FILE,
|
|
PROP_SEARCH,
|
|
PROP_SHOW_MAP,
|
|
PROP_VIEW,
|
|
N_PROPS
|
|
};
|
|
|
|
static void ide_editor_page_update_reveal_timer (IdeEditorPage *self);
|
|
|
|
G_DEFINE_FINAL_TYPE (IdeEditorPage, ide_editor_page, IDE_TYPE_PAGE)
|
|
|
|
DZL_DEFINE_COUNTER (instances, "Editor", "N Views", "Number of editor views");
|
|
|
|
static GParamSpec *properties [N_PROPS];
|
|
static FcConfig *localFontConfig;
|
|
|
|
static void
|
|
ide_editor_page_load_fonts (IdeEditorPage *self)
|
|
{
|
|
PangoFontMap *font_map;
|
|
PangoFontDescription *font_desc;
|
|
|
|
if (g_once_init_enter (&localFontConfig))
|
|
{
|
|
const gchar *font_path = PACKAGE_DATADIR "/gnome-builder/fonts/BuilderBlocks.ttf";
|
|
FcConfig *config = FcInitLoadConfigAndFonts ();
|
|
|
|
if (g_getenv ("GB_IN_TREE_FONTS") != NULL)
|
|
font_path = "data/fonts/BuilderBlocks.ttf";
|
|
|
|
if (!g_file_test (font_path, G_FILE_TEST_IS_REGULAR))
|
|
g_warning ("Failed to locate \"%s\"", font_path);
|
|
|
|
FcConfigAppFontAddFile (config, (const FcChar8 *)font_path);
|
|
|
|
g_once_init_leave (&localFontConfig, config);
|
|
}
|
|
|
|
font_map = pango_cairo_font_map_new_for_font_type (CAIRO_FONT_TYPE_FT);
|
|
pango_fc_font_map_set_config (PANGO_FC_FONT_MAP (font_map), localFontConfig);
|
|
gtk_widget_set_font_map (GTK_WIDGET (self->map), font_map);
|
|
font_desc = pango_font_description_from_string ("BuilderBlocks");
|
|
pango_font_description_set_absolute_size (font_desc, (96.0/72.0) * 2 * PANGO_SCALE);
|
|
|
|
g_assert (localFontConfig != NULL);
|
|
g_assert (font_map != NULL);
|
|
g_assert (font_desc != NULL);
|
|
|
|
g_object_set (self->map, "font-desc", font_desc, NULL);
|
|
|
|
pango_font_description_free (font_desc);
|
|
g_object_unref (font_map);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_update_icon (IdeEditorPage *self)
|
|
{
|
|
g_autofree gchar *name = NULL;
|
|
g_autofree gchar *content_type = NULL;
|
|
g_autofree gchar *sniff = NULL;
|
|
g_autoptr(GIcon) icon = NULL;
|
|
GtkTextIter begin, end;
|
|
GFile *file;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_BUFFER (self->buffer));
|
|
|
|
/* Get first 1024 bytes to help determine content type */
|
|
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end);
|
|
if (gtk_text_iter_get_offset (&end) > 1024)
|
|
gtk_text_iter_set_offset (&end, 1024);
|
|
sniff = gtk_text_iter_get_slice (&begin, &end);
|
|
|
|
/* Now get basename for content type */
|
|
file = ide_buffer_get_file (self->buffer);
|
|
name = g_file_get_basename (file);
|
|
|
|
/* Guess content type */
|
|
content_type = g_content_type_guess (name, (const guchar *)sniff, strlen (sniff), NULL);
|
|
|
|
/* Update icon to match guess */
|
|
icon = ide_g_content_type_get_symbolic_icon (content_type, name);
|
|
ide_page_set_icon (IDE_PAGE (self), icon);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_buffer_notify_failed (IdeEditorPage *self,
|
|
GParamSpec *pspec,
|
|
IdeBuffer *buffer)
|
|
{
|
|
gboolean failed;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
|
|
failed = ide_buffer_get_failed (buffer);
|
|
|
|
ide_page_set_failed (IDE_PAGE (self), failed);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_stop_search (IdeEditorPage *self,
|
|
IdeEditorSearchBar *search_bar)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_EDITOR_SEARCH_BAR (search_bar));
|
|
|
|
gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
|
|
gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_notify_child_revealed (IdeEditorPage *self,
|
|
GParamSpec *pspec,
|
|
GtkRevealer *revealer)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (GTK_IS_REVEALER (revealer));
|
|
|
|
if (gtk_revealer_get_child_revealed (revealer))
|
|
{
|
|
GtkWidget *toplevel = gtk_widget_get_ancestor (GTK_WIDGET (revealer), GTK_TYPE_WINDOW);
|
|
GtkWidget *focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
|
|
|
|
/* Only focus the search bar if it doesn't already have focus,
|
|
* as it can reselect the search text.
|
|
*/
|
|
if (focus == NULL || !gtk_widget_is_ancestor (focus, GTK_WIDGET (revealer)))
|
|
gtk_widget_grab_focus (GTK_WIDGET (self->search_bar));
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ide_editor_page_focus_in_event (IdeEditorPage *self,
|
|
GdkEventFocus *focus,
|
|
IdeSourceView *source_view)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_SOURCE_VIEW (source_view));
|
|
|
|
ide_page_mark_used (IDE_PAGE (self));
|
|
|
|
return GDK_EVENT_PROPAGATE;
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_buffer_loaded (IdeEditorPage *self,
|
|
IdeBuffer *buffer)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
|
|
ide_editor_page_update_icon (self);
|
|
|
|
/* Scroll to the insertion location once the buffer
|
|
* has loaded. This is useful if it is not onscreen.
|
|
*/
|
|
ide_source_view_scroll_to_insert (self->source_view);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_buffer_modified_changed (IdeEditorPage *self,
|
|
IdeBuffer *buffer)
|
|
{
|
|
gboolean modified = FALSE;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
|
|
if (!ide_buffer_get_loading (buffer))
|
|
modified = gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (buffer));
|
|
|
|
ide_page_set_modified (IDE_PAGE (self), modified);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_buffer_notify_language_cb (IdeExtensionSetAdapter *set,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *exten,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *language_id = user_data;
|
|
|
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
|
|
g_assert (plugin_info != NULL);
|
|
g_assert (IDE_IS_EDITOR_PAGE_ADDIN (exten));
|
|
|
|
ide_editor_page_addin_language_changed (IDE_EDITOR_PAGE_ADDIN (exten), language_id);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_buffer_notify_language (IdeEditorPage *self,
|
|
GParamSpec *pspec,
|
|
IdeBuffer *buffer)
|
|
{
|
|
const gchar *lang_id = NULL;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
|
|
if (self->addins == NULL)
|
|
return;
|
|
|
|
lang_id = ide_buffer_get_language_id (buffer);
|
|
|
|
/* Update extensions that change based on language */
|
|
ide_extension_set_adapter_set_value (self->addins, lang_id);
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
ide_editor_page_buffer_notify_language_cb,
|
|
(gpointer)lang_id);
|
|
|
|
ide_editor_page_update_icon (self);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_buffer_notify_style_scheme (IdeEditorPage *self,
|
|
GParamSpec *pspec,
|
|
IdeBuffer *buffer)
|
|
{
|
|
g_autofree gchar *background = NULL;
|
|
g_autofree gchar *foreground = NULL;
|
|
GtkSourceStyleScheme *scheme;
|
|
GtkSourceStyle *style;
|
|
gboolean background_set = FALSE;
|
|
gboolean foreground_set = FALSE;
|
|
GdkRGBA rgba;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
|
|
if (NULL == (scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (buffer))) ||
|
|
NULL == (style = gtk_source_style_scheme_get_style (scheme, "text")))
|
|
goto unset_primary_color;
|
|
|
|
g_object_get (style,
|
|
"background-set", &background_set,
|
|
"background", &background,
|
|
"foreground-set", &foreground_set,
|
|
"foreground", &foreground,
|
|
NULL);
|
|
|
|
if (!background_set || background == NULL || !gdk_rgba_parse (&rgba, background))
|
|
goto unset_primary_color;
|
|
|
|
if (background_set && background != NULL && gdk_rgba_parse (&rgba, background))
|
|
ide_page_set_primary_color_bg (IDE_PAGE (self), &rgba);
|
|
else
|
|
goto unset_primary_color;
|
|
|
|
if (foreground_set && foreground != NULL && gdk_rgba_parse (&rgba, foreground))
|
|
ide_page_set_primary_color_fg (IDE_PAGE (self), &rgba);
|
|
else
|
|
ide_page_set_primary_color_fg (IDE_PAGE (self), NULL);
|
|
|
|
return;
|
|
|
|
unset_primary_color:
|
|
ide_page_set_primary_color_bg (IDE_PAGE (self), NULL);
|
|
ide_page_set_primary_color_fg (IDE_PAGE (self), NULL);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page__buffer_notify_changed_on_volume (IdeEditorPage *self,
|
|
GParamSpec *pspec,
|
|
IdeBuffer *buffer)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
|
|
gtk_revealer_set_reveal_child (self->modified_revealer,
|
|
ide_buffer_get_changed_on_volume (buffer));
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_hide_reload_bar (IdeEditorPage *self,
|
|
GtkWidget *button)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
gtk_revealer_set_reveal_child (self->modified_revealer, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
ide_editor_page_source_view_event (IdeEditorPage *self,
|
|
GdkEvent *event,
|
|
IdeSourceView *source_view)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (event != NULL);
|
|
g_assert (IDE_IS_SOURCE_VIEW (source_view) || GTK_SOURCE_IS_MAP (source_view));
|
|
|
|
if (self->auto_hide_map)
|
|
{
|
|
ide_editor_page_update_reveal_timer (self);
|
|
gtk_revealer_set_reveal_child (self->map_revealer, TRUE);
|
|
}
|
|
|
|
return GDK_EVENT_PROPAGATE;
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_bind_signals (IdeEditorPage *self,
|
|
IdeBuffer *buffer,
|
|
DzlSignalGroup *buffer_signals)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
g_assert (DZL_IS_SIGNAL_GROUP (buffer_signals));
|
|
|
|
ide_editor_page_buffer_modified_changed (self, buffer);
|
|
ide_editor_page_buffer_notify_language (self, NULL, buffer);
|
|
ide_editor_page_buffer_notify_style_scheme (self, NULL, buffer);
|
|
ide_editor_page_buffer_notify_failed (self, NULL, buffer);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_set_buffer (IdeEditorPage *self,
|
|
IdeBuffer *buffer)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (!buffer || IDE_IS_BUFFER (buffer));
|
|
|
|
if (g_set_object (&self->buffer, buffer))
|
|
{
|
|
dzl_signal_group_set_target (self->buffer_signals, buffer);
|
|
dzl_binding_group_set_source (self->buffer_bindings, buffer);
|
|
gtk_text_view_set_buffer (GTK_TEXT_VIEW (self->source_view),
|
|
GTK_TEXT_BUFFER (buffer));
|
|
gtk_drag_dest_unset (GTK_WIDGET (self->source_view));
|
|
ide_editor_page_update_icon (self);
|
|
}
|
|
}
|
|
|
|
static IdePage *
|
|
ide_editor_page_create_split (IdePage *view)
|
|
{
|
|
IdeEditorPage *self = (IdeEditorPage *)view;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
return g_object_new (IDE_TYPE_EDITOR_PAGE,
|
|
"buffer", self->buffer,
|
|
"visible", TRUE,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_notify_frame_set (IdeExtensionSetAdapter *set,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *exten,
|
|
gpointer user_data)
|
|
{
|
|
IdeFrame *frame = user_data;
|
|
IdeEditorPageAddin *addin = (IdeEditorPageAddin *)exten;
|
|
|
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
|
|
g_assert (plugin_info != NULL);
|
|
g_assert (IDE_IS_EDITOR_PAGE_ADDIN (addin));
|
|
g_assert (IDE_IS_FRAME (frame));
|
|
|
|
ide_editor_page_addin_frame_set (addin, frame);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_addin_added (IdeExtensionSetAdapter *set,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *exten,
|
|
gpointer user_data)
|
|
{
|
|
IdeEditorPage *self = user_data;
|
|
IdeEditorPageAddin *addin = (IdeEditorPageAddin *)exten;
|
|
|
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
|
|
g_assert (plugin_info != NULL);
|
|
g_assert (IDE_IS_EDITOR_PAGE_ADDIN (addin));
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
ide_editor_page_addin_load (addin, self);
|
|
|
|
/*
|
|
* Notify of the current frame, but refetch the frame pointer just
|
|
* to be sure we aren't re-using an old pointer in case we're racing
|
|
* with a finalizer.
|
|
*/
|
|
if (self->last_frame_ptr != NULL)
|
|
{
|
|
GtkWidget *frame = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_FRAME);
|
|
if (frame != NULL)
|
|
ide_editor_page_addin_frame_set (addin, IDE_FRAME (frame));
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_addin_removed (IdeExtensionSetAdapter *set,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *exten,
|
|
gpointer user_data)
|
|
{
|
|
IdeEditorPage *self = user_data;
|
|
IdeEditorPageAddin *addin = (IdeEditorPageAddin *)exten;
|
|
|
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
|
|
g_assert (plugin_info != NULL);
|
|
g_assert (IDE_IS_EDITOR_PAGE_ADDIN (addin));
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
ide_editor_page_addin_unload (addin, self);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_hierarchy_changed (GtkWidget *widget,
|
|
GtkWidget *old_toplevel)
|
|
{
|
|
IdeEditorPage *self = (IdeEditorPage *)widget;
|
|
IdeFrame *frame;
|
|
IdeContext *context;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (!old_toplevel || GTK_IS_WIDGET (old_toplevel));
|
|
|
|
/*
|
|
* We don't need to chain up today, but if IdePage starts
|
|
* using the hierarchy_changed signal to handle anything, we want
|
|
* to make sure we aren't surprised.
|
|
*/
|
|
if (GTK_WIDGET_CLASS (ide_editor_page_parent_class)->hierarchy_changed)
|
|
GTK_WIDGET_CLASS (ide_editor_page_parent_class)->hierarchy_changed (widget, old_toplevel);
|
|
|
|
context = ide_widget_get_context (GTK_WIDGET (self));
|
|
frame = (IdeFrame *)gtk_widget_get_ancestor (widget, IDE_TYPE_FRAME);
|
|
|
|
/*
|
|
* We don't want to create addins until the widget has been placed into
|
|
* the widget tree. That way the addins can get access to the context
|
|
* or other useful details.
|
|
*/
|
|
if (context != NULL && self->addins == NULL)
|
|
{
|
|
self->addins = ide_extension_set_adapter_new (IDE_OBJECT (context),
|
|
peas_engine_get_default (),
|
|
IDE_TYPE_EDITOR_PAGE_ADDIN,
|
|
"Editor-Page-Languages",
|
|
ide_editor_page_get_language_id (self));
|
|
|
|
g_signal_connect (self->addins,
|
|
"extension-added",
|
|
G_CALLBACK (ide_editor_page_addin_added),
|
|
self);
|
|
|
|
g_signal_connect (self->addins,
|
|
"extension-removed",
|
|
G_CALLBACK (ide_editor_page_addin_removed),
|
|
self);
|
|
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
ide_editor_page_addin_added,
|
|
self);
|
|
}
|
|
|
|
/*
|
|
* If we have been moved into a new frame, notify the addins of the
|
|
* hierarchy change.
|
|
*/
|
|
if (frame != NULL && frame != self->last_frame_ptr && self->addins != NULL)
|
|
{
|
|
self->last_frame_ptr = frame;
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
ide_editor_page_notify_frame_set,
|
|
frame);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_update_map (IdeEditorPage *self)
|
|
{
|
|
GtkWidget *parent;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
parent = gtk_widget_get_parent (GTK_WIDGET (self->map));
|
|
|
|
g_object_ref (self->map);
|
|
|
|
gtk_container_remove (GTK_CONTAINER (parent), GTK_WIDGET (self->map));
|
|
|
|
if (self->auto_hide_map)
|
|
gtk_container_add (GTK_CONTAINER (self->map_revealer), GTK_WIDGET (self->map));
|
|
else
|
|
gtk_container_add (GTK_CONTAINER (self->scroller_box), GTK_WIDGET (self->map));
|
|
|
|
gtk_widget_set_visible (GTK_WIDGET (self->map_revealer), self->show_map && self->auto_hide_map);
|
|
gtk_widget_set_visible (GTK_WIDGET (self->map), self->show_map);
|
|
gtk_revealer_set_reveal_child (self->map_revealer, self->show_map);
|
|
|
|
ide_editor_page_update_reveal_timer (self);
|
|
|
|
g_object_unref (self->map);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_buffer_notify_file (IdeEditorPage *self,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
|
|
}
|
|
|
|
static void
|
|
search_revealer_notify_reveal_child (IdeEditorPage *self,
|
|
GParamSpec *pspec,
|
|
GtkRevealer *revealer)
|
|
{
|
|
IdeCompletion *completion;
|
|
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
g_return_if_fail (pspec != NULL);
|
|
g_return_if_fail (GTK_IS_REVEALER (revealer));
|
|
|
|
completion = ide_source_view_get_completion (IDE_SOURCE_VIEW (self->source_view));
|
|
|
|
if (!gtk_revealer_get_reveal_child (revealer))
|
|
{
|
|
ide_editor_search_end_interactive (self->search);
|
|
|
|
/* Restore completion that we blocked below. */
|
|
ide_completion_unblock_interactive (completion);
|
|
}
|
|
else
|
|
{
|
|
ide_editor_search_begin_interactive (self->search);
|
|
|
|
/*
|
|
* Block the completion while the search bar is set. It only
|
|
* slows things down like replace functionality. We'll
|
|
* restore it above when we clear state.
|
|
*/
|
|
ide_completion_block_interactive (completion);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_focus_location (IdeEditorPage *self,
|
|
IdeLocation *location,
|
|
IdeSourceView *source_view)
|
|
{
|
|
GtkWidget *editor;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (location != NULL);
|
|
g_assert (IDE_IS_SOURCE_VIEW (source_view));
|
|
|
|
editor = gtk_widget_get_ancestor (GTK_WIDGET (self), IDE_TYPE_EDITOR_SURFACE);
|
|
ide_editor_surface_focus_location (IDE_EDITOR_SURFACE (editor), location);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_clear_search (IdeEditorPage *self,
|
|
IdeSourceView *view)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_EDITOR_SEARCH (self->search));
|
|
g_assert (IDE_IS_SOURCE_VIEW (view));
|
|
|
|
ide_editor_search_set_search_text (self->search, NULL);
|
|
ide_editor_search_set_visible (self->search, FALSE);
|
|
gtk_revealer_set_reveal_child (self->search_revealer, FALSE);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_move_search (IdeEditorPage *self,
|
|
GtkDirectionType dir,
|
|
gboolean extend_selection,
|
|
gboolean select_match,
|
|
gboolean exclusive,
|
|
gboolean apply_count,
|
|
gboolean at_word_boundaries,
|
|
IdeSourceView *view)
|
|
{
|
|
IdeEditorSearchSelect sel = 0;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_EDITOR_SEARCH (self->search));
|
|
g_assert (IDE_IS_SOURCE_VIEW (view));
|
|
|
|
if (extend_selection && select_match)
|
|
sel = IDE_EDITOR_SEARCH_SELECT_WITH_RESULT;
|
|
else if (extend_selection)
|
|
sel = IDE_EDITOR_SEARCH_SELECT_TO_RESULT;
|
|
|
|
ide_editor_search_set_extend_selection (self->search, sel);
|
|
ide_editor_search_set_visible (self->search, TRUE);
|
|
|
|
if (apply_count)
|
|
{
|
|
ide_editor_search_set_repeat (self->search, ide_source_view_get_count (view));
|
|
g_signal_emit_by_name (view, "clear-count");
|
|
}
|
|
|
|
ide_editor_search_set_at_word_boundaries (self->search, at_word_boundaries);
|
|
|
|
switch (dir)
|
|
{
|
|
case GTK_DIR_DOWN:
|
|
case GTK_DIR_RIGHT:
|
|
ide_editor_search_set_reverse (self->search, FALSE);
|
|
ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_NEXT);
|
|
break;
|
|
|
|
case GTK_DIR_TAB_FORWARD:
|
|
if (extend_selection)
|
|
ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_FORWARD);
|
|
else
|
|
ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_NEXT);
|
|
break;
|
|
|
|
case GTK_DIR_UP:
|
|
case GTK_DIR_LEFT:
|
|
ide_editor_search_set_reverse (self->search, TRUE);
|
|
ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_NEXT);
|
|
break;
|
|
|
|
case GTK_DIR_TAB_BACKWARD:
|
|
if (extend_selection)
|
|
ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_BACKWARD);
|
|
else
|
|
ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_PREVIOUS);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_set_search_text (IdeEditorPage *self,
|
|
const gchar *search_text,
|
|
gboolean from_selection,
|
|
IdeSourceView *view)
|
|
{
|
|
g_autofree gchar *freeme = NULL;
|
|
GtkTextIter begin;
|
|
GtkTextIter end;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
g_assert (IDE_IS_EDITOR_SEARCH (self->search));
|
|
g_assert (search_text != NULL || from_selection);
|
|
g_assert (IDE_IS_SOURCE_VIEW (view));
|
|
|
|
/* Use interactive mode if we're copying from the clipboard, because that
|
|
* is usually going to be followed by focusing the search box and we want
|
|
* to make sure the occurrance count is updated.
|
|
*/
|
|
|
|
if (from_selection)
|
|
ide_editor_search_begin_interactive (self->search);
|
|
|
|
if (from_selection)
|
|
{
|
|
if (gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self->buffer), &begin, &end))
|
|
search_text = freeme = gtk_text_iter_get_slice (&begin, &end);
|
|
}
|
|
|
|
ide_editor_search_set_search_text (self->search, search_text);
|
|
ide_editor_search_set_regex_enabled (self->search, FALSE);
|
|
|
|
if (from_selection)
|
|
ide_editor_search_end_interactive (self->search);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_constructed (GObject *object)
|
|
{
|
|
IdeEditorPage *self = (IdeEditorPage *)object;
|
|
GtkSourceGutterRenderer *renderer;
|
|
GtkSourceGutter *gutter;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
G_OBJECT_CLASS (ide_editor_page_parent_class)->constructed (object);
|
|
|
|
gutter = gtk_source_view_get_gutter (GTK_SOURCE_VIEW (self->map), GTK_TEXT_WINDOW_LEFT);
|
|
renderer = g_object_new (IDE_TYPE_LINE_CHANGE_GUTTER_RENDERER,
|
|
"size", 1,
|
|
"visible", TRUE,
|
|
NULL);
|
|
gtk_source_gutter_insert (gutter, renderer, 0);
|
|
|
|
_ide_editor_page_init_actions (self);
|
|
_ide_editor_page_init_shortcuts (self);
|
|
_ide_editor_page_init_settings (self);
|
|
|
|
g_signal_connect_swapped (self->source_view,
|
|
"focus-in-event",
|
|
G_CALLBACK (ide_editor_page_focus_in_event),
|
|
self);
|
|
|
|
g_signal_connect_swapped (self->source_view,
|
|
"motion-notify-event",
|
|
G_CALLBACK (ide_editor_page_source_view_event),
|
|
self);
|
|
|
|
g_signal_connect_swapped (self->source_view,
|
|
"scroll-event",
|
|
G_CALLBACK (ide_editor_page_source_view_event),
|
|
self);
|
|
|
|
g_signal_connect_swapped (self->source_view,
|
|
"focus-location",
|
|
G_CALLBACK (ide_editor_page_focus_location),
|
|
self);
|
|
|
|
g_signal_connect_swapped (self->source_view,
|
|
"set-search-text",
|
|
G_CALLBACK (ide_editor_page_set_search_text),
|
|
self);
|
|
|
|
g_signal_connect_swapped (self->source_view,
|
|
"clear-search",
|
|
G_CALLBACK (ide_editor_page_clear_search),
|
|
self);
|
|
|
|
g_signal_connect_swapped (self->source_view,
|
|
"move-search",
|
|
G_CALLBACK (ide_editor_page_move_search),
|
|
self);
|
|
|
|
g_signal_connect_swapped (self->map,
|
|
"motion-notify-event",
|
|
G_CALLBACK (ide_editor_page_source_view_event),
|
|
self);
|
|
|
|
|
|
|
|
/*
|
|
* We want to track when the search revealer is visible. We will discard
|
|
* the search context when the revealer is not visible so that we don't
|
|
* continue performing expensive buffer operations.
|
|
*/
|
|
g_signal_connect_swapped (self->search_revealer,
|
|
"notify::reveal-child",
|
|
G_CALLBACK (search_revealer_notify_reveal_child),
|
|
self);
|
|
|
|
self->search = ide_editor_search_new (GTK_SOURCE_VIEW (self->source_view));
|
|
ide_editor_search_bar_set_search (self->search_bar, self->search);
|
|
gtk_widget_insert_action_group (GTK_WIDGET (self), "editor-search",
|
|
G_ACTION_GROUP (self->search));
|
|
|
|
ide_editor_page_load_fonts (self);
|
|
ide_editor_page_update_map (self);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_destroy (GtkWidget *widget)
|
|
{
|
|
IdeEditorPage *self = (IdeEditorPage *)widget;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
/*
|
|
* WORKAROUND: We need to reset the drag dest to avoid warnings by Gtk
|
|
* reseting the target list for the source view.
|
|
*/
|
|
if (self->source_view != NULL)
|
|
gtk_drag_dest_set (GTK_WIDGET (self->source_view),
|
|
GTK_DEST_DEFAULT_ALL,
|
|
NULL, 0, GDK_ACTION_COPY);
|
|
|
|
dzl_clear_source (&self->toggle_map_source);
|
|
|
|
ide_clear_and_destroy_object (&self->addins);
|
|
|
|
gtk_widget_insert_action_group (widget, "editor-search", NULL);
|
|
gtk_widget_insert_action_group (widget, "editor-page", NULL);
|
|
|
|
g_cancellable_cancel (self->destroy_cancellable);
|
|
g_clear_object (&self->destroy_cancellable);
|
|
|
|
g_clear_object (&self->search);
|
|
g_clear_object (&self->editor_settings);
|
|
g_clear_object (&self->insight_settings);
|
|
|
|
g_clear_object (&self->buffer);
|
|
|
|
if (self->buffer_bindings != NULL)
|
|
{
|
|
dzl_binding_group_set_source (self->buffer_bindings, NULL);
|
|
g_clear_object (&self->buffer_bindings);
|
|
}
|
|
|
|
if (self->buffer_signals != NULL)
|
|
{
|
|
dzl_signal_group_set_target (self->buffer_signals, NULL);
|
|
g_clear_object (&self->buffer_signals);
|
|
}
|
|
|
|
GTK_WIDGET_CLASS (ide_editor_page_parent_class)->destroy (widget);
|
|
}
|
|
|
|
static GFile *
|
|
ide_editor_page_get_file_or_directory (IdePage *page)
|
|
{
|
|
GFile *ret = ide_editor_page_get_file (IDE_EDITOR_PAGE (page));
|
|
return ret ? g_object_ref (ret) : NULL;
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_finalize (GObject *object)
|
|
{
|
|
G_OBJECT_CLASS (ide_editor_page_parent_class)->finalize (object);
|
|
|
|
DZL_COUNTER_DEC (instances);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
IdeEditorPage *self = IDE_EDITOR_PAGE (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_AUTO_HIDE_MAP:
|
|
g_value_set_boolean (value, ide_editor_page_get_auto_hide_map (self));
|
|
break;
|
|
|
|
case PROP_BUFFER:
|
|
g_value_set_object (value, ide_editor_page_get_buffer (self));
|
|
break;
|
|
|
|
case PROP_BUFFER_FILE:
|
|
g_value_set_object (value, ide_buffer_get_file (self->buffer));
|
|
break;
|
|
case PROP_VIEW:
|
|
g_value_set_object (value, ide_editor_page_get_view (self));
|
|
break;
|
|
|
|
case PROP_SEARCH:
|
|
g_value_set_object (value, ide_editor_page_get_search (self));
|
|
break;
|
|
|
|
case PROP_SHOW_MAP:
|
|
g_value_set_boolean (value, ide_editor_page_get_show_map (self));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
IdeEditorPage *self = IDE_EDITOR_PAGE (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_AUTO_HIDE_MAP:
|
|
ide_editor_page_set_auto_hide_map (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_BUFFER:
|
|
ide_editor_page_set_buffer (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_SHOW_MAP:
|
|
ide_editor_page_set_show_map (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_class_init (IdeEditorPageClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
IdePageClass *page_class = IDE_PAGE_CLASS (klass);
|
|
|
|
object_class->finalize = ide_editor_page_finalize;
|
|
object_class->constructed = ide_editor_page_constructed;
|
|
object_class->get_property = ide_editor_page_get_property;
|
|
object_class->set_property = ide_editor_page_set_property;
|
|
|
|
widget_class->destroy = ide_editor_page_destroy;
|
|
widget_class->hierarchy_changed = ide_editor_page_hierarchy_changed;
|
|
|
|
page_class->create_split = ide_editor_page_create_split;
|
|
page_class->get_file_or_directory = ide_editor_page_get_file_or_directory;
|
|
|
|
properties [PROP_BUFFER] =
|
|
g_param_spec_object ("buffer",
|
|
"Buffer",
|
|
"The buffer for the view",
|
|
IDE_TYPE_BUFFER,
|
|
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/* It's really just there to get notify:: support for the buffer's file property
|
|
* but through the page, for the session addin.
|
|
*/
|
|
properties [PROP_BUFFER_FILE] =
|
|
g_param_spec_object ("buffer-file",
|
|
"Buffer file",
|
|
"The buffer file for the view's buffer",
|
|
G_TYPE_FILE,
|
|
(G_PARAM_STATIC_STRINGS | G_PARAM_READABLE));
|
|
|
|
properties [PROP_SEARCH] =
|
|
g_param_spec_object ("search",
|
|
"Search",
|
|
"An search helper for the document",
|
|
IDE_TYPE_EDITOR_SEARCH,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
properties [PROP_SHOW_MAP] =
|
|
g_param_spec_boolean ("show-map",
|
|
"Show Map",
|
|
"If the overview map should be shown",
|
|
FALSE,
|
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
|
|
|
properties [PROP_AUTO_HIDE_MAP] =
|
|
g_param_spec_boolean ("auto-hide-map",
|
|
"Auto Hide Map",
|
|
"If the overview map should be auto-hidden",
|
|
FALSE,
|
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
|
|
|
properties [PROP_VIEW] =
|
|
g_param_spec_object ("view",
|
|
"View",
|
|
"The view for editing the buffer",
|
|
IDE_TYPE_SOURCE_VIEW,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
|
|
|
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/libide-editor/ui/ide-editor-page.ui");
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, map);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, map_revealer);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, overlay);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, progress_bar);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, scroller);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, scroller_box);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, search_bar);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, search_revealer);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, modified_revealer);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, modified_cancel_button);
|
|
gtk_widget_class_bind_template_child (widget_class, IdeEditorPage, source_view);
|
|
gtk_widget_class_bind_template_callback (widget_class, ide_editor_page_notify_child_revealed);
|
|
gtk_widget_class_bind_template_callback (widget_class, ide_editor_page_stop_search);
|
|
|
|
g_type_ensure (IDE_TYPE_SOURCE_VIEW);
|
|
g_type_ensure (IDE_TYPE_EDITOR_SEARCH_BAR);
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_init (IdeEditorPage *self)
|
|
{
|
|
DZL_COUNTER_INC (instances);
|
|
|
|
gtk_widget_init_template (GTK_WIDGET (self));
|
|
|
|
ide_page_set_can_split (IDE_PAGE (self), TRUE);
|
|
ide_page_set_menu_id (IDE_PAGE (self), "ide-editor-page-document-menu");
|
|
|
|
self->destroy_cancellable = g_cancellable_new ();
|
|
|
|
/* Setup signals to monitor on the buffer. */
|
|
self->buffer_signals = dzl_signal_group_new (IDE_TYPE_BUFFER);
|
|
|
|
dzl_signal_group_connect_swapped (self->buffer_signals,
|
|
"loaded",
|
|
G_CALLBACK (ide_editor_page_buffer_loaded),
|
|
self);
|
|
|
|
dzl_signal_group_connect_swapped (self->buffer_signals,
|
|
"modified-changed",
|
|
G_CALLBACK (ide_editor_page_buffer_modified_changed),
|
|
self);
|
|
|
|
dzl_signal_group_connect_swapped (self->buffer_signals,
|
|
"notify::file",
|
|
G_CALLBACK (ide_editor_page_buffer_notify_file),
|
|
self);
|
|
|
|
dzl_signal_group_connect_swapped (self->buffer_signals,
|
|
"notify::failed",
|
|
G_CALLBACK (ide_editor_page_buffer_notify_failed),
|
|
self);
|
|
|
|
dzl_signal_group_connect_swapped (self->buffer_signals,
|
|
"notify::language",
|
|
G_CALLBACK (ide_editor_page_buffer_notify_language),
|
|
self);
|
|
|
|
dzl_signal_group_connect_swapped (self->buffer_signals,
|
|
"notify::style-scheme",
|
|
G_CALLBACK (ide_editor_page_buffer_notify_style_scheme),
|
|
self);
|
|
dzl_signal_group_connect_swapped (self->buffer_signals,
|
|
"notify::changed-on-volume",
|
|
G_CALLBACK (ide_editor_page__buffer_notify_changed_on_volume),
|
|
self);
|
|
|
|
g_signal_connect_swapped (self->buffer_signals,
|
|
"bind",
|
|
G_CALLBACK (ide_editor_page_bind_signals),
|
|
self);
|
|
|
|
g_signal_connect_object (self->modified_cancel_button,
|
|
"clicked",
|
|
G_CALLBACK (ide_editor_page_hide_reload_bar),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
|
|
/* Setup bindings for the buffer. */
|
|
self->buffer_bindings = dzl_binding_group_new ();
|
|
dzl_binding_group_bind (self->buffer_bindings, "title", self, "title", 0);
|
|
|
|
/* Load our custom font for the overview map. */
|
|
gtk_source_map_set_view (self->map, GTK_SOURCE_VIEW (self->source_view));
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_get_buffer:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Gets the underlying buffer for the view.
|
|
*
|
|
* Returns: (transfer none): An #IdeBuffer
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeBuffer *
|
|
ide_editor_page_get_buffer (IdeEditorPage *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
|
|
|
|
return self->buffer;
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_get_view:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Gets the #IdeSourceView that is part of the #IdeEditorPage.
|
|
*
|
|
* Returns: (transfer none): An #IdeSourceView
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeSourceView *
|
|
ide_editor_page_get_view (IdeEditorPage *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
|
|
|
|
return self->source_view;
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_get_language_id:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* This is a helper to get the language-id of the underlying buffer.
|
|
*
|
|
* Returns: (nullable): the language-id as a string, or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
const gchar *
|
|
ide_editor_page_get_language_id (IdeEditorPage *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
|
|
|
|
if (self->buffer != NULL)
|
|
{
|
|
GtkSourceLanguage *language;
|
|
|
|
language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->buffer));
|
|
|
|
if (language != NULL)
|
|
return gtk_source_language_get_id (language);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_scroll_to_line:
|
|
* @self: a #IdeEditorPage
|
|
* @line: the line to scroll to
|
|
*
|
|
* This is a helper to quickly jump to a given line without all the frills. It
|
|
* will also ensure focus on the editor view, so that refocusing the view
|
|
* afterwards does not cause the view to restore the cursor to the previous
|
|
* location.
|
|
*
|
|
* This will move the insert cursor.
|
|
*
|
|
* Lines start from 0.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_editor_page_scroll_to_line (IdeEditorPage *self,
|
|
guint line)
|
|
{
|
|
ide_editor_page_scroll_to_line_offset (self, line, 0);
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_scroll_to_line_offset:
|
|
* @self: a #IdeEditorPage
|
|
* @line: the line to scroll to
|
|
* @line_offset: the line offset
|
|
*
|
|
* Like ide_editor_page_scroll_to_line() but allows specifying the
|
|
* line offset (column) to place the cursor on.
|
|
*
|
|
* This will move the insert cursor.
|
|
*
|
|
* Lines and offsets start from 0.
|
|
*
|
|
* If @line_offset is zero, the first non-space character of @line will be
|
|
* used instead.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_editor_page_scroll_to_line_offset (IdeEditorPage *self,
|
|
guint line,
|
|
guint line_offset)
|
|
{
|
|
GtkTextIter iter;
|
|
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
g_return_if_fail (self->buffer != NULL);
|
|
g_return_if_fail (line <= G_MAXINT);
|
|
|
|
gtk_widget_grab_focus (GTK_WIDGET (self->source_view));
|
|
|
|
gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (self->buffer), &iter,
|
|
line, line_offset);
|
|
|
|
if (line_offset == 0)
|
|
{
|
|
while (!gtk_text_iter_ends_line (&iter) &&
|
|
g_unichar_isspace (gtk_text_iter_get_char (&iter)))
|
|
{
|
|
if (!gtk_text_iter_forward_char (&iter))
|
|
break;
|
|
}
|
|
}
|
|
|
|
gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self->buffer), &iter, &iter);
|
|
ide_source_view_scroll_to_insert (self->source_view);
|
|
}
|
|
|
|
gboolean
|
|
ide_editor_page_get_auto_hide_map (IdeEditorPage *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), FALSE);
|
|
|
|
return self->auto_hide_map;
|
|
}
|
|
|
|
static gboolean
|
|
ide_editor_page_auto_hide_cb (gpointer user_data)
|
|
{
|
|
IdeEditorPage *self = user_data;
|
|
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
self->toggle_map_source = 0;
|
|
gtk_revealer_set_reveal_child (self->map_revealer, FALSE);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
ide_editor_page_update_reveal_timer (IdeEditorPage *self)
|
|
{
|
|
g_assert (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
dzl_clear_source (&self->toggle_map_source);
|
|
|
|
if (self->auto_hide_map && gtk_revealer_get_reveal_child (self->map_revealer))
|
|
{
|
|
self->toggle_map_source =
|
|
gdk_threads_add_timeout_seconds_full (G_PRIORITY_LOW,
|
|
AUTO_HIDE_TIMEOUT_SECONDS,
|
|
ide_editor_page_auto_hide_cb,
|
|
g_object_ref (self),
|
|
g_object_unref);
|
|
}
|
|
}
|
|
|
|
void
|
|
ide_editor_page_set_auto_hide_map (IdeEditorPage *self,
|
|
gboolean auto_hide_map)
|
|
{
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
auto_hide_map = !!auto_hide_map;
|
|
|
|
if (auto_hide_map != self->auto_hide_map)
|
|
{
|
|
self->auto_hide_map = auto_hide_map;
|
|
ide_editor_page_update_map (self);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_AUTO_HIDE_MAP]);
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
ide_editor_page_get_show_map (IdeEditorPage *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), FALSE);
|
|
|
|
return self->show_map;
|
|
}
|
|
|
|
void
|
|
ide_editor_page_set_show_map (IdeEditorPage *self,
|
|
gboolean show_map)
|
|
{
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
show_map = !!show_map;
|
|
|
|
if (show_map != self->show_map)
|
|
{
|
|
self->show_map = show_map;
|
|
g_object_set (self->scroller,
|
|
"vscrollbar-policy", show_map ? GTK_POLICY_EXTERNAL : GTK_POLICY_AUTOMATIC,
|
|
NULL);
|
|
ide_editor_page_update_map (self);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_AUTO_HIDE_MAP]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_set_language:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* This is a convenience function to set the language on the underlying
|
|
* #IdeBuffer text buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_editor_page_set_language (IdeEditorPage *self,
|
|
GtkSourceLanguage *language)
|
|
{
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
g_return_if_fail (!language || GTK_SOURCE_IS_LANGUAGE (language));
|
|
|
|
gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (self->buffer), language);
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_get_language:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Gets the #GtkSourceLanguage that is used by the underlying buffer.
|
|
*
|
|
* Returns: (transfer none) (nullable): a #GtkSourceLanguage or %NULL.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
GtkSourceLanguage *
|
|
ide_editor_page_get_language (IdeEditorPage *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
|
|
|
|
return gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self->buffer));
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_move_next_error:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Moves to the next error, if any.
|
|
*
|
|
* If there is no error, the insertion cursor is not moved.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_editor_page_move_next_error (IdeEditorPage *self)
|
|
{
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
g_signal_emit_by_name (self->source_view, "move-error", GTK_DIR_DOWN);
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_move_previous_error:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Moves the insertion cursor to the previous error.
|
|
*
|
|
* If there is no error, the insertion cursor is not moved.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_editor_page_move_previous_error (IdeEditorPage *self)
|
|
{
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
|
|
g_signal_emit_by_name (self->source_view, "move-error", GTK_DIR_UP);
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_move_next_search_result:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Moves the insertion cursor to the next search result.
|
|
*
|
|
* If there is no search result, the insertion cursor is not moved.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_editor_page_move_next_search_result (IdeEditorPage *self)
|
|
{
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
g_return_if_fail (self->destroy_cancellable != NULL);
|
|
g_return_if_fail (self->buffer != NULL);
|
|
|
|
ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_NEXT);
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_move_previous_search_result:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Moves the insertion cursor to the previous search result.
|
|
*
|
|
* If there is no search result, the insertion cursor is not moved.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_editor_page_move_previous_search_result (IdeEditorPage *self)
|
|
{
|
|
g_return_if_fail (IDE_IS_EDITOR_PAGE (self));
|
|
g_return_if_fail (self->destroy_cancellable != NULL);
|
|
g_return_if_fail (self->buffer != NULL);
|
|
|
|
ide_editor_search_move (self->search, IDE_EDITOR_SEARCH_PREVIOUS);
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_get_search:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Gets the #IdeEditorSearch used to search within the document.
|
|
*
|
|
* Returns: (transfer none): An #IdeEditorSearch
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeEditorSearch *
|
|
ide_editor_page_get_search (IdeEditorPage *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
|
|
|
|
return self->search;
|
|
}
|
|
|
|
/**
|
|
* ide_editor_page_get_file:
|
|
* @self: a #IdeEditorPage
|
|
*
|
|
* Gets the #GFile that represents the current file. This may be a temporary
|
|
* file, but a #GFile will still be used for the temporary file.
|
|
*
|
|
* Returns: (transfer none): a #GFile for the current buffer
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
GFile *
|
|
ide_editor_page_get_file (IdeEditorPage *self)
|
|
{
|
|
IdeBuffer *buffer;
|
|
|
|
g_return_val_if_fail (IDE_IS_EDITOR_PAGE (self), NULL);
|
|
|
|
if ((buffer = ide_editor_page_get_buffer (self)))
|
|
return ide_buffer_get_file (buffer);
|
|
|
|
return NULL;
|
|
}
|