4193 lines
129 KiB
C
4193 lines
129 KiB
C
/* ide-buffer.c
|
|
*
|
|
* Copyright 2018-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-buffer"
|
|
|
|
#include "config.h"
|
|
|
|
#include <dazzle.h>
|
|
#include <glib/gi18n.h>
|
|
#include <libide-io.h>
|
|
#include <libide-plugins.h>
|
|
#include <libide-threading.h>
|
|
|
|
#include "ide-buffer.h"
|
|
#include "ide-buffer-addin.h"
|
|
#include "ide-buffer-addin-private.h"
|
|
#include "ide-buffer-manager.h"
|
|
#include "ide-buffer-private.h"
|
|
#include "ide-code-action-provider.h"
|
|
#include "ide-code-enums.h"
|
|
#include "ide-diagnostic.h"
|
|
#include "ide-diagnostics.h"
|
|
#include "ide-file-settings.h"
|
|
#include "ide-formatter.h"
|
|
#include "ide-formatter-options.h"
|
|
#include "ide-gfile-private.h"
|
|
#include "ide-highlight-engine.h"
|
|
#include "ide-location.h"
|
|
#include "ide-range.h"
|
|
#include "ide-source-iter.h"
|
|
#include "ide-source-style-scheme.h"
|
|
#include "ide-symbol-resolver.h"
|
|
#include "ide-unsaved-files.h"
|
|
|
|
#define SETTLING_DELAY_MSEC 333
|
|
|
|
#define TAG_ERROR "diagnostician::error"
|
|
#define TAG_WARNING "diagnostician::warning"
|
|
#define TAG_DEPRECATED "diagnostician::deprecated"
|
|
#define TAG_UNUSED "diagnostician::unused"
|
|
#define TAG_NOTE "diagnostician::note"
|
|
#define TAG_SNIPPET_TAB_STOP "snippet::tab-stop"
|
|
#define TAG_DEFINITION "action::hover-definition"
|
|
#define TAG_CURRENT_BKPT "debugger::current-breakpoint"
|
|
|
|
#define DEPRECATED_COLOR "#babdb6"
|
|
#define UNUSED_COLOR "#c17d11"
|
|
#define ERROR_COLOR "#ff0000"
|
|
#define NOTE_COLOR "#708090"
|
|
#define WARNING_COLOR "#fcaf3e"
|
|
#define CURRENT_BKPT_FG "#fffffe"
|
|
#define CURRENT_BKPT_BG "#fcaf3e"
|
|
|
|
struct _IdeBuffer
|
|
{
|
|
GtkSourceBuffer parent_instance;
|
|
|
|
/* Owned references */
|
|
IdeExtensionSetAdapter *addins;
|
|
IdeExtensionSetAdapter *symbol_resolvers;
|
|
IdeExtensionAdapter *rename_provider;
|
|
IdeExtensionAdapter *formatter;
|
|
IdeExtensionAdapter *code_action_provider;
|
|
IdeBufferManager *buffer_manager;
|
|
IdeBufferChangeMonitor *change_monitor;
|
|
GBytes *content;
|
|
IdeDiagnostics *diagnostics;
|
|
GError *failure;
|
|
IdeFileSettings *file_settings;
|
|
IdeHighlightEngine *highlight_engine;
|
|
GtkSourceFile *source_file;
|
|
GFile *readlink_file;
|
|
|
|
IdeTask *in_flight_symbol_at_location;
|
|
gint in_flight_symbol_at_location_pos;
|
|
|
|
/* Scalars */
|
|
guint change_count;
|
|
guint settling_source;
|
|
gint hold;
|
|
|
|
/* Bit-fields */
|
|
IdeBufferState state : 3;
|
|
guint can_restore_cursor : 1;
|
|
guint is_temporary : 1;
|
|
guint enable_addins : 1;
|
|
guint changed_on_volume : 1;
|
|
guint read_only : 1;
|
|
guint highlight_diagnostics : 1;
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
IdeNotification *notif;
|
|
GFile *file;
|
|
guint highlight_syntax : 1;
|
|
} LoadState;
|
|
|
|
typedef struct
|
|
{
|
|
GFile *file;
|
|
IdeNotification *notif;
|
|
GtkSourceFile *source_file;
|
|
} SaveState;
|
|
|
|
typedef struct
|
|
{
|
|
GPtrArray *resolvers;
|
|
IdeLocation *location;
|
|
IdeSymbol *symbol;
|
|
} LookUpSymbolData;
|
|
|
|
G_DEFINE_FINAL_TYPE (IdeBuffer, ide_buffer, GTK_SOURCE_TYPE_BUFFER)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_BUFFER_MANAGER,
|
|
PROP_CHANGE_MONITOR,
|
|
PROP_CHANGED_ON_VOLUME,
|
|
PROP_ENABLE_ADDINS,
|
|
PROP_DIAGNOSTICS,
|
|
PROP_FAILED,
|
|
PROP_FILE,
|
|
PROP_FILE_SETTINGS,
|
|
PROP_HAS_DIAGNOSTICS,
|
|
PROP_HAS_SYMBOL_RESOLVERS,
|
|
PROP_HIGHLIGHT_DIAGNOSTICS,
|
|
PROP_IS_TEMPORARY,
|
|
PROP_LANGUAGE_ID,
|
|
PROP_READ_ONLY,
|
|
PROP_STATE,
|
|
PROP_STYLE_SCHEME_NAME,
|
|
PROP_TITLE,
|
|
N_PROPS
|
|
};
|
|
|
|
enum {
|
|
CHANGE_SETTLED,
|
|
CURSOR_MOVED,
|
|
LINE_FLAGS_CHANGED,
|
|
LOADED,
|
|
REQUEST_SCROLL_TO_INSERT,
|
|
N_SIGNALS
|
|
};
|
|
|
|
static GParamSpec *properties [N_PROPS];
|
|
static guint signals [N_SIGNALS];
|
|
|
|
static void lookup_symbol_data_free (LookUpSymbolData *data);
|
|
static void apply_style (GtkTextTag *tag,
|
|
const gchar *first_property,
|
|
...);
|
|
static void load_state_free (LoadState *state);
|
|
static void save_state_free (SaveState *state);
|
|
static void ide_buffer_save_file_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data);
|
|
static void ide_buffer_load_file_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data);
|
|
static void ide_buffer_progress_cb (goffset current_num_bytes,
|
|
goffset total_num_bytes,
|
|
gpointer user_data);
|
|
static void ide_buffer_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
static void ide_buffer_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void ide_buffer_constructed (GObject *object);
|
|
static void ide_buffer_dispose (GObject *object);
|
|
static void ide_buffer_notify_language (IdeBuffer *self,
|
|
GParamSpec *pspec,
|
|
gpointer user_data);
|
|
static void ide_buffer_notify_style_scheme (IdeBuffer *self,
|
|
GParamSpec *pspec,
|
|
gpointer unused);
|
|
static void ide_buffer_reload_file_settings (IdeBuffer *self);
|
|
static void ide_buffer_set_file_settings (IdeBuffer *self,
|
|
IdeFileSettings *file_settings);
|
|
static void ide_buffer_emit_cursor_moved (IdeBuffer *self);
|
|
static void ide_buffer_changed (GtkTextBuffer *buffer);
|
|
static void ide_buffer_delete_range (GtkTextBuffer *buffer,
|
|
GtkTextIter *start,
|
|
GtkTextIter *end);
|
|
static void ide_buffer_insert_text (GtkTextBuffer *buffer,
|
|
GtkTextIter *location,
|
|
const gchar *text,
|
|
gint len);
|
|
static void ide_buffer_mark_set (GtkTextBuffer *buffer,
|
|
const GtkTextIter *iter,
|
|
GtkTextMark *mark);
|
|
static void ide_buffer_delay_settling (IdeBuffer *self);
|
|
static gboolean ide_buffer_settled_cb (gpointer user_data);
|
|
static void ide_buffer_apply_diagnostics (IdeBuffer *self);
|
|
static void ide_buffer_clear_diagnostics (IdeBuffer *self);
|
|
static void ide_buffer_apply_diagnostic (IdeBuffer *self,
|
|
IdeDiagnostic *diagnostics);
|
|
static void ide_buffer_init_tags (IdeBuffer *self);
|
|
static void ide_buffer_on_tag_added (IdeBuffer *self,
|
|
GtkTextTag *tag,
|
|
GtkTextTagTable *table);
|
|
static void ide_buffer_get_symbol_resolvers_cb (IdeExtensionSetAdapter *set,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *exten,
|
|
gpointer user_data);
|
|
static void ide_buffer_symbol_resolver_removed (IdeExtensionSetAdapter *adapter,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *extension,
|
|
gpointer user_data);
|
|
static void ide_buffer_symbol_resolver_added (IdeExtensionSetAdapter *adapter,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *extension,
|
|
gpointer user_data);
|
|
static gboolean ide_buffer_can_do_newline_hack (IdeBuffer *self,
|
|
guint len);
|
|
static void ide_buffer_guess_language (IdeBuffer *self);
|
|
static void ide_buffer_real_loaded (IdeBuffer *self);
|
|
static void settle_async (IdeBuffer *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data);
|
|
static gboolean settle_finish (IdeBuffer *self,
|
|
GAsyncResult *result,
|
|
GError **error);
|
|
|
|
static void
|
|
load_state_free (LoadState *state)
|
|
{
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (state != NULL);
|
|
|
|
g_clear_object (&state->notif);
|
|
g_clear_object (&state->file);
|
|
g_slice_free (LoadState, state);
|
|
}
|
|
|
|
static void
|
|
save_state_free (SaveState *state)
|
|
{
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (state != NULL);
|
|
|
|
g_clear_object (&state->notif);
|
|
g_clear_object (&state->file);
|
|
g_clear_object (&state->source_file);
|
|
g_slice_free (SaveState, state);
|
|
}
|
|
|
|
static void
|
|
lookup_symbol_data_free (LookUpSymbolData *data)
|
|
{
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
|
|
g_clear_pointer (&data->resolvers, g_ptr_array_unref);
|
|
g_clear_object (&data->location);
|
|
g_clear_object (&data->symbol);
|
|
g_slice_free (LookUpSymbolData, data);
|
|
}
|
|
|
|
IdeBuffer *
|
|
_ide_buffer_new (IdeBufferManager *buffer_manager,
|
|
GFile *file,
|
|
gboolean enable_addins,
|
|
gboolean is_temporary)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER_MANAGER (buffer_manager), NULL);
|
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
|
|
|
return g_object_new (IDE_TYPE_BUFFER,
|
|
"buffer-manager", buffer_manager,
|
|
"file", file,
|
|
"enable-addins", enable_addins,
|
|
"is-temporary", is_temporary,
|
|
"implicit-trailing-newline", FALSE,
|
|
NULL);
|
|
}
|
|
|
|
void
|
|
_ide_buffer_set_file (IdeBuffer *self,
|
|
GFile *file)
|
|
{
|
|
GFile *location;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (G_IS_FILE (file));
|
|
|
|
location = gtk_source_file_get_location (self->source_file);
|
|
|
|
if (location == NULL || !g_file_equal (file, location))
|
|
{
|
|
gtk_source_file_set_location (self->source_file, file);
|
|
g_clear_object (&self->readlink_file);
|
|
self->readlink_file = _ide_g_file_readlink (file);
|
|
ide_buffer_reload_file_settings (self);
|
|
|
|
if (self->addins != NULL && self->enable_addins)
|
|
{
|
|
IdeBufferFileLoad closure = { self, file };
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
_ide_buffer_addin_file_loaded_cb,
|
|
&closure);
|
|
}
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_buffer_set_state (IdeBuffer *self,
|
|
IdeBufferState state)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (state == IDE_BUFFER_STATE_READY ||
|
|
state == IDE_BUFFER_STATE_LOADING ||
|
|
state == IDE_BUFFER_STATE_SAVING ||
|
|
state == IDE_BUFFER_STATE_FAILED);
|
|
|
|
if (self->state != state)
|
|
{
|
|
self->state = state;
|
|
if (self->state != IDE_BUFFER_STATE_FAILED)
|
|
g_clear_pointer (&self->failure, g_error_free);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_STATE]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_buffer_update_implicit_newline (IdeBuffer *self)
|
|
{
|
|
IdeFileSettings *file_settings;
|
|
gboolean was_implicit;
|
|
gboolean now_implicit;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (!ide_buffer_get_loading (self));
|
|
|
|
if (!(file_settings = ide_buffer_get_file_settings (self)))
|
|
IDE_EXIT;
|
|
|
|
/* If the new file-settings object does not match our
|
|
* current setting for trailing newlines, then we need
|
|
* to adjust that immediately to ensure our buffer contains
|
|
* the contents as expected.
|
|
*
|
|
* Otherwise, buffer change monitors will not line up with
|
|
* the the expectation on storage.
|
|
*/
|
|
was_implicit = gtk_source_buffer_get_implicit_trailing_newline (GTK_SOURCE_BUFFER (self));
|
|
now_implicit = ide_file_settings_get_insert_trailing_newline (file_settings);
|
|
|
|
if (was_implicit != now_implicit)
|
|
{
|
|
GtkTextIter iter;
|
|
|
|
gtk_source_buffer_set_implicit_trailing_newline (GTK_SOURCE_BUFFER (self), now_implicit);
|
|
|
|
if (now_implicit)
|
|
{
|
|
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (self), &iter);
|
|
|
|
if (gtk_text_iter_starts_line (&iter))
|
|
{
|
|
GtkTextIter begin = iter;
|
|
|
|
gtk_text_iter_backward_char (&begin);
|
|
|
|
if (!gtk_text_iter_equal (&begin, &iter))
|
|
{
|
|
gboolean modified = gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (self));
|
|
|
|
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (self));
|
|
gtk_text_buffer_delete (GTK_TEXT_BUFFER (self), &begin, &iter);
|
|
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (self));
|
|
|
|
if (!modified)
|
|
gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (self), modified);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gboolean modified = gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (self));
|
|
|
|
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (self), &iter);
|
|
|
|
gtk_text_buffer_begin_user_action (GTK_TEXT_BUFFER (self));
|
|
gtk_text_buffer_insert (GTK_TEXT_BUFFER (self), &iter, "\n", 1);
|
|
gtk_text_buffer_end_user_action (GTK_TEXT_BUFFER (self));
|
|
|
|
if (!modified)
|
|
gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (self), modified);
|
|
}
|
|
}
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_real_loaded (IdeBuffer *self)
|
|
{
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
ide_buffer_update_implicit_newline (self);
|
|
|
|
if (self->buffer_manager != NULL)
|
|
_ide_buffer_manager_buffer_loaded (self->buffer_manager, self);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_notify_language (IdeBuffer *self,
|
|
GParamSpec *pspec,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *lang_id;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
ide_buffer_reload_file_settings (self);
|
|
|
|
lang_id = ide_buffer_get_language_id (self);
|
|
|
|
if (self->addins != NULL && self->enable_addins)
|
|
{
|
|
IdeBufferLanguageSet state = { self, lang_id };
|
|
|
|
ide_extension_set_adapter_set_value (self->addins, state.language_id);
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
_ide_buffer_addin_language_set_cb,
|
|
&state);
|
|
}
|
|
|
|
if (self->symbol_resolvers)
|
|
ide_extension_set_adapter_set_value (self->symbol_resolvers, lang_id);
|
|
|
|
if (self->rename_provider)
|
|
ide_extension_adapter_set_value (self->rename_provider, lang_id);
|
|
|
|
if (self->formatter)
|
|
ide_extension_adapter_set_value (self->formatter, lang_id);
|
|
|
|
if (self->code_action_provider)
|
|
ide_extension_adapter_set_value (self->code_action_provider, lang_id);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_constructed (GObject *object)
|
|
{
|
|
IdeBuffer *self = (IdeBuffer *)object;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
G_OBJECT_CLASS (ide_buffer_parent_class)->constructed (object);
|
|
|
|
ide_buffer_init_tags (self);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_dispose (GObject *object)
|
|
{
|
|
IdeBuffer *self = (IdeBuffer *)object;
|
|
IdeObjectBox *box;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
|
|
g_clear_handle_id (&self->settling_source, g_source_remove);
|
|
|
|
/* Remove ourselves from the object-tree if necessary */
|
|
if ((box = ide_object_box_from_object (object)) &&
|
|
!ide_object_in_destruction (IDE_OBJECT (box)))
|
|
ide_object_destroy (IDE_OBJECT (box));
|
|
|
|
ide_clear_and_destroy_object (&self->addins);
|
|
ide_clear_and_destroy_object (&self->rename_provider);
|
|
ide_clear_and_destroy_object (&self->symbol_resolvers);
|
|
ide_clear_and_destroy_object (&self->formatter);
|
|
ide_clear_and_destroy_object (&self->code_action_provider);
|
|
ide_clear_and_destroy_object (&self->highlight_engine);
|
|
g_clear_object (&self->buffer_manager);
|
|
ide_clear_and_destroy_object (&self->change_monitor);
|
|
g_clear_pointer (&self->content, g_bytes_unref);
|
|
g_clear_object (&self->diagnostics);
|
|
ide_clear_and_destroy_object (&self->file_settings);
|
|
|
|
G_OBJECT_CLASS (ide_buffer_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_finalize (GObject *object)
|
|
{
|
|
IdeBuffer *self = (IdeBuffer *)object;
|
|
|
|
g_clear_object (&self->source_file);
|
|
g_clear_object (&self->readlink_file);
|
|
g_clear_pointer (&self->failure, g_error_free);
|
|
|
|
G_OBJECT_CLASS (ide_buffer_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
IdeBuffer *self = IDE_BUFFER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_CHANGE_MONITOR:
|
|
g_value_set_object (value, ide_buffer_get_change_monitor (self));
|
|
break;
|
|
|
|
case PROP_CHANGED_ON_VOLUME:
|
|
g_value_set_boolean (value, ide_buffer_get_changed_on_volume (self));
|
|
break;
|
|
|
|
case PROP_ENABLE_ADDINS:
|
|
g_value_set_boolean (value, self->enable_addins);
|
|
break;
|
|
|
|
case PROP_DIAGNOSTICS:
|
|
g_value_set_object (value, ide_buffer_get_diagnostics (self));
|
|
break;
|
|
|
|
case PROP_FAILED:
|
|
g_value_set_boolean (value, ide_buffer_get_failed (self));
|
|
break;
|
|
|
|
case PROP_FILE:
|
|
g_value_set_object (value, ide_buffer_get_file (self));
|
|
break;
|
|
|
|
case PROP_FILE_SETTINGS:
|
|
g_value_set_object (value, ide_buffer_get_file_settings (self));
|
|
break;
|
|
|
|
case PROP_HAS_DIAGNOSTICS:
|
|
g_value_set_boolean (value, ide_buffer_has_diagnostics (self));
|
|
break;
|
|
|
|
case PROP_HAS_SYMBOL_RESOLVERS:
|
|
g_value_set_boolean (value, ide_buffer_has_symbol_resolvers (self));
|
|
break;
|
|
|
|
case PROP_HIGHLIGHT_DIAGNOSTICS:
|
|
g_value_set_boolean (value, ide_buffer_get_highlight_diagnostics (self));
|
|
break;
|
|
|
|
case PROP_LANGUAGE_ID:
|
|
g_value_set_string (value, ide_buffer_get_language_id (self));
|
|
break;
|
|
|
|
case PROP_IS_TEMPORARY:
|
|
g_value_set_boolean (value, ide_buffer_get_is_temporary (self));
|
|
break;
|
|
|
|
case PROP_READ_ONLY:
|
|
g_value_set_boolean (value, ide_buffer_get_read_only (self));
|
|
break;
|
|
|
|
case PROP_STATE:
|
|
g_value_set_enum (value, ide_buffer_get_state (self));
|
|
break;
|
|
|
|
case PROP_STYLE_SCHEME_NAME:
|
|
g_value_set_string (value, ide_buffer_get_style_scheme_name (self));
|
|
break;
|
|
|
|
case PROP_TITLE:
|
|
g_value_take_string (value, ide_buffer_dup_title (self));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_buffer_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
IdeBuffer *self = IDE_BUFFER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_BUFFER_MANAGER:
|
|
self->buffer_manager = g_value_dup_object (value);
|
|
break;
|
|
|
|
case PROP_CHANGE_MONITOR:
|
|
ide_buffer_set_change_monitor (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_ENABLE_ADDINS:
|
|
self->enable_addins = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_DIAGNOSTICS:
|
|
ide_buffer_set_diagnostics (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_FILE:
|
|
_ide_buffer_set_file (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_HIGHLIGHT_DIAGNOSTICS:
|
|
ide_buffer_set_highlight_diagnostics (self, g_value_get_boolean (value));
|
|
break;
|
|
|
|
case PROP_LANGUAGE_ID:
|
|
ide_buffer_set_language_id (self, g_value_get_string (value));
|
|
break;
|
|
|
|
case PROP_IS_TEMPORARY:
|
|
self->is_temporary = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_STYLE_SCHEME_NAME:
|
|
ide_buffer_set_style_scheme_name (self, g_value_get_string (value));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_buffer_class_init (IdeBufferClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GtkTextBufferClass *buffer_class = GTK_TEXT_BUFFER_CLASS (klass);
|
|
|
|
object_class->constructed = ide_buffer_constructed;
|
|
object_class->dispose = ide_buffer_dispose;
|
|
object_class->finalize = ide_buffer_finalize;
|
|
object_class->get_property = ide_buffer_get_property;
|
|
object_class->set_property = ide_buffer_set_property;
|
|
|
|
buffer_class->changed = ide_buffer_changed;
|
|
buffer_class->delete_range = ide_buffer_delete_range;
|
|
buffer_class->insert_text = ide_buffer_insert_text;
|
|
buffer_class->mark_set = ide_buffer_mark_set;
|
|
|
|
/**
|
|
* IdeBuffer:buffer-manager:
|
|
*
|
|
* Sets the "buffer-manager" property, which is used by the buffer to
|
|
* clean-up state when the buffer is no longer in use.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_BUFFER_MANAGER] =
|
|
g_param_spec_object ("buffer-manager",
|
|
"Buffer Manager",
|
|
"The buffer manager for the context.",
|
|
IDE_TYPE_BUFFER_MANAGER,
|
|
(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:change-monitor:
|
|
*
|
|
* The "change-monitor" property is an #IdeBufferChangeMonitor that will be
|
|
* used to track changes in the #IdeBuffer. This can be used to show line
|
|
* changes in the editor gutter.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_CHANGE_MONITOR] =
|
|
g_param_spec_object ("change-monitor",
|
|
"Change Monitor",
|
|
"Change Monitor",
|
|
IDE_TYPE_BUFFER_CHANGE_MONITOR,
|
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:changed-on-volume:
|
|
*
|
|
* The "changed-on-volume" property is set to %TRUE when it has been
|
|
* discovered that the file represented by the #IdeBuffer has changed
|
|
* externally to Builder.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_CHANGED_ON_VOLUME] =
|
|
g_param_spec_boolean ("changed-on-volume",
|
|
"Changed On Volume",
|
|
"If the buffer has been modified externally",
|
|
FALSE,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:enable-addins:
|
|
*
|
|
* The "enable-addins" property determines whether addins will be aware of
|
|
* this buffer. When set to %FALSE no ide_buffer_addin_*() functions will be
|
|
* called on this buffer.
|
|
*
|
|
* Since: 41.0
|
|
*/
|
|
properties [PROP_ENABLE_ADDINS] =
|
|
g_param_spec_boolean ("enable-addins",
|
|
"Enable Addins",
|
|
"Whether to enable addins for this buffer",
|
|
TRUE,
|
|
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY));
|
|
|
|
/**
|
|
* IdeBuffer:diagnostics:
|
|
*
|
|
* The "diagnostics" property contains an #IdeDiagnostics that represent
|
|
* the diagnostics found in the buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_DIAGNOSTICS] =
|
|
g_param_spec_object ("diagnostics",
|
|
"Diagnostics",
|
|
"The diagnostics for the buffer",
|
|
IDE_TYPE_DIAGNOSTICS,
|
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:failed:
|
|
*
|
|
* The "failed" property is %TRUE when the buffer has entered a failed
|
|
* state such as when loading or saving the buffer to disk.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_FAILED] =
|
|
g_param_spec_boolean ("failed",
|
|
"Failed",
|
|
"If the buffer has entered a failed state",
|
|
FALSE,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:file:
|
|
*
|
|
* The "file" property is the underlying file represented by the buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_FILE] =
|
|
g_param_spec_object ("file",
|
|
"File",
|
|
"The file the buffer represents",
|
|
G_TYPE_FILE,
|
|
(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:file-settings:
|
|
*
|
|
* The "file-settings" property are the settings to be used by the buffer
|
|
* and source-view for the underlying file.
|
|
*
|
|
* These are automatically discovered and kept up to date based on the
|
|
* #IdeFileSettings extension points.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_FILE_SETTINGS] =
|
|
g_param_spec_object ("file-settings",
|
|
"File Settings",
|
|
"The file settings for the buffer",
|
|
IDE_TYPE_FILE_SETTINGS,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:has-diagnostics:
|
|
*
|
|
* The "has-diagnostics" property denotes that there are a non-zero number
|
|
* of diangostics registered for the buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_HAS_DIAGNOSTICS] =
|
|
g_param_spec_boolean ("has-diagnostics",
|
|
"Has Diagnostics",
|
|
"The diagnostics for the buffer",
|
|
FALSE,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:has-symbol-resolvers:
|
|
*
|
|
* The "has-symbol-resolvers" property is %TRUE if there are any symbol
|
|
* resolvers loaded.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_HAS_SYMBOL_RESOLVERS] =
|
|
g_param_spec_boolean ("has-symbol-resolvers",
|
|
"Has symbol resolvers",
|
|
"If there is at least one symbol resolver available",
|
|
FALSE,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:highlight-diagnostics:
|
|
*
|
|
* The "highlight-diagnostics" property indicates that diagnostics which
|
|
* are discovered should be styled.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_HIGHLIGHT_DIAGNOSTICS] =
|
|
g_param_spec_boolean ("highlight-diagnostics",
|
|
"Highlight Diagnostics",
|
|
"If diagnostics should be highlighted",
|
|
TRUE,
|
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:is-temporary:
|
|
*
|
|
* The "is-temporary" property denotes the #IdeBuffer:file property points
|
|
* to a temporary file. When saving the the buffer, various UI components
|
|
* know to check this property and provide a file chooser to allow the user
|
|
* to select the destination file.
|
|
*
|
|
* Upon saving the file, the property will change to %FALSE.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_IS_TEMPORARY] =
|
|
g_param_spec_boolean ("is-temporary",
|
|
"Is Temporary",
|
|
"If the file property is a temporary file",
|
|
FALSE,
|
|
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:language-id:
|
|
*
|
|
* The "language-id" property is a convenience property to set the
|
|
* #GtkSourceBuffer:langauge property using a string name.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_LANGUAGE_ID] =
|
|
g_param_spec_string ("language-id",
|
|
"Language Id",
|
|
"The language identifier as a string",
|
|
NULL,
|
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:read-only:
|
|
*
|
|
* The "read-only" property is set to %TRUE when it has been
|
|
* discovered that the file represented by the #IdeBuffer is read-only
|
|
* on the underlying storage.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_READ_ONLY] =
|
|
g_param_spec_boolean ("read-only",
|
|
"Read Only",
|
|
"If the buffer's file is read-only",
|
|
FALSE,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:state:
|
|
*
|
|
* The "state" property can be used to determine if the buffer is
|
|
* currently performing any specific background work, such as loading
|
|
* from or saving a buffer to storage.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_STATE] =
|
|
g_param_spec_enum ("state",
|
|
"State",
|
|
"The state for the buffer",
|
|
IDE_TYPE_BUFFER_STATE,
|
|
IDE_BUFFER_STATE_READY,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:style-scheme-name:
|
|
*
|
|
* The "style-scheme-name" is the name of the style scheme that is used.
|
|
* It is a convenience property so that you do not need to use the
|
|
* #GtkSourceStyleSchemeManager to lookup style schemes.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_STYLE_SCHEME_NAME] =
|
|
g_param_spec_string ("style-scheme-name",
|
|
"Style Scheme Name",
|
|
"The name of the GtkSourceStyleScheme to use",
|
|
NULL,
|
|
(G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* IdeBuffer:title:
|
|
*
|
|
* The "title" for the buffer which includes some variant of the path
|
|
* to the underlying file.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
properties [PROP_TITLE] =
|
|
g_param_spec_string ("title",
|
|
"Title",
|
|
"The title for the buffer",
|
|
NULL,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
|
|
|
/**
|
|
* IdeBuffer::change-settled:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* The "change-settled" signal is emitted when the buffer has stopped
|
|
* being edited for a short period of time. This is useful to connect
|
|
* to when you want to perform work as the user is editing, but you
|
|
* don't want to get in the way of their editing.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
signals [CHANGE_SETTLED] =
|
|
g_signal_new ("change-settled",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
g_signal_set_va_marshaller (signals [CHANGE_SETTLED],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
g_cclosure_marshal_VOID__VOIDv);
|
|
|
|
/**
|
|
* IdeBuffer::cursor-moved:
|
|
* @self: an #IdeBuffer
|
|
* @location: a #GtkTextIter
|
|
*
|
|
* This signal is emitted when the insertion location has moved. You might
|
|
* want to attach to this signal to update the location of the insert mark in
|
|
* the display.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
signals [CURSOR_MOVED] =
|
|
g_signal_new ("cursor-moved",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_VOID__BOXED,
|
|
G_TYPE_NONE,
|
|
1,
|
|
GTK_TYPE_TEXT_ITER | G_SIGNAL_TYPE_STATIC_SCOPE);
|
|
g_signal_set_va_marshaller (signals [CURSOR_MOVED],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
g_cclosure_marshal_VOID__BOXEDv);
|
|
|
|
/**
|
|
* IdeBuffer::line-flags-changed:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* The "line-flags-changed" signal is emitted when the buffer has detected
|
|
* ancillary information has changed for lines in the buffer. Such information
|
|
* might include diagnostics or version control information.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
signals [LINE_FLAGS_CHANGED] =
|
|
g_signal_new_class_handler ("line-flags-changed",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
NULL,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
g_signal_set_va_marshaller (signals [LINE_FLAGS_CHANGED],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
g_cclosure_marshal_VOID__VOIDv);
|
|
|
|
/**
|
|
* IdeBuffer::loaded:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* The "loaded" signal is emitted after the buffer is loaded.
|
|
*
|
|
* This is useful to watch if you want to perform a given action but do
|
|
* not want to interfere with buffer loading.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
signals [LOADED] =
|
|
g_signal_new_class_handler ("loaded",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_CALLBACK (ide_buffer_real_loaded),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
g_signal_set_va_marshaller (signals [LOADED],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
g_cclosure_marshal_VOID__VOIDv);
|
|
|
|
/**
|
|
* IdeBuffer::request-scroll-to-insert:
|
|
*
|
|
* Requests that attached views scroll to insert location.
|
|
*
|
|
* This is generally only used when loading a buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
signals [REQUEST_SCROLL_TO_INSERT] =
|
|
g_signal_new_class_handler ("request-scroll-to-insert",
|
|
G_TYPE_FROM_CLASS (klass),
|
|
G_SIGNAL_RUN_LAST,
|
|
NULL,
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
g_signal_set_va_marshaller (signals [REQUEST_SCROLL_TO_INSERT],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
g_cclosure_marshal_VOID__VOIDv);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_init (IdeBuffer *self)
|
|
{
|
|
self->in_flight_symbol_at_location_pos = -1;
|
|
self->source_file = gtk_source_file_new ();
|
|
self->can_restore_cursor = TRUE;
|
|
self->highlight_diagnostics = TRUE;
|
|
self->enable_addins = TRUE;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
|
|
g_signal_connect (self,
|
|
"notify::language",
|
|
G_CALLBACK (ide_buffer_notify_language),
|
|
NULL);
|
|
|
|
g_signal_connect (self,
|
|
"notify::style-scheme",
|
|
G_CALLBACK (ide_buffer_notify_style_scheme),
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_rename_provider_notify_extension (IdeBuffer *self,
|
|
GParamSpec *pspec,
|
|
IdeExtensionAdapter *adapter)
|
|
{
|
|
IdeRenameProvider *provider;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
|
|
|
|
if ((provider = ide_extension_adapter_get_extension (adapter)))
|
|
{
|
|
g_object_set (provider, "buffer", self, NULL);
|
|
ide_rename_provider_load (provider);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_buffer_formatter_notify_extension (IdeBuffer *self,
|
|
GParamSpec *pspec,
|
|
IdeExtensionAdapter *adapter)
|
|
{
|
|
IdeFormatter *formatter;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
|
|
|
|
if ((formatter = ide_extension_adapter_get_extension (adapter)))
|
|
ide_formatter_load (formatter);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_code_action_provider_notify_extension (IdeBuffer *self,
|
|
GParamSpec *pspec,
|
|
IdeExtensionAdapter *adapter)
|
|
{
|
|
IdeCodeActionProvider *code_action_provider;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
|
|
|
|
if ((code_action_provider = ide_extension_adapter_get_extension (adapter)))
|
|
ide_code_action_provider_load (code_action_provider);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_symbol_resolver_added (IdeExtensionSetAdapter *adapter,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *extension,
|
|
gpointer user_data)
|
|
{
|
|
IdeSymbolResolver *resolver = (IdeSymbolResolver *)extension;
|
|
IdeBuffer *self = user_data;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
|
|
g_assert (plugin_info != NULL);
|
|
g_assert (IDE_IS_SYMBOL_RESOLVER (resolver));
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
IDE_TRACE_MSG ("Loading symbol resolver %s", G_OBJECT_TYPE_NAME (resolver));
|
|
|
|
ide_symbol_resolver_load (resolver);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SYMBOL_RESOLVERS]);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_symbol_resolver_removed (IdeExtensionSetAdapter *adapter,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *extension,
|
|
gpointer user_data)
|
|
{
|
|
IdeSymbolResolver *resolver = (IdeSymbolResolver *)extension;
|
|
IdeBuffer *self = user_data;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
|
|
g_assert (plugin_info != NULL);
|
|
g_assert (IDE_IS_SYMBOL_RESOLVER (resolver));
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
IDE_TRACE_MSG ("Unloading symbol resolver %s", G_OBJECT_TYPE_NAME (resolver));
|
|
|
|
ide_symbol_resolver_unload (resolver);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_SYMBOL_RESOLVERS]);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
void
|
|
_ide_buffer_attach (IdeBuffer *self,
|
|
IdeObject *parent)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_OBJECT_BOX (parent));
|
|
g_return_if_fail (ide_object_box_contains (IDE_OBJECT_BOX (parent), self));
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (self->addins == NULL);
|
|
g_return_if_fail (self->highlight_engine == NULL);
|
|
g_return_if_fail (self->formatter == NULL);
|
|
g_return_if_fail (self->rename_provider == NULL);
|
|
|
|
/* Setup the semantic highlight engine */
|
|
self->highlight_engine = ide_highlight_engine_new (self);
|
|
|
|
/* Load buffer addins */
|
|
self->addins = ide_extension_set_adapter_new (parent,
|
|
peas_engine_get_default (),
|
|
IDE_TYPE_BUFFER_ADDIN,
|
|
"Buffer-Addin-Languages",
|
|
ide_buffer_get_language_id (self));
|
|
g_signal_connect (self->addins,
|
|
"extension-added",
|
|
G_CALLBACK (_ide_buffer_addin_load_cb),
|
|
self);
|
|
g_signal_connect (self->addins,
|
|
"extension-removed",
|
|
G_CALLBACK (_ide_buffer_addin_unload_cb),
|
|
self);
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
_ide_buffer_addin_load_cb,
|
|
self);
|
|
|
|
/* Setup our rename provider, if any */
|
|
self->rename_provider = ide_extension_adapter_new (parent,
|
|
peas_engine_get_default (),
|
|
IDE_TYPE_RENAME_PROVIDER,
|
|
"Rename-Provider-Languages",
|
|
ide_buffer_get_language_id (self));
|
|
g_signal_connect_object (self->rename_provider,
|
|
"notify::extension",
|
|
G_CALLBACK (ide_buffer_rename_provider_notify_extension),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
ide_buffer_rename_provider_notify_extension (self, NULL, self->rename_provider);
|
|
|
|
/* Setup our formatter, if any */
|
|
self->formatter = ide_extension_adapter_new (parent,
|
|
peas_engine_get_default (),
|
|
IDE_TYPE_FORMATTER,
|
|
"Formatter-Languages",
|
|
ide_buffer_get_language_id (self));
|
|
g_signal_connect_object (self->formatter,
|
|
"notify::extension",
|
|
G_CALLBACK (ide_buffer_formatter_notify_extension),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
ide_buffer_formatter_notify_extension (self, NULL, self->formatter);
|
|
|
|
/* Setup our code action provider, if any */
|
|
self->code_action_provider = ide_extension_adapter_new (parent,
|
|
peas_engine_get_default (),
|
|
IDE_TYPE_CODE_ACTION_PROVIDER,
|
|
"Code-Action-Languages",
|
|
ide_buffer_get_language_id (self));
|
|
|
|
g_signal_connect_object (self->code_action_provider,
|
|
"notify::extension",
|
|
G_CALLBACK (ide_buffer_code_action_provider_notify_extension),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
ide_buffer_code_action_provider_notify_extension (self, NULL, self->code_action_provider);
|
|
|
|
/* Setup symbol resolvers */
|
|
self->symbol_resolvers = ide_extension_set_adapter_new (parent,
|
|
peas_engine_get_default (),
|
|
IDE_TYPE_SYMBOL_RESOLVER,
|
|
"Symbol-Resolver-Languages",
|
|
ide_buffer_get_language_id (self));
|
|
g_signal_connect_object (self->symbol_resolvers,
|
|
"extension-added",
|
|
G_CALLBACK (ide_buffer_symbol_resolver_added),
|
|
self,
|
|
0);
|
|
g_signal_connect_object (self->symbol_resolvers,
|
|
"extension-removed",
|
|
G_CALLBACK (ide_buffer_symbol_resolver_removed),
|
|
self,
|
|
0);
|
|
ide_extension_set_adapter_foreach (self->symbol_resolvers,
|
|
ide_buffer_symbol_resolver_added,
|
|
self);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_file:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the #IdeBuffer:file property.
|
|
*
|
|
* Returns: (transfer none): a #GFile
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
GFile *
|
|
ide_buffer_get_file (IdeBuffer *self)
|
|
{
|
|
GFile *ret;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
ret = gtk_source_file_get_location (self->source_file);
|
|
|
|
g_return_val_if_fail (G_IS_FILE (ret), NULL);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_dup_uri:
|
|
* @self: a #IdeBuffer
|
|
*
|
|
* Gets the URI for the underlying file and returns a copy of it.
|
|
*
|
|
* Returns: (transfer full): a new string
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gchar *
|
|
ide_buffer_dup_uri (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
return g_file_get_uri (ide_buffer_get_file (self));
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_is_temporary:
|
|
*
|
|
* Checks if the buffer represents a temporary file.
|
|
*
|
|
* This is useful to check by views that want to provide a save-as dialog
|
|
* when the user requests to save the buffer.
|
|
*
|
|
* Returns: %TRUE if the buffer is for a temporary file
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_get_is_temporary (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
return self->is_temporary;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_state:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the #IdeBuffer:state property.
|
|
*
|
|
* This will changed while files are loaded or saved to disk.
|
|
*
|
|
* Returns: an #IdeBufferState
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeBufferState
|
|
ide_buffer_get_state (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), 0);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), 0);
|
|
|
|
return self->state;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_progress_cb (goffset current_num_bytes,
|
|
goffset total_num_bytes,
|
|
gpointer user_data)
|
|
{
|
|
IdeNotification *notif = user_data;
|
|
gdouble progress = 0.0;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_NOTIFICATION (notif));
|
|
|
|
if (total_num_bytes)
|
|
progress = (gdouble)current_num_bytes / (gdouble)total_num_bytes;
|
|
|
|
ide_notification_set_progress (notif, progress);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_load_file_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GtkSourceFileLoader *loader = (GtkSourceFileLoader *)object;
|
|
g_autoptr(IdeTask) task = user_data;
|
|
g_autoptr(GError) error = NULL;
|
|
GtkTextIter iter;
|
|
LoadState *state;
|
|
IdeBuffer *self;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (GTK_SOURCE_IS_FILE_LOADER (loader));
|
|
g_assert (G_IS_ASYNC_RESULT (result));
|
|
g_assert (IDE_IS_TASK (task));
|
|
|
|
self = ide_task_get_source_object (task);
|
|
state = ide_task_get_task_data (task);
|
|
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (state != NULL);
|
|
g_assert (G_IS_FILE (state->file));
|
|
g_assert (IDE_IS_NOTIFICATION (state->notif));
|
|
|
|
if (!gtk_source_file_loader_load_finish (loader, result, &error))
|
|
{
|
|
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
|
|
{
|
|
ide_buffer_set_state (self, IDE_BUFFER_STATE_FAILED);
|
|
ide_notification_set_progress (state->notif, 0.0);
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
IDE_EXIT;
|
|
}
|
|
|
|
g_clear_error (&error);
|
|
}
|
|
|
|
/* First move the insert cursor back to 0:0, plugins might move it
|
|
* but we certainly don't want to leave it at the end.
|
|
*/
|
|
gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (self), &iter);
|
|
gtk_text_buffer_select_range (GTK_TEXT_BUFFER (self), &iter, &iter);
|
|
|
|
/* Assume we are at newest state at end of file-load operation */
|
|
_ide_buffer_set_changed_on_volume (self, FALSE);
|
|
|
|
ide_highlight_engine_unpause (self->highlight_engine);
|
|
ide_buffer_set_state (self, IDE_BUFFER_STATE_READY);
|
|
ide_notification_set_progress (state->notif, 1.0);
|
|
ide_task_return_boolean (task, TRUE);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
void
|
|
_ide_buffer_load_file_async (IdeBuffer *self,
|
|
IdeNotification *notif,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(GtkSourceFileLoader) loader = NULL;
|
|
g_autoptr(IdeTask) task = NULL;
|
|
LoadState *state;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
g_return_if_fail (ide_buffer_get_file (self) != NULL);
|
|
|
|
task = ide_task_new (self, cancellable, callback, user_data);
|
|
ide_task_set_source_tag (task, _ide_buffer_load_file_async);
|
|
|
|
if (self->state != IDE_BUFFER_STATE_READY &&
|
|
self->state != IDE_BUFFER_STATE_FAILED)
|
|
{
|
|
ide_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_BUSY,
|
|
"Cannot load file while buffer is busy");
|
|
IDE_EXIT;
|
|
}
|
|
|
|
state = g_slice_new0 (LoadState);
|
|
state->file = g_object_ref (ide_buffer_get_file (self));
|
|
state->notif = notif ? g_object_ref (notif) : ide_notification_new ();
|
|
state->highlight_syntax = gtk_source_buffer_get_highlight_syntax (GTK_SOURCE_BUFFER (self));
|
|
ide_task_set_task_data (task, state, load_state_free);
|
|
|
|
ide_buffer_set_state (self, IDE_BUFFER_STATE_LOADING);
|
|
|
|
/* Disable some features while we reload */
|
|
gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (self), FALSE);
|
|
ide_highlight_engine_pause (self->highlight_engine);
|
|
|
|
loader = gtk_source_file_loader_new (GTK_SOURCE_BUFFER (self), self->source_file);
|
|
gtk_source_file_loader_load_async (loader,
|
|
G_PRIORITY_DEFAULT,
|
|
cancellable,
|
|
ide_buffer_progress_cb,
|
|
g_object_ref (state->notif),
|
|
g_object_unref,
|
|
ide_buffer_load_file_cb,
|
|
g_steal_pointer (&task));
|
|
|
|
/* Load file settings immediately so that we can increase the chance
|
|
* they are settled by the the load operation is finished. The modelines
|
|
* file settings will auto-monitor for IdeBufferManager::buffer-loaded
|
|
* and settle the file settings when we complete.
|
|
*/
|
|
ide_buffer_reload_file_settings (self);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
/**
|
|
* _ide_buffer_load_file_finish:
|
|
* @self: an #IdeBuffer
|
|
* @result: a #GAsyncResult
|
|
* @error: a location for a #GError, or %NULL
|
|
*
|
|
* This should be called by the buffer manager to complete loading the initial
|
|
* state of a buffer. It can also be used to reload a buffer after it was
|
|
* modified on disk.
|
|
*
|
|
* You MUST call this function after using _ide_buffer_load_file_async() so
|
|
* that the completion of signals and addins may be notified.
|
|
*
|
|
* Returns: %TRUE if the file was successfully loaded
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
_ide_buffer_load_file_finish (IdeBuffer *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
LoadState *state;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
|
|
|
|
if (!ide_task_propagate_boolean (IDE_TASK (result), error))
|
|
return FALSE;
|
|
|
|
/* Restore various buffer features we disabled while loading */
|
|
state = ide_task_get_task_data (IDE_TASK (result));
|
|
if (state->highlight_syntax)
|
|
gtk_source_buffer_set_highlight_syntax (GTK_SOURCE_BUFFER (self), TRUE);
|
|
|
|
/* Guess the syntax language now if necessary */
|
|
if (!gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self)))
|
|
ide_buffer_guess_language (self);
|
|
|
|
/* Let consumers know they can access the buffer now */
|
|
g_signal_emit (self, signals [LOADED], 0);
|
|
|
|
/* Notify buffer addins that a file has been loaded */
|
|
if (self->addins != NULL && self->enable_addins)
|
|
{
|
|
IdeBufferFileLoad closure = { self, state->file };
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
_ide_buffer_addin_file_loaded_cb,
|
|
&closure);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_save_file_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
GtkSourceFileSaver *saver = (GtkSourceFileSaver *)object;
|
|
g_autoptr(IdeTask) task = user_data;
|
|
g_autoptr(GError) error = NULL;
|
|
IdeBuffer *self;
|
|
SaveState *state;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (GTK_SOURCE_IS_FILE_SAVER (saver));
|
|
g_assert (G_IS_ASYNC_RESULT (result));
|
|
g_assert (IDE_IS_TASK (task));
|
|
|
|
self = ide_task_get_source_object (task);
|
|
state = ide_task_get_task_data (task);
|
|
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (state != NULL);
|
|
g_assert (G_IS_FILE (state->file));
|
|
g_assert (IDE_IS_NOTIFICATION (state->notif));
|
|
|
|
if (!gtk_source_file_saver_save_finish (saver, result, &error))
|
|
{
|
|
ide_notification_set_progress (state->notif, 0.0);
|
|
ide_buffer_set_state (self, IDE_BUFFER_STATE_FAILED);
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
IDE_EXIT;
|
|
}
|
|
|
|
ide_notification_set_progress (state->notif, 1.0);
|
|
ide_buffer_set_state (self, IDE_BUFFER_STATE_READY);
|
|
|
|
/* Treat our save as freshest. It's possible we race, as we'd need an etag to
|
|
* detect that, probably fine in all but the most slowest of races.
|
|
*/
|
|
_ide_buffer_set_changed_on_volume (self, FALSE);
|
|
|
|
/* Notify addins that a save has completed */
|
|
if (self->addins != NULL && self->enable_addins)
|
|
{
|
|
IdeBufferFileSave closure = { self, state->file };
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
_ide_buffer_addin_file_saved_cb,
|
|
&closure);
|
|
}
|
|
|
|
if (self->buffer_manager != NULL)
|
|
_ide_buffer_manager_buffer_saved (self->buffer_manager, self);
|
|
else
|
|
g_critical ("Attempt to save buffer without access to buffer-manager");
|
|
|
|
ide_task_return_boolean (task, TRUE);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_save_file_settle_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
IdeBuffer *self = (IdeBuffer *)object;
|
|
g_autoptr(IdeTask) task = user_data;
|
|
g_autoptr(GtkSourceFileSaver) saver = NULL;
|
|
SaveState *state;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (G_IS_ASYNC_RESULT (result));
|
|
g_assert (IDE_IS_TASK (task));
|
|
|
|
settle_finish (self, result, NULL);
|
|
|
|
state = ide_task_get_task_data (task);
|
|
|
|
g_assert (state != NULL);
|
|
g_assert (G_IS_FILE (state->file));
|
|
g_assert (IDE_IS_NOTIFICATION (state->notif));
|
|
g_assert (GTK_SOURCE_IS_FILE (state->source_file));
|
|
|
|
if (self->addins != NULL && self->enable_addins)
|
|
{
|
|
IdeBufferFileSave closure = { self, state->file };
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
_ide_buffer_addin_save_file_cb,
|
|
&closure);
|
|
}
|
|
|
|
saver = gtk_source_file_saver_new (GTK_SOURCE_BUFFER (self), state->source_file);
|
|
/* At this point, we've notified the user of changes to the underlying file using
|
|
* the infobar, so just save the file knowing that we are overwriting things.
|
|
*/
|
|
gtk_source_file_saver_set_flags (saver,
|
|
(GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_INVALID_CHARS |
|
|
GTK_SOURCE_FILE_SAVER_FLAGS_IGNORE_MODIFICATION_TIME));
|
|
gtk_source_file_saver_save_async (saver,
|
|
G_PRIORITY_DEFAULT,
|
|
ide_task_get_cancellable (task),
|
|
ide_buffer_progress_cb,
|
|
g_object_ref (state->notif),
|
|
g_object_unref,
|
|
ide_buffer_save_file_cb,
|
|
g_object_ref (task));
|
|
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_save_file_async:
|
|
* @self: an #IdeBuffer
|
|
* @file: (nullable): a #GFile or %NULL
|
|
* @cancellable: (nullable): a #GCancellable
|
|
* @callback: a #GAsyncReadyCallback to execute upon completion
|
|
* @user_data: closure data for @callback
|
|
*
|
|
* Asynchronously saves the buffer contents to @file.
|
|
*
|
|
* If @file is %NULL, then the #IdeBuffer:file property is used.
|
|
*
|
|
* The buffer is marked as busy during the operation, and must not have
|
|
* further editing until the operation is complete.
|
|
*
|
|
* @callback is executed upon completion and should call
|
|
* ide_buffer_save_file_finish() to get the result of the operation.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_save_file_async (IdeBuffer *self,
|
|
GFile *file,
|
|
GCancellable *cancellable,
|
|
IdeNotification **notif,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(IdeTask) task = NULL;
|
|
g_autoptr(GtkSourceFile) alternate = NULL;
|
|
g_autoptr(IdeNotification) local_notif = NULL;
|
|
GtkSourceFile *source_file;
|
|
SaveState *state;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (!file || G_IS_FILE (file));
|
|
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
ide_clear_param (notif, NULL);
|
|
|
|
/* If the user is requesting to save a file and our current file
|
|
* is a temporary file, then we want to transition to become that
|
|
* file instead of our temporary one.
|
|
*/
|
|
if (file != NULL && self->is_temporary)
|
|
{
|
|
_ide_buffer_set_file (self, file);
|
|
self->is_temporary = FALSE;
|
|
|
|
/* The buffer might be empty, so mark it as modified so we really save */
|
|
gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (self), TRUE);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_IS_TEMPORARY]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
|
|
}
|
|
|
|
if (file == NULL)
|
|
file = ide_buffer_get_file (self);
|
|
|
|
local_notif = ide_notification_new ();
|
|
ide_notification_set_has_progress (local_notif, TRUE);
|
|
|
|
state = g_slice_new0 (SaveState);
|
|
state->file = g_object_ref (file);
|
|
state->notif = g_object_ref (local_notif);
|
|
|
|
task = ide_task_new (self, cancellable, callback, user_data);
|
|
ide_task_set_source_tag (task, ide_buffer_save_file_async);
|
|
ide_task_set_task_data (task, state, save_state_free);
|
|
|
|
/* Keep buffer alive during save operation */
|
|
ide_buffer_hold (self);
|
|
g_signal_connect_object (task,
|
|
"notify::completed",
|
|
G_CALLBACK (ide_buffer_release),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
|
|
if (self->state == IDE_BUFFER_STATE_SAVING)
|
|
{
|
|
/* TODO: We could save in-flight tasks and chain to them */
|
|
ide_task_return_boolean (task, TRUE);
|
|
IDE_EXIT;
|
|
}
|
|
|
|
if (self->state != IDE_BUFFER_STATE_READY)
|
|
{
|
|
ide_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_BUSY,
|
|
"Failed to save buffer as it is busy");
|
|
IDE_EXIT;
|
|
}
|
|
|
|
source_file = self->source_file;
|
|
|
|
if (file && !g_file_equal (file, ide_buffer_get_file (self)))
|
|
{
|
|
alternate = gtk_source_file_new ();
|
|
gtk_source_file_set_location (alternate, file);
|
|
source_file = alternate;
|
|
}
|
|
|
|
state->source_file = g_object_ref (source_file);
|
|
|
|
/* Possibly avoid any writing if we can detect a no-change state */
|
|
if (file == NULL || g_file_equal (file, ide_buffer_get_file (self)))
|
|
{
|
|
if (!self->changed_on_volume &&
|
|
!gtk_text_buffer_get_modified (GTK_TEXT_BUFFER (self)))
|
|
{
|
|
ide_notification_set_progress (local_notif, 1.0);
|
|
ide_task_return_boolean (task, TRUE);
|
|
IDE_GOTO (set_out_param);
|
|
}
|
|
}
|
|
|
|
ide_buffer_set_state (self, IDE_BUFFER_STATE_SAVING);
|
|
|
|
settle_async (self,
|
|
cancellable,
|
|
ide_buffer_save_file_settle_cb,
|
|
g_steal_pointer (&task));
|
|
|
|
set_out_param:
|
|
if (notif != NULL)
|
|
*notif = g_steal_pointer (&local_notif);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_save_file_finish:
|
|
* @self: an #IdeBuffer
|
|
* @result: a #GAsyncResult provided to callback
|
|
* @error: a location for a #GError, or %NULL
|
|
*
|
|
* Completes an asynchronous request to save the buffer via
|
|
* ide_buffer_save_file_async().
|
|
*
|
|
* Returns: %TRUE if successful; otherwise %FALSE and @error is set.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_save_file_finish (IdeBuffer *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
|
|
|
|
return ide_task_propagate_boolean (IDE_TASK (result), error);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_language_id:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* A helper to get the language identifier of the buffers current language.
|
|
*
|
|
* Returns: (nullable): a string containing the language id, or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
const gchar *
|
|
ide_buffer_get_language_id (IdeBuffer *self)
|
|
{
|
|
GtkSourceLanguage *lang;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
if ((lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self))))
|
|
return gtk_source_language_get_id (lang);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
ide_buffer_set_language_id (IdeBuffer *self,
|
|
const gchar *language_id)
|
|
{
|
|
GtkSourceLanguage *language = NULL;
|
|
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
if (language_id != NULL)
|
|
{
|
|
GtkSourceLanguageManager *manager;
|
|
|
|
manager = gtk_source_language_manager_get_default ();
|
|
language = gtk_source_language_manager_get_language (manager, language_id);
|
|
}
|
|
|
|
gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (self), language);
|
|
}
|
|
|
|
IdeHighlightEngine *
|
|
_ide_buffer_get_highlight_engine (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
return self->highlight_engine;
|
|
}
|
|
|
|
void
|
|
_ide_buffer_set_failure (IdeBuffer *self,
|
|
const GError *error)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
if (error == self->failure)
|
|
return;
|
|
|
|
if (error != NULL)
|
|
self->state = IDE_BUFFER_STATE_FAILED;
|
|
|
|
g_clear_pointer (&self->failure, g_error_free);
|
|
self->failure = g_error_copy (error);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FAILED]);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_failure:
|
|
*
|
|
* Gets a #GError representing a failure that has occurred for the
|
|
* buffer.
|
|
*
|
|
* Returns: (transfer none): a #GError, or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
const GError *
|
|
ide_buffer_get_failure (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
return self->failure;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_failed:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the #IdeBuffer:failed property, denoting if the buffer has failed
|
|
* in some aspect such as loading or saving.
|
|
*
|
|
* Returns: %TRUE if the buffer is in a failed state
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_get_failed (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
return self->state == IDE_BUFFER_STATE_FAILED;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_set_file_settings (IdeBuffer *self,
|
|
IdeFileSettings *file_settings)
|
|
{
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (IDE_IS_FILE_SETTINGS (file_settings));
|
|
|
|
if (self->file_settings == file_settings)
|
|
return;
|
|
|
|
ide_clear_and_destroy_object (&self->file_settings);
|
|
self->file_settings = g_object_ref (file_settings);
|
|
|
|
if (!ide_buffer_get_loading (self))
|
|
ide_buffer_update_implicit_newline (self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_FILE_SETTINGS]);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_reload_file_settings (IdeBuffer *self)
|
|
{
|
|
IdeObjectBox *box;
|
|
const gchar *lang_id;
|
|
GFile *file;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
file = ide_buffer_get_file (self);
|
|
lang_id = ide_buffer_get_language_id (self);
|
|
|
|
/* Bail if we'll just create the same settings as before */
|
|
if (self->file_settings != NULL &&
|
|
(g_file_equal (file, ide_file_settings_get_file (self->file_settings)) &&
|
|
ide_str_equal0 (lang_id, ide_file_settings_get_language (self->file_settings))))
|
|
return;
|
|
|
|
/* Now apply the settings (and they'll settle in the background) */
|
|
if ((box = ide_object_box_from_object (G_OBJECT (self))))
|
|
{
|
|
g_autoptr(IdeFileSettings) file_settings = NULL;
|
|
|
|
file_settings = ide_file_settings_new (IDE_OBJECT (box), file, lang_id);
|
|
ide_buffer_set_file_settings (self, file_settings);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_buffer_emit_cursor_moved (IdeBuffer *self)
|
|
{
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
if (!ide_buffer_get_loading (self))
|
|
{
|
|
GtkTextMark *mark;
|
|
GtkTextIter iter;
|
|
|
|
mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self));
|
|
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), &iter, mark);
|
|
g_signal_emit (self, signals [CURSOR_MOVED], 0, &iter);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_loading:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* This checks to see if the buffer is currently loading. This is equivalent
|
|
* to calling ide_buffer_get_state() and checking for %IDE_BUFFER_STATE_LOADING.
|
|
*
|
|
* Returns: %TRUE if the buffer is loading; otherwise %FALSE.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_get_loading (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
return ide_buffer_get_state (self) == IDE_BUFFER_STATE_LOADING;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_changed (GtkTextBuffer *buffer)
|
|
{
|
|
IdeBuffer *self = (IdeBuffer *)buffer;
|
|
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->changed (buffer);
|
|
|
|
g_clear_object (&self->in_flight_symbol_at_location);
|
|
self->in_flight_symbol_at_location_pos = -1;
|
|
|
|
self->change_count++;
|
|
g_clear_pointer (&self->content, g_bytes_unref);
|
|
ide_buffer_delay_settling (self);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_delete_range (GtkTextBuffer *buffer,
|
|
GtkTextIter *begin,
|
|
GtkTextIter *end)
|
|
{
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
g_assert (begin != NULL);
|
|
g_assert (end != NULL);
|
|
|
|
#ifdef IDE_ENABLE_TRACE
|
|
{
|
|
gint begin_line, begin_offset;
|
|
gint end_line, end_offset;
|
|
|
|
begin_line = gtk_text_iter_get_line (begin);
|
|
begin_offset = gtk_text_iter_get_line_offset (begin);
|
|
end_line = gtk_text_iter_get_line (end);
|
|
end_offset = gtk_text_iter_get_line_offset (end);
|
|
|
|
IDE_TRACE_MSG ("delete-range (%d:%d, %d:%d)",
|
|
begin_line, begin_offset,
|
|
end_line, end_offset);
|
|
}
|
|
#endif
|
|
|
|
GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->delete_range (buffer, begin, end);
|
|
|
|
ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_insert_text (GtkTextBuffer *buffer,
|
|
GtkTextIter *location,
|
|
const gchar *text,
|
|
gint len)
|
|
{
|
|
gboolean recheck_language = FALSE;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (buffer));
|
|
g_assert (location != NULL);
|
|
g_assert (text != NULL);
|
|
|
|
/*
|
|
* If we are inserting a \n at the end of the first line, then we might want
|
|
* to adjust the GtkSourceBuffer:language property to reflect the format.
|
|
* This is similar to emacs "modelines", which is apparently a bit of an
|
|
* overloaded term as is not to be confused with editor setting modelines.
|
|
*/
|
|
if ((gtk_text_iter_get_line (location) == 0) && gtk_text_iter_ends_line (location) &&
|
|
((text [0] == '\n') || ((len > 1) && (strchr (text, '\n') != NULL))))
|
|
recheck_language = TRUE;
|
|
|
|
GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->insert_text (buffer, location, text, len);
|
|
|
|
ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
|
|
|
|
if G_UNLIKELY (recheck_language)
|
|
ide_buffer_guess_language (IDE_BUFFER (buffer));
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_mark_set (GtkTextBuffer *buffer,
|
|
const GtkTextIter *iter,
|
|
GtkTextMark *mark)
|
|
{
|
|
IdeBuffer *self = (IdeBuffer *)buffer;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
GTK_TEXT_BUFFER_CLASS (ide_buffer_parent_class)->mark_set (buffer, iter, mark);
|
|
|
|
if (!ide_buffer_get_loading (self))
|
|
{
|
|
if (mark == gtk_text_buffer_get_insert (buffer))
|
|
ide_buffer_emit_cursor_moved (IDE_BUFFER (buffer));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_changed_on_volume:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Returns %TRUE if the #IdeBuffer is known to have been modified on storage
|
|
* externally from this #IdeBuffer.
|
|
*
|
|
* Returns: %TRUE if @self is known to be modified on storage
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_get_changed_on_volume (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
return self->changed_on_volume;
|
|
}
|
|
|
|
/**
|
|
* _ide_buffer_set_changed_on_volume:
|
|
* @self: an #IdeBuffer
|
|
* @changed_on_volume: if the buffer was changed externally
|
|
*
|
|
* Sets the #IdeBuffer:changed-on-volume property.
|
|
*
|
|
* Set this to %TRUE if the buffer has been discovered to have changed
|
|
* outside of this buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
_ide_buffer_set_changed_on_volume (IdeBuffer *self,
|
|
gboolean changed_on_volume)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
changed_on_volume = !!changed_on_volume;
|
|
|
|
if (changed_on_volume != self->changed_on_volume)
|
|
{
|
|
self->changed_on_volume = changed_on_volume;
|
|
if (changed_on_volume)
|
|
gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (self), TRUE);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHANGED_ON_VOLUME]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_read_only:
|
|
*
|
|
* This function returns %TRUE if the underlying file has been discovered to
|
|
* be read-only. This may be used by the interface to display information to
|
|
* the user about saving the file.
|
|
*
|
|
* Returns: %TRUE if the underlying file is read-only
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_get_read_only (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
return self->read_only;
|
|
}
|
|
|
|
/**
|
|
* _ide_buffer_set_read_only:
|
|
* @self: an #IdeBuffer
|
|
* @read_only: if the buffer is read-only
|
|
*
|
|
* Sets the #IdeBuffer:read-only property, which should be set when the buffer
|
|
* has been discovered to be read-only on disk.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
_ide_buffer_set_read_only (IdeBuffer *self,
|
|
gboolean read_only)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
read_only = !!read_only;
|
|
|
|
if (read_only != self->read_only)
|
|
{
|
|
self->read_only = read_only;
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_READ_ONLY]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_style_scheme_name:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the name of the #GtkSourceStyleScheme from the #IdeBuffer:style-scheme
|
|
* property.
|
|
*
|
|
* Returns: (nullable): a string containing the style scheme or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
const gchar *
|
|
ide_buffer_get_style_scheme_name (IdeBuffer *self)
|
|
{
|
|
GtkSourceStyleScheme *scheme;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
if ((scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (self))))
|
|
return gtk_source_style_scheme_get_id (scheme);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_set_style_scheme_name:
|
|
* @self: an #IdeBuffer
|
|
* @style_scheme_name: (nullable): string containing the style scheme's name
|
|
*
|
|
* Sets the #IdeBuffer:style-scheme property by locating the style scheme
|
|
* matching @style_scheme_name.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_set_style_scheme_name (IdeBuffer *self,
|
|
const gchar *style_scheme_name)
|
|
{
|
|
GtkSourceStyleSchemeManager *manager;
|
|
GtkSourceStyleScheme *scheme;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
if ((manager = gtk_source_style_scheme_manager_get_default ()) &&
|
|
(scheme = gtk_source_style_scheme_manager_get_scheme (manager, style_scheme_name)))
|
|
gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (self), scheme);
|
|
else
|
|
gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (self), NULL);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_dup_title:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets a string to represent the title of the buffer. An attempt is made to
|
|
* make this relative to the project workdir if possible.
|
|
*
|
|
* Returns: (transfer full): a string containing a title
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gchar *
|
|
ide_buffer_dup_title (IdeBuffer *self)
|
|
{
|
|
g_autoptr(IdeContext) context = NULL;
|
|
g_autoptr(GFile) workdir = NULL;
|
|
g_autoptr(GFile) home = NULL;
|
|
GFile *file;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
file = ide_buffer_get_file (self);
|
|
|
|
if (self->is_temporary)
|
|
return g_file_get_basename (file);
|
|
|
|
/* Unlikely, but better to be safe */
|
|
if (!(context = ide_buffer_ref_context (self)))
|
|
return g_file_get_basename (file);
|
|
|
|
workdir = ide_context_ref_workdir (context);
|
|
|
|
if (g_file_has_prefix (file, workdir))
|
|
return g_file_get_relative_path (workdir, file);
|
|
|
|
home = g_file_new_for_path (g_get_home_dir ());
|
|
|
|
if (g_file_has_prefix (file, home))
|
|
{
|
|
g_autofree gchar *relative = g_file_get_relative_path (home, file);
|
|
return g_strdup_printf ("~/%s", relative);
|
|
}
|
|
|
|
if (!g_file_is_native (file))
|
|
return g_file_get_uri (file);
|
|
else
|
|
return g_file_get_path (file);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_highlight_diagnostics:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Checks if diagnostics should be highlighted.
|
|
*
|
|
* Returns: %TRUE if diagnostics should be highlighted
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_get_highlight_diagnostics (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
return self->highlight_diagnostics;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_set_highlight_diagnostics:
|
|
* @self: an #IdeBuffer
|
|
* @highlight_diagnostics: if diagnostics should be highlighted
|
|
*
|
|
* Sets the #IdeBuffer:highlight-diagnostics property.
|
|
*
|
|
* If set to %TRUE, diagnostics will be styled in the buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_set_highlight_diagnostics (IdeBuffer *self,
|
|
gboolean highlight_diagnostics)
|
|
{
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
highlight_diagnostics = !!highlight_diagnostics;
|
|
|
|
if (self->highlight_diagnostics != highlight_diagnostics)
|
|
{
|
|
ide_buffer_clear_diagnostics (self);
|
|
self->highlight_diagnostics = highlight_diagnostics;
|
|
ide_buffer_apply_diagnostics (self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HIGHLIGHT_DIAGNOSTICS]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_iter_location:
|
|
* @self: an #IdeBuffer
|
|
* @iter: a #GtkTextIter
|
|
*
|
|
* Gets an #IdeLocation for the position represented by @iter.
|
|
*
|
|
* Returns: (transfer full): an #IdeLocation
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeLocation *
|
|
ide_buffer_get_iter_location (IdeBuffer *self,
|
|
const GtkTextIter *iter)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
g_return_val_if_fail (iter != NULL, NULL);
|
|
|
|
return ide_location_new_with_offset (ide_buffer_get_file (self),
|
|
gtk_text_iter_get_line (iter),
|
|
gtk_text_iter_get_line_offset (iter),
|
|
gtk_text_iter_get_offset (iter));
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_selection_range:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets an #IdeRange to represent the current buffer selection.
|
|
*
|
|
* Returns: (transfer full): an #IdeRange
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeRange *
|
|
ide_buffer_get_selection_range (IdeBuffer *self)
|
|
{
|
|
g_autoptr(IdeLocation) begin = NULL;
|
|
g_autoptr(IdeLocation) end = NULL;
|
|
GtkTextIter begin_iter;
|
|
GtkTextIter end_iter;
|
|
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self), &begin_iter, &end_iter);
|
|
gtk_text_iter_order (&begin_iter, &end_iter);
|
|
|
|
begin = ide_buffer_get_iter_location (self, &begin_iter);
|
|
end = ide_buffer_get_iter_location (self, &end_iter);
|
|
|
|
return ide_range_new (begin, end);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_change_count:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the monotonic change count for the buffer.
|
|
*
|
|
* Returns: the change count for the buffer
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
guint
|
|
ide_buffer_get_change_count (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), 0);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), 0);
|
|
|
|
return self->change_count;
|
|
}
|
|
|
|
static gboolean
|
|
ide_buffer_settled_cb (gpointer user_data)
|
|
{
|
|
IdeBuffer *self = user_data;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
self->settling_source = 0;
|
|
g_signal_emit (self, signals [CHANGE_SETTLED], 0);
|
|
|
|
if (self->addins != NULL && self->enable_addins)
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
_ide_buffer_addin_change_settled_cb,
|
|
self);
|
|
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_delay_settling (IdeBuffer *self)
|
|
{
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
g_clear_handle_id (&self->settling_source, g_source_remove);
|
|
self->settling_source = gdk_threads_add_timeout (SETTLING_DELAY_MSEC,
|
|
ide_buffer_settled_cb,
|
|
self);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_set_diagnostics:
|
|
* @self: an #IdeBuffer
|
|
* @diagnostics: (nullable): an #IdeDiagnostics
|
|
*
|
|
* Sets the #IdeDiagnostics for the buffer. These will be used to highlight
|
|
* the buffer for errors and warnings if #IdeBuffer:highlight-diagnostics
|
|
* is %TRUE.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_set_diagnostics (IdeBuffer *self,
|
|
IdeDiagnostics *diagnostics)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (!diagnostics || IDE_IS_DIAGNOSTICS (diagnostics));
|
|
|
|
if (diagnostics == self->diagnostics)
|
|
return;
|
|
|
|
if (self->diagnostics)
|
|
{
|
|
ide_buffer_clear_diagnostics (self);
|
|
g_clear_object (&self->diagnostics);
|
|
}
|
|
|
|
if (diagnostics)
|
|
{
|
|
IdeCodeActionProvider *code_action_provider;
|
|
self->diagnostics = g_object_ref (diagnostics);
|
|
code_action_provider = ide_extension_adapter_get_extension (self->code_action_provider);
|
|
if (code_action_provider)
|
|
ide_code_action_provider_set_diagnostics (IDE_CODE_ACTION_PROVIDER (code_action_provider), self->diagnostics);
|
|
ide_buffer_apply_diagnostics (self);
|
|
}
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_DIAGNOSTICS]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_DIAGNOSTICS]);
|
|
|
|
_ide_buffer_line_flags_changed (self);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_diagnostics:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the #IdeDiagnostics for the buffer if any have been registered.
|
|
*
|
|
* Returns: (transfer none) (nullable): an #IdeDiagnostics or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeDiagnostics *
|
|
ide_buffer_get_diagnostics (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
return self->diagnostics;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_has_diagnostics:
|
|
* @self: a #IdeBuffer
|
|
*
|
|
* Returns %TRUE if any diagnostics have been registered for the buffer.
|
|
*
|
|
* Returns: %TRUE if there are a non-zero number of diagnostics.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_has_diagnostics (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
if (self->diagnostics)
|
|
return g_list_model_get_n_items (G_LIST_MODEL (self->diagnostics)) > 0;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_clear_diagnostics (IdeBuffer *self)
|
|
{
|
|
GtkTextTagTable *table;
|
|
GtkTextTag *tag;
|
|
GtkTextIter begin;
|
|
GtkTextIter end;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
if (!self->highlight_diagnostics)
|
|
return;
|
|
|
|
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (self), &begin, &end);
|
|
|
|
table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (self));
|
|
|
|
if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_NOTE)))
|
|
dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
|
|
|
|
if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_WARNING)))
|
|
dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
|
|
|
|
if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_DEPRECATED)))
|
|
dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
|
|
|
|
if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_UNUSED)))
|
|
dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
|
|
|
|
if (NULL != (tag = gtk_text_tag_table_lookup (table, TAG_ERROR)))
|
|
dzl_gtk_text_buffer_remove_tag (GTK_TEXT_BUFFER (self), tag, &begin, &end, TRUE);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_apply_diagnostic (IdeBuffer *self,
|
|
IdeDiagnostic *diagnostic)
|
|
{
|
|
IdeDiagnosticSeverity severity;
|
|
const gchar *tag_name = NULL;
|
|
IdeLocation *location;
|
|
guint n_ranges;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (IDE_IS_DIAGNOSTIC (diagnostic));
|
|
|
|
severity = ide_diagnostic_get_severity (diagnostic);
|
|
|
|
switch (severity)
|
|
{
|
|
case IDE_DIAGNOSTIC_NOTE:
|
|
tag_name = TAG_NOTE;
|
|
break;
|
|
|
|
case IDE_DIAGNOSTIC_UNUSED:
|
|
tag_name = TAG_UNUSED;
|
|
break;
|
|
|
|
case IDE_DIAGNOSTIC_DEPRECATED:
|
|
tag_name = TAG_DEPRECATED;
|
|
break;
|
|
|
|
case IDE_DIAGNOSTIC_WARNING:
|
|
tag_name = TAG_WARNING;
|
|
break;
|
|
|
|
case IDE_DIAGNOSTIC_ERROR:
|
|
case IDE_DIAGNOSTIC_FATAL:
|
|
tag_name = TAG_ERROR;
|
|
break;
|
|
|
|
case IDE_DIAGNOSTIC_IGNORED:
|
|
default:
|
|
return;
|
|
}
|
|
|
|
n_ranges = ide_diagnostic_get_n_ranges (diagnostic);
|
|
if (n_ranges == 0)
|
|
{
|
|
if ((location = ide_diagnostic_get_location (diagnostic)))
|
|
{
|
|
GtkTextIter begin_iter;
|
|
GtkTextIter end_iter;
|
|
|
|
ide_buffer_get_iter_at_location (self, &begin_iter, location);
|
|
end_iter = begin_iter;
|
|
|
|
if (gtk_text_iter_ends_line (&end_iter))
|
|
{
|
|
gtk_text_iter_backward_char (&begin_iter);
|
|
}
|
|
else
|
|
{
|
|
/* Only highlight to next word */
|
|
if (_ide_source_iter_inside_word (&end_iter) ||
|
|
_ide_source_iter_starts_word (&end_iter))
|
|
_ide_source_iter_forward_visible_word_end (&end_iter);
|
|
else
|
|
gtk_text_iter_forward_to_line_end (&end_iter);
|
|
}
|
|
|
|
gtk_text_buffer_apply_tag_by_name (GTK_TEXT_BUFFER (self), tag_name, &begin_iter, &end_iter);
|
|
}
|
|
}
|
|
|
|
for (guint i = 0; i < n_ranges; i++)
|
|
{
|
|
GtkTextIter begin_iter;
|
|
GtkTextIter end_iter;
|
|
IdeLocation *begin;
|
|
IdeLocation *end;
|
|
IdeRange *range;
|
|
GFile *file;
|
|
|
|
range = ide_diagnostic_get_range (diagnostic, i);
|
|
begin = ide_range_get_begin (range);
|
|
end = ide_range_get_end (range);
|
|
file = ide_location_get_file (begin);
|
|
|
|
if (file != NULL)
|
|
{
|
|
if (!g_file_equal (file, ide_buffer_get_file (self)))
|
|
continue;
|
|
}
|
|
|
|
ide_buffer_get_iter_at_location (self, &begin_iter, begin);
|
|
ide_buffer_get_iter_at_location (self, &end_iter, end);
|
|
|
|
if (gtk_text_iter_equal (&begin_iter, &end_iter))
|
|
{
|
|
if (!gtk_text_iter_ends_line (&end_iter))
|
|
gtk_text_iter_forward_char (&end_iter);
|
|
else
|
|
gtk_text_iter_backward_char (&begin_iter);
|
|
}
|
|
|
|
gtk_text_buffer_apply_tag_by_name (GTK_TEXT_BUFFER (self), tag_name, &begin_iter, &end_iter);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_buffer_apply_diagnostics (IdeBuffer *self)
|
|
{
|
|
guint n_items;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
if (!self->highlight_diagnostics)
|
|
return;
|
|
|
|
if (self->diagnostics == NULL)
|
|
return;
|
|
|
|
n_items = g_list_model_get_n_items (G_LIST_MODEL (self->diagnostics));
|
|
|
|
for (guint i = 0; i < n_items; i++)
|
|
{
|
|
g_autoptr(IdeDiagnostic) diagnostic = NULL;
|
|
|
|
diagnostic = g_list_model_get_item (G_LIST_MODEL (self->diagnostics), i);
|
|
ide_buffer_apply_diagnostic (self, diagnostic);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_iter_at_location:
|
|
* @self: an #IdeBuffer
|
|
* @iter: (out): a #GtkTextIter
|
|
* @location: a #IdeLocation
|
|
*
|
|
* Set @iter to the position designated by @location.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_get_iter_at_location (IdeBuffer *self,
|
|
GtkTextIter *iter,
|
|
IdeLocation *location)
|
|
{
|
|
gint line;
|
|
gint line_offset;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (iter != NULL);
|
|
g_return_if_fail (location != NULL);
|
|
|
|
line = ide_location_get_line (location);
|
|
line_offset = ide_location_get_line_offset (location);
|
|
|
|
gtk_text_buffer_get_iter_at_line_offset (GTK_TEXT_BUFFER (self),
|
|
iter,
|
|
MAX (0, line),
|
|
MAX (0, line_offset));
|
|
|
|
/* Advance to first non-space if offset < 0 */
|
|
if (line_offset < 0)
|
|
{
|
|
while (!gtk_text_iter_ends_line (iter))
|
|
{
|
|
if (!g_unichar_isspace (gtk_text_iter_get_char (iter)))
|
|
break;
|
|
gtk_text_iter_forward_char (iter);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_change_monitor:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the #IdeBuffer:change-monitor for the buffer.
|
|
*
|
|
* Returns: (transfer none) (nullable): an #IdeBufferChangeMonitor or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeBufferChangeMonitor *
|
|
ide_buffer_get_change_monitor (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
return self->change_monitor;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_set_change_monitor:
|
|
* @self: an #IdeBuffer
|
|
* @change_monitor: (nullable): an #IdeBufferChangeMonitor or %NULL
|
|
*
|
|
* Sets an #IdeBufferChangeMonitor to use for the buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_set_change_monitor (IdeBuffer *self,
|
|
IdeBufferChangeMonitor *change_monitor)
|
|
{
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (!change_monitor || IDE_IS_BUFFER_CHANGE_MONITOR (change_monitor));
|
|
|
|
if (g_set_object (&self->change_monitor, change_monitor))
|
|
{
|
|
/* Destroy change monitor with us if we can */
|
|
if (change_monitor && ide_object_is_root (IDE_OBJECT (change_monitor)))
|
|
{
|
|
IdeObjectBox *box = ide_object_box_from_object (G_OBJECT (self));
|
|
ide_object_append (IDE_OBJECT (box), IDE_OBJECT (change_monitor));
|
|
}
|
|
|
|
if (change_monitor != NULL)
|
|
ide_buffer_change_monitor_reload (change_monitor);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CHANGE_MONITOR]);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
ide_buffer_can_do_newline_hack (IdeBuffer *self,
|
|
guint len)
|
|
{
|
|
guint next_pow2;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
/*
|
|
* If adding two bytes to our length (one for \n and one for \0) is still
|
|
* under the next power of two, then we can avoid making a copy of the buffer
|
|
* when saving the buffer to our drafts.
|
|
*
|
|
* HACK: This relies on the fact that GtkTextBuffer returns a GString
|
|
* allocated string which grows the string in powers of two.
|
|
*/
|
|
|
|
if ((len == 0) || (len & (len - 1)) == 0)
|
|
return FALSE;
|
|
|
|
next_pow2 = len;
|
|
next_pow2 |= next_pow2 >> 1;
|
|
next_pow2 |= next_pow2 >> 2;
|
|
next_pow2 |= next_pow2 >> 4;
|
|
next_pow2 |= next_pow2 >> 8;
|
|
next_pow2 |= next_pow2 >> 16;
|
|
next_pow2++;
|
|
|
|
return ((len + 2) < next_pow2);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_dup_content:
|
|
* @self: an #IdeBuffer.
|
|
*
|
|
* Gets the contents of the buffer as GBytes.
|
|
*
|
|
* By using this function to get the bytes, you allow #IdeBuffer to avoid
|
|
* calculating the buffer text unnecessarily, potentially saving on
|
|
* allocations.
|
|
*
|
|
* Additionally, this allows the buffer to update the state in #IdeUnsavedFiles
|
|
* if the content is out of sync.
|
|
*
|
|
* Returns: (transfer full): a #GBytes containing the buffer content.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
GBytes *
|
|
ide_buffer_dup_content (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
if (self->content == NULL)
|
|
{
|
|
g_autoptr(IdeContext) context = NULL;
|
|
IdeUnsavedFiles *unsaved_files;
|
|
GtkTextIter begin;
|
|
GtkTextIter end;
|
|
GFile *file;
|
|
gchar *text;
|
|
gsize len;
|
|
|
|
gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (self), &begin, &end);
|
|
text = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (self), &begin, &end, TRUE);
|
|
|
|
/*
|
|
* If implicit newline is set, add a \n in place of the \0 and avoid
|
|
* duplicating the buffer. Make sure to track length beforehand, since we
|
|
* would overwrite afterwards. Since conversion to \r\n is dealth with
|
|
* during save operations, this should be fine for both. The unsaved
|
|
* files will restore to a buffer, for which \n is acceptable.
|
|
*/
|
|
len = strlen (text);
|
|
if (gtk_source_buffer_get_implicit_trailing_newline (GTK_SOURCE_BUFFER (self)) &&
|
|
(len == 0 || text[len - 1] != '\n'))
|
|
{
|
|
if (!ide_buffer_can_do_newline_hack (self, len))
|
|
{
|
|
gchar *copy;
|
|
|
|
copy = g_malloc (len + 2);
|
|
memcpy (copy, text, len);
|
|
g_free (text);
|
|
text = copy;
|
|
}
|
|
|
|
text [len] = '\n';
|
|
text [++len] = '\0';
|
|
}
|
|
|
|
/*
|
|
* We pass a buffer that is longer than the length we tell GBytes about.
|
|
* This way, compilers that don't want to see the trailing \0 can ignore
|
|
* that data, but compilers that rely on valid C strings can also rely
|
|
* on the buffer to be valid.
|
|
*/
|
|
self->content = g_bytes_new_take (g_steal_pointer (&text), len);
|
|
|
|
/* Only persist if we have access to the object tree */
|
|
if (self->buffer_manager != NULL &&
|
|
!ide_object_in_destruction (IDE_OBJECT (self->buffer_manager)))
|
|
{
|
|
file = ide_buffer_get_file (self);
|
|
context = ide_buffer_ref_context (IDE_BUFFER (self));
|
|
unsaved_files = ide_unsaved_files_from_context (context);
|
|
ide_unsaved_files_update (unsaved_files, file, self->content);
|
|
}
|
|
}
|
|
|
|
return g_bytes_ref (self->content);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_format_selection_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
IdeFormatter *formatter = (IdeFormatter *)object;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(IdeTask) task = user_data;
|
|
|
|
g_assert (IDE_IS_FORMATTER (object));
|
|
g_assert (G_IS_ASYNC_RESULT (result));
|
|
g_assert (IDE_IS_TASK (task));
|
|
|
|
if (!ide_formatter_format_finish (formatter, result, &error))
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
else
|
|
ide_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_format_selection_range_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
IdeFormatter *formatter = (IdeFormatter *)object;
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(IdeTask) task = user_data;
|
|
|
|
g_assert (IDE_IS_FORMATTER (object));
|
|
g_assert (G_IS_ASYNC_RESULT (result));
|
|
g_assert (IDE_IS_TASK (task));
|
|
|
|
if (!ide_formatter_format_range_finish (formatter, result, &error))
|
|
ide_task_return_error (task, g_steal_pointer (&error));
|
|
else
|
|
ide_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_format_selection_async:
|
|
* @self: an #IdeBuffer
|
|
* @options: options for the formatting
|
|
* @cancellable: (nullable): a #GCancellable, or %NULL
|
|
* @callback: the callback upon completion
|
|
* @user_data: user data for @callback
|
|
*
|
|
* Formats the selection using an available #IdeFormatter for the buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_format_selection_async (IdeBuffer *self,
|
|
IdeFormatterOptions *options,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(IdeTask) task = NULL;
|
|
IdeFormatter *formatter;
|
|
GtkTextIter begin;
|
|
GtkTextIter end;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (IDE_IS_FORMATTER_OPTIONS (options));
|
|
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
|
|
task = ide_task_new (self, cancellable, callback, user_data);
|
|
ide_task_set_source_tag (task, ide_buffer_format_selection_async);
|
|
|
|
if (!(formatter = ide_extension_adapter_get_extension (self->formatter)))
|
|
{
|
|
const gchar *language_id = ide_buffer_get_language_id (self);
|
|
|
|
if (language_id == NULL)
|
|
language_id = "none";
|
|
|
|
ide_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"No formatter registered for language %s",
|
|
language_id);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
if (!gtk_text_buffer_get_selection_bounds (GTK_TEXT_BUFFER (self), &begin, &end))
|
|
{
|
|
ide_formatter_format_async (formatter,
|
|
self,
|
|
options,
|
|
cancellable,
|
|
ide_buffer_format_selection_cb,
|
|
g_steal_pointer (&task));
|
|
IDE_EXIT;
|
|
}
|
|
|
|
gtk_text_iter_order (&begin, &end);
|
|
|
|
ide_formatter_format_range_async (formatter,
|
|
self,
|
|
options,
|
|
&begin,
|
|
&end,
|
|
cancellable,
|
|
ide_buffer_format_selection_range_cb,
|
|
g_steal_pointer (&task));
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_format_selection_finish:
|
|
* @self: an #IdeBuffer
|
|
* @result: a #GAsyncResult
|
|
* @error: a location for a #GError, or %NULL
|
|
*
|
|
* Completes an asynchronous request to ide_buffer_format_selection_async().
|
|
*
|
|
* Returns: %TRUE if successful; otherwise %FALSE and @error is set.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_format_selection_finish (IdeBuffer *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
gboolean ret;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
g_return_val_if_fail (IDE_IS_TASK (result), FALSE);
|
|
|
|
ret = ide_task_propagate_boolean (IDE_TASK (result), error);
|
|
|
|
IDE_RETURN (ret);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_query_code_action_cb(GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
IdeCodeActionProvider *code_action_provider = (IdeCodeActionProvider *)object;
|
|
|
|
g_autoptr(GError) error = NULL;
|
|
g_autoptr(IdeTask) task = user_data;
|
|
g_autoptr(GPtrArray) code_actions = NULL;
|
|
|
|
g_assert(IDE_IS_CODE_ACTION_PROVIDER(object));
|
|
g_assert(G_IS_ASYNC_RESULT(result));
|
|
g_assert(IDE_IS_TASK(task));
|
|
|
|
code_actions = ide_code_action_provider_query_finish(code_action_provider, result, &error);
|
|
|
|
if (!code_actions)
|
|
ide_task_return_error(task, g_steal_pointer(&error));
|
|
else
|
|
ide_task_return_pointer(task, g_steal_pointer(&code_actions), g_ptr_array_unref);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_code_action_query_async:
|
|
* @self: an #IdeBuffer
|
|
* @cancellable: (nullable): a #GCancellable, or %NULL
|
|
* @callback: the callback upon completion
|
|
* @user_data: user data for @callback
|
|
*
|
|
* Queries for code actions in the current buffer.
|
|
*
|
|
* Since: 42.0
|
|
*/
|
|
void
|
|
ide_buffer_code_action_query_async(IdeBuffer *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(IdeTask) task = NULL;
|
|
IdeCodeActionProvider *code_action_provider;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_return_if_fail(IDE_IS_MAIN_THREAD());
|
|
g_return_if_fail(IDE_IS_BUFFER(self));
|
|
g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable));
|
|
|
|
task = ide_task_new(self, cancellable, callback, user_data);
|
|
ide_task_set_source_tag(task, ide_buffer_code_action_query_async);
|
|
|
|
if (!(code_action_provider = ide_extension_adapter_get_extension(self->code_action_provider)))
|
|
{
|
|
const gchar *language_id = ide_buffer_get_language_id(self);
|
|
|
|
if (language_id == NULL)
|
|
language_id = "none";
|
|
|
|
ide_task_return_new_error(task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
"No code action provider registered for language %s",
|
|
language_id);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
ide_code_action_provider_query_async(code_action_provider,
|
|
self,
|
|
cancellable,
|
|
ide_buffer_query_code_action_cb,
|
|
g_steal_pointer(&task));
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_code_action_query_finish:
|
|
* @self: an #IdeBuffer
|
|
* @result: a #GAsyncResult
|
|
* @error: a location for a #GError, or %NULL
|
|
*
|
|
* Completes an asynchronous request to ide_buffer_query_code_action_async().
|
|
*
|
|
* Returns: (transfer full) (element-type IdeCodeAction): a #GPtrArray of #IdeCodeAction.
|
|
*
|
|
* Since: 42.0
|
|
*/
|
|
GPtrArray*
|
|
ide_buffer_code_action_query_finish(IdeBuffer *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
GPtrArray* ret;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_return_val_if_fail(IDE_IS_MAIN_THREAD(), NULL);
|
|
g_return_val_if_fail(IDE_IS_BUFFER(self), NULL);
|
|
g_return_val_if_fail(IDE_IS_TASK(result), NULL);
|
|
|
|
ret = ide_task_propagate_pointer(IDE_TASK(result), error);
|
|
|
|
IDE_RETURN(ret);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_insert_location:
|
|
*
|
|
* Gets the location of the insert mark as an #IdeLocation.
|
|
*
|
|
* Returns: (transfer full): An #IdeLocation
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeLocation *
|
|
ide_buffer_get_insert_location (IdeBuffer *self)
|
|
{
|
|
GtkTextMark *mark;
|
|
GtkTextIter iter;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self));
|
|
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), &iter, mark);
|
|
|
|
return ide_buffer_get_iter_location (self, &iter);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_word_at_iter:
|
|
* @self: an #IdeBuffer.
|
|
* @iter: a #GtkTextIter.
|
|
*
|
|
* Gets the word found under the position denoted by @iter.
|
|
*
|
|
* Returns: (transfer full): A newly allocated string.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gchar *
|
|
ide_buffer_get_word_at_iter (IdeBuffer *self,
|
|
const GtkTextIter *iter)
|
|
{
|
|
GtkTextIter begin;
|
|
GtkTextIter end;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
g_return_val_if_fail (iter != NULL, NULL);
|
|
|
|
end = begin = *iter;
|
|
|
|
if (!_ide_source_iter_starts_word (&begin))
|
|
_ide_source_iter_backward_extra_natural_word_start (&begin);
|
|
|
|
if (!_ide_source_iter_ends_word (&end))
|
|
_ide_source_iter_forward_extra_natural_word_end (&end);
|
|
|
|
return gtk_text_iter_get_slice (&begin, &end);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_rename_provider:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the #IdeRenameProvider for this buffer, or %NULL.
|
|
*
|
|
* Returns: (nullable) (transfer none): An #IdeRenameProvider or %NULL if
|
|
* there is no #IdeRenameProvider that can statisfy the buffer.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeRenameProvider *
|
|
ide_buffer_get_rename_provider (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
if (self->rename_provider != NULL)
|
|
return ide_extension_adapter_get_extension (self->rename_provider);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_file_settings:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the #IdeBuffer:file-settings property.
|
|
*
|
|
* The #IdeFileSettings are updated when changes to the file or language
|
|
* syntax are chnaged.
|
|
*
|
|
* Returns: (transfer none) (nullable): an #IdeFileSettings or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeFileSettings *
|
|
ide_buffer_get_file_settings (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
return self->file_settings;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_ref_context:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Locates the #IdeContext for the buffer and returns it.
|
|
*
|
|
* Returns: (transfer full): an #IdeContext
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeContext *
|
|
ide_buffer_ref_context (IdeBuffer *self)
|
|
{
|
|
g_autoptr(IdeObject) root = NULL;
|
|
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
if (self->buffer_manager != NULL)
|
|
root = ide_object_ref_root (IDE_OBJECT (self->buffer_manager));
|
|
|
|
g_return_val_if_fail (root != NULL, NULL);
|
|
g_return_val_if_fail (IDE_IS_CONTEXT (root), NULL);
|
|
|
|
return IDE_CONTEXT (g_steal_pointer (&root));
|
|
}
|
|
|
|
static void
|
|
apply_style (GtkTextTag *tag,
|
|
const gchar *first_property,
|
|
...)
|
|
{
|
|
va_list args;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (!tag || GTK_IS_TEXT_TAG (tag));
|
|
g_assert (first_property != NULL);
|
|
|
|
if (tag == NULL)
|
|
return;
|
|
|
|
va_start (args, first_property);
|
|
g_object_set_valist (G_OBJECT (tag), first_property, args);
|
|
va_end (args);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_notify_style_scheme (IdeBuffer *self,
|
|
GParamSpec *pspec,
|
|
gpointer unused)
|
|
{
|
|
GtkSourceStyleScheme *style_scheme;
|
|
GtkTextTagTable *table;
|
|
GdkRGBA deprecated_rgba;
|
|
GdkRGBA unused_rgba;
|
|
GdkRGBA error_rgba;
|
|
GdkRGBA note_rgba;
|
|
GdkRGBA warning_rgba;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (pspec != NULL);
|
|
|
|
style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (self));
|
|
table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (self));
|
|
|
|
#define GET_TAG(name) (gtk_text_tag_table_lookup(table, name))
|
|
|
|
if (style_scheme != NULL)
|
|
{
|
|
/* These are a fall-back if our style scheme isn't installed. */
|
|
gdk_rgba_parse (&deprecated_rgba, DEPRECATED_COLOR);
|
|
gdk_rgba_parse (&unused_rgba, UNUSED_COLOR);
|
|
gdk_rgba_parse (&error_rgba, ERROR_COLOR);
|
|
gdk_rgba_parse (¬e_rgba, NOTE_COLOR);
|
|
gdk_rgba_parse (&warning_rgba, WARNING_COLOR);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme,
|
|
TAG_DEPRECATED,
|
|
GET_TAG (TAG_DEPRECATED)))
|
|
apply_style (GET_TAG (TAG_DEPRECATED),
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", &deprecated_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme,
|
|
TAG_UNUSED,
|
|
GET_TAG (TAG_UNUSED)))
|
|
apply_style (GET_TAG (TAG_UNUSED),
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", &unused_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme,
|
|
TAG_ERROR,
|
|
GET_TAG (TAG_ERROR)))
|
|
apply_style (GET_TAG (TAG_ERROR),
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", &error_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme,
|
|
TAG_NOTE,
|
|
GET_TAG (TAG_NOTE)))
|
|
apply_style (GET_TAG (TAG_NOTE),
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", ¬e_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme,
|
|
TAG_WARNING,
|
|
GET_TAG (TAG_WARNING)))
|
|
apply_style (GET_TAG (TAG_WARNING),
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", &warning_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme,
|
|
TAG_SNIPPET_TAB_STOP,
|
|
GET_TAG (TAG_SNIPPET_TAB_STOP)))
|
|
apply_style (GET_TAG (TAG_SNIPPET_TAB_STOP),
|
|
"underline", PANGO_UNDERLINE_SINGLE,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme,
|
|
TAG_DEFINITION,
|
|
GET_TAG (TAG_DEFINITION)))
|
|
apply_style (GET_TAG (TAG_DEFINITION),
|
|
"underline", PANGO_UNDERLINE_SINGLE,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme,
|
|
TAG_CURRENT_BKPT,
|
|
GET_TAG (TAG_CURRENT_BKPT)))
|
|
apply_style (GET_TAG (TAG_CURRENT_BKPT),
|
|
"paragraph-background", CURRENT_BKPT_BG,
|
|
"foreground", CURRENT_BKPT_FG,
|
|
NULL);
|
|
}
|
|
|
|
#undef GET_TAG
|
|
|
|
if (self->addins != NULL && self->enable_addins)
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
_ide_buffer_addin_style_scheme_changed_cb,
|
|
self);
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_STYLE_SCHEME_NAME]);
|
|
|
|
}
|
|
|
|
static void
|
|
ide_buffer_on_tag_added (IdeBuffer *self,
|
|
GtkTextTag *tag,
|
|
GtkTextTagTable *table)
|
|
{
|
|
GtkTextTag *chunk_tag;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (GTK_IS_TEXT_TAG (tag));
|
|
g_assert (GTK_IS_TEXT_TAG_TABLE (table));
|
|
|
|
/* Adjust priority of our tab-stop tag. */
|
|
chunk_tag = gtk_text_tag_table_lookup (table, TAG_SNIPPET_TAB_STOP);
|
|
if (chunk_tag != NULL)
|
|
gtk_text_tag_set_priority (chunk_tag,
|
|
gtk_text_tag_table_get_size (table) - 1);
|
|
}
|
|
|
|
static void
|
|
ide_buffer_init_tags (IdeBuffer *self)
|
|
{
|
|
GtkTextTagTable *tag_table;
|
|
GtkSourceStyleScheme *style_scheme;
|
|
g_autoptr(GtkTextTag) deprecated_tag = NULL;
|
|
g_autoptr(GtkTextTag) unused_tag = NULL;
|
|
g_autoptr(GtkTextTag) error_tag = NULL;
|
|
g_autoptr(GtkTextTag) note_tag = NULL;
|
|
g_autoptr(GtkTextTag) warning_tag = NULL;
|
|
GdkRGBA deprecated_rgba;
|
|
GdkRGBA unused_rgba;
|
|
GdkRGBA error_rgba;
|
|
GdkRGBA note_rgba;
|
|
GdkRGBA warning_rgba;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
tag_table = gtk_text_buffer_get_tag_table (GTK_TEXT_BUFFER (self));
|
|
style_scheme = gtk_source_buffer_get_style_scheme (GTK_SOURCE_BUFFER (self));
|
|
|
|
/* These are fall-back if our style scheme isn't installed. */
|
|
gdk_rgba_parse (&deprecated_rgba, DEPRECATED_COLOR);
|
|
gdk_rgba_parse (&unused_rgba, UNUSED_COLOR);
|
|
gdk_rgba_parse (&error_rgba, ERROR_COLOR);
|
|
gdk_rgba_parse (¬e_rgba, NOTE_COLOR);
|
|
gdk_rgba_parse (&warning_rgba, WARNING_COLOR);
|
|
|
|
/*
|
|
* NOTE:
|
|
*
|
|
* The tag table assigns priority upon insert. Each successive insert
|
|
* is higher priority than the last.
|
|
*/
|
|
|
|
deprecated_tag = gtk_text_tag_new (TAG_DEPRECATED);
|
|
unused_tag = gtk_text_tag_new (TAG_UNUSED);
|
|
error_tag = gtk_text_tag_new (TAG_ERROR);
|
|
note_tag = gtk_text_tag_new (TAG_NOTE);
|
|
warning_tag = gtk_text_tag_new (TAG_WARNING);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme, TAG_DEPRECATED, deprecated_tag))
|
|
apply_style (deprecated_tag,
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", &deprecated_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme, TAG_UNUSED, unused_tag))
|
|
apply_style (unused_tag,
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", &unused_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme, TAG_ERROR, error_tag))
|
|
apply_style (error_tag,
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", &error_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme, TAG_NOTE, note_tag))
|
|
apply_style (note_tag,
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", ¬e_rgba,
|
|
NULL);
|
|
|
|
if (!ide_source_style_scheme_apply_style (style_scheme, TAG_NOTE, warning_tag))
|
|
apply_style (warning_tag,
|
|
"underline", PANGO_UNDERLINE_ERROR,
|
|
"underline-rgba", &warning_rgba,
|
|
NULL);
|
|
|
|
gtk_text_tag_table_add (tag_table, deprecated_tag);
|
|
gtk_text_tag_table_add (tag_table, unused_tag);
|
|
gtk_text_tag_table_add (tag_table, error_tag);
|
|
gtk_text_tag_table_add (tag_table, note_tag);
|
|
gtk_text_tag_table_add (tag_table, warning_tag);
|
|
|
|
gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (self), TAG_SNIPPET_TAB_STOP,
|
|
NULL);
|
|
gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (self), TAG_DEFINITION,
|
|
"underline", PANGO_UNDERLINE_SINGLE,
|
|
NULL);
|
|
gtk_text_buffer_create_tag (GTK_TEXT_BUFFER (self), TAG_CURRENT_BKPT,
|
|
"paragraph-background", CURRENT_BKPT_BG,
|
|
"foreground", CURRENT_BKPT_FG,
|
|
NULL);
|
|
|
|
g_signal_connect_object (tag_table,
|
|
"tag-added",
|
|
G_CALLBACK (ide_buffer_on_tag_added),
|
|
self,
|
|
G_CONNECT_SWAPPED);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_formatter:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets an #IdeFormatter for the buffer, if any.
|
|
*
|
|
* Returns: (transfer none) (nullable): an #IdeFormatter or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeFormatter *
|
|
ide_buffer_get_formatter (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
if (self->formatter == NULL)
|
|
return NULL;
|
|
|
|
return ide_extension_adapter_get_extension (self->formatter);
|
|
}
|
|
|
|
void
|
|
_ide_buffer_sync_to_unsaved_files (IdeBuffer *self)
|
|
{
|
|
GBytes *content;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
if ((content = ide_buffer_dup_content (self)))
|
|
g_bytes_unref (content);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_rehighlight:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Force @self to rebuild the highlighted words.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_rehighlight (IdeBuffer *self)
|
|
{
|
|
IDE_ENTRY;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
/* In case we are disposing */
|
|
if (self->highlight_engine == NULL || ide_buffer_get_loading (self))
|
|
IDE_EXIT;
|
|
|
|
if (gtk_source_buffer_get_highlight_syntax (GTK_SOURCE_BUFFER (self)))
|
|
ide_highlight_engine_rebuild (self->highlight_engine);
|
|
else
|
|
ide_highlight_engine_clear (self->highlight_engine);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
static void
|
|
ide_buffer_get_symbol_at_location_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
IdeSymbolResolver *symbol_resolver = (IdeSymbolResolver *)object;
|
|
g_autoptr(IdeSymbol) symbol = NULL;
|
|
g_autoptr(IdeTask) task = user_data;
|
|
g_autoptr(GError) error = NULL;
|
|
LookUpSymbolData *data;
|
|
IdeBuffer *self;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_SYMBOL_RESOLVER (symbol_resolver));
|
|
g_assert (G_IS_ASYNC_RESULT (result));
|
|
g_assert (IDE_IS_TASK (task));
|
|
|
|
data = ide_task_get_task_data (task);
|
|
self = ide_task_get_source_object (task);
|
|
g_assert (data->resolvers != NULL);
|
|
g_assert (data->resolvers->len > 0);
|
|
|
|
g_clear_object (&self->in_flight_symbol_at_location);
|
|
self->in_flight_symbol_at_location_pos = -1;
|
|
|
|
if ((symbol = ide_symbol_resolver_lookup_symbol_finish (symbol_resolver, result, &error)))
|
|
{
|
|
/*
|
|
* Store symbol which has definition location. If no symbol has
|
|
* definition location then store symbol which has declaration location.
|
|
*/
|
|
if ((data->symbol == NULL) ||
|
|
(ide_symbol_get_location (symbol) != NULL) ||
|
|
(ide_symbol_get_location (data->symbol) == NULL &&
|
|
ide_symbol_get_header_location (symbol)))
|
|
{
|
|
g_clear_object (&data->symbol);
|
|
data->symbol = g_steal_pointer (&symbol);
|
|
}
|
|
}
|
|
|
|
g_ptr_array_remove_index (data->resolvers, data->resolvers->len - 1);
|
|
|
|
if (data->resolvers->len > 0)
|
|
{
|
|
IdeSymbolResolver *resolver;
|
|
GCancellable *cancellable;
|
|
|
|
resolver = g_ptr_array_index (data->resolvers, data->resolvers->len - 1);
|
|
cancellable = ide_task_get_cancellable (task);
|
|
|
|
ide_symbol_resolver_lookup_symbol_async (resolver,
|
|
data->location,
|
|
cancellable,
|
|
ide_buffer_get_symbol_at_location_cb,
|
|
g_steal_pointer (&task));
|
|
}
|
|
else if (data->symbol == NULL)
|
|
{
|
|
ide_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_FOUND,
|
|
"Symbol not found");
|
|
}
|
|
else
|
|
{
|
|
ide_task_return_object (task, g_steal_pointer (&data->symbol));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_symbol_at_location_async:
|
|
* @self: an #IdeBuffer
|
|
* @location: a #GtkTextIter indicating a position to search for a symbol
|
|
* @cancellable: a #GCancellable
|
|
* @callback: a #GAsyncReadyCallback
|
|
* @user_data: a #gpointer to hold user data
|
|
*
|
|
* Asynchronously get a possible symbol at @location.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_get_symbol_at_location_async (IdeBuffer *self,
|
|
const GtkTextIter *location,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(IdeLocation) srcloc = NULL;
|
|
g_autoptr(IdeTask) task = NULL;
|
|
g_autoptr(GPtrArray) resolvers = NULL;
|
|
IdeSymbolResolver *resolver;
|
|
LookUpSymbolData *data;
|
|
guint line;
|
|
guint line_offset;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (location != NULL);
|
|
g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
|
|
resolvers = ide_buffer_get_symbol_resolvers (self);
|
|
IDE_PTR_ARRAY_SET_FREE_FUNC (resolvers, g_object_unref);
|
|
|
|
task = ide_task_new (self, cancellable, callback, user_data);
|
|
ide_task_set_source_tag (task, ide_buffer_get_symbol_at_location_async);
|
|
|
|
if (resolvers->len == 0)
|
|
{
|
|
ide_task_return_new_error (task,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
_("The current language lacks a symbol resolver."));
|
|
return;
|
|
}
|
|
|
|
/* If this query is the same as one in-flight, then try to chain
|
|
* to that query instead of competing and duplicating work.
|
|
*/
|
|
if (self->in_flight_symbol_at_location_pos == (int)gtk_text_iter_get_offset (location) &&
|
|
self->in_flight_symbol_at_location != NULL)
|
|
{
|
|
ide_task_chain (self->in_flight_symbol_at_location, task);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
g_set_object (&self->in_flight_symbol_at_location, task);
|
|
self->in_flight_symbol_at_location_pos = gtk_text_iter_get_offset (location);
|
|
}
|
|
|
|
_ide_buffer_sync_to_unsaved_files (self);
|
|
|
|
line = gtk_text_iter_get_line (location);
|
|
line_offset = gtk_text_iter_get_line_offset (location);
|
|
srcloc = ide_location_new (ide_buffer_get_file (self), line, line_offset);
|
|
|
|
data = g_slice_new0 (LookUpSymbolData);
|
|
data->resolvers = g_steal_pointer (&resolvers);
|
|
data->location = g_steal_pointer (&srcloc);
|
|
ide_task_set_task_data (task, data, lookup_symbol_data_free);
|
|
|
|
/* Try lookup_symbol on each symbol resolver one by by one. */
|
|
resolver = g_ptr_array_index (data->resolvers, data->resolvers->len - 1);
|
|
ide_symbol_resolver_lookup_symbol_async (resolver,
|
|
data->location,
|
|
cancellable,
|
|
ide_buffer_get_symbol_at_location_cb,
|
|
g_steal_pointer (&task));
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_symbol_at_location_finish:
|
|
* @self: an #IdeBuffer
|
|
* @result: a #GAsyncResult
|
|
* @error: a location for a #GError
|
|
*
|
|
* Completes an asynchronous request to locate a symbol at a location.
|
|
*
|
|
* Returns: (transfer full): An #IdeSymbol or %NULL.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeSymbol *
|
|
ide_buffer_get_symbol_at_location_finish (IdeBuffer *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
g_return_val_if_fail (IDE_IS_TASK (result), NULL);
|
|
|
|
return ide_task_propagate_object (IDE_TASK (result), error);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_selection_bounds:
|
|
* @self: an #IdeBuffer
|
|
* @insert: (out): a #GtkTextIter to get the insert position
|
|
* @selection: (out): a #GtkTextIter to get the selection position
|
|
*
|
|
* This function acts like gtk_text_buffer_get_selection_bounds() except that
|
|
* it always places the location of the insert mark at @insert and the location
|
|
* of the selection mark at @selection.
|
|
*
|
|
* Calling gtk_text_iter_order() with the results of this function would be
|
|
* equivalent to calling gtk_text_buffer_get_selection_bounds().
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_get_selection_bounds (IdeBuffer *self,
|
|
GtkTextIter *insert,
|
|
GtkTextIter *selection)
|
|
{
|
|
GtkTextMark *mark;
|
|
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
if (insert != NULL)
|
|
{
|
|
mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER (self));
|
|
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), insert, mark);
|
|
}
|
|
|
|
if (selection != NULL)
|
|
{
|
|
mark = gtk_text_buffer_get_selection_bound (GTK_TEXT_BUFFER (self));
|
|
gtk_text_buffer_get_iter_at_mark (GTK_TEXT_BUFFER (self), selection, mark);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_buffer_get_symbol_resolvers_cb (IdeExtensionSetAdapter *set,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *exten,
|
|
gpointer user_data)
|
|
{
|
|
IdeSymbolResolver *resolver = (IdeSymbolResolver *)exten;
|
|
GPtrArray *ar = user_data;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
|
|
g_assert (plugin_info != NULL);
|
|
g_assert (IDE_IS_SYMBOL_RESOLVER (resolver));
|
|
g_assert (ar != NULL);
|
|
|
|
g_ptr_array_add (ar, g_object_ref (resolver));
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_symbol_resolvers:
|
|
* @self: an #IdeBuffer
|
|
*
|
|
* Gets the symbol resolvers for the buffer based on the current language. The
|
|
* resolvers in the resulting array are sorted by priority.
|
|
*
|
|
* Returns: (transfer full) (element-type IdeSymbolResolver): a #GPtrArray
|
|
* of #IdeSymbolResolver.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
GPtrArray *
|
|
ide_buffer_get_symbol_resolvers (IdeBuffer *self)
|
|
{
|
|
GPtrArray *ar;
|
|
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
ar = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
if (self->symbol_resolvers != NULL)
|
|
ide_extension_set_adapter_foreach_by_priority (self->symbol_resolvers,
|
|
ide_buffer_get_symbol_resolvers_cb,
|
|
ar);
|
|
|
|
return IDE_PTR_ARRAY_STEAL_FULL (&ar);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_get_line_text:
|
|
* @self: a #IdeBuffer
|
|
* @line: a line number starting from 0
|
|
*
|
|
* Gets the contents of a single line within the buffer.
|
|
*
|
|
* Returns: (transfer full) (nullable): a string containing the line's text
|
|
* or %NULL if the line does not exist.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gchar *
|
|
ide_buffer_get_line_text (IdeBuffer *self,
|
|
guint line)
|
|
{
|
|
GtkTextIter begin;
|
|
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
gtk_text_buffer_get_iter_at_line (GTK_TEXT_BUFFER (self), &begin, line);
|
|
|
|
if (gtk_text_iter_get_line (&begin) == line)
|
|
{
|
|
GtkTextIter end = begin;
|
|
|
|
if (gtk_text_iter_ends_line (&end) ||
|
|
gtk_text_iter_forward_to_line_end (&end))
|
|
return gtk_text_iter_get_slice (&begin, &end);
|
|
}
|
|
|
|
return g_strdup ("");
|
|
}
|
|
|
|
static void
|
|
ide_buffer_guess_language (IdeBuffer *self)
|
|
{
|
|
GtkSourceLanguageManager *manager;
|
|
GtkSourceLanguage *lang;
|
|
g_autofree gchar *basename = NULL;
|
|
g_autofree gchar *content_type = NULL;
|
|
g_autofree gchar *line = NULL;
|
|
const gchar *lang_id;
|
|
const gchar *path;
|
|
GFile *file;
|
|
gboolean uncertain = FALSE;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
|
|
line = ide_buffer_get_line_text (self, 0);
|
|
file = ide_buffer_get_file (self);
|
|
|
|
basename = g_file_get_basename (file);
|
|
|
|
if (!g_file_is_native (file))
|
|
path = basename;
|
|
else
|
|
path = g_file_peek_path (file);
|
|
|
|
manager = gtk_source_language_manager_get_default ();
|
|
lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (self));
|
|
|
|
content_type = g_content_type_guess (path, (const guchar *)line, strlen (line), &uncertain);
|
|
if (uncertain && lang != NULL)
|
|
return;
|
|
|
|
/* First try with full path, then with shortname */
|
|
if (!(lang = gtk_source_language_manager_guess_language (manager, path, content_type)) &&
|
|
!(lang = gtk_source_language_manager_guess_language (manager, basename, content_type)))
|
|
return;
|
|
|
|
lang_id = gtk_source_language_get_id (lang);
|
|
|
|
/* Override to python3 by default for now, until shared-mime-info
|
|
* gets a better way to detect the difference between the two.
|
|
*/
|
|
if (ide_str_equal0 (lang_id, "python"))
|
|
{
|
|
lang_id = "python3";
|
|
lang = gtk_source_language_manager_get_language (manager, lang_id);
|
|
}
|
|
|
|
if (!ide_str_equal0 (lang_id, ide_buffer_get_language_id (self)))
|
|
gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (self), lang);
|
|
}
|
|
|
|
gboolean
|
|
_ide_buffer_can_restore_cursor (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
return self->can_restore_cursor;
|
|
}
|
|
|
|
void
|
|
_ide_buffer_cancel_cursor_restore (IdeBuffer *self)
|
|
{
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
self->can_restore_cursor = FALSE;
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_hold:
|
|
* @self: a #IdeBuffer
|
|
*
|
|
* Increases the "hold count" of the #IdeBuffer by one.
|
|
*
|
|
* The hold count is similar to a reference count, as it allows the buffer
|
|
* manager to know when a buffer may be destroyed cleanly.
|
|
*
|
|
* Doing so ensures that the buffer wont be unloaded or have reference
|
|
* cycles broken.
|
|
*
|
|
* Release the hold with ide_buffer_release().
|
|
*
|
|
* When the hold count reaches zero, the buffer will be destroyed.
|
|
*
|
|
* Returns: (transfer full): @self
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeBuffer *
|
|
ide_buffer_hold (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
self->hold++;
|
|
|
|
return g_object_ref (self);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_release:
|
|
* @self: a #IdeBuffer
|
|
*
|
|
* Releases the "hold count" on a buffer.
|
|
*
|
|
* The buffer will be destroyed and unloaded when the hold count
|
|
* reaches zero.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_buffer_release (IdeBuffer *self)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
g_return_if_fail (self->hold > 0);
|
|
|
|
self->hold--;
|
|
|
|
if (self->hold == 0)
|
|
{
|
|
IdeObjectBox *box = ide_object_box_from_object (G_OBJECT (self));
|
|
|
|
if (box != NULL)
|
|
ide_object_destroy (IDE_OBJECT (box));
|
|
}
|
|
|
|
g_object_unref (self);
|
|
}
|
|
|
|
IdeExtensionSetAdapter *
|
|
_ide_buffer_get_addins (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), NULL);
|
|
|
|
return self->addins;
|
|
}
|
|
|
|
void
|
|
_ide_buffer_line_flags_changed (IdeBuffer *self)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
g_signal_emit (self, signals [LINE_FLAGS_CHANGED], 0);
|
|
}
|
|
|
|
/**
|
|
* ide_buffer_has_symbol_resolvers:
|
|
* @self: a #IdeBuffer
|
|
*
|
|
* Checks if any symbol resolvers are available.
|
|
*
|
|
* Returns: %TRUE if at least one symbol resolvers is available
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
gboolean
|
|
ide_buffer_has_symbol_resolvers (IdeBuffer *self)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
|
|
return self->symbol_resolvers != NULL &&
|
|
ide_extension_set_adapter_get_n_extensions (self->symbol_resolvers) > 0;
|
|
}
|
|
|
|
static void
|
|
settle_cb (GObject *object,
|
|
GAsyncResult *result,
|
|
gpointer user_data)
|
|
{
|
|
IdeBufferAddin *addin = (IdeBufferAddin *)object;
|
|
g_autoptr(IdeTask) task = user_data;
|
|
g_autoptr(GError) error = NULL;
|
|
gint *n_active;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER_ADDIN (addin));
|
|
g_assert (G_IS_ASYNC_RESULT (result));
|
|
g_assert (IDE_IS_TASK (task));
|
|
|
|
n_active = ide_task_get_task_data (task);
|
|
|
|
if (!ide_buffer_addin_settle_finish (addin, result, &error))
|
|
g_warning ("Buffer addin \"%s\" failed to settle: %s",
|
|
G_OBJECT_TYPE_NAME (addin),
|
|
error->message);
|
|
|
|
(*n_active)--;
|
|
|
|
if (*n_active == 0)
|
|
ide_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
static void
|
|
settle_foreach_cb (IdeExtensionSetAdapter *set,
|
|
PeasPluginInfo *plugin_info,
|
|
PeasExtension *exten,
|
|
gpointer user_data)
|
|
{
|
|
IdeBufferAddin *addin = (IdeBufferAddin *)exten;
|
|
IdeTask *task = user_data;
|
|
gint *n_active;
|
|
|
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (set));
|
|
g_assert (plugin_info != NULL);
|
|
g_assert (IDE_IS_BUFFER_ADDIN (addin));
|
|
g_assert (IDE_IS_TASK (task));
|
|
|
|
n_active = ide_task_get_task_data (task);
|
|
|
|
(*n_active)++;
|
|
|
|
ide_buffer_addin_settle_async (addin,
|
|
ide_task_get_cancellable (task),
|
|
settle_cb,
|
|
g_object_ref (task));
|
|
}
|
|
|
|
static void
|
|
settle_async (IdeBuffer *self,
|
|
GCancellable *cancellable,
|
|
GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
g_autoptr(IdeTask) task = NULL;
|
|
gint *n_active;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
|
|
|
|
n_active = g_new0 (gint, 1);
|
|
|
|
task = ide_task_new (self, cancellable, callback, user_data);
|
|
ide_task_set_source_tag (task, settle_async);
|
|
ide_task_set_task_data (task, n_active, g_free);
|
|
|
|
if (self->addins != NULL && self->enable_addins)
|
|
ide_extension_set_adapter_foreach (self->addins,
|
|
settle_foreach_cb,
|
|
task);
|
|
|
|
if (*n_active == 0)
|
|
ide_task_return_boolean (task, TRUE);
|
|
|
|
IDE_EXIT;
|
|
}
|
|
|
|
static gboolean
|
|
settle_finish (IdeBuffer *self,
|
|
GAsyncResult *result,
|
|
GError **error)
|
|
{
|
|
gboolean ret;
|
|
|
|
IDE_ENTRY;
|
|
|
|
g_assert (IDE_IS_MAIN_THREAD ());
|
|
g_assert (IDE_IS_BUFFER (self));
|
|
g_assert (IDE_IS_TASK (result));
|
|
|
|
ret = ide_task_propagate_boolean (IDE_TASK (result), error);
|
|
|
|
IDE_RETURN (ret);
|
|
}
|
|
|
|
void
|
|
_ide_buffer_request_scroll_to_cursor (IdeBuffer *self)
|
|
{
|
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
|
g_return_if_fail (IDE_IS_BUFFER (self));
|
|
|
|
g_signal_emit (self, signals [REQUEST_SCROLL_TO_INSERT], 0);
|
|
}
|
|
|
|
gboolean
|
|
_ide_buffer_is_file (IdeBuffer *self,
|
|
GFile *nolink_file)
|
|
{
|
|
g_return_val_if_fail (IDE_IS_BUFFER (self), FALSE);
|
|
g_return_val_if_fail (G_IS_FILE (nolink_file), FALSE);
|
|
|
|
return g_file_equal (nolink_file, ide_buffer_get_file (self)) ||
|
|
g_file_equal (nolink_file, self->readlink_file);
|
|
}
|