ide-code-index-index.c 17.9 KB
Newer Older
1
2
/* ide-code-index-index.c
 *
3
 * Copyright © 2017 Anoop Chandu <anoopchandu96@gmail.com>
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 *
 * 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/>.
 */

#define G_LOG_DOMAIN "ide-code-index-index"

#include <glib/gprintf.h>
22
#include <glib/gi18n.h>
23
24
25
26
27
28
29
30
31
32
33
34

#include "ide-code-index-search-result.h"
#include "ide-code-index-index.h"
#include "ide-persistent-map.h"

/*
 * This class will store index of all directories and will have a map of
 * directory and Indexes (DzlFuzzyIndex & IdePersistentMap)
 */

struct _IdeCodeIndexIndex
{
35
  IdeObject   parent_instance;
36

37
  GMutex      mutex;
38
39
40
41
42
43
  GHashTable *directories;
  GPtrArray  *indexes;
};

typedef struct
{
44
45
  GFile            *directory;
  GFile            *source_directory;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
  DzlFuzzyIndex    *symbol_names;
  IdePersistentMap *symbol_keys;
} DirectoryIndex;

typedef struct
{
  gchar   *query;
  DzlHeap *fuzzy_matches;
  guint    curr_index;
  gsize    max_results;
} PopulateTaskData;

/*
 * Represents a match. It contains match, matches from which it came and
 * index from which matches came
 */
typedef struct
{
  DzlFuzzyIndex      *index;
  GListModel         *list;
  DzlFuzzyIndexMatch *match;
  guint               match_num;
} FuzzyMatch;

G_DEFINE_TYPE (IdeCodeIndexIndex, ide_code_index_index, IDE_TYPE_OBJECT)

static void directory_index_free (DirectoryIndex *data);

74
DZL_DEFINE_COUNTER (code_indexes, "Code Indexes", "Instances", "Number of loaded code indexes")
75
76
77
78
79
80
81
G_DEFINE_AUTOPTR_CLEANUP_FUNC (DirectoryIndex, directory_index_free)

static void
directory_index_free (DirectoryIndex *data)
{
  g_clear_object (&data->symbol_names);
  g_clear_object (&data->symbol_keys);
82
  g_clear_object (&data->directory);
83
  g_slice_free (DirectoryIndex, data);
84
85

  DZL_COUNTER_DEC (code_indexes);
86
87
88
89
90
91
92
93
94
95
96
97
98
99
}

static void
populate_task_data_free (PopulateTaskData *data)
{
  g_clear_pointer (&data->query, g_free);

  for (guint i = 0; i < data->fuzzy_matches->len; i++)
    {
      g_clear_object (&(dzl_heap_index(data->fuzzy_matches, FuzzyMatch, i).list));
      g_clear_object (&(dzl_heap_index(data->fuzzy_matches, FuzzyMatch, i).match));
    }

  g_clear_pointer (&data->fuzzy_matches, dzl_heap_unref);
100

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
  g_slice_free (PopulateTaskData, data);
}

static int
fuzzy_match_compare (const FuzzyMatch *a,
                     const FuzzyMatch *b)
{
  float diff;

  diff = dzl_fuzzy_index_match_get_score (a->match) -
          dzl_fuzzy_index_match_get_score (b->match);

  if (diff < 0)
    return -1;
  else if (diff > 0)
    return 1;
  else
    return 0;
}

/* This function will load indexes and returns them */
static DirectoryIndex *
123
directory_index_new (GFile         *directory,
124
                     GFile         *source_directory,
125
126
                     GCancellable  *cancellable,
                     GError       **error)
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
{
  g_autoptr(GFile) keys_file = NULL;
  g_autoptr(GFile) names_file = NULL;
  g_autoptr(DzlFuzzyIndex) symbol_names = NULL;
  g_autoptr(IdePersistentMap) symbol_keys = NULL;
  g_autoptr(DirectoryIndex) dir_index = NULL;

  g_assert (G_IS_FILE (directory));
  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));

  symbol_keys = ide_persistent_map_new ();
  keys_file = g_file_get_child (directory, "SymbolKeys");

  if (!ide_persistent_map_load_file (symbol_keys, keys_file, cancellable, error))
    return NULL;

  symbol_names = dzl_fuzzy_index_new ();
  names_file = g_file_get_child (directory, "SymbolNames");

  if (!dzl_fuzzy_index_load_file (symbol_names, names_file, cancellable, error))
    return NULL;

  dir_index = g_slice_new0 (DirectoryIndex);
  dir_index->symbol_keys = g_steal_pointer (&symbol_keys);
  dir_index->symbol_names = g_steal_pointer (&symbol_names);
