gem-graph-client/libide/code/ide-diagnostic.c

777 lines
20 KiB
C
Raw Normal View History

/* ide-diagnostic.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-diagnostic"
#include "config.h"
#include "ide-code-enums.h"
#include "ide-diagnostic.h"
#include "ide-location.h"
#include "ide-range.h"
#include "ide-text-edit.h"
typedef struct
{
IdeDiagnosticSeverity severity;
guint hash;
gchar *text;
IdeLocation *location;
GPtrArray *ranges;
GPtrArray *fixits;
IdeMarkedKind marked_kind;
} IdeDiagnosticPrivate;
enum {
PROP_0,
PROP_DISPLAY_TEXT,
PROP_LOCATION,
PROP_SEVERITY,
PROP_TEXT,
N_PROPS
};
G_DEFINE_TYPE_WITH_PRIVATE (IdeDiagnostic, ide_diagnostic, IDE_TYPE_OBJECT)
static GParamSpec *properties [N_PROPS];
static void
ide_diagnostic_set_location (IdeDiagnostic *self,
IdeLocation *location)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
g_set_object (&priv->location, location);
}
static void
ide_diagnostic_set_text (IdeDiagnostic *self,
const gchar *text)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
priv->text = g_strdup (text);
}
static void
ide_diagnostic_set_severity (IdeDiagnostic *self,
IdeDiagnosticSeverity severity)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
priv->severity = severity;
}
static void
ide_diagnostic_finalize (GObject *object)
{
IdeDiagnostic *self = (IdeDiagnostic *)object;
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_clear_pointer (&priv->text, g_free);
g_clear_pointer (&priv->ranges, g_ptr_array_unref);
g_clear_pointer (&priv->fixits, g_ptr_array_unref);
g_clear_object (&priv->location);
G_OBJECT_CLASS (ide_diagnostic_parent_class)->finalize (object);
}
static void
ide_diagnostic_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
IdeDiagnostic *self = IDE_DIAGNOSTIC (object);
switch (prop_id)
{
case PROP_LOCATION:
g_value_set_object (value, ide_diagnostic_get_location (self));
break;
case PROP_SEVERITY:
g_value_set_enum (value, ide_diagnostic_get_severity (self));
break;
case PROP_DISPLAY_TEXT:
g_value_take_string (value, ide_diagnostic_get_text_for_display (self));
break;
case PROP_TEXT:
g_value_set_string (value, ide_diagnostic_get_text (self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_diagnostic_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
IdeDiagnostic *self = IDE_DIAGNOSTIC (object);
switch (prop_id)
{
case PROP_LOCATION:
ide_diagnostic_set_location (self, g_value_get_object (value));
break;
case PROP_SEVERITY:
ide_diagnostic_set_severity (self, g_value_get_enum (value));
break;
case PROP_TEXT:
ide_diagnostic_set_text (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static void
ide_diagnostic_class_init (IdeDiagnosticClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = ide_diagnostic_finalize;
object_class->get_property = ide_diagnostic_get_property;
object_class->set_property = ide_diagnostic_set_property;
properties [PROP_LOCATION] =
g_param_spec_object ("location",
"Location",
"The location of the diagnostic",
IDE_TYPE_LOCATION,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
properties [PROP_SEVERITY] =
g_param_spec_enum ("severity",
"Severity",
"The severity of the diagnostic",
IDE_TYPE_DIAGNOSTIC_SEVERITY,
IDE_DIAGNOSTIC_IGNORED,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
properties [PROP_TEXT] =
g_param_spec_string ("text",
"Text",
"The text of the diagnostic",
NULL,
(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
properties [PROP_DISPLAY_TEXT] =
g_param_spec_string ("display-text",
"Display Text",
"The text formatted for display",
NULL,
(G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
g_object_class_install_properties (object_class, N_PROPS, properties);
}
static void
ide_diagnostic_init (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
priv->marked_kind = IDE_MARKED_KIND_PLAINTEXT;
}
/**
* ide_diagnostic_get_location:
* @self: a #IdeDiagnostic
*
* Gets the location of the diagnostic.
*
* See also: ide_diagnostic_get_range().
*
* Returns: (transfer none) (nullable): an #IdeLocation or %NULL
*
* Since: 3.32
*/
IdeLocation *
ide_diagnostic_get_location (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
if (priv->location != NULL)
return priv->location;
if (priv->ranges != NULL && priv->ranges->len > 0)
{
IdeRange *range = g_ptr_array_index (priv->ranges, 0);
return ide_range_get_begin (range);
}
return NULL;
}
/**
* ide_diagnostic_get_file:
* @self: a #IdeDiagnostic
*
* Gets the file containing the diagnostic, if any.
*
* Returns: (transfer none) (nullable): an #IdeLocation or %NULL
*
* Since: 3.32
*/
GFile *
ide_diagnostic_get_file (IdeDiagnostic *self)
{
IdeLocation *location;
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
if ((location = ide_diagnostic_get_location (self)))
return ide_location_get_file (location);
return NULL;
}
/**
* ide_diagnostic_get_text_for_display:
* @self: an #IdeDiagnostic
*
* This creates a new string that is formatted using the diagnostics
* line number, column, severity, and message text in the format
* "line:column: severity: message".
*
* This can be convenient when wanting to quickly display a
* diagnostic such as in a tooltip.
*
* Returns: (transfer full): string containing the text formatted for
* display.
*
* Since: 3.32
*/
gchar *
ide_diagnostic_get_text_for_display (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
IdeLocation *location;
const gchar *severity;
guint line = 0;
guint column = 0;
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
severity = ide_diagnostic_severity_to_string (priv->severity);
location = ide_diagnostic_get_location (self);
if (location != NULL)
{
line = ide_location_get_line (location) + 1;
column = ide_location_get_line_offset (location) + 1;
}
return g_strdup_printf ("%u:%u: %s: %s", line, column, severity, priv->text);
}
/**
* ide_diagnostic_severity_to_string:
* @severity: a #IdeDiagnosticSeverity
*
* Returns a string suitable to represent the diagnsotic severity.
*
* Returns: a string
*
* Since: 3.32
*/
const gchar *
ide_diagnostic_severity_to_string (IdeDiagnosticSeverity severity)
{
switch (severity)
{
case IDE_DIAGNOSTIC_IGNORED:
return "ignored";
case IDE_DIAGNOSTIC_NOTE:
return "note";
case IDE_DIAGNOSTIC_UNUSED:
return "unused";
case IDE_DIAGNOSTIC_DEPRECATED:
return "deprecated";
case IDE_DIAGNOSTIC_WARNING:
return "warning";
case IDE_DIAGNOSTIC_ERROR:
return "error";
case IDE_DIAGNOSTIC_FATAL:
return "fatal";
default:
return "unknown";
}
}
guint
ide_diagnostic_get_n_ranges (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
return priv->ranges ? priv->ranges->len : 0;
}
/**
* ide_diagnostic_get_range:
*
* Retrieves the range found at @index. It is a programming error to call this
* function with a value greater or equal to ide_diagnostic_get_n_ranges().
*
* Returns: (transfer none) (nullable): An #IdeRange
*
* Since: 3.32
*/
IdeRange *
ide_diagnostic_get_range (IdeDiagnostic *self,
guint index)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
if (priv->ranges)
{
if (index < priv->ranges->len)
return g_ptr_array_index (priv->ranges, index);
}
return NULL;
}
guint
ide_diagnostic_get_n_fixits (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
return (priv->fixits != NULL) ? priv->fixits->len : 0;
}
/**
* ide_diagnostic_get_fixit:
* @self: an #IdeDiagnostic.
* @index: The index of the fixit.
*
* Gets the fixit denoted by @index. This value should be less than the value
* returned from ide_diagnostic_get_n_fixits().
*
* Returns: (transfer none) (nullable): An #IdeTextEdit
*
* Since: 3.32
*/
IdeTextEdit *
ide_diagnostic_get_fixit (IdeDiagnostic *self,
guint index)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (self, NULL);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
g_return_val_if_fail (priv->fixits, NULL);
if (priv->fixits != NULL)
{
if (index < priv->fixits->len)
return g_ptr_array_index (priv->fixits, index);
}
return NULL;
}
gint
ide_diagnostic_compare (IdeDiagnostic *a,
IdeDiagnostic *b)
{
IdeDiagnosticPrivate *priv_a = ide_diagnostic_get_instance_private (a);
IdeDiagnosticPrivate *priv_b = ide_diagnostic_get_instance_private (b);
gint ret;
g_assert (IDE_IS_DIAGNOSTIC (a));
g_assert (IDE_IS_DIAGNOSTIC (b));
/* Severity is 0..N where N is more important. So reverse comparator. */
if (0 != (ret = (gint)priv_b->severity - (gint)priv_a->severity))
return ret;
if (priv_a->location && priv_b->location)
{
if (0 != (ret = ide_location_compare (priv_a->location, priv_b->location)))
return ret;
}
return g_strcmp0 (priv_a->text, priv_b->text);
}
const gchar *
ide_diagnostic_get_text (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
return priv->text;
}
IdeDiagnosticSeverity
ide_diagnostic_get_severity (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
return priv->severity;
}
/**
* ide_diagnostic_add_range:
* @self: a #IdeDiagnostic
* @range: an #IdeRange
*
* Adds a source range to the diagnostic.
*
* Since: 3.32
*/
void
ide_diagnostic_add_range (IdeDiagnostic *self,
IdeRange *range)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
g_return_if_fail (IDE_IS_RANGE (range));
if (priv->ranges == NULL)
priv->ranges = g_ptr_array_new_with_free_func (g_object_unref);
g_ptr_array_add (priv->ranges, g_object_ref (range));
}
/**
* ide_diagnostic_take_range:
* @self: a #IdeDiagnostic
* @range: (transfer full): an #IdeRange
*
* Adds a source range to the diagnostic, but does not increment the
* reference count of @range.
*
* Since: 3.32
*/
void
ide_diagnostic_take_range (IdeDiagnostic *self,
IdeRange *range)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
g_return_if_fail (IDE_IS_RANGE (range));
if (priv->ranges == NULL)
priv->ranges = g_ptr_array_new_with_free_func (g_object_unref);
g_ptr_array_add (priv->ranges, g_steal_pointer (&range));
}
/**
* ide_diagnostic_add_fixit:
* @self: a #IdeDiagnostic
* @fixit: an #IdeTextEdit
*
* Adds a source fixit to the diagnostic.
*
* Since: 3.32
*/
void
ide_diagnostic_add_fixit (IdeDiagnostic *self,
IdeTextEdit *fixit)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
g_return_if_fail (IDE_IS_TEXT_EDIT (fixit));
if (priv->fixits == NULL)
priv->fixits = g_ptr_array_new_with_free_func (g_object_unref);
g_ptr_array_add (priv->fixits, g_object_ref (fixit));
}
/**
* ide_diagnostic_take_fixit:
* @self: a #IdeDiagnostic
* @fixit: (transfer full): an #IdeTextEdit
*
* Adds a source fixit to the diagnostic, but does not increment the
* reference count of @fixit.
*
* Since: 3.32
*/
void
ide_diagnostic_take_fixit (IdeDiagnostic *self,
IdeTextEdit *fixit)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
g_return_if_fail (IDE_IS_TEXT_EDIT (fixit));
if (priv->fixits == NULL)
priv->fixits = g_ptr_array_new_with_free_func (g_object_unref);
g_ptr_array_add (priv->fixits, g_steal_pointer (&fixit));
}
IdeDiagnostic *
ide_diagnostic_new (IdeDiagnosticSeverity severity,
const gchar *message,
IdeLocation *location)
{
return g_object_new (IDE_TYPE_DIAGNOSTIC,
"severity", severity,
"location", location,
"text", message,
NULL);
}
guint
ide_diagnostic_hash (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), 0);
if (priv->hash == 0)
{
guint hash = g_str_hash (priv->text ?: "");
if (priv->location)
hash ^= ide_location_hash (priv->location);
if (priv->fixits)
hash ^= g_int_hash (&priv->fixits->len);
if (priv->ranges)
hash ^= g_int_hash (&priv->ranges->len);
priv->hash = hash;
}
return priv->hash;
}
/**
* ide_diagnostic_to_variant:
* @self: a #IdeDiagnostic
*
* Creates a #GVariant to represent the diagnostic. This can be useful when
* working in subprocesses to serialize the diagnostic.
*
* This function will never return a floating variant.
*
* Returns: (transfer full): a #GVariant
*
* Since: 3.32
*/
GVariant *
ide_diagnostic_to_variant (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
GVariantDict dict;
g_return_val_if_fail (self != NULL, NULL);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), NULL);
g_variant_dict_init (&dict, NULL);
g_variant_dict_insert (&dict, "text", "s", priv->text ?: "");
g_variant_dict_insert (&dict, "severity", "u", priv->severity);
if (priv->location != NULL)
{
g_autoptr(GVariant) vloc = ide_location_to_variant (priv->location);
if (vloc != NULL)
g_variant_dict_insert_value (&dict, "location", vloc);
}
if (priv->ranges != NULL && priv->ranges->len > 0)
{
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
for (guint i = 0; i < priv->ranges->len; i++)
{
IdeRange *range = g_ptr_array_index (priv->ranges, i);
g_autoptr(GVariant) vrange = ide_range_to_variant (range);
g_variant_builder_add_value (&builder, vrange);
}
g_variant_dict_insert_value (&dict, "ranges", g_variant_builder_end (&builder));
}
if (priv->fixits != NULL && priv->fixits->len > 0)
{
GVariantBuilder builder;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
for (guint i = 0; i < priv->fixits->len; i++)
{
IdeTextEdit *fixit = g_ptr_array_index (priv->fixits, i);
g_autoptr(GVariant) vfixit = ide_text_edit_to_variant (fixit);
g_variant_builder_add_value (&builder, vfixit);
}
g_variant_dict_insert_value (&dict, "fixits", g_variant_builder_end (&builder));
}
return g_variant_take_ref (g_variant_dict_end (&dict));
}
/**
* ide_diagnostic_new_from_variant:
* @variant: (nullable): a #GVariant or %NULL
*
* Creates a new #GVariant using the data contained in @variant.
*
* If @variant is %NULL or Upon failure, %NULL is returned.
*
* Returns: (nullable) (transfer full): a #IdeDiagnostic or %NULL
*
* Since: 3.32
*/
IdeDiagnostic *
ide_diagnostic_new_from_variant (GVariant *variant)
{
g_autoptr(IdeLocation) loc = NULL;
g_autoptr(GVariant) vloc = NULL;
g_autoptr(GVariant) unboxed = NULL;
g_autoptr(GVariant) ranges = NULL;
g_autoptr(GVariant) fixits = NULL;
IdeDiagnostic *self;
GVariantDict dict;
GVariantIter iter;
const gchar *text;
guint32 severity;
if (variant == NULL)
return NULL;
if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
variant = unboxed = g_variant_get_variant (variant);
if (!g_variant_is_of_type (variant, G_VARIANT_TYPE_VARDICT))
return NULL;
g_variant_dict_init (&dict, variant);
if (!g_variant_dict_lookup (&dict, "text", "&s", &text))
text = NULL;
if (!g_variant_dict_lookup (&dict, "severity", "u", &severity))
severity = 0;
if ((vloc = g_variant_dict_lookup_value (&dict, "location", NULL)))
loc = ide_location_new_from_variant (vloc);
if (!(self = ide_diagnostic_new (severity, text, loc)))
goto failure;
/* Ranges */
if ((ranges = g_variant_dict_lookup_value (&dict, "ranges", NULL)))
{
GVariant *vrange;
g_variant_iter_init (&iter, ranges);
while ((vrange = g_variant_iter_next_value (&iter)))
{
IdeRange *range;
if ((range = ide_range_new_from_variant (vrange)))
ide_diagnostic_take_range (self, g_steal_pointer (&range));
g_variant_unref (vrange);
}
}
/* Fixits */
if ((fixits = g_variant_dict_lookup_value (&dict, "fixits", NULL)))
{
GVariant *vfixit;
g_variant_iter_init (&iter, fixits);
while ((vfixit = g_variant_iter_next_value (&iter)))
{
IdeTextEdit *fixit;
if ((fixit = ide_text_edit_new_from_variant (vfixit)))
ide_diagnostic_take_fixit (self, g_steal_pointer (&fixit));
g_variant_unref (vfixit);
}
}
failure:
g_variant_dict_clear (&dict);
return self;
}
IdeMarkedKind
ide_diagnostic_get_marked_kind (IdeDiagnostic *self)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_val_if_fail (IDE_IS_DIAGNOSTIC (self), IDE_MARKED_KIND_PLAINTEXT);
return priv->marked_kind;
}
void
ide_diagnostic_set_marked_kind (IdeDiagnostic *self,
IdeMarkedKind marked_kind)
{
IdeDiagnosticPrivate *priv = ide_diagnostic_get_instance_private (self);
g_return_if_fail (IDE_IS_DIAGNOSTIC (self));
priv->marked_kind = marked_kind;
}