1 /* ide-location.c
2  *
3  * Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * SPDX-License-Identifier: GPL-3.0-or-later
19  */
20 
21 #define G_LOG_DOMAIN "ide-location"
22 
23 #include "config.h"
24 
25 #include "ide-location.h"
26 
27 typedef struct
28 {
29   GFile *file;
30   gint   line;
31   gint   line_offset;
32   gint   offset;
33 } IdeLocationPrivate;
34 
35 enum {
36   PROP_0,
37   PROP_FILE,
38   PROP_LINE,
39   PROP_LINE_OFFSET,
40   PROP_OFFSET,
41   N_PROPS
42 };
43 
G_DEFINE_TYPE_WITH_PRIVATE(IdeLocation,ide_location,G_TYPE_OBJECT)44 G_DEFINE_TYPE_WITH_PRIVATE (IdeLocation, ide_location, G_TYPE_OBJECT)
45 
46 static GParamSpec *properties [N_PROPS];
47 
48 static void
49 ide_location_set_file (IdeLocation *self,
50                        GFile       *file)
51 {
52   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
53 
54   g_assert (IDE_IS_LOCATION (self));
55 
56   g_set_object (&priv->file, file);
57 }
58 
59 static void
ide_location_set_line(IdeLocation * self,gint line)60 ide_location_set_line (IdeLocation *self,
61                        gint         line)
62 {
63   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
64 
65   g_assert (IDE_IS_LOCATION (self));
66 
67   priv->line = CLAMP (line, -1, G_MAXINT);
68 }
69 
70 static void
ide_location_set_line_offset(IdeLocation * self,gint line_offset)71 ide_location_set_line_offset (IdeLocation *self,
72                               gint         line_offset)
73 {
74   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
75 
76   g_assert (IDE_IS_LOCATION (self));
77 
78   priv->line_offset = CLAMP (line_offset, -1, G_MAXINT);
79 }
80 
81 static void
ide_location_set_offset(IdeLocation * self,gint offset)82 ide_location_set_offset (IdeLocation *self,
83                          gint         offset)
84 {
85   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
86 
87   g_assert (IDE_IS_LOCATION (self));
88 
89   priv->offset = CLAMP (offset, -1, G_MAXINT);
90 }
91 
92 static void
ide_location_dispose(GObject * object)93 ide_location_dispose (GObject *object)
94 {
95   IdeLocation *self = (IdeLocation *)object;
96   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
97 
98   g_clear_object (&priv->file);
99 
100   G_OBJECT_CLASS (ide_location_parent_class)->dispose (object);
101 }
102 
103 static void
ide_location_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)104 ide_location_get_property (GObject    *object,
105                            guint       prop_id,
106                            GValue     *value,
107                            GParamSpec *pspec)
108 {
109   IdeLocation *self = IDE_LOCATION (object);
110 
111   switch (prop_id)
112     {
113     case PROP_FILE:
114       g_value_set_object (value, ide_location_get_file (self));
115       break;
116 
117     case PROP_LINE:
118       g_value_set_int (value, ide_location_get_line (self));
119       break;
120 
121     case PROP_LINE_OFFSET:
122       g_value_set_int (value, ide_location_get_line_offset (self));
123       break;
124 
125     case PROP_OFFSET:
126       g_value_set_int (value, ide_location_get_offset (self));
127       break;
128 
129     default:
130       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
131     }
132 }
133 
134 static void
ide_location_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)135 ide_location_set_property (GObject      *object,
136                            guint         prop_id,
137                            const GValue *value,
138                            GParamSpec   *pspec)
139 {
140   IdeLocation *self = IDE_LOCATION (object);
141 
142   switch (prop_id)
143     {
144     case PROP_FILE:
145       ide_location_set_file (self, g_value_get_object (value));
146       break;
147 
148     case PROP_LINE:
149       ide_location_set_line (self, g_value_get_int (value));
150       break;
151 
152     case PROP_LINE_OFFSET:
153       ide_location_set_line_offset (self, g_value_get_int (value));
154       break;
155 
156     case PROP_OFFSET:
157       ide_location_set_offset (self, g_value_get_int (value));
158       break;
159 
160     default:
161       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
162     }
163 }
164 
165 static void
ide_location_class_init(IdeLocationClass * klass)166 ide_location_class_init (IdeLocationClass *klass)
167 {
168   GObjectClass *object_class = G_OBJECT_CLASS (klass);
169 
170   object_class->dispose = ide_location_dispose;
171   object_class->get_property = ide_location_get_property;
172   object_class->set_property = ide_location_set_property;
173 
174   properties [PROP_FILE] =
175     g_param_spec_object ("file",
176                          "File",
177                          "The file representing the location",
178                          G_TYPE_FILE,
179                          (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
180 
181   properties [PROP_LINE] =
182     g_param_spec_int ("line",
183                       "Line",
184                       "The line number within the file, starting from 0 or -1 for unknown",
185                       -1, G_MAXINT, -1,
186                       (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
187 
188   properties [PROP_LINE_OFFSET] =
189     g_param_spec_int ("line-offset",
190                       "Line Offset",
191                       "The offset within the line, starting from 0 or -1 for unknown",
192                       -1, G_MAXINT, -1,
193                       (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
194 
195   properties [PROP_OFFSET] =
196     g_param_spec_int ("offset",
197                       "Offset",
198                       "The offset within the file in characters, or -1 if unknown",
199                       -1, G_MAXINT, -1,
200                       (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
201 
202   g_object_class_install_properties (object_class, N_PROPS, properties);
203 }
204 
205 static void
ide_location_init(IdeLocation * self)206 ide_location_init (IdeLocation *self)
207 {
208   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
209 
210   priv->line = -1;
211   priv->line_offset = -1;
212   priv->offset = -1;
213 }
214 
215 /**
216  * ide_location_get_file:
217  * @self: a #IdeLocation
218  *
219  * Gets the file within the location.
220  *
221  * Returns: (transfer none) (nullable): a #GFile or %NULL
222  *
223  * Since: 3.32
224  */
225 GFile *
ide_location_get_file(IdeLocation * self)226 ide_location_get_file (IdeLocation *self)
227 {
228   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
229 
230   g_return_val_if_fail (IDE_IS_LOCATION (self), NULL);
231 
232   return priv->file;
233 }
234 
235 /**
236  * ide_location_get_line:
237  * @self: a #IdeLocation
238  *
239  * Gets the line within the #IdeLocation:file, or -1 if it is unknown.
240  *
241  * Returns: the line number, or -1.
242  *
243  * Since: 3.32
244  */
245 gint
ide_location_get_line(IdeLocation * self)246 ide_location_get_line (IdeLocation *self)
247 {
248   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
249 
250   g_return_val_if_fail (IDE_IS_LOCATION (self), -1);
251 
252   return priv->line;
253 }
254 
255 /**
256  * ide_location_get_line_offset:
257  * @self: a #IdeLocation
258  *
259  * Gets the offset within the #IdeLocation:line, or -1 if it is unknown.
260  *
261  * Returns: the line offset, or -1.
262  *
263  * Since: 3.32
264  */
265 gint
ide_location_get_line_offset(IdeLocation * self)266 ide_location_get_line_offset (IdeLocation *self)
267 {
268   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
269 
270   g_return_val_if_fail (IDE_IS_LOCATION (self), -1);
271 
272   return priv->line_offset;
273 }
274 
275 /**
276  * ide_location_get_offset:
277  * @self: a #IdeLocation
278  *
279  * Gets the offset within the file in characters, or -1 if it is unknown.
280  *
281  * Returns: the line offset, or -1.
282  *
283  * Since: 3.32
284  */
285 gint
ide_location_get_offset(IdeLocation * self)286 ide_location_get_offset (IdeLocation *self)
287 {
288   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
289 
290   g_return_val_if_fail (IDE_IS_LOCATION (self), -1);
291 
292   return priv->offset;
293 }
294 
295 /**
296  * ide_location_dup:
297  * @self: a #IdeLocation
298  *
299  * Makes a deep copy of @self.
300  *
301  * Returns: (transfer full): a new #IdeLocation
302  *
303  * Since: 3.32
304  */
305 IdeLocation *
ide_location_dup(IdeLocation * self)306 ide_location_dup (IdeLocation *self)
307 {
308   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
309 
310   g_return_val_if_fail (!self || IDE_IS_LOCATION (self), NULL);
311 
312   if (self == NULL)
313     return NULL;
314 
315   return g_object_new (IDE_TYPE_LOCATION,
316                        "file", priv->file,
317                        "line", priv->line,
318                        "line-offset", priv->line_offset,
319                        "offset", priv->offset,
320                        NULL);
321 }
322 
323 /**
324  * ide_location_to_variant:
325  * @self: a #IdeLocation
326  *
327  * Serializes the location into a variant that can be used to transport
328  * across IPC boundaries.
329  *
330  * This function will never return a variant with a floating reference.
331  *
332  * Returns: (transfer full): a #GVariant
333  *
334  * Since: 3.32
335  */
336 GVariant *
ide_location_to_variant(IdeLocation * self)337 ide_location_to_variant (IdeLocation *self)
338 {
339   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
340   g_autofree gchar *uri = NULL;
341   GVariantDict dict;
342 
343   g_return_val_if_fail (self != NULL, NULL);
344 
345   g_variant_dict_init (&dict, NULL);
346 
347   uri = g_file_get_uri (priv->file);
348 
349   g_variant_dict_insert (&dict, "uri", "s", uri);
350   g_variant_dict_insert (&dict, "line", "i", priv->line);
351   g_variant_dict_insert (&dict, "line-offset", "i", priv->line_offset);
352 
353   return g_variant_take_ref (g_variant_dict_end (&dict));
354 }
355 
356 IdeLocation *
ide_location_new(GFile * file,gint line,gint line_offset)357 ide_location_new (GFile *file,
358                   gint   line,
359                   gint   line_offset)
360 {
361   g_return_val_if_fail (G_IS_FILE (file), NULL);
362 
363   line = CLAMP (line, -1, G_MAXINT);
364   line_offset = CLAMP (line_offset, -1, G_MAXINT);
365 
366   return g_object_new (IDE_TYPE_LOCATION,
367                        "file", file,
368                        "line", line,
369                        "line-offset", line_offset,
370                        NULL);
371 }
372 
373 /**
374  * ide_location_new_with_offset:
375  * @file: a #GFile
376  * @line: a line number starting from 0, or -1 if unknown
377  * @line_offset: a line offset starting from 0, or -1 if unknown
378  * @offset: a charcter offset in file starting from 0, or -1 if unknown
379  *
380  * Returns: (transfer full): an #IdeLocation
381  *
382  * Since: 3.32
383  */
384 IdeLocation *
ide_location_new_with_offset(GFile * file,gint line,gint line_offset,gint offset)385 ide_location_new_with_offset (GFile *file,
386                               gint   line,
387                               gint   line_offset,
388                               gint   offset)
389 {
390   g_return_val_if_fail (G_IS_FILE (file), NULL);
391 
392   line = CLAMP (line, -1, G_MAXINT);
393   line_offset = CLAMP (line_offset, -1, G_MAXINT);
394   offset = CLAMP (offset, -1, G_MAXINT);
395 
396   return g_object_new (IDE_TYPE_LOCATION,
397                        "file", file,
398                        "line", line,
399                        "line-offset", line_offset,
400                        "offset", offset,
401                        NULL);
402 }
403 
404 /**
405  * ide_location_new_from_variant:
406  * @variant: (nullable): a #GVariant or %NULL
407  *
408  * Creates a new #IdeLocation using the serialized form from a
409  * previously serialized #GVariant.
410  *
411  * As a convenience, if @variant is %NULL, %NULL is returned.
412  *
413  * See also: ide_location_to_variant()
414  *
415  * Returns: (transfer full) (nullable): a #GVariant if succesful;
416  *   otherwise %NULL.
417  *
418  * Since: 3.32
419  */
420 IdeLocation *
ide_location_new_from_variant(GVariant * variant)421 ide_location_new_from_variant (GVariant *variant)
422 {
423   g_autoptr(GVariant) unboxed = NULL;
424   g_autoptr(GFile) file = NULL;
425   IdeLocation *self = NULL;
426   GVariantDict dict;
427   const gchar *uri;
428   guint32 line;
429   guint32 line_offset;
430 
431   if (variant == NULL)
432     return NULL;
433 
434   if (g_variant_is_of_type (variant, G_VARIANT_TYPE_VARIANT))
435     variant = unboxed = g_variant_get_variant (variant);
436 
437   g_variant_dict_init (&dict, variant);
438 
439   if (!g_variant_dict_lookup (&dict, "uri", "&s", &uri))
440     goto failure;
441 
442   if (!g_variant_dict_lookup (&dict, "line", "i", &line))
443     line = 0;
444 
445   if (!g_variant_dict_lookup (&dict, "line-offset", "i", &line_offset))
446     line_offset = 0;
447 
448   file = g_file_new_for_uri (uri);
449 
450   self = ide_location_new (file, line, line_offset);
451 
452 failure:
453   g_variant_dict_clear (&dict);
454 
455   return self;
456 }
457 
458 static gint
file_compare(GFile * a,GFile * b)459 file_compare (GFile *a,
460               GFile *b)
461 {
462   g_autofree gchar *uri_a = g_file_get_uri (a);
463   g_autofree gchar *uri_b = g_file_get_uri (b);
464 
465   return g_strcmp0 (uri_a, uri_b);
466 }
467 
468 gboolean
ide_location_compare(IdeLocation * a,IdeLocation * b)469 ide_location_compare (IdeLocation *a,
470                       IdeLocation *b)
471 {
472   IdeLocationPrivate *priv_a = ide_location_get_instance_private (a);
473   IdeLocationPrivate *priv_b = ide_location_get_instance_private (b);
474   gint ret;
475 
476   g_assert (IDE_IS_LOCATION (a));
477   g_assert (IDE_IS_LOCATION (b));
478 
479   if (priv_a->file && priv_b->file)
480     {
481       if (0 != (ret = file_compare (priv_a->file, priv_b->file)))
482         return ret;
483     }
484   else if (priv_a->file)
485     return -1;
486   else if (priv_b->file)
487     return 1;
488 
489   if (0 != (ret = priv_a->line - priv_b->line))
490     return ret;
491 
492   return priv_a->line_offset - priv_b->line_offset;
493 }
494 
495 guint
ide_location_hash(IdeLocation * self)496 ide_location_hash (IdeLocation *self)
497 {
498   IdeLocationPrivate *priv = ide_location_get_instance_private (self);
499 
500   g_return_val_if_fail (IDE_IS_LOCATION (self), 0);
501 
502   return g_file_hash (priv->file) ^ g_int_hash (&priv->line) ^ g_int_hash (&priv->line_offset);
503 }
504