556 lines
15 KiB
C
556 lines
15 KiB
C
/* ide-diagnostics.c
|
|
*
|
|
* Copyright 2015-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"
|
|
|
|
#include "config.h"
|
|
|
|
#include "ide-diagnostic.h"
|
|
#include "ide-diagnostics.h"
|
|
#include "ide-location.h"
|
|
|
|
typedef struct
|
|
{
|
|
GPtrArray *items;
|
|
GHashTable *caches;
|
|
guint n_warnings;
|
|
guint n_errors;
|
|
} IdeDiagnosticsPrivate;
|
|
|
|
typedef struct
|
|
{
|
|
gint line : 28;
|
|
IdeDiagnosticSeverity severity : 4;
|
|
} IdeDiagnosticsCacheLine;
|
|
|
|
typedef struct
|
|
{
|
|
GFile *file;
|
|
GArray *lines;
|
|
} IdeDiagnosticsCache;
|
|
|
|
typedef struct
|
|
{
|
|
guint begin;
|
|
guint end;
|
|
} LookupKey;
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_HAS_ERRORS,
|
|
PROP_HAS_WARNINGS,
|
|
PROP_N_ERRORS,
|
|
PROP_N_WARNINGS,
|
|
N_PROPS
|
|
};
|
|
|
|
static void list_model_iface_init (GListModelInterface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (IdeDiagnostics, ide_diagnostics, IDE_TYPE_OBJECT,
|
|
G_ADD_PRIVATE (IdeDiagnostics)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, list_model_iface_init))
|
|
|
|
static GParamSpec *properties [N_PROPS];
|
|
|
|
static void
|
|
ide_diagnostics_cache_free (gpointer data)
|
|
{
|
|
IdeDiagnosticsCache *cache = data;
|
|
|
|
g_clear_object (&cache->file);
|
|
g_clear_pointer (&cache->lines, g_array_unref);
|
|
g_slice_free (IdeDiagnosticsCache, cache);
|
|
}
|
|
|
|
static void
|
|
ide_diagnostics_finalize (GObject *object)
|
|
{
|
|
IdeDiagnostics *self = (IdeDiagnostics *)object;
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
g_clear_pointer (&priv->items, g_ptr_array_unref);
|
|
g_clear_pointer (&priv->caches, g_hash_table_unref);
|
|
|
|
G_OBJECT_CLASS (ide_diagnostics_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
ide_diagnostics_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
IdeDiagnostics *self = IDE_DIAGNOSTICS (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_HAS_WARNINGS:
|
|
g_value_set_boolean (value, ide_diagnostics_get_has_warnings (self));
|
|
break;
|
|
|
|
case PROP_HAS_ERRORS:
|
|
g_value_set_boolean (value, ide_diagnostics_get_has_errors (self));
|
|
break;
|
|
|
|
case PROP_N_ERRORS:
|
|
g_value_set_uint (value, ide_diagnostics_get_n_errors (self));
|
|
break;
|
|
|
|
case PROP_N_WARNINGS:
|
|
g_value_set_uint (value, ide_diagnostics_get_n_warnings (self));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ide_diagnostics_class_init (IdeDiagnosticsClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = ide_diagnostics_finalize;
|
|
object_class->get_property = ide_diagnostics_get_property;
|
|
|
|
properties [PROP_HAS_WARNINGS] =
|
|
g_param_spec_boolean ("has-warnings",
|
|
"Has Warnings",
|
|
"If there are any warnings in the diagnostic set",
|
|
FALSE,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
properties [PROP_HAS_ERRORS] =
|
|
g_param_spec_boolean ("has-errors",
|
|
"Has Errors",
|
|
"If there are any errors in the diagnostic set",
|
|
FALSE,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
properties [PROP_N_WARNINGS] =
|
|
g_param_spec_uint ("n-warnings",
|
|
"N Warnings",
|
|
"Number of warnings in diagnostic set",
|
|
0, G_MAXUINT, 0,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
properties [PROP_N_ERRORS] =
|
|
g_param_spec_uint ("n-errors",
|
|
"N Errors",
|
|
"Number of errors in diagnostic set",
|
|
0, G_MAXUINT, 0,
|
|
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, properties);
|
|
}
|
|
|
|
static void
|
|
ide_diagnostics_init (IdeDiagnostics *self)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
priv->items = g_ptr_array_new_with_free_func (g_object_unref);
|
|
}
|
|
|
|
IdeDiagnostics *
|
|
ide_diagnostics_new (void)
|
|
{
|
|
return g_object_new (IDE_TYPE_DIAGNOSTICS, NULL);
|
|
}
|
|
|
|
void
|
|
ide_diagnostics_add (IdeDiagnostics *self,
|
|
IdeDiagnostic *diagnostic)
|
|
{
|
|
g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
|
|
g_return_if_fail (IDE_IS_DIAGNOSTIC (diagnostic));
|
|
|
|
ide_diagnostics_take (self, g_object_ref (diagnostic));
|
|
}
|
|
|
|
void
|
|
ide_diagnostics_take (IdeDiagnostics *self,
|
|
IdeDiagnostic *diagnostic)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
IdeDiagnosticSeverity severity;
|
|
guint position;
|
|
|
|
g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
|
|
g_return_if_fail (IDE_IS_DIAGNOSTIC (diagnostic));
|
|
|
|
severity = ide_diagnostic_get_severity (diagnostic);
|
|
|
|
position = priv->items->len;
|
|
g_ptr_array_add (priv->items, g_steal_pointer (&diagnostic));
|
|
g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
|
|
|
|
switch (severity)
|
|
{
|
|
case IDE_DIAGNOSTIC_ERROR:
|
|
case IDE_DIAGNOSTIC_FATAL:
|
|
priv->n_errors++;
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_ERRORS]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_ERRORS]);
|
|
break;
|
|
|
|
case IDE_DIAGNOSTIC_WARNING:
|
|
case IDE_DIAGNOSTIC_DEPRECATED:
|
|
case IDE_DIAGNOSTIC_UNUSED:
|
|
priv->n_warnings++;
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_HAS_WARNINGS]);
|
|
g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_N_WARNINGS]);
|
|
break;
|
|
|
|
case IDE_DIAGNOSTIC_IGNORED:
|
|
case IDE_DIAGNOSTIC_NOTE:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void
|
|
ide_diagnostics_merge (IdeDiagnostics *self,
|
|
IdeDiagnostics *other)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
IdeDiagnosticsPrivate *other_priv = ide_diagnostics_get_instance_private (other);
|
|
guint position;
|
|
|
|
g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
|
|
g_return_if_fail (IDE_IS_DIAGNOSTICS (other));
|
|
|
|
position = priv->items->len;
|
|
|
|
for (guint i = 0; i < other_priv->items->len; i++)
|
|
{
|
|
IdeDiagnostic *diagnostic = g_ptr_array_index (other_priv->items, i);
|
|
ide_diagnostics_take (self, g_object_ref (diagnostic));
|
|
}
|
|
|
|
g_list_model_items_changed (G_LIST_MODEL (self), position, 0, other_priv->items->len);
|
|
}
|
|
|
|
gboolean
|
|
ide_diagnostics_get_has_errors (IdeDiagnostics *self)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), FALSE);
|
|
|
|
return priv->n_errors > 0;
|
|
}
|
|
|
|
guint
|
|
ide_diagnostics_get_n_errors (IdeDiagnostics *self)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
|
|
|
|
return priv->n_errors;
|
|
}
|
|
|
|
gboolean
|
|
ide_diagnostics_get_has_warnings (IdeDiagnostics *self)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), FALSE);
|
|
|
|
return priv->n_warnings > 0;
|
|
}
|
|
|
|
guint
|
|
ide_diagnostics_get_n_warnings (IdeDiagnostics *self)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
|
|
|
|
return priv->n_warnings;
|
|
}
|
|
|
|
static GType
|
|
ide_diagnostics_get_item_type (GListModel *model)
|
|
{
|
|
return IDE_TYPE_DIAGNOSTIC;
|
|
}
|
|
|
|
static guint
|
|
ide_diagnostics_get_n_items (GListModel *model)
|
|
{
|
|
IdeDiagnostics *self = (IdeDiagnostics *)model;
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), 0);
|
|
|
|
return priv->items->len;
|
|
}
|
|
|
|
static gpointer
|
|
ide_diagnostics_get_item (GListModel *model,
|
|
guint position)
|
|
{
|
|
IdeDiagnostics *self = (IdeDiagnostics *)model;
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), NULL);
|
|
|
|
if (position < priv->items->len)
|
|
return g_object_ref (g_ptr_array_index (priv->items, position));
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
list_model_iface_init (GListModelInterface *iface)
|
|
{
|
|
iface->get_n_items = ide_diagnostics_get_n_items;
|
|
iface->get_item_type = ide_diagnostics_get_item_type;
|
|
iface->get_item = ide_diagnostics_get_item;
|
|
}
|
|
|
|
static gint
|
|
compare_lines (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
const IdeDiagnosticsCacheLine *line_a = a;
|
|
const IdeDiagnosticsCacheLine *line_b = b;
|
|
|
|
return line_a->line - line_b->line;
|
|
}
|
|
|
|
static void
|
|
ide_diagnostics_build_caches (IdeDiagnostics *self)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
g_autoptr(GHashTable) caches = NULL;
|
|
IdeDiagnosticsCache *cache;
|
|
GHashTableIter iter;
|
|
GFile *file;
|
|
|
|
g_assert (IDE_IS_DIAGNOSTICS (self));
|
|
g_assert (priv->caches == NULL);
|
|
|
|
caches = g_hash_table_new_full (g_file_hash,
|
|
(GEqualFunc)g_file_equal,
|
|
g_object_unref,
|
|
ide_diagnostics_cache_free);
|
|
|
|
for (guint i = 0; i < priv->items->len; i++)
|
|
{
|
|
IdeDiagnostic *diag = g_ptr_array_index (priv->items, i);
|
|
IdeDiagnosticsCacheLine val;
|
|
IdeLocation *location;
|
|
|
|
if (!(file = ide_diagnostic_get_file (diag)))
|
|
continue;
|
|
|
|
if (!(location = ide_diagnostic_get_location (diag)))
|
|
continue;
|
|
|
|
if (!(cache = g_hash_table_lookup (caches, file)))
|
|
{
|
|
cache = g_slice_new0 (IdeDiagnosticsCache);
|
|
cache->file = g_object_ref (file);
|
|
cache->lines = g_array_new (FALSE, FALSE, sizeof (IdeDiagnosticsCacheLine));
|
|
g_hash_table_insert (caches, g_object_ref (file), cache);
|
|
}
|
|
|
|
val.severity = ide_diagnostic_get_severity (diag);
|
|
val.line = ide_location_get_line (location);
|
|
|
|
g_array_append_val (cache->lines, val);
|
|
}
|
|
|
|
g_hash_table_iter_init (&iter, caches);
|
|
|
|
while (g_hash_table_iter_next (&iter, (gpointer *)&file, (gpointer *)&cache))
|
|
g_array_sort (cache->lines, compare_lines);
|
|
|
|
priv->caches = g_steal_pointer (&caches);
|
|
}
|
|
|
|
/**
|
|
* ide_diagnostics_foreach_line_in_range:
|
|
* @self: an #IdeDiagnostics
|
|
* @file: a #GFile
|
|
* @begin_line: the starting line
|
|
* @end_line: the ending line
|
|
* @callback: (scope call): a callback to execute for each matching line
|
|
* @user_data: user data for @callback
|
|
*
|
|
* This function calls @callback for every line with diagnostics between
|
|
* @begin_line and @end_line. This is useful when drawing information about
|
|
* diagnostics in an editor where a known number of lines are visible.
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
void
|
|
ide_diagnostics_foreach_line_in_range (IdeDiagnostics *self,
|
|
GFile *file,
|
|
guint begin_line,
|
|
guint end_line,
|
|
IdeDiagnosticsLineCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
const IdeDiagnosticsCache *cache;
|
|
|
|
g_return_if_fail (IDE_IS_DIAGNOSTICS (self));
|
|
g_return_if_fail (G_IS_FILE (file));
|
|
|
|
if (priv->items->len == 0)
|
|
return;
|
|
|
|
if (priv->caches == NULL)
|
|
ide_diagnostics_build_caches (self);
|
|
|
|
if (!(cache = g_hash_table_lookup (priv->caches, file)))
|
|
return;
|
|
|
|
for (guint i = 0; i < cache->lines->len; i++)
|
|
{
|
|
const IdeDiagnosticsCacheLine *line = &g_array_index (cache->lines, IdeDiagnosticsCacheLine, i);
|
|
|
|
if (line->line < begin_line)
|
|
continue;
|
|
|
|
if (line->line > end_line)
|
|
break;
|
|
|
|
callback (line->line, line->severity, user_data);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* ide_diagnostics_get_diagnostic_at_line:
|
|
* @self: a #IdeDiagnostics
|
|
* @file: the target file
|
|
* @line: a line number
|
|
*
|
|
* Locates an #IdeDiagnostic in @file at @line.
|
|
*
|
|
* Returns: (transfer none) (nullable): an #IdeDiagnostic or %NULL
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeDiagnostic *
|
|
ide_diagnostics_get_diagnostic_at_line (IdeDiagnostics *self,
|
|
GFile *file,
|
|
guint line)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
|
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), NULL);
|
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
|
|
|
for (guint i = 0; i < priv->items->len; i++)
|
|
{
|
|
IdeDiagnostic *diag = g_ptr_array_index (priv->items, i);
|
|
IdeLocation *loc = ide_diagnostic_get_location (diag);
|
|
GFile *loc_file;
|
|
guint loc_line;
|
|
|
|
if (loc == NULL)
|
|
continue;
|
|
|
|
loc_file = ide_location_get_file (loc);
|
|
loc_line = ide_location_get_line (loc);
|
|
|
|
if (loc_line == line && g_file_equal (file, loc_file))
|
|
return diag;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ide_diagnostics_get_diagnostics_at_line:
|
|
* @self: a #IdeDiagnostics
|
|
* @file: the target file
|
|
* @line: a line number
|
|
*
|
|
* Locates all #IdeDiagnostic in @file at @line.
|
|
*
|
|
* Returns: (transfer full) (element-type IdeDiagnostic) (nullable): an #GPtrArray or %NULL
|
|
*
|
|
* Since: 3.38
|
|
*/
|
|
GPtrArray *
|
|
ide_diagnostics_get_diagnostics_at_line (IdeDiagnostics *self,
|
|
GFile *file,
|
|
guint line)
|
|
{
|
|
IdeDiagnosticsPrivate *priv = ide_diagnostics_get_instance_private (self);
|
|
g_autoptr(GPtrArray) valid_diag = NULL;
|
|
|
|
g_return_val_if_fail (IDE_IS_DIAGNOSTICS (self), NULL);
|
|
g_return_val_if_fail (G_IS_FILE (file), NULL);
|
|
|
|
valid_diag = g_ptr_array_new_with_free_func (g_object_unref);
|
|
|
|
for (guint i = 0; i < priv->items->len; i++)
|
|
{
|
|
IdeDiagnostic *diag = g_ptr_array_index (priv->items, i);
|
|
IdeLocation *loc = ide_diagnostic_get_location (diag);
|
|
GFile *loc_file;
|
|
guint loc_line;
|
|
|
|
if (loc == NULL)
|
|
continue;
|
|
|
|
loc_file = ide_location_get_file (loc);
|
|
loc_line = ide_location_get_line (loc);
|
|
|
|
if (loc_line == line && g_file_equal (file, loc_file))
|
|
g_ptr_array_add (valid_diag, g_object_ref(diag));
|
|
}
|
|
|
|
if (valid_diag->len != 0)
|
|
return IDE_PTR_ARRAY_STEAL_FULL (&valid_diag);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ide_diagnostics_new_from_array:
|
|
* @array: (nullable) (element-type IdeDiagnostic): optional array
|
|
* of diagnostics to add.
|
|
*
|
|
* Returns: (transfer full): an #IdeDiagnostics
|
|
*
|
|
* Since: 3.32
|
|
*/
|
|
IdeDiagnostics *
|
|
ide_diagnostics_new_from_array (GPtrArray *array)
|
|
{
|
|
IdeDiagnostics *ret = ide_diagnostics_new ();
|
|
|
|
if (array != NULL)
|
|
{
|
|
for (guint i = 0; i < array->len; i++)
|
|
ide_diagnostics_add (ret, g_ptr_array_index (array, i));
|
|
}
|
|
|
|
return g_steal_pointer (&ret);
|
|
}
|