1183 lines
35 KiB
C
1183 lines
35 KiB
C
|
/* ide-diagnostics-manager.c
|
||
|
*
|
||
|
* Copyright 2016-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-diagnostics-manager"
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include <gtksourceview/gtksource.h>
|
||
|
#include <libide-plugins.h>
|
||
|
|
||
|
#include "ide-buffer.h"
|
||
|
#include "ide-buffer-manager.h"
|
||
|
#include "ide-buffer-private.h"
|
||
|
#include "ide-diagnostic.h"
|
||
|
#include "ide-diagnostic-provider.h"
|
||
|
#include "ide-diagnostics.h"
|
||
|
#include "ide-diagnostics-manager.h"
|
||
|
#include "ide-diagnostics-manager-private.h"
|
||
|
|
||
|
#define DEFAULT_DIAGNOSE_DELAY 333
|
||
|
#define DIAG_GROUP_MAGIC 0xF1282727
|
||
|
#define IS_DIAGNOSTICS_GROUP(g) ((g) && (g)->magic == DIAG_GROUP_MAGIC)
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
/*
|
||
|
* Used to give ourself a modicum of assurance our structure hasn't
|
||
|
* been miss-used.
|
||
|
*/
|
||
|
guint magic;
|
||
|
|
||
|
/*
|
||
|
* This is our identifier for the diagnostics. We use this as the key in
|
||
|
* the hash table so that we can quickly find the target buffer. If the
|
||
|
* IdeBuffer:file property changes, we will have to fallback to the
|
||
|
* buffer to clear old entries.
|
||
|
*/
|
||
|
GFile *file;
|
||
|
|
||
|
/*
|
||
|
* This hash table uses the given provider as the key and the last
|
||
|
* reported IdeDiagnostics as the value.
|
||
|
*/
|
||
|
GHashTable *diagnostics_by_provider;
|
||
|
|
||
|
/*
|
||
|
* This extension set adapter is used to update the providers that are
|
||
|
* available based on the buffers current language. They may change
|
||
|
* at runtime due to the buffers language changing. When that happens
|
||
|
* we purge items from @diagnostics_by_provider and queue a diagnose
|
||
|
* request of the new provider.
|
||
|
*/
|
||
|
IdeExtensionSetAdapter *adapter;
|
||
|
|
||
|
/* The most recent bytes we received for a future diagnosis. */
|
||
|
GBytes *contents;
|
||
|
|
||
|
/* The last language id we were notified about */
|
||
|
const gchar *lang_id;
|
||
|
|
||
|
/*
|
||
|
* This is our sequence number for diagnostics. It is monotonically
|
||
|
* increasing with every diagnostic discovered.
|
||
|
*/
|
||
|
guint sequence;
|
||
|
|
||
|
/*
|
||
|
* If we are currently diagnosing, then this will be set to a
|
||
|
* number greater than zero.
|
||
|
*/
|
||
|
guint in_diagnose;
|
||
|
|
||
|
/*
|
||
|
* If we need a diagnose this bit will be set. If we complete a
|
||
|
* diagnosis and this bit is set, then we will automatically queue
|
||
|
* another diagnose upon completion.
|
||
|
*/
|
||
|
guint needs_diagnose : 1;
|
||
|
|
||
|
/*
|
||
|
* This bit is set if we know the file or buffer has diagnostics. This
|
||
|
* is useful when we've cleaned up our extensions and no longer have
|
||
|
* the diagnostics loaded in memory, but we know that it previously
|
||
|
* had diagnostics which have not been rectified.
|
||
|
*/
|
||
|
guint has_diagnostics : 1;
|
||
|
|
||
|
/*
|
||
|
* This bit is set when the group has been removed from the
|
||
|
* IdeDiagnosticsManager. That allows the providers to cleanup
|
||
|
* as necessary when their async operations complete.
|
||
|
*/
|
||
|
guint was_removed : 1;
|
||
|
|
||
|
} IdeDiagnosticsGroup;
|
||
|
|
||
|
struct _IdeDiagnosticsManager
|
||
|
{
|
||
|
IdeObject parent_instance;
|
||
|
|
||
|
/*
|
||
|
* This hashtable contains a mapping of GFile to the IdeDiagnosticsGroup
|
||
|
* for the file. When a buffer is renamed (the IdeBuffer:file property
|
||
|
* is changed) we need to update this entry so it reflects the new
|
||
|
* location.
|
||
|
*/
|
||
|
GHashTable *groups_by_file;
|
||
|
|
||
|
/*
|
||
|
* If any group has a queued diagnose in process, this will be set so
|
||
|
* we can coalesce the dispatch of everything at the same time.
|
||
|
*/
|
||
|
guint queued_diagnose_source;
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
PROP_0,
|
||
|
PROP_BUSY,
|
||
|
N_PROPS
|
||
|
};
|
||
|
|
||
|
enum {
|
||
|
CHANGED,
|
||
|
N_SIGNALS
|
||
|
};
|
||
|
|
||
|
|
||
|
static gboolean ide_diagnostics_manager_clear_by_provider (IdeDiagnosticsManager *self,
|
||
|
IdeDiagnosticProvider *provider);
|
||
|
static void ide_diagnostics_manager_add_diagnostic (IdeDiagnosticsManager *self,
|
||
|
IdeDiagnosticProvider *provider,
|
||
|
IdeDiagnostic *diagnostic);
|
||
|
static void ide_diagnostics_group_queue_diagnose (IdeDiagnosticsGroup *group,
|
||
|
IdeDiagnosticsManager *self);
|
||
|
|
||
|
|
||
|
static GParamSpec *properties [N_PROPS];
|
||
|
static guint signals [N_SIGNALS];
|
||
|
|
||
|
G_DEFINE_FINAL_TYPE (IdeDiagnosticsManager, ide_diagnostics_manager, IDE_TYPE_OBJECT)
|
||
|
|
||
|
static void
|
||
|
free_diagnostics (gpointer data)
|
||
|
{
|
||
|
IdeDiagnostics *diagnostics = data;
|
||
|
|
||
|
g_clear_object (&diagnostics);
|
||
|
}
|
||
|
|
||
|
static inline guint
|
||
|
diagnostics_get_size (IdeDiagnostics *diags)
|
||
|
{
|
||
|
return diags ? g_list_model_get_n_items (G_LIST_MODEL (diags)) : 0;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_group_finalize (IdeDiagnosticsGroup *group)
|
||
|
{
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
group->magic = 0;
|
||
|
|
||
|
g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
|
||
|
g_clear_pointer (&group->contents, g_bytes_unref);
|
||
|
ide_clear_and_destroy_object (&group->adapter);
|
||
|
g_clear_object (&group->file);
|
||
|
}
|
||
|
|
||
|
static IdeDiagnosticsGroup *
|
||
|
ide_diagnostics_group_new (GFile *file)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (G_IS_FILE (file));
|
||
|
|
||
|
group = g_rc_box_new0 (IdeDiagnosticsGroup);
|
||
|
group->magic = DIAG_GROUP_MAGIC;
|
||
|
group->file = g_object_ref (file);
|
||
|
|
||
|
return group;
|
||
|
}
|
||
|
|
||
|
static IdeDiagnosticsGroup *
|
||
|
ide_diagnostics_group_ref (IdeDiagnosticsGroup *group)
|
||
|
{
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
return g_rc_box_acquire (group);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_group_unref (IdeDiagnosticsGroup *group)
|
||
|
{
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
g_rc_box_release_full (group, (GDestroyNotify)ide_diagnostics_group_finalize);
|
||
|
}
|
||
|
|
||
|
static guint
|
||
|
ide_diagnostics_group_has_diagnostics (IdeDiagnosticsGroup *group)
|
||
|
{
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
if (group->diagnostics_by_provider != NULL)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
gpointer value;
|
||
|
|
||
|
g_hash_table_iter_init (&iter, group->diagnostics_by_provider);
|
||
|
|
||
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
||
|
{
|
||
|
IdeDiagnostics *diagnostics = value;
|
||
|
|
||
|
if (diagnostics_get_size (diagnostics) > 0)
|
||
|
return TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_diagnostics_group_can_dispose (IdeDiagnosticsGroup *group)
|
||
|
{
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
/*
|
||
|
* We can cleanup this group if we don't have a buffer loaded and
|
||
|
* the adapters have been unloaded and there are no diagnostics
|
||
|
* registered for the group.
|
||
|
*/
|
||
|
|
||
|
return group->adapter == NULL &&
|
||
|
group->has_diagnostics == FALSE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_group_add (IdeDiagnosticsGroup *group,
|
||
|
IdeDiagnosticProvider *provider,
|
||
|
IdeDiagnostic *diagnostic)
|
||
|
{
|
||
|
IdeDiagnostics *diagnostics;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
|
||
|
g_assert (diagnostic != NULL);
|
||
|
|
||
|
if (group->diagnostics_by_provider == NULL)
|
||
|
group->diagnostics_by_provider = g_hash_table_new_full (NULL, NULL, NULL, free_diagnostics);
|
||
|
|
||
|
diagnostics = g_hash_table_lookup (group->diagnostics_by_provider, provider);
|
||
|
|
||
|
if (diagnostics == NULL)
|
||
|
{
|
||
|
diagnostics = ide_diagnostics_new ();
|
||
|
g_hash_table_insert (group->diagnostics_by_provider, provider, diagnostics);
|
||
|
}
|
||
|
|
||
|
ide_diagnostics_add (diagnostics, diagnostic);
|
||
|
|
||
|
group->has_diagnostics = TRUE;
|
||
|
group->sequence++;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_group_diagnose_cb (GObject *object,
|
||
|
GAsyncResult *result,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)object;
|
||
|
g_autoptr(IdeDiagnosticsManager) self = user_data;
|
||
|
g_autoptr(IdeDiagnostics) diagnostics = NULL;
|
||
|
g_autoptr(GError) error = NULL;
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
gboolean changed;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
|
||
|
g_assert (G_IS_ASYNC_RESULT (result));
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
|
||
|
IDE_TRACE_MSG ("%s diagnosis completed", G_OBJECT_TYPE_NAME (provider));
|
||
|
|
||
|
diagnostics = ide_diagnostic_provider_diagnose_finish (provider, result, &error);
|
||
|
|
||
|
if (error != NULL &&
|
||
|
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) &&
|
||
|
!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
|
||
|
g_debug ("%s", error->message);
|
||
|
|
||
|
/*
|
||
|
* This fetches the group our provider belongs to. Since the group is
|
||
|
* reference counted (and we only release it when our provider is
|
||
|
* finalized), we should be guaranteed we have a valid group.
|
||
|
*/
|
||
|
group = g_object_get_data (G_OBJECT (provider), "IDE_DIAGNOSTICS_GROUP");
|
||
|
|
||
|
if (group == NULL)
|
||
|
{
|
||
|
/* Warning and bail if we failed to get the diagnostic group.
|
||
|
* This shouldn't be happening, but I have definitely seen it
|
||
|
* so it is probably related to disposal.
|
||
|
*/
|
||
|
g_warning ("Failed to locate group, possibly disposed.");
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
/*
|
||
|
* Clear all of our old diagnostics no matter where they ended up.
|
||
|
*/
|
||
|
changed = ide_diagnostics_manager_clear_by_provider (self, provider);
|
||
|
|
||
|
/*
|
||
|
* The following adds diagnostics to the appropriate group, but tries the
|
||
|
* group we belong to first as our fast path. That will almost always be
|
||
|
* the case, except when a diagnostic came up for a header or something
|
||
|
* while parsing a given file.
|
||
|
*/
|
||
|
if (diagnostics != NULL)
|
||
|
{
|
||
|
guint length = diagnostics_get_size (diagnostics);
|
||
|
|
||
|
for (guint i = 0; i < length; i++)
|
||
|
{
|
||
|
g_autoptr(IdeDiagnostic) diagnostic = g_list_model_get_item (G_LIST_MODEL (diagnostics), i);
|
||
|
GFile *file = ide_diagnostic_get_file (diagnostic);
|
||
|
|
||
|
if (file != NULL)
|
||
|
{
|
||
|
if (g_file_equal (file, group->file))
|
||
|
ide_diagnostics_group_add (group, provider, diagnostic);
|
||
|
else
|
||
|
ide_diagnostics_manager_add_diagnostic (self, provider, diagnostic);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (length > 0)
|
||
|
changed = TRUE;
|
||
|
}
|
||
|
|
||
|
group->in_diagnose--;
|
||
|
|
||
|
/*
|
||
|
* Ensure we increment our sequence number even when no diagnostics were
|
||
|
* reported. This ensures that the gutter gets cleared and line-flags
|
||
|
* cache updated.
|
||
|
*/
|
||
|
group->sequence++;
|
||
|
|
||
|
/*
|
||
|
* Since the individual groups have sequence numbers associated with changes,
|
||
|
* it's okay to emit this for every provider completion. That allows the UIs
|
||
|
* to update faster as each provider completes at the expensive of a little
|
||
|
* more CPU activity.
|
||
|
*/
|
||
|
if (changed)
|
||
|
g_signal_emit (self, signals [CHANGED], 0);
|
||
|
|
||
|
/*
|
||
|
* If there are no more diagnostics providers active and the group needs
|
||
|
* another diagnosis, then we can start the next one now.
|
||
|
*
|
||
|
* If we are completing this diagnosis and the buffer was already released
|
||
|
* (and other diagnose providers have unloaded), we might be able to clean
|
||
|
* up the group and be done with things.
|
||
|
*/
|
||
|
if (group->was_removed == FALSE && group->in_diagnose == 0 && group->needs_diagnose)
|
||
|
{
|
||
|
ide_diagnostics_group_queue_diagnose (group, self);
|
||
|
}
|
||
|
else if (ide_diagnostics_group_can_dispose (group))
|
||
|
{
|
||
|
group->was_removed = TRUE;
|
||
|
g_hash_table_remove (self->groups_by_file, group->file);
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_group_diagnose_foreach (IdeExtensionSetAdapter *adapter,
|
||
|
PeasPluginInfo *plugin_info,
|
||
|
PeasExtension *exten,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)exten;
|
||
|
IdeDiagnosticsManager *self = user_data;
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
|
||
|
g_assert (plugin_info != NULL);
|
||
|
g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
|
||
|
|
||
|
group = g_object_get_data (G_OBJECT (provider), "IDE_DIAGNOSTICS_GROUP");
|
||
|
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
group->in_diagnose++;
|
||
|
|
||
|
#ifdef IDE_ENABLE_TRACE
|
||
|
{
|
||
|
g_autofree gchar *uri = g_file_get_uri (group->file);
|
||
|
IDE_TRACE_MSG ("Beginning diagnose on %s with provider %s",
|
||
|
uri, G_OBJECT_TYPE_NAME (provider));
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
ide_diagnostic_provider_diagnose_async (provider,
|
||
|
group->file,
|
||
|
group->contents,
|
||
|
group->lang_id,
|
||
|
NULL,
|
||
|
ide_diagnostics_group_diagnose_cb,
|
||
|
g_object_ref (self));
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_group_diagnose (IdeDiagnosticsGroup *group,
|
||
|
IdeDiagnosticsManager *self)
|
||
|
{
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (group->in_diagnose == FALSE);
|
||
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (group->adapter));
|
||
|
|
||
|
group->needs_diagnose = FALSE;
|
||
|
group->has_diagnostics = FALSE;
|
||
|
|
||
|
if (group->contents == NULL)
|
||
|
group->contents = g_bytes_new ("", 0);
|
||
|
|
||
|
ide_extension_set_adapter_foreach (group->adapter,
|
||
|
ide_diagnostics_group_diagnose_foreach,
|
||
|
self);
|
||
|
|
||
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_BUSY]);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_diagnostics_manager_begin_diagnose (gpointer data)
|
||
|
{
|
||
|
IdeDiagnosticsManager *self = data;
|
||
|
GHashTableIter iter;
|
||
|
gpointer value;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
|
||
|
self->queued_diagnose_source = 0;
|
||
|
|
||
|
g_hash_table_iter_init (&iter, self->groups_by_file);
|
||
|
|
||
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group = value;
|
||
|
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
if (group->needs_diagnose && group->adapter != NULL && group->in_diagnose == 0)
|
||
|
ide_diagnostics_group_diagnose (group, self);
|
||
|
}
|
||
|
|
||
|
IDE_RETURN (G_SOURCE_REMOVE);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_group_queue_diagnose (IdeDiagnosticsGroup *group,
|
||
|
IdeDiagnosticsManager *self)
|
||
|
{
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
|
||
|
/*
|
||
|
* This checks to see if we are diagnosing and if not queues a diagnose.
|
||
|
* If a diagnosis is already running, we don't need to do anything now
|
||
|
* because the completion of the diagnose will tick off the next diagnose
|
||
|
* upon seening group->needs_diagnose==TRUE.
|
||
|
*/
|
||
|
|
||
|
group->needs_diagnose = TRUE;
|
||
|
|
||
|
if (group->in_diagnose == 0 && self->queued_diagnose_source == 0)
|
||
|
self->queued_diagnose_source = g_timeout_add_full (G_PRIORITY_LOW,
|
||
|
DEFAULT_DIAGNOSE_DELAY,
|
||
|
ide_diagnostics_manager_begin_diagnose,
|
||
|
self, NULL);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_manager_finalize (GObject *object)
|
||
|
{
|
||
|
IdeDiagnosticsManager *self = (IdeDiagnosticsManager *)object;
|
||
|
|
||
|
g_clear_handle_id (&self->queued_diagnose_source, g_source_remove);
|
||
|
g_clear_pointer (&self->groups_by_file, g_hash_table_unref);
|
||
|
|
||
|
G_OBJECT_CLASS (ide_diagnostics_manager_parent_class)->finalize (object);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_manager_get_property (GObject *object,
|
||
|
guint prop_id,
|
||
|
GValue *value,
|
||
|
GParamSpec *pspec)
|
||
|
{
|
||
|
IdeDiagnosticsManager *self = (IdeDiagnosticsManager *)object;
|
||
|
|
||
|
switch (prop_id)
|
||
|
{
|
||
|
case PROP_BUSY:
|
||
|
g_value_set_boolean (value, ide_diagnostics_manager_get_busy (self));
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_manager_class_init (IdeDiagnosticsManagerClass *klass)
|
||
|
{
|
||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
|
|
||
|
object_class->finalize = ide_diagnostics_manager_finalize;
|
||
|
object_class->get_property = ide_diagnostics_manager_get_property;
|
||
|
|
||
|
properties [PROP_BUSY] =
|
||
|
g_param_spec_boolean ("busy",
|
||
|
"Busy",
|
||
|
"If the diagnostics manager is busy",
|
||
|
FALSE,
|
||
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
||
|
|
||
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||
|
|
||
|
/**
|
||
|
* IdeDiagnosticsManager::changed:
|
||
|
* @self: an #IdeDiagnosticsManager
|
||
|
*
|
||
|
* This signal is emitted when the diagnostics have changed for any
|
||
|
* file managed by the IdeDiagnosticsManager.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
signals [CHANGED] =
|
||
|
g_signal_new ("changed",
|
||
|
G_TYPE_FROM_CLASS (klass),
|
||
|
G_SIGNAL_RUN_LAST,
|
||
|
0, NULL, NULL, NULL, G_TYPE_NONE, 0);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_manager_init (IdeDiagnosticsManager *self)
|
||
|
{
|
||
|
self->groups_by_file = g_hash_table_new_full (g_file_hash,
|
||
|
(GEqualFunc)g_file_equal,
|
||
|
NULL,
|
||
|
(GDestroyNotify)ide_diagnostics_group_unref);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_manager_add_diagnostic (IdeDiagnosticsManager *self,
|
||
|
IdeDiagnosticProvider *provider,
|
||
|
IdeDiagnostic *diagnostic)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
GFile *file;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
|
||
|
g_assert (diagnostic != NULL);
|
||
|
|
||
|
/*
|
||
|
* This is our slow path for adding a diagnostic to the system. We have
|
||
|
* to locate the proper group for the diagnostic and then insert it
|
||
|
* into that group.
|
||
|
*/
|
||
|
|
||
|
if (NULL == (file = ide_diagnostic_get_file (diagnostic)))
|
||
|
return;
|
||
|
|
||
|
group = g_hash_table_lookup (self->groups_by_file, file);
|
||
|
|
||
|
if (group == NULL)
|
||
|
{
|
||
|
group = ide_diagnostics_group_new (file);
|
||
|
g_hash_table_insert (self->groups_by_file, group->file, group);
|
||
|
}
|
||
|
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
ide_diagnostics_group_add (group, provider, diagnostic);
|
||
|
}
|
||
|
|
||
|
static IdeDiagnosticsGroup *
|
||
|
ide_diagnostics_manager_find_group (IdeDiagnosticsManager *self,
|
||
|
GFile *file)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_assert (G_IS_FILE (file));
|
||
|
|
||
|
if (!(group = g_hash_table_lookup (self->groups_by_file, file)))
|
||
|
{
|
||
|
group = ide_diagnostics_group_new (file);
|
||
|
g_hash_table_insert (self->groups_by_file, group->file, group);
|
||
|
}
|
||
|
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
return group;
|
||
|
}
|
||
|
|
||
|
static IdeDiagnosticsGroup *
|
||
|
ide_diagnostics_manager_find_group_from_adapter (IdeDiagnosticsManager *self,
|
||
|
IdeExtensionSetAdapter *adapter)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
gpointer value;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
|
||
|
|
||
|
g_hash_table_iter_init (&iter, self->groups_by_file);
|
||
|
|
||
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group = value;
|
||
|
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
if (group->adapter == adapter)
|
||
|
return group;
|
||
|
}
|
||
|
|
||
|
g_assert_not_reached ();
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_manager_provider_invalidated (IdeDiagnosticsManager *self,
|
||
|
IdeDiagnosticProvider *provider)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
|
||
|
|
||
|
group = g_object_get_data (G_OBJECT (provider), "IDE_DIAGNOSTICS_GROUP");
|
||
|
|
||
|
ide_diagnostics_group_queue_diagnose (group, self);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_manager_extension_added (IdeExtensionSetAdapter *adapter,
|
||
|
PeasPluginInfo *plugin_info,
|
||
|
PeasExtension *exten,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)exten;
|
||
|
IdeDiagnosticsManager *self = user_data;
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (adapter));
|
||
|
g_assert (plugin_info != NULL);
|
||
|
g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
|
||
|
group = ide_diagnostics_manager_find_group_from_adapter (self, adapter);
|
||
|
|
||
|
/*
|
||
|
* We will need access to the group upon completion of the diagnostics,
|
||
|
* so we add a reference to the group and allow it to be automatically
|
||
|
* cleaned up when the provider finalizes.
|
||
|
*/
|
||
|
g_object_set_data_full (G_OBJECT (provider),
|
||
|
"IDE_DIAGNOSTICS_GROUP",
|
||
|
ide_diagnostics_group_ref (group),
|
||
|
(GDestroyNotify)ide_diagnostics_group_unref);
|
||
|
|
||
|
/*
|
||
|
* We insert a dummy entry into the hashtable upon creation so
|
||
|
* that when an async diagnosis completes we can use the presence
|
||
|
* of this key to know if we've been unloaded.
|
||
|
*/
|
||
|
g_hash_table_insert (group->diagnostics_by_provider, provider, NULL);
|
||
|
|
||
|
/*
|
||
|
* We need to keep track of when the provider has been invalidated so
|
||
|
* that we can queue another request to fetch the diagnostics.
|
||
|
*/
|
||
|
g_signal_connect_object (provider,
|
||
|
"invalidated",
|
||
|
G_CALLBACK (ide_diagnostics_manager_provider_invalidated),
|
||
|
self,
|
||
|
G_CONNECT_SWAPPED);
|
||
|
|
||
|
ide_diagnostic_provider_load (provider);
|
||
|
|
||
|
ide_diagnostics_group_queue_diagnose (group, self);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
static gboolean
|
||
|
ide_diagnostics_manager_clear_by_provider (IdeDiagnosticsManager *self,
|
||
|
IdeDiagnosticProvider *provider)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
gpointer value;
|
||
|
gboolean changed = FALSE;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_assert (IDE_IS_DIAGNOSTIC_PROVIDER (provider));
|
||
|
|
||
|
g_hash_table_iter_init (&iter, self->groups_by_file);
|
||
|
|
||
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group = value;
|
||
|
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
if (group->diagnostics_by_provider != NULL)
|
||
|
{
|
||
|
g_hash_table_remove (group->diagnostics_by_provider, provider);
|
||
|
|
||
|
/*
|
||
|
* If we caused this hashtable to become empty, we can release the
|
||
|
* hashtable. The hashtable is guaranteed to not be empty if there
|
||
|
* are other providers loaded for this group.
|
||
|
*/
|
||
|
if (g_hash_table_size (group->diagnostics_by_provider) == 0)
|
||
|
g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
|
||
|
|
||
|
/*
|
||
|
* TODO: If this provider is not part of this group, we can possibly
|
||
|
* dispose of the group if there are no diagnostics.
|
||
|
*/
|
||
|
|
||
|
changed = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ide_diagnostics_manager_extension_removed (IdeExtensionSetAdapter *adapter,
|
||
|
PeasPluginInfo *plugin_info,
|
||
|
PeasExtension *exten,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
IdeDiagnosticProvider *provider = (IdeDiagnosticProvider *)exten;
|
||
|
IdeDiagnosticsManager *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_DIAGNOSTIC_PROVIDER (provider));
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
|
||
|
g_signal_handlers_disconnect_by_func (provider,
|
||
|
G_CALLBACK (ide_diagnostics_manager_provider_invalidated),
|
||
|
self);
|
||
|
|
||
|
/*
|
||
|
* The goal of the following is to reomve our diagnostics from any file
|
||
|
* that has been loaded. It is possible for diagnostic providers to effect
|
||
|
* files outside the buffer they are loaded for and this ensures that we
|
||
|
* clean those up.
|
||
|
*/
|
||
|
ide_diagnostics_manager_clear_by_provider (self, provider);
|
||
|
|
||
|
/* Clear the diagnostics group */
|
||
|
g_object_set_data (G_OBJECT (provider), "IDE_DIAGNOSTICS_GROUP", NULL);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_diagnostics_manager_get_busy:
|
||
|
*
|
||
|
* Gets if the diagnostics manager is currently executing a diagnosis.
|
||
|
*
|
||
|
* Returns: %TRUE if the #IdeDiagnosticsManager is busy diagnosing.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
gboolean
|
||
|
ide_diagnostics_manager_get_busy (IdeDiagnosticsManager *self)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
gpointer value;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
|
||
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self), FALSE);
|
||
|
|
||
|
g_hash_table_iter_init (&iter, self->groups_by_file);
|
||
|
|
||
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group = value;
|
||
|
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
if (group->in_diagnose > 0)
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_diagnostics_manager_get_diagnostics_for_file:
|
||
|
* @self: An #IdeDiagnosticsManager
|
||
|
* @file: a #GFile to retrieve diagnostics for
|
||
|
*
|
||
|
* This function collects all of the diagnostics that have been collected
|
||
|
* for @file and returns them as a new #IdeDiagnostics to the caller.
|
||
|
*
|
||
|
* The #IdeDiagnostics structure will contain zero items if there are
|
||
|
* no diagnostics discovered. Therefore, this function will never return
|
||
|
* a %NULL value.
|
||
|
*
|
||
|
* Returns: (transfer full): A new #IdeDiagnostics.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
IdeDiagnostics *
|
||
|
ide_diagnostics_manager_get_diagnostics_for_file (IdeDiagnosticsManager *self,
|
||
|
GFile *file)
|
||
|
{
|
||
|
g_autoptr(IdeDiagnostics) ret = NULL;
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
|
||
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self), NULL);
|
||
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
||
|
|
||
|
ret = ide_diagnostics_new ();
|
||
|
|
||
|
group = g_hash_table_lookup (self->groups_by_file, file);
|
||
|
|
||
|
if (group != NULL && group->diagnostics_by_provider != NULL)
|
||
|
{
|
||
|
GHashTableIter iter;
|
||
|
gpointer value;
|
||
|
|
||
|
g_hash_table_iter_init (&iter, group->diagnostics_by_provider);
|
||
|
|
||
|
while (g_hash_table_iter_next (&iter, NULL, &value))
|
||
|
{
|
||
|
IdeDiagnostics *diagnostics = value;
|
||
|
guint length;
|
||
|
|
||
|
if (diagnostics == NULL)
|
||
|
continue;
|
||
|
|
||
|
length = g_list_model_get_n_items (G_LIST_MODEL (diagnostics));
|
||
|
|
||
|
for (guint i = 0; i < length; i++)
|
||
|
{
|
||
|
g_autoptr(IdeDiagnostic) diagnostic = NULL;
|
||
|
|
||
|
diagnostic = g_list_model_get_item (G_LIST_MODEL (diagnostics), i);
|
||
|
ide_diagnostics_add (ret, diagnostic);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return g_steal_pointer (&ret);
|
||
|
}
|
||
|
|
||
|
guint
|
||
|
ide_diagnostics_manager_get_sequence_for_file (IdeDiagnosticsManager *self,
|
||
|
GFile *file)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_MAIN_THREAD (), 0);
|
||
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self), 0);
|
||
|
g_return_val_if_fail (G_IS_FILE (file), 0);
|
||
|
|
||
|
group = g_hash_table_lookup (self->groups_by_file, file);
|
||
|
|
||
|
if (group != NULL)
|
||
|
{
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
g_assert (G_IS_FILE (group->file));
|
||
|
g_assert (g_file_equal (group->file, file));
|
||
|
|
||
|
return group->sequence;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_diagnostics_manager_rediagnose:
|
||
|
* @self: an #IdeDiagnosticsManager
|
||
|
* @buffer: an #IdeBuffer
|
||
|
*
|
||
|
* Requests that the diagnostics be reloaded for @buffer.
|
||
|
*
|
||
|
* You may want to call this if you changed something that a buffer depends on,
|
||
|
* and want to seamlessly update its diagnostics with that updated information.
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
void
|
||
|
ide_diagnostics_manager_rediagnose (IdeDiagnosticsManager *self,
|
||
|
IdeBuffer *buffer)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
GFile *file;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
||
|
g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_return_if_fail (IDE_IS_BUFFER (buffer));
|
||
|
|
||
|
file = ide_buffer_get_file (buffer);
|
||
|
group = ide_diagnostics_manager_find_group (self, file);
|
||
|
|
||
|
ide_diagnostics_group_queue_diagnose (group, self);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* ide_diagnostics_manager_from_context:
|
||
|
* @context: an #IdeContext
|
||
|
*
|
||
|
* Gets the diagnostics manager for the context.
|
||
|
*
|
||
|
* Returns: (transfer none): an #IdeDiagnosticsManager
|
||
|
*
|
||
|
* Since: 3.32
|
||
|
*/
|
||
|
IdeDiagnosticsManager *
|
||
|
ide_diagnostics_manager_from_context (IdeContext *context)
|
||
|
{
|
||
|
IdeDiagnosticsManager *self;
|
||
|
|
||
|
g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
|
||
|
|
||
|
ide_object_lock (IDE_OBJECT (context));
|
||
|
if (!(self = ide_context_peek_child_typed (context, IDE_TYPE_DIAGNOSTICS_MANAGER)))
|
||
|
{
|
||
|
g_autoptr(IdeDiagnosticsManager) created = NULL;
|
||
|
created = ide_object_ensure_child_typed (IDE_OBJECT (context),
|
||
|
IDE_TYPE_DIAGNOSTICS_MANAGER);
|
||
|
self = ide_context_peek_child_typed (context, IDE_TYPE_DIAGNOSTICS_MANAGER);
|
||
|
}
|
||
|
ide_object_unlock (IDE_OBJECT (context));
|
||
|
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_ide_diagnostics_manager_file_closed (IdeDiagnosticsManager *self,
|
||
|
GFile *file)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
gboolean has_diagnostics;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
||
|
g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_return_if_fail (G_IS_FILE (file));
|
||
|
|
||
|
/*
|
||
|
* The goal here is to cleanup everything we can about this group that
|
||
|
* is part of a loaded buffer. We might want to keep the group around
|
||
|
* in case it is useful from other providers.
|
||
|
*/
|
||
|
|
||
|
group = ide_diagnostics_manager_find_group (self, file);
|
||
|
|
||
|
g_assert (group != NULL);
|
||
|
g_assert (IS_DIAGNOSTICS_GROUP (group));
|
||
|
|
||
|
/* Clear some state we've been tracking */
|
||
|
g_clear_pointer (&group->contents, g_bytes_unref);
|
||
|
group->lang_id = NULL;
|
||
|
group->needs_diagnose = FALSE;
|
||
|
|
||
|
/*
|
||
|
* We track if we have diagnostics now so that after we unload the
|
||
|
* the providers, we can save that bit for later.
|
||
|
*/
|
||
|
has_diagnostics = ide_diagnostics_group_has_diagnostics (group);
|
||
|
|
||
|
/*
|
||
|
* Force our diagnostic providers to unload. This will cause them
|
||
|
* extension-removed signal to be called for each provider which
|
||
|
* in turn will perform per-provider cleanup including the removal
|
||
|
* of its diagnostics from all groups. (A provider can in practice
|
||
|
* affect another group since a .c file could create a diagnostic
|
||
|
* for a .h).
|
||
|
*/
|
||
|
ide_clear_and_destroy_object (&group->adapter);
|
||
|
|
||
|
/*
|
||
|
* Even after unloading the diagnostic providers, we might still have
|
||
|
* diagnostics that were created from other files (this could happen when
|
||
|
* one diagnostic is created for a header from a source file). So we don't
|
||
|
* want to wipe out the hashtable unless everything was unloaded. The other
|
||
|
* provider will cleanup during its own destruction.
|
||
|
*/
|
||
|
if (group->diagnostics_by_provider != NULL &&
|
||
|
g_hash_table_size (group->diagnostics_by_provider) == 0)
|
||
|
g_clear_pointer (&group->diagnostics_by_provider, g_hash_table_unref);
|
||
|
|
||
|
group->has_diagnostics = has_diagnostics;
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_ide_diagnostics_manager_file_changed (IdeDiagnosticsManager *self,
|
||
|
GFile *file,
|
||
|
GBytes *contents,
|
||
|
const gchar *lang_id)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
||
|
g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_return_if_fail (G_IS_FILE (file));
|
||
|
|
||
|
group = ide_diagnostics_manager_find_group (self, file);
|
||
|
|
||
|
g_clear_pointer (&group->contents, g_bytes_unref);
|
||
|
|
||
|
group->lang_id = g_intern_string (lang_id);
|
||
|
group->contents = contents ? g_bytes_ref (contents) : NULL;
|
||
|
|
||
|
ide_diagnostics_group_queue_diagnose (group, self);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_ide_diagnostics_manager_language_changed (IdeDiagnosticsManager *self,
|
||
|
GFile *file,
|
||
|
const gchar *lang_id)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
g_return_if_fail (IDE_IS_MAIN_THREAD ());
|
||
|
g_return_if_fail (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
|
||
|
group = ide_diagnostics_manager_find_group (self, file);
|
||
|
group->lang_id = g_intern_string (lang_id);
|
||
|
|
||
|
if (group->adapter != NULL)
|
||
|
ide_extension_set_adapter_set_value (group->adapter, lang_id);
|
||
|
|
||
|
ide_diagnostics_group_queue_diagnose (group, self);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
_ide_diagnostics_manager_file_opened (IdeDiagnosticsManager *self,
|
||
|
GFile *file,
|
||
|
const gchar *lang_id)
|
||
|
{
|
||
|
IdeDiagnosticsGroup *group;
|
||
|
|
||
|
IDE_ENTRY;
|
||
|
|
||
|
g_assert (IDE_IS_MAIN_THREAD ());
|
||
|
g_assert (IDE_IS_DIAGNOSTICS_MANAGER (self));
|
||
|
g_assert (G_IS_FILE (file));
|
||
|
|
||
|
group = ide_diagnostics_manager_find_group (self, file);
|
||
|
|
||
|
if (group->diagnostics_by_provider == NULL)
|
||
|
group->diagnostics_by_provider =
|
||
|
g_hash_table_new_full (NULL, NULL, NULL, free_diagnostics);
|
||
|
|
||
|
group->lang_id = g_intern_string (lang_id);
|
||
|
|
||
|
if (group->adapter == NULL)
|
||
|
{
|
||
|
group->adapter = ide_extension_set_adapter_new (IDE_OBJECT (self),
|
||
|
peas_engine_get_default (),
|
||
|
IDE_TYPE_DIAGNOSTIC_PROVIDER,
|
||
|
"Diagnostic-Provider-Languages",
|
||
|
lang_id);
|
||
|
|
||
|
g_signal_connect_object (group->adapter,
|
||
|
"extension-added",
|
||
|
G_CALLBACK (ide_diagnostics_manager_extension_added),
|
||
|
self,
|
||
|
0);
|
||
|
|
||
|
g_signal_connect_object (group->adapter,
|
||
|
"extension-removed",
|
||
|
G_CALLBACK (ide_diagnostics_manager_extension_removed),
|
||
|
self,
|
||
|
0);
|
||
|
|
||
|
ide_extension_set_adapter_foreach (group->adapter,
|
||
|
ide_diagnostics_manager_extension_added,
|
||
|
self);
|
||
|
}
|
||
|
|
||
|
g_assert (IDE_IS_EXTENSION_SET_ADAPTER (group->adapter));
|
||
|
g_assert (g_hash_table_lookup (self->groups_by_file, file) == group);
|
||
|
|
||
|
ide_diagnostics_group_queue_diagnose (group, self);
|
||
|
|
||
|
IDE_EXIT;
|
||
|
}
|
||
|
|