152
153
154
  dir_index->directory = g_object_ref (directory);
  dir_index->source_directory = g_object_ref (source_directory);

155
  DZL_COUNTER_INC (code_indexes);
156
157
158
159

  return g_steal_pointer (&dir_index);
}

160
161
162
163
/**
 * ide_code_index_index_load:
 * @self: a #IdeCodeIndexIndex
 * @directory: a #GFile of the directory to load
164
 * @source_directory: a #GFile of the directory containing the sources
165
166
167
168
169
170
171
172
173
174
175
 * @cancellable: a #GCancellable or %NULL
 * @error: a #GError or %NULL
 *
 * This function will load the index of a directory and update old index
 * pointer if it exists.
 *
 * Returns: %TRUE if successful; otherwise %FALSE and @error is set.
 *
 * Thread safety: you may call this function from a thread so long as the
 *   thread has a reference to @self.
 */
176
177
178
gboolean
ide_code_index_index_load (IdeCodeIndexIndex   *self,
                           GFile               *directory,
179
                           GFile               *source_directory,
180
181
182
183
                           GCancellable        *cancellable,
                           GError             **error)
{
  g_autoptr(DirectoryIndex) dir_index = NULL;
184
  g_autoptr(GMutexLocker) locker = NULL;
185
186
187
188
189
190
191
192
  g_autofree gchar *dir_name = NULL;
  gpointer value;

  g_return_val_if_fail (IDE_IS_CODE_INDEX_INDEX (self), FALSE);
  g_return_val_if_fail (G_IS_FILE (directory), FALSE);
  g_return_val_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable), FALSE);

  dir_name = g_file_get_path (directory);
193
194
195
196
  g_debug ("Loading code index from %s", dir_name);

  if (!(dir_index = directory_index_new (directory, source_directory, cancellable, error)))
    return FALSE;
197

198
  locker = g_mutex_locker_new (&self->mutex);
199

200
  if (g_hash_table_lookup_extended (self->directories, dir_name, NULL, &value))
201
202
203
    {
      guint i = GPOINTER_TO_UINT (value);

204
205
206
      g_assert (i < self->indexes->len);
      g_assert (self->indexes->len > 0);

207
      /* update current directory index by clearing old one */
208
      directory_index_free (g_ptr_array_index (self->indexes, i));
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
      g_ptr_array_index (self->indexes, i) = g_steal_pointer (&dir_index);
    }
  else
    {
      g_hash_table_insert (self->directories,
                           g_steal_pointer (&dir_name),
                           GUINT_TO_POINTER (self->indexes->len));

      g_ptr_array_add (self->indexes, g_steal_pointer (&dir_index));
    }

  return TRUE;
}

/* Create a new IdeCodeIndexSearchResult based on match from fuzzy index */
static IdeCodeIndexSearchResult *
225
226
ide_code_index_index_create_search_result (IdeContext       *context,
                                           const FuzzyMatch *fuzzy_match)
