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