1 /* gbp-code-index-service.c
2 *
3 * Copyright 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 "gbp-code-index-service"
22
23 #include "config.h"
24
25 #include <glib/gi18n.h>
26 #include <gtksourceview/gtksource.h>
27 #include <libide-code.h>
28 #include <libide-foundry.h>
29 #include <libide-projects.h>
30 #include <libide-vcs.h>
31 #include <libpeas/peas.h>
32
33 #include "gbp-code-index-executor.h"
34 #include "gbp-code-index-plan.h"
35 #include "gbp-code-index-service.h"
36 #include "indexer-info.h"
37
38 #define DELAY_FOR_INDEXING_MSEC 500
39
40 struct _GbpCodeIndexService
41 {
42 IdeObject parent_instance;
43
44 IdeNotification *notif;
45 IdeCodeIndexIndex *index;
46 GCancellable *cancellable;
47
48 guint queued_source;
49
50 guint build_inhibit : 1;
51 guint needs_indexing : 1;
52 guint indexing : 1;
53 guint started : 1;
54 guint paused : 1;
55 };
56
57 typedef struct
58 {
59 IdeCodeIndexIndex *index;
60 GFile *workdir;
61 GFile *indexdir;
62 } LoadIndexes;
63
64 enum {
65 PROP_0,
66 PROP_PAUSED,
67 N_PROPS
68 };
69
70 G_DEFINE_FINAL_TYPE (GbpCodeIndexService, gbp_code_index_service, IDE_TYPE_OBJECT)
71
72 static void gbp_code_index_service_index_async (GbpCodeIndexService *self,
73 GCancellable *cancellable,
74 GAsyncReadyCallback callback,
75 gpointer user_data);
76 static gboolean gbp_code_index_service_index_finish (GbpCodeIndexService *self,
77 GAsyncResult *result,
78 GError **error);
79 static void gbp_code_index_service_reload_indexes (GbpCodeIndexService *self);
80
81 static GParamSpec *properties [N_PROPS];
82
83 static void
load_indexes_free(LoadIndexes * state)84 load_indexes_free (LoadIndexes *state)
85 {
86 g_clear_object (&state->index);
87 g_clear_object (&state->indexdir);
88 g_clear_object (&state->workdir);
89 g_slice_free (LoadIndexes, state);
90 }
91
92 static void
update_notification(GbpCodeIndexService * self)93 update_notification (GbpCodeIndexService *self)
94 {
95 gboolean visible;
96
97 g_assert (IDE_IS_MAIN_THREAD ());
98 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
99
100 visible = self->indexing || self->paused;
101
102 if (ide_object_is_root (IDE_OBJECT (self->notif)) && visible)
103 ide_notification_attach (self->notif, IDE_OBJECT (self));
104 else if (!ide_object_is_root (IDE_OBJECT (self->notif)) && !visible)
105 ide_notification_withdraw (self->notif);
106 }
107
108 static gchar *
gbp_code_index_service_repr(IdeObject * object)109 gbp_code_index_service_repr (IdeObject *object)
110 {
111 GbpCodeIndexService *self = (GbpCodeIndexService *)object;
112
113 return g_strdup_printf ("%s started=%d paused=%d",
114 G_OBJECT_TYPE_NAME (self), self->started, self->paused);
115 }
116
117 static void
gbp_code_index_service_destroy(IdeObject * object)118 gbp_code_index_service_destroy (IdeObject *object)
119 {
120 GbpCodeIndexService *self = (GbpCodeIndexService *)object;
121
122 if (self->started)
123 gbp_code_index_service_stop (self);
124
125 g_cancellable_cancel (self->cancellable);
126 g_clear_object (&self->cancellable);
127 g_clear_handle_id (&self->queued_source, g_source_remove);
128
129 ide_clear_and_destroy_object (&self->index);
130
131 if (self->notif)
132 {
133 ide_notification_withdraw (self->notif);
134 g_clear_object (&self->notif);
135 }
136
137 IDE_OBJECT_CLASS (gbp_code_index_service_parent_class)->destroy (object);
138 }
139
140 static void
gbp_code_index_service_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)141 gbp_code_index_service_get_property (GObject *object,
142 guint prop_id,
143 GValue *value,
144 GParamSpec *pspec)
145 {
146 GbpCodeIndexService *self = GBP_CODE_INDEX_SERVICE (object);
147
148 switch (prop_id)
149 {
150 case PROP_PAUSED:
151 g_value_set_boolean (value, gbp_code_index_service_get_paused (self));
152 break;
153
154 default:
155 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
156 }
157 }
158
159 static void
gbp_code_index_service_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)160 gbp_code_index_service_set_property (GObject *object,
161 guint prop_id,
162 const GValue *value,
163 GParamSpec *pspec)
164 {
165 GbpCodeIndexService *self = GBP_CODE_INDEX_SERVICE (object);
166
167 switch (prop_id)
168 {
169 case PROP_PAUSED:
170 gbp_code_index_service_set_paused (self, g_value_get_boolean (value));
171 break;
172
173 default:
174 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
175 }
176 }
177
178 static void
gbp_code_index_service_class_init(GbpCodeIndexServiceClass * klass)179 gbp_code_index_service_class_init (GbpCodeIndexServiceClass *klass)
180 {
181 GObjectClass *object_class = G_OBJECT_CLASS (klass);
182 IdeObjectClass *i_object_class = IDE_OBJECT_CLASS (klass);
183
184 object_class->get_property = gbp_code_index_service_get_property;
185 object_class->set_property = gbp_code_index_service_set_property;
186
187 i_object_class->destroy = gbp_code_index_service_destroy;
188 i_object_class->repr = gbp_code_index_service_repr;
189
190 properties [PROP_PAUSED] =
191 g_param_spec_boolean ("paused",
192 "Paused",
193 "If the service is paused",
194 FALSE,
195 (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
196
197 g_object_class_install_properties (object_class, N_PROPS, properties);
198 }
199
200 static void
gbp_code_index_service_init(GbpCodeIndexService * self)201 gbp_code_index_service_init (GbpCodeIndexService *self)
202 {
203 g_autoptr(GIcon) icon = NULL;
204
205 icon = g_themed_icon_new ("media-playback-pause-symbolic");
206
207 self->notif = ide_notification_new ();
208 ide_notification_set_id (self->notif, "org.gnome.builder.code-index");
209 ide_notification_set_title (self->notif, _("Indexing Source Code"));
210 ide_notification_set_body (self->notif, _("Search, diagnostics, and autocompletion may be limited until complete."));
211 ide_notification_set_has_progress (self->notif, TRUE);
212 ide_notification_set_progress (self->notif, 0);
213 ide_notification_add_button (self->notif, NULL, icon, "code-index.paused");
214
215 self->index = ide_code_index_index_new (IDE_OBJECT (self));
216 }
217
218 static void
gbp_code_index_service_index_cb(GObject * object,GAsyncResult * result,gpointer user_data)219 gbp_code_index_service_index_cb (GObject *object,
220 GAsyncResult *result,
221 gpointer user_data)
222 {
223 GbpCodeIndexService *self = (GbpCodeIndexService *)object;
224 g_autoptr(GError) error = NULL;
225
226 g_assert (IDE_IS_MAIN_THREAD ());
227 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
228 g_assert (G_IS_ASYNC_RESULT (result));
229
230 if (!gbp_code_index_service_index_finish (self, result, &error))
231 {
232 if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
233 g_warning ("Code indexing failed: %s", error->message);
234 }
235 }
236
237 static gboolean
gbp_code_index_service_queue_index_cb(gpointer user_data)238 gbp_code_index_service_queue_index_cb (gpointer user_data)
239 {
240 GbpCodeIndexService *self = user_data;
241 g_autoptr(IdeContext) context = NULL;
242 IdeBuildManager *build_manager;
243 IdePipeline *pipeline;
244
245 IDE_ENTRY;
246
247 g_assert (IDE_IS_MAIN_THREAD ());
248 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
249
250 self->queued_source = 0;
251
252 g_cancellable_cancel (self->cancellable);
253 g_clear_object (&self->cancellable);
254 self->cancellable = g_cancellable_new ();
255
256 if (!ide_object_in_destruction (IDE_OBJECT (self)) &&
257 (context = ide_object_ref_context (IDE_OBJECT (self))) &&
258 ide_context_has_project (context) &&
259 (build_manager = ide_build_manager_from_context (context)) &&
260 (pipeline = ide_build_manager_get_pipeline (build_manager)) &&
261 ide_pipeline_has_configured (pipeline))
262 gbp_code_index_service_index_async (self,
263 self->cancellable,
264 gbp_code_index_service_index_cb,
265 NULL);
266
267 IDE_RETURN (G_SOURCE_REMOVE);
268 }
269
270 static void
gbp_code_index_service_queue_index(GbpCodeIndexService * self)271 gbp_code_index_service_queue_index (GbpCodeIndexService *self)
272 {
273 g_assert (IDE_IS_MAIN_THREAD ());
274 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
275
276 self->needs_indexing = TRUE;
277
278 if (self->indexing || self->paused)
279 return;
280
281 g_clear_handle_id (&self->queued_source, g_source_remove);
282 self->queued_source = g_timeout_add (DELAY_FOR_INDEXING_MSEC,
283 gbp_code_index_service_queue_index_cb,
284 self);
285 }
286
287 static void
gbp_code_index_service_pause(GbpCodeIndexService * self)288 gbp_code_index_service_pause (GbpCodeIndexService *self)
289 {
290 g_assert (IDE_IS_MAIN_THREAD ());
291 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
292
293 self->paused = TRUE;
294
295 g_cancellable_cancel (self->cancellable);
296 g_clear_object (&self->cancellable);
297 g_clear_handle_id (&self->queued_source, g_source_remove);
298
299 if (!ide_object_in_destruction (IDE_OBJECT (self)))
300 update_notification (self);
301
302 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PAUSED]);
303 }
304
305 static void
gbp_code_index_service_unpause(GbpCodeIndexService * self)306 gbp_code_index_service_unpause (GbpCodeIndexService *self)
307 {
308 g_assert (IDE_IS_MAIN_THREAD ());
309 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
310
311 self->paused = FALSE;
312
313 if (!ide_object_in_destruction (IDE_OBJECT (self)))
314 {
315 gbp_code_index_service_queue_index (self);
316 update_notification (self);
317 }
318
319 g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_PAUSED]);
320 }
321
322 static void
gbp_code_index_service_execute_cb(GObject * object,GAsyncResult * result,gpointer user_data)323 gbp_code_index_service_execute_cb (GObject *object,
324 GAsyncResult *result,
325 gpointer user_data)
326 {
327 GbpCodeIndexExecutor *executor = (GbpCodeIndexExecutor *)object;
328 g_autoptr(IdeTask) task = user_data;
329 g_autoptr(GError) error = NULL;
330
331 IDE_ENTRY;
332
333 g_assert (IDE_IS_MAIN_THREAD ());
334 g_assert (GBP_IS_CODE_INDEX_EXECUTOR (executor));
335 g_assert (G_IS_ASYNC_RESULT (result));
336 g_assert (IDE_IS_TASK (task));
337
338 if (!gbp_code_index_executor_execute_finish (executor, result, &error))
339 ide_task_return_error (task, g_steal_pointer (&error));
340 else
341 ide_task_return_boolean (task, TRUE);
342
343 ide_object_destroy (IDE_OBJECT (executor));
344
345 IDE_EXIT;
346 }
347
348 static void
gbp_code_index_service_load_flags_cb(GObject * object,GAsyncResult * result,gpointer user_data)349 gbp_code_index_service_load_flags_cb (GObject *object,
350 GAsyncResult *result,
351 gpointer user_data)
352 {
353 GbpCodeIndexPlan *plan = (GbpCodeIndexPlan *)object;
354 g_autoptr(GbpCodeIndexExecutor) executor = NULL;
355 g_autoptr(IdeTask) task = user_data;
356 g_autoptr(GError) error = NULL;
357 GbpCodeIndexService *self;
358
359 IDE_ENTRY;
360
361 g_assert (IDE_IS_MAIN_THREAD ());
362 g_assert (GBP_IS_CODE_INDEX_PLAN (plan));
363 g_assert (G_IS_ASYNC_RESULT (result));
364 g_assert (IDE_IS_TASK (task));
365
366 if (!gbp_code_index_plan_load_flags_finish (plan, result, &error))
367 {
368 ide_task_return_error (task, g_steal_pointer (&error));
369 IDE_EXIT;
370 }
371
372 if (ide_task_return_error_if_cancelled (task))
373 IDE_EXIT;
374
375 self = ide_task_get_source_object (task);
376 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
377
378 executor = gbp_code_index_executor_new (plan);
379 ide_object_append (IDE_OBJECT (self), IDE_OBJECT (executor));
380
381 gbp_code_index_executor_execute_async (executor,
382 self->notif,
383 ide_task_get_cancellable (task),
384 gbp_code_index_service_execute_cb,
385 g_object_ref (task));
386
387 IDE_EXIT;
388 }
389
390 static void
gbp_code_index_service_cull_index_cb(GObject * object,GAsyncResult * result,gpointer user_data)391 gbp_code_index_service_cull_index_cb (GObject *object,
392 GAsyncResult *result,
393 gpointer user_data)
394 {
395 GbpCodeIndexPlan *plan = (GbpCodeIndexPlan *)object;
396 g_autoptr(IdeTask) task = user_data;
397 g_autoptr(GError) error = NULL;
398 IdeContext *context;
399
400 IDE_ENTRY;
401
402 g_assert (IDE_IS_MAIN_THREAD ());
403 g_assert (GBP_IS_CODE_INDEX_PLAN (plan));
404 g_assert (G_IS_ASYNC_RESULT (result));
405 g_assert (IDE_IS_TASK (task));
406
407 if (!gbp_code_index_plan_cull_indexed_finish (plan, result, &error))
408 {
409 ide_task_return_error (task, g_steal_pointer (&error));
410 IDE_EXIT;
411 }
412
413 if (ide_task_return_error_if_cancelled (task))
414 IDE_EXIT;
415
416 context = ide_task_get_task_data (task);
417 g_assert (IDE_IS_CONTEXT (context));
418
419 gbp_code_index_plan_load_flags_async (plan,
420 context,
421 ide_task_get_cancellable (task),
422 gbp_code_index_service_load_flags_cb,
423 g_object_ref (task));
424
425 IDE_EXIT;
426 }
427
428 static void
gbp_code_index_service_populate_cb(GObject * object,GAsyncResult * result,gpointer user_data)429 gbp_code_index_service_populate_cb (GObject *object,
430 GAsyncResult *result,
431 gpointer user_data)
432 {
433 GbpCodeIndexPlan *plan = (GbpCodeIndexPlan *)object;
434 g_autoptr(IdeTask) task = user_data;
435 g_autoptr(GError) error = NULL;
436 IdeContext *context;
437
438 IDE_ENTRY;
439
440 g_assert (IDE_IS_MAIN_THREAD ());
441 g_assert (GBP_IS_CODE_INDEX_PLAN (plan));
442 g_assert (G_IS_ASYNC_RESULT (result));
443 g_assert (IDE_IS_TASK (task));
444
445 if (!gbp_code_index_plan_populate_finish (plan, result, &error))
446 {
447 ide_task_return_error (task, g_steal_pointer (&error));
448 IDE_EXIT;
449 }
450
451 if (ide_task_return_error_if_cancelled (task))
452 IDE_EXIT;
453
454 context = ide_task_get_task_data (task);
455 g_assert (IDE_IS_CONTEXT (context));
456
457 gbp_code_index_plan_cull_indexed_async (plan,
458 context,
459 ide_task_get_cancellable (task),
460 gbp_code_index_service_cull_index_cb,
461 g_object_ref (task));
462
463 IDE_EXIT;
464 }
465
466 static void
gbp_code_index_service_index_async(GbpCodeIndexService * self,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)467 gbp_code_index_service_index_async (GbpCodeIndexService *self,
468 GCancellable *cancellable,
469 GAsyncReadyCallback callback,
470 gpointer user_data)
471 {
472 g_autoptr(GbpCodeIndexPlan) plan = NULL;
473 g_autoptr(IdeContext) context = NULL;
474 g_autoptr(IdeTask) task = NULL;
475
476 IDE_ENTRY;
477
478 g_assert (IDE_IS_MAIN_THREAD ());
479 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
480 g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
481
482 if (cancellable == NULL)
483 g_warning ("Attempt to index without a valid cancellable. This will affect pausibility.");
484
485 self->indexing = TRUE;
486 self->needs_indexing = FALSE;
487
488 task = ide_task_new (self, cancellable, callback, user_data);
489 ide_task_set_source_tag (task, gbp_code_index_service_index_async);
490
491 if (ide_task_return_error_if_cancelled (task))
492 IDE_EXIT;
493
494 context = ide_object_ref_context (IDE_OBJECT (self));
495 g_assert (IDE_IS_CONTEXT (context));
496
497 ide_task_set_task_data (task, g_object_ref (context), g_object_unref);
498
499 plan = gbp_code_index_plan_new ();
500
501 gbp_code_index_plan_populate_async (plan,
502 context,
503 cancellable,
504 gbp_code_index_service_populate_cb,
505 g_steal_pointer (&task));
506
507 update_notification (self);
508
509 IDE_EXIT;
510 }
511
512 static gboolean
gbp_code_index_service_index_finish(GbpCodeIndexService * self,GAsyncResult * result,GError ** error)513 gbp_code_index_service_index_finish (GbpCodeIndexService *self,
514 GAsyncResult *result,
515 GError **error)
516 {
517 g_assert (IDE_IS_MAIN_THREAD ());
518 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
519 g_assert (IDE_IS_TASK (result));
520
521 self->indexing = FALSE;
522
523 if (!ide_object_in_destruction (IDE_OBJECT (self)))
524 {
525 update_notification (self);
526 gbp_code_index_service_reload_indexes (self);
527 }
528
529 return ide_task_propagate_boolean (IDE_TASK (result), error);
530 }
531
532 static void
gbp_code_index_service_buffer_saved_cb(GbpCodeIndexService * self,IdeBuffer * buffer,IdeBufferManager * buffer_manager)533 gbp_code_index_service_buffer_saved_cb (GbpCodeIndexService *self,
534 IdeBuffer *buffer,
535 IdeBufferManager *buffer_manager)
536 {
537 GtkSourceLanguage *lang;
538
539 g_assert (IDE_IS_MAIN_THREAD ());
540 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
541 g_assert (IDE_IS_BUFFER (buffer));
542 g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
543
544 /*
545 * Only update the index if the file save will result in a change to the
546 * directory's index. We determine that by if an indexer is available.
547 */
548
549 if ((lang = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer))))
550 {
551 const gchar *lang_id = gtk_source_language_get_id (lang);
552 const GList *list = peas_engine_get_plugin_list (peas_engine_get_default ());
553
554 for (const GList *iter = list; iter; iter = iter->next)
555 {
556 PeasPluginInfo *plugin_info = iter->data;
557 const gchar *languages = peas_plugin_info_get_external_data (plugin_info,
558 "Code-Indexer-Languages");
559
560 /* Not exact check, but good enough for now */
561 if (languages != NULL && strstr (languages, lang_id) != NULL)
562 {
563 gbp_code_index_service_queue_index (self);
564 break;
565 }
566 }
567 }
568 }
569
570 static void
gbp_code_index_service_build_started_cb(GbpCodeIndexService * self,IdePipeline * pipeline,IdeBuildManager * build_manager)571 gbp_code_index_service_build_started_cb (GbpCodeIndexService *self,
572 IdePipeline *pipeline,
573 IdeBuildManager *build_manager)
574 {
575 g_assert (IDE_IS_MAIN_THREAD ());
576 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
577 g_assert (IDE_IS_PIPELINE (pipeline));
578 g_assert (IDE_IS_BUILD_MANAGER (build_manager));
579
580 /* If we are starting a new build that is going to ensure that we reach to
581 * the configure phase (or further), then delay any index building until
582 * after that operation completes. There is no need to compete for resources
583 * while building (especially if indexing might fail anyway).
584 */
585 if (ide_pipeline_get_requested_phase (pipeline) >= IDE_PIPELINE_PHASE_CONFIGURE)
586 {
587 self->build_inhibit = TRUE;
588 g_cancellable_cancel (self->cancellable);
589 g_clear_object (&self->cancellable);
590 }
591 }
592
593 static void
gbp_code_index_service_build_failed_cb(GbpCodeIndexService * self,IdePipeline * pipeline,IdeBuildManager * build_manager)594 gbp_code_index_service_build_failed_cb (GbpCodeIndexService *self,
595 IdePipeline *pipeline,
596 IdeBuildManager *build_manager)
597 {
598 g_assert (IDE_IS_MAIN_THREAD ());
599 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
600 g_assert (IDE_IS_PIPELINE (pipeline));
601 g_assert (IDE_IS_BUILD_MANAGER (build_manager));
602
603 self->build_inhibit = FALSE;
604 }
605
606 static void
gbp_code_index_service_build_finished_cb(GbpCodeIndexService * self,IdePipeline * pipeline,IdeBuildManager * build_manager)607 gbp_code_index_service_build_finished_cb (GbpCodeIndexService *self,
608 IdePipeline *pipeline,
609 IdeBuildManager *build_manager)
610 {
611 g_assert (IDE_IS_MAIN_THREAD ());
612 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
613 g_assert (IDE_IS_PIPELINE (pipeline));
614 g_assert (IDE_IS_BUILD_MANAGER (build_manager));
615
616 /*
617 * If we paused building due to inhibition while building, then we need to
618 * possibly restore the build process and queue a new indexing.
619 */
620
621 if (self->build_inhibit)
622 {
623 self->build_inhibit = FALSE;
624
625 if (ide_pipeline_has_configured (pipeline))
626 gbp_code_index_service_queue_index (self);
627 }
628 }
629
630 static void
gbp_code_index_service_vcs_changed_cb(GbpCodeIndexService * self,IdeVcs * vcs)631 gbp_code_index_service_vcs_changed_cb (GbpCodeIndexService *self,
632 IdeVcs *vcs)
633 {
634 IDE_ENTRY;
635
636 g_assert (IDE_IS_MAIN_THREAD ());
637 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
638 g_assert (IDE_IS_VCS (vcs));
639
640 /* Possibly switched branches, queue re-indexing */
641 gbp_code_index_service_queue_index (self);
642
643 IDE_EXIT;
644 }
645
646 static void
gbp_code_index_service_file_trashed_cb(GbpCodeIndexService * self,GFile * file,IdeProject * project)647 gbp_code_index_service_file_trashed_cb (GbpCodeIndexService *self,
648 GFile *file,
649 IdeProject *project)
650 {
651 g_assert (IDE_IS_MAIN_THREAD ());
652 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
653 g_assert (G_IS_FILE (file));
654 g_assert (IDE_IS_PROJECT (project));
655
656 gbp_code_index_service_queue_index (self);
657 }
658
659 static void
gbp_code_index_service_file_renamed_cb(GbpCodeIndexService * self,GFile * src_file,GFile * dst_file,IdeProject * project)660 gbp_code_index_service_file_renamed_cb (GbpCodeIndexService *self,
661 GFile *src_file,
662 GFile *dst_file,
663 IdeProject *project)
664 {
665 g_assert (IDE_IS_MAIN_THREAD ());
666 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
667 g_assert (G_IS_FILE (src_file));
668 g_assert (G_IS_FILE (dst_file));
669 g_assert (IDE_IS_PROJECT (project));
670
671 gbp_code_index_service_queue_index (self);
672 }
673
674 static void
gbp_code_index_service_load_indexes_cb(GFile * directory,GPtrArray * file_infos,gpointer user_data)675 gbp_code_index_service_load_indexes_cb (GFile *directory,
676 GPtrArray *file_infos,
677 gpointer user_data)
678 {
679 LoadIndexes *state = user_data;
680 g_autoptr(GFile) source_directory = NULL;
681
682 g_assert (G_IS_FILE (directory));
683 g_assert (state != NULL);
684
685 if (!g_file_equal (directory, state->indexdir))
686 {
687 g_autofree gchar *relative = NULL;
688
689 relative = g_file_get_relative_path (state->indexdir, directory);
690 source_directory = g_file_get_child (state->workdir, relative);
691 }
692 else
693 {
694 source_directory = g_object_ref (state->workdir);
695 }
696
697 ide_code_index_index_load (state->index,
698 directory,
699 source_directory,
700 NULL,
701 NULL);
702 }
703
704 static void
gbp_code_index_service_load_indexes(IdeTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)705 gbp_code_index_service_load_indexes (IdeTask *task,
706 gpointer source_object,
707 gpointer task_data,
708 GCancellable *cancellable)
709 {
710 LoadIndexes *state = task_data;
711
712 g_assert (IDE_IS_TASK (task));
713 g_assert (GBP_IS_CODE_INDEX_SERVICE (source_object));
714 g_assert (state != NULL);
715 g_assert (IDE_IS_CODE_INDEX_INDEX (state->index));
716 g_assert (G_IS_FILE (state->workdir));
717 g_assert (G_IS_FILE (state->indexdir));
718
719 ide_g_file_walk (state->indexdir,
720 NULL,
721 cancellable,
722 gbp_code_index_service_load_indexes_cb,
723 state);
724
725 ide_task_return_boolean (task, TRUE);
726 }
727
728 static void
gbp_code_index_service_reload_indexes(GbpCodeIndexService * self)729 gbp_code_index_service_reload_indexes (GbpCodeIndexService *self)
730 {
731 g_autoptr(IdeContext) context = NULL;
732 g_autoptr(IdeTask) task = NULL;
733 LoadIndexes *state;
734
735 g_assert (IDE_IS_MAIN_THREAD ());
736 g_assert (GBP_IS_CODE_INDEX_SERVICE (self));
737
738 if (!(context = ide_object_ref_context (IDE_OBJECT (self))))
739 return;
740
741 state = g_slice_new0 (LoadIndexes);
742 state->index = g_object_ref (self->index);
743 state->workdir = ide_context_ref_workdir (context);
744 state->indexdir = ide_context_cache_file (context, "code-index", NULL);
745
746 task = ide_task_new (self, NULL, NULL, NULL);
747 ide_task_set_source_tag (task, gbp_code_index_service_reload_indexes);
748 ide_task_set_task_data (task, state, load_indexes_free);
749 ide_task_run_in_thread (task, gbp_code_index_service_load_indexes);
750 }
751
752 void
gbp_code_index_service_start(GbpCodeIndexService * self)753 gbp_code_index_service_start (GbpCodeIndexService *self)
754 {
755 g_autoptr(IdeContext) context = NULL;
756 g_autoptr(GFile) index_dir = NULL;
757 IdeBufferManager *buffer_manager;
758 IdeBuildManager *build_manager;
759 IdeProject *project;
760 IdeVcs *vcs;
761 gboolean has_index;
762
763 IDE_ENTRY;
764
765 g_return_if_fail (IDE_IS_MAIN_THREAD ());
766 g_return_if_fail (GBP_IS_CODE_INDEX_SERVICE (self));
767 g_return_if_fail (self->started == FALSE);
768 g_return_if_fail (!ide_object_in_destruction (IDE_OBJECT (self)));
769
770 self->started = TRUE;
771
772 if (!(context = ide_object_ref_context (IDE_OBJECT (self))))
773 {
774 g_warning ("Attempt to start code-index service without access to context");
775 IDE_EXIT;
776 }
777
778 buffer_manager = ide_buffer_manager_from_context (context);
779
780 g_signal_connect_object (buffer_manager,
781 "buffer-saved",
782 G_CALLBACK (gbp_code_index_service_buffer_saved_cb),
783 self,
784 G_CONNECT_SWAPPED);
785
786 build_manager = ide_build_manager_from_context (context);
787
788 g_signal_connect_object (build_manager,
789 "build-failed",
790 G_CALLBACK (gbp_code_index_service_build_failed_cb),
791 self,
792 G_CONNECT_SWAPPED);
793
794 g_signal_connect_object (build_manager,
795 "build-finished",
796 G_CALLBACK (gbp_code_index_service_build_finished_cb),
797 self,
798 G_CONNECT_SWAPPED);
799
800 g_signal_connect_object (build_manager,
801 "build-started",
802 G_CALLBACK (gbp_code_index_service_build_started_cb),
803 self,
804 G_CONNECT_SWAPPED);
805
806 vcs = ide_vcs_from_context (context);
807
808 g_signal_connect_object (vcs,
809 "changed",
810 G_CALLBACK (gbp_code_index_service_vcs_changed_cb),
811 self,
812 G_CONNECT_SWAPPED);
813
814 project = ide_project_from_context (context);
815
816 g_signal_connect_object (project,
817 "file-trashed",
818 G_CALLBACK (gbp_code_index_service_file_trashed_cb),
819 self,
820 G_CONNECT_SWAPPED);
821
822 g_signal_connect_object (project,
823 "file-renamed",
824 G_CALLBACK (gbp_code_index_service_file_renamed_cb),
825 self,
826 G_CONNECT_SWAPPED);
827
828 index_dir = ide_context_cache_file (context, "code-index", NULL);
829 has_index = g_file_query_exists (index_dir, NULL);
830
831 if (!self->paused)
832 {
833 /*
834 * We only want to immediately start indexing at startup if the project
835 * does not yet have an index. Otherwise, we want to wait for a user
836 * action to cause the indexes to be rebuilt so that we don't risk
837 * annoying the user with build actions.
838 */
839 if (!has_index && !ide_build_manager_get_busy (build_manager))
840 gbp_code_index_service_queue_index (self);
841 }
842
843 gbp_code_index_service_reload_indexes (self);
844
845 IDE_EXIT;
846 }
847
848 void
gbp_code_index_service_stop(GbpCodeIndexService * self)849 gbp_code_index_service_stop (GbpCodeIndexService *self)
850 {
851 g_return_if_fail (IDE_IS_MAIN_THREAD ());
852 g_return_if_fail (GBP_IS_CODE_INDEX_SERVICE (self));
853
854 if (!self->started)
855 return;
856
857 self->started = FALSE;
858
859 g_cancellable_cancel (self->cancellable);
860 g_clear_object (&self->cancellable);
861 g_clear_handle_id (&self->queued_source, g_source_remove);
862
863 if (self->notif)
864 {
865 ide_notification_withdraw (self->notif);
866 g_clear_object (&self->notif);
867 }
868 }
869
870 gboolean
gbp_code_index_service_get_paused(GbpCodeIndexService * self)871 gbp_code_index_service_get_paused (GbpCodeIndexService *self)
872 {
873 g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
874 g_return_val_if_fail (GBP_IS_CODE_INDEX_SERVICE (self), FALSE);
875
876 return self->paused;
877 }
878
879 void
gbp_code_index_service_set_paused(GbpCodeIndexService * self,gboolean paused)880 gbp_code_index_service_set_paused (GbpCodeIndexService *self,
881 gboolean paused)
882 {
883 g_return_if_fail (IDE_IS_MAIN_THREAD ());
884 g_return_if_fail (GBP_IS_CODE_INDEX_SERVICE (self));
885
886 paused = !!paused;
887
888 if (paused != self->paused)
889 {
890 if (paused)
891 gbp_code_index_service_pause (self);
892 else
893 gbp_code_index_service_unpause (self);
894 }
895 }
896
897 static IdeCodeIndexer *
create_indexer(GbpCodeIndexService * self,const gchar * module_name)898 create_indexer (GbpCodeIndexService *self,
899 const gchar *module_name)
900 {
901 PeasEngine *engine = peas_engine_get_default ();
902 PeasPluginInfo *plugin_info;
903
904 g_return_val_if_fail (module_name != NULL, NULL);
905
906 if ((plugin_info = peas_engine_get_plugin_info (engine, module_name)) &&
907 peas_plugin_info_is_loaded (plugin_info))
908 {
909 PeasExtension *exten;
910
911 exten = peas_engine_create_extension (peas_engine_get_default (),
912 plugin_info,
913 IDE_TYPE_CODE_INDEXER,
914 NULL);
915
916 if (IDE_IS_OBJECT (exten))
917 ide_object_append (IDE_OBJECT (self), IDE_OBJECT (exten));
918
919 return IDE_CODE_INDEXER (exten);
920 }
921
922 return NULL;
923 }
924
925 IdeCodeIndexer *
gbp_code_index_service_get_indexer(GbpCodeIndexService * self,const gchar * lang_id,const gchar * path)926 gbp_code_index_service_get_indexer (GbpCodeIndexService *self,
927 const gchar *lang_id,
928 const gchar *path)
929 {
930 g_autoptr(GPtrArray) indexers = NULL;
931
932 g_return_val_if_fail (GBP_IS_CODE_INDEX_SERVICE (self), NULL);
933
934 indexers = collect_indexer_info ();
935
936 if (lang_id != NULL)
937 {
938 for (guint i = 0; i < indexers->len; i++)
939 {
940 const IndexerInfo *info = g_ptr_array_index (indexers, i);
941
942 if (info->lang_ids == NULL)
943 continue;
944
945 for (guint j = 0; info->lang_ids[j]; j++)
946 {
947 if (g_str_equal (lang_id, info->lang_ids[j]))
948 return create_indexer (self, info->module_name);
949 }
950 }
951 }
952
953 if (path != NULL)
954 {
955 g_autofree gchar *name = g_path_get_basename (path);
956 g_autofree gchar *reversed = g_utf8_strreverse (name, -1);
957
958 for (guint i = 0; i < indexers->len; i++)
959 {
960 const IndexerInfo *info = g_ptr_array_index (indexers, i);
961
962 if (indexer_info_matches (info, name, reversed, NULL))
963 return create_indexer (self, info->module_name);
964 }
965 }
966
967 return NULL;
968 }
969
970 GbpCodeIndexService *
gbp_code_index_service_from_context(IdeContext * context)971 gbp_code_index_service_from_context (IdeContext *context)
972 {
973 GbpCodeIndexService *ret;
974
975 g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
976
977 if (!(ret = ide_context_peek_child_typed (context, GBP_TYPE_CODE_INDEX_SERVICE)))
978 {
979 g_autoptr(GbpCodeIndexService) self = NULL;
980
981 self = g_object_new (GBP_TYPE_CODE_INDEX_SERVICE,
982 "parent", context,
983 NULL);
984 gbp_code_index_service_start (self);
985 ret = ide_context_peek_child_typed (context, GBP_TYPE_CODE_INDEX_SERVICE);
986 }
987
988 return ret;
989 }
990
991 IdeCodeIndexIndex *
gbp_code_index_service_get_index(GbpCodeIndexService * self)992 gbp_code_index_service_get_index (GbpCodeIndexService *self)
993 {
994 g_return_val_if_fail (GBP_IS_CODE_INDEX_SERVICE (self), NULL);
995
996 return self->index;
997 }
998