227
{
228
229
230
  g_autoptr(IdeFile) file = NULL;
  g_autoptr(IdeSourceLocation) location = NULL;
  g_autoptr(GString) subtitle = NULL;
231
232
  const gchar *key;
  const gchar *icon_name;
233
234
235
  const gchar *shortname;
  const gchar *path;
  GVariant *value;
236
237
238
239
240
241
  gfloat score;
  guint file_id;
  guint line;
  guint line_offset;
  guint kind;
  guint flags;
242
  gchar num [20];
243

244
  g_assert (IDE_IS_CONTEXT (context));
245
246
247
248
249
250
  g_assert (fuzzy_match != NULL);

  value = dzl_fuzzy_index_match_get_document (fuzzy_match->match);

  g_variant_get (value, "(uuuuu)", &file_id, &line, &line_offset, &flags, &kind);

251
252
253
254
255
256
  /* Ignore variables in global search */
  if (kind == IDE_SYMBOL_VARIABLE)
    return NULL;

  key = dzl_fuzzy_index_match_get_key (fuzzy_match->match);

257
  g_snprintf (num, sizeof num, "%u", file_id);
258

259
  path = dzl_fuzzy_index_get_metadata_string (fuzzy_match->index, num);
260

261
262
263
264
265
266
  file = ide_file_new_for_path (context, path);
  location = ide_source_location_new (file, line - 1, line_offset - 1, 0);

  icon_name = ide_symbol_kind_get_icon_name (kind);
  score = dzl_fuzzy_index_match_get_score (fuzzy_match->match);

267
  subtitle = g_string_new (NULL);
268

269
270
  if (NULL != (shortname = strrchr (path, G_DIR_SEPARATOR)))
    g_string_append (subtitle, shortname + 1);
271

272
  if ((kind == IDE_SYMBOL_FUNCTION) && !(flags & IDE_SYMBOL_FLAGS_IS_DEFINITION))
273
274
275
276
277
278
279
280
    {
      /* translators: "Declaration" is describing a function that is defined in a header
       *              file (.h) rather than a source file (.c).
       */
      g_string_append_printf (subtitle, " (%s)", _("Declaration"));
    }

  return ide_code_index_search_result_new (key + 2, subtitle->str, icon_name, location, score);
281
282
283
}

static void
284
285
286
ide_code_index_index_query_cb (GObject      *object,
                               GAsyncResult *result,
                               gpointer      user_data)
287
288
289
290
{
  DzlFuzzyIndex *index = (DzlFuzzyIndex *)object;
  g_autoptr(GTask) task = (GTask *)user_data;
  g_autoptr(GListModel) list = NULL;
291
  g_autoptr(GMutexLocker) locker = NULL;
292
  g_autoptr(GError) error = NULL;
293
  IdeCodeIndexIndex *self;
294
  PopulateTaskData *data;
295

296
  g_assert (IDE_IS_MAIN_THREAD ());
297
  g_assert (DZL_IS_FUZZY_INDEX (index));
298
  g_assert (G_IS_ASYNC_RESULT (result));
299
300
301
  g_assert (G_IS_TASK (task));

  self = g_task_get_source_object (task);
302
303
  g_assert (IDE_IS_CODE_INDEX_INDEX (self));

304
305
  locker = g_mutex_locker_new (&self->mutex);

306
  data = g_task_get_task_data (task);
307
  g_assert (data != NULL);
308

309
310
311
312
  list = dzl_fuzzy_index_query_finish (index, result, &error);
  g_assert (!list || G_IS_LIST_MODEL (list));

  if (list != NULL)
313
314
315
    {
      if (g_list_model_get_n_items (list))
        {
316
          FuzzyMatch fuzzy_match = {0};
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349

          fuzzy_match.index = index;
          fuzzy_match.match = g_list_model_get_item (list, 0);
          fuzzy_match.list = g_steal_pointer (&list);
          fuzzy_match.match_num = 0;

          dzl_heap_insert_val (data->fuzzy_matches, fuzzy_match);
        }
    }
  else
    {
      g_message ("%s", error->message);
    }

  data->curr_index++;

  if (data->curr_index < self->indexes->len)
    {
      DirectoryIndex *dir_index;
      GCancellable *cancellable;

      dir_index = g_ptr_array_index (self->indexes, data->curr_index);
      cancellable = g_task_get_cancellable (task);

      dzl_fuzzy_index_query_async (dir_index->symbol_names,
                                   data->query,
                                   data->max_results,
                                   cancellable,
                                   ide_code_index_index_query_cb,
                                   g_steal_pointer (&task));
    }
  else
    {
350
351
      g_autoptr(GPtrArray) results = g_ptr_array_new_with_free_func (g_object_unref);
      IdeContext *context = ide_object_get_context (IDE_OBJECT (self));
352
353
354
355
356

      /*
       * Extract match from heap with max score, get next item from the list from which
       * the max score match came from and insert that into heap.
       */
357
      while (data->max_results > 0 && data->fuzzy_matches->len > 0)
358
        {
359
          IdeCodeIndexSearchResult *item;
360
361
362
          FuzzyMatch fuzzy_match;

          dzl_heap_extract (data->fuzzy_matches, &fuzzy_match);
363

364
          item = ide_code_index_index_create_search_result (context, &fuzzy_match);
365
366
          if (item != NULL)
            g_ptr_array_add (results, item);
367

368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
          data->max_results--;

          g_clear_object (&fuzzy_match.match);

          fuzzy_match.match_num++;

          if (fuzzy_match.match_num < g_list_model_get_n_items (fuzzy_match.list))
            {
              fuzzy_match.match = g_list_model_get_item (fuzzy_match.list, fuzzy_match.match_num);
              dzl_heap_insert_val (data->fuzzy_matches, fuzzy_match);
            }
          else
            {
              g_clear_object (&fuzzy_match.list);
            }
        }

385
386
387
      g_task_return_pointer (task,
                             g_steal_pointer (&results),
                             (GDestroyNotify)g_ptr_array_unref);
388
389
390
391
392
393
394
395
396
397
398
    }
}

void
ide_code_index_index_populate_async (IdeCodeIndexIndex   *self,
                                     const gchar         *query,
                                     gsize                max_results,
                                     GCancellable        *cancellable,
                                     GAsyncReadyCallback  callback,
                                     gpointer             user_data)
{
399
  g_autoptr(GMutexLocker) locker = NULL;
400
401
  g_autoptr(GTask) task = NULL;
  g_auto(GStrv) str = NULL;
402
  PopulateTaskData *data;
403

404
  g_return_if_fail (IDE_IS_MAIN_THREAD ());
405
406
407
408
409
  g_return_if_fail (IDE_IS_CODE_INDEX_INDEX (self));
  g_return_if_fail (query != NULL);
  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));

  task = g_task_new (self, cancellable, callback, user_data);
410
411
  g_task_set_source_tag (task, ide_code_index_index_populate_async);
  g_task_set_priority (task, G_PRIORITY_LOW);
412
413
414
415

  data = g_slice_new0 (PopulateTaskData);
  data->max_results = max_results;
  data->curr_index = 0;
416
417
  data->fuzzy_matches = dzl_heap_new (sizeof (FuzzyMatch),
                                      (GCompareFunc)fuzzy_match_compare);
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452

  /* Replace "<symbol type prefix><space>" with <symbol code>INFORMATION SEPARATOR ONE  */

  str = g_strsplit (query, " ", 2);

  if (str[1] == NULL)
    {
      data->query = g_strconcat ("\x1F", query, NULL);
    }
  else if (str[1] != NULL)
    {
      gchar *prefix = "\0";

      if (g_str_has_prefix ("function", str[0]))
        prefix = "f";
      else if (g_str_has_prefix ("variable", str[0]))
        prefix = "v";
      else if (g_str_has_prefix ("struct", str[0]))
        prefix = "s";
      else if (g_str_has_prefix ("union", str[0]))
        prefix = "u";
      else if (g_str_has_prefix ("enum", str[0]))
        prefix = "e";
      else if (g_str_has_prefix ("class", str[0]))
        prefix = "c";
      else if (g_str_has_prefix ("constant", str[0]))
        prefix = "a";
      else if (g_str_has_prefix ("macro", str[0]))
        prefix = "m";

      data->query = g_strconcat (prefix, "\x1F", str[1], NULL);
    }

  g_task_set_task_data (task, data, (GDestroyNotify)populate_task_data_free);

453
454
  locker = g_mutex_locker_new (&self->mutex);

455
456
  if (data->curr_index < self->indexes->len)
    {
457
      DirectoryIndex *dir_index = g_ptr_array_index (self->indexes, data->curr_index);
458
459
460
461
462
463
464
465

      dzl_fuzzy_index_query_async (dir_index->symbol_names,
                                   data->query,
                                   data->max_results,
                                   cancellable,
                                   ide_code_index_index_query_cb,
                                   g_steal_pointer (&task));
    }
466
467
468
469
470
471
  else
    {
      g_task_return_pointer (task,
                             g_ptr_array_new_with_free_func (g_object_unref),
                             (GDestroyNotify) g_ptr_array_unref);
    }
472
473
474
475
476
477
478
}

GPtrArray *
ide_code_index_index_populate_finish (IdeCodeIndexIndex *self,
                                      GAsyncResult      *result,
                                      GError           **error)
{
479
  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
480
481
  g_return_val_if_fail (IDE_IS_CODE_INDEX_INDEX (self), NULL);
  g_return_val_if_fail (G_IS_TASK (result), NULL);
482

483
  return g_task_propagate_pointer (G_TASK (result), error);
484
485
486
}

IdeSymbol *
487
488
ide_code_index_index_lookup_symbol (IdeCodeIndexIndex *self,
                                    const gchar       *key)
489
{
490
491
492
  g_autoptr(IdeSourceLocation) declaration = NULL;
  g_autoptr(IdeSourceLocation) definition = NULL;
  g_autoptr(IdeFile) file = NULL;
493
  g_autoptr(GMutexLocker) locker = NULL;
494
495
496
497
  g_autofree gchar *name = NULL;
  IdeSymbolKind kind = IDE_SYMBOL_NONE;
  IdeSymbolFlags flags = IDE_SYMBOL_FLAGS_NONE;
  DzlFuzzyIndex *symbol_names = NULL;
498
499
500
  const DirectoryIndex *dir_index;
  IdeContext *context;
  const gchar *filename;
501
  guint32 file_id = 0;
502
503
504
505
  guint32 line = 0;
  guint32 line_offset = 0;
  gchar num[20];

506
  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), NULL);
507
  g_return_val_if_fail (IDE_IS_CODE_INDEX_INDEX (self), NULL);
508
  g_return_val_if_fail (key != NULL, NULL);
509

510
  g_debug ("Searching declaration with key: %s", key);
511

512
  locker = g_mutex_locker_new (&self->mutex);
513
514
515
516
517

  for (guint i = 0; i < self->indexes->len; i++)
    {
      g_autoptr(GVariant) variant = NULL;

518
519
      dir_index = g_ptr_array_index (self->indexes, i);

520
      if (!(variant = ide_persistent_map_lookup_value (dir_index->symbol_keys, key)))
521
522
523
524
525
526
527
528
529
530
        continue;

      symbol_names = dir_index->symbol_names;

      g_variant_get (variant, "(uuuu)", &file_id, &line, &line_offset, &flags);

      if (flags & IDE_SYMBOL_FLAGS_IS_DEFINITION)
        break;
    }

531
  if (file_id == 0)
532
533
534
535
536
    {
      g_debug ("symbol location not found");
      return NULL;
    }

537
538
539
  g_assert (dir_index != NULL);
  g_assert (dir_index->symbol_names == symbol_names);

540
541
  g_snprintf (num, sizeof(num), "%u", file_id);

542
543
544
  filename = dzl_fuzzy_index_get_metadata_string (symbol_names, num);
  context = ide_object_get_context (IDE_OBJECT (self));
  file = ide_file_new_for_path (context, filename);
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561

  if (flags & IDE_SYMBOL_FLAGS_IS_DEFINITION)
    definition = ide_source_location_new (file, line - 1, line_offset - 1, 0);
  else
    declaration = ide_source_location_new (file, line - 1, line_offset - 1, 0);

  return ide_symbol_new (name, kind, flags, declaration, definition, NULL);
}

static void
ide_code_index_index_finalize (GObject *object)
{
  IdeCodeIndexIndex *self = (IdeCodeIndexIndex *)object;

  g_clear_pointer (&self->directories, g_hash_table_unref);
  g_clear_pointer (&self->indexes, g_ptr_array_unref);

562
  g_mutex_clear (&self->mutex);
563
564
565
566
567
568
569
570
571
572

  G_OBJECT_CLASS (ide_code_index_index_parent_class)->finalize (object);
}

static void
ide_code_index_index_init (IdeCodeIndexIndex *self)
{
  self->directories = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  self->indexes = g_ptr_array_new_with_free_func ((GDestroyNotify)directory_index_free);

573
  g_mutex_init (&self->mutex);
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
}

static void
ide_code_index_index_class_init (IdeCodeIndexIndexClass *klass)
{
  GObjectClass *object_class = (GObjectClass *)klass;

  object_class->finalize = ide_code_index_index_finalize;
}

IdeCodeIndexIndex *
ide_code_index_index_new (IdeContext *context)
{
  return g_object_new (IDE_TYPE_CODE_INDEX_INDEX,
                       "context", context,
                       NULL);
}