[gnome-builder] ctags: perform more incremental ctags updates
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] ctags: perform more incremental ctags updates
- Date: Sun, 19 Mar 2017 10:58:53 +0000 (UTC)
commit 04204e93dd221cab8e88a40399927fc753126383
Author: Christian Hergert <chergert redhat com>
Date: Sun Mar 19 03:55:22 2017 -0700
ctags: perform more incremental ctags updates
This changes how we do ctags indexes to be a bit more friendly on larger
codebases. Now, we do a single index pass at startup of the project to
update all our ctags indexes. Then, when a file is saved, we only reindex
that directory, ignoring the other directories.
When reloading the indexes, we only pop the cache if the mtime is newer
so that old indexes shouldn't get reloaded/replaced.
In practice, if i save a file like src/foo.c, only src/tags will get
reloaded. Note that src/tags is actually based on the tags directory
in ~/.cache/gnome-builder/tags/$project_id, not the actual source tree.
I think there is a bunch more we can still improve, but this should make
things a bit better.
plugins/ctags/ctags-plugin.c | 1 -
plugins/ctags/ide-ctags-builder.c | 456 ++++++++++++++++++++-----------------
plugins/ctags/ide-ctags-builder.h | 7 +-
plugins/ctags/ide-ctags-index.c | 10 +-
plugins/ctags/ide-ctags-index.h | 1 +
plugins/ctags/ide-ctags-service.c | 258 +++++++++++++++-------
6 files changed, 435 insertions(+), 298 deletions(-)
---
diff --git a/plugins/ctags/ctags-plugin.c b/plugins/ctags/ctags-plugin.c
index 803d88d..7cbde47 100644
--- a/plugins/ctags/ctags-plugin.c
+++ b/plugins/ctags/ctags-plugin.c
@@ -39,7 +39,6 @@ void
peas_register_types (PeasObjectModule *module)
{
_ide_ctags_index_register_type (G_TYPE_MODULE (module));
- _ide_ctags_builder_register_type (G_TYPE_MODULE (module));
_ide_ctags_completion_item_register_type (G_TYPE_MODULE (module));
_ide_ctags_completion_provider_register_type (G_TYPE_MODULE (module));
_ide_ctags_highlighter_register_type (G_TYPE_MODULE (module));
diff --git a/plugins/ctags/ide-ctags-builder.c b/plugins/ctags/ide-ctags-builder.c
index a88eaa6..219c87d 100644
--- a/plugins/ctags/ide-ctags-builder.c
+++ b/plugins/ctags/ide-ctags-builder.c
@@ -1,6 +1,6 @@
/* ide-ctags-builder.c
*
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2017 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
@@ -18,308 +18,338 @@
#define G_LOG_DOMAIN "ide-ctags-builder"
-#include <egg-counter.h>
-#include <glib/gi18n.h>
-#include <glib/gstdio.h>
-#include <ide.h>
-
#include "ide-ctags-builder.h"
-#define BUILD_CTAGS_DELAY_SECONDS 10
-
-EGG_DEFINE_COUNTER (instances, "IdeCtagsBuilder", "Instances", "Number of IdeCtagsBuilder instances.")
-EGG_DEFINE_COUNTER (parse_count, "IdeCtagsBuilder", "Build Count", "Number of build attempts.");
-
struct _IdeCtagsBuilder
{
- IdeObject parent_instance;
-
- GSettings *settings;
-
- GQuark ctags_path;
-
- guint build_timeout;
-
- guint is_building : 1;
+ IdeObject parent;
};
-enum {
- TAGS_BUILT,
- LAST_SIGNAL
-};
+typedef struct
+{
+ GFile *directory;
+ GFile *destination;
+ gchar *ctags;
+ guint recursive : 1;
+} BuildTaskData;
-G_DEFINE_DYNAMIC_TYPE (IdeCtagsBuilder, ide_ctags_builder, IDE_TYPE_OBJECT)
+static void tags_builder_iface_init (IdeTagsBuilderInterface *iface);
-static guint signals [LAST_SIGNAL];
+G_DEFINE_TYPE_WITH_CODE (IdeCtagsBuilder, ide_ctags_builder, IDE_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (IDE_TYPE_TAGS_BUILDER, tags_builder_iface_init))
-IdeCtagsBuilder *
-ide_ctags_builder_new (void)
-{
- return g_object_new (IDE_TYPE_CTAGS_BUILDER, NULL);
-}
+static GHashTable *ignored;
static void
-ide_ctags_builder_build_cb (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
+build_task_data_free (gpointer data)
{
- IdeCtagsBuilder *self = (IdeCtagsBuilder *)object;
- GTask *task = (GTask *)result;
- GFile *file;
- GError *error = NULL;
+ BuildTaskData *task_data = data;
- IDE_ENTRY;
+ g_clear_object (&task_data->directory);
+ g_clear_object (&task_data->destination);
+ g_clear_pointer (&task_data->ctags, g_free);
- g_assert (IDE_IS_CTAGS_BUILDER (self));
- g_assert (G_IS_TASK (task));
+ g_slice_free (BuildTaskData, task_data);
+}
- if (g_task_propagate_boolean (task, &error))
- {
- file = g_task_get_task_data (task);
- g_assert (G_IS_FILE (file));
- g_signal_emit (self, signals [TAGS_BUILT], 0, file);
- }
- else
- {
- g_warning ("%s", error->message);
- g_clear_error (&error);
- }
+static void
+ide_ctags_builder_class_init (IdeCtagsBuilderClass *klass)
+{
+ ignored = g_hash_table_new (g_str_hash, g_str_equal);
- self->is_building = FALSE;
+ /* TODO: We need a really fast, *THREAD-SAFE* access to determine
+ * if files are ignored via the VCS.
+ */
- IDE_EXIT;
+ g_hash_table_insert (ignored, ".git", NULL);
+ g_hash_table_insert (ignored, ".bzr", NULL);
+ g_hash_table_insert (ignored, ".svn", NULL);
+ g_hash_table_insert (ignored, ".flatpak-builder", NULL);
+ g_hash_table_insert (ignored, ".libs", NULL);
+ g_hash_table_insert (ignored, ".deps", NULL);
+ g_hash_table_insert (ignored, "autom4te.cache", NULL);
+ g_hash_table_insert (ignored, "build-aux", NULL);
}
static void
-ide_ctags_builder_process_wait_cb (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
+ide_ctags_builder_init (IdeCtagsBuilder *self)
{
- IdeSubprocess *process = (IdeSubprocess *)object;
- g_autoptr(GTask) task = user_data;
- GError *error = NULL;
-
- IDE_ENTRY;
-
- g_assert (IDE_IS_SUBPROCESS (process));
- g_assert (G_IS_TASK (task));
+}
- if (!ide_subprocess_wait_finish (process, result, &error))
- g_task_return_error (task, error);
- else
- g_task_return_boolean (task, TRUE);
+IdeTagsBuilder *
+ide_ctags_builder_new (IdeContext *context)
+{
+ g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
- IDE_EXIT;
+ return g_object_new (IDE_TYPE_CTAGS_BUILDER,
+ "context", context,
+ NULL);
}
-static void
-ide_ctags_builder_build_worker (GTask *task,
- gpointer source_object,
- gpointer task_data,
- GCancellable *cancellable)
+static gboolean
+ide_ctags_builder_build (IdeCtagsBuilder *self,
+ const gchar *ctags,
+ GFile *directory,
+ GFile *destination,
+ gboolean recursive,
+ GCancellable *cancellable)
{
- IdeCtagsBuilder *self = source_object;
- g_autoptr(GFile) workdir = NULL;
g_autoptr(IdeSubprocessLauncher) launcher = NULL;
- g_autoptr(IdeSubprocess) process = NULL;
- g_autofree gchar *tags_file = NULL;
- g_autofree gchar *tags_filename = NULL;
- g_autofree gchar *workpath = NULL;
+ g_autoptr(IdeSubprocess) subprocess = NULL;
+ g_autoptr(GPtrArray) directories = NULL;
+ g_autoptr(GPtrArray) dest_directories = NULL;
+ g_autoptr(GFile) tags_file = NULL;
+ g_autoptr(GFileEnumerator) enumerator = NULL;
+ g_autoptr(GError) error = NULL;
+ g_autofree gchar *cwd = NULL;
+ g_autofree gchar *dest_dir = NULL;
g_autofree gchar *options_path = NULL;
- g_autofree gchar *tagsdir = NULL;
- IdeContext *context;
- IdeProject *project;
- GError *error = NULL;
- IdeVcs *vcs;
-
- IDE_ENTRY;
+ g_autofree gchar *tags_path = NULL;
+ g_autoptr(GString) filenames = NULL;
+ GOutputStream *stdin_stream;
+ gpointer infoptr;
- g_assert (G_IS_TASK (task));
g_assert (IDE_IS_CTAGS_BUILDER (self));
- g_assert (task_data == NULL);
- g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_assert (G_IS_FILE (directory));
+ g_assert (G_IS_FILE (destination));
- /*
- * Get our necessary components, and then release the context hold
- * which we acquired before passing work to this thread.
- */
- context = ide_object_get_context (IDE_OBJECT (self));
- project = ide_context_get_project (context);
- vcs = ide_context_get_vcs (context);
- workdir = g_object_ref (ide_vcs_get_working_directory (vcs));
- tags_filename = g_strconcat (ide_project_get_id (project), ".tags", NULL);
- tags_file = g_build_filename (g_get_user_cache_dir (),
- ide_get_program_name (),
- "tags",
- tags_filename,
- NULL);
+ dest_dir = g_file_get_path (destination);
+ if (0 != g_mkdir_with_parents (dest_dir, 0750))
+ return FALSE;
+
+ tags_file = g_file_get_child (destination, "tags");
+ tags_path = g_file_get_path (tags_file);
+ cwd = g_file_get_path (directory);
options_path = g_build_filename (g_get_user_config_dir (),
ide_get_program_name (),
"ctags.conf",
NULL);
- ide_object_release (IDE_OBJECT (self));
+ directories = g_ptr_array_new_with_free_func (g_object_unref);
+ dest_directories = g_ptr_array_new_with_free_func (g_object_unref);
+ filenames = g_string_new (NULL);
- /*
- * If the file is not native, ctags can't generate anything for us.
- */
- if (!(workpath = g_file_get_path (workdir)))
- {
- g_task_return_new_error (task,
- G_IO_ERROR,
- G_IO_ERROR_INVALID_FILENAME,
- "ctags can only operate on local files.");
- IDE_EXIT;
- }
-
- /* create the directory if necessary */
- tagsdir = g_path_get_dirname (tags_file);
- if (!g_file_test (tagsdir, G_FILE_TEST_IS_DIR))
- g_mkdir_with_parents (tagsdir, 0750);
+ launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDIN_PIPE |
+ G_SUBPROCESS_FLAGS_STDERR_SILENCE);
- /* remove the existing tags file (we already have it in memory anyway) */
- if (g_file_test (tags_file, G_FILE_TEST_EXISTS))
- g_unlink (tags_file);
+ ide_subprocess_launcher_set_cwd (launcher, cwd);
+ ide_subprocess_launcher_setenv (launcher, "TMPDIR", cwd, TRUE);
+ ide_subprocess_launcher_set_stdout_file_path (launcher, tags_path);
- launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
-
- ide_subprocess_launcher_push_argv (launcher, g_quark_to_string (self->ctags_path));
+ ide_subprocess_launcher_push_argv (launcher, ctags);
ide_subprocess_launcher_push_argv (launcher, "-f");
ide_subprocess_launcher_push_argv (launcher, "-");
- ide_subprocess_launcher_push_argv (launcher, "--recurse=yes");
ide_subprocess_launcher_push_argv (launcher, "--tag-relative=no");
ide_subprocess_launcher_push_argv (launcher, "--exclude=.git");
ide_subprocess_launcher_push_argv (launcher, "--exclude=.bzr");
ide_subprocess_launcher_push_argv (launcher, "--exclude=.svn");
+ ide_subprocess_launcher_push_argv (launcher, "--exclude=.flatpak-builder");
ide_subprocess_launcher_push_argv (launcher, "--sort=yes");
ide_subprocess_launcher_push_argv (launcher, "--languages=all");
ide_subprocess_launcher_push_argv (launcher, "--file-scope=yes");
ide_subprocess_launcher_push_argv (launcher, "--c-kinds=+defgpstx");
+
if (g_file_test (options_path, G_FILE_TEST_IS_REGULAR))
{
ide_subprocess_launcher_push_argv (launcher, "--options");
ide_subprocess_launcher_push_argv (launcher, options_path);
}
- ide_subprocess_launcher_push_argv (launcher, ".");
+
+ /* Read filenames from stdin, which we will provided below */
+ ide_subprocess_launcher_push_argv (launcher, "-L");
+ ide_subprocess_launcher_push_argv (launcher, "-");
+
+ subprocess = ide_subprocess_launcher_spawn (launcher, cancellable, &error);
+
+ if (subprocess == NULL)
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
+
+ stdin_stream = ide_subprocess_get_stdin_pipe (subprocess);
/*
- * Create our arguments to launch the ctags generation process.
- */
- ide_subprocess_launcher_set_cwd (launcher, workpath);
- ide_subprocess_launcher_set_stdout_file_path (launcher, tags_file);
- /*
- * ctags can sometimes write to TMPDIR for incremental writes so that it
- * can sort internally. On large files this can cause us to run out of
- * tmpfs. Instead, just use the home dir which should map to something
- * that is persistent.
+ * We do our own recursive building of ctags instead of --recursive=yes
+ * so that we can have smaller files to update. This helps on larger
+ * projects where we would have to rescan the whole project after a
+ * file is saved.
+ *
+ * Additionally, while walking the file-system tree, we append files
+ * to stdin of our ctags process to tell it to process them.
*/
- ide_subprocess_launcher_setenv (launcher, "TMPDIR", tagsdir, TRUE);
- process = ide_subprocess_launcher_spawn (launcher, cancellable, &error);
- EGG_COUNTER_INC (parse_count);
+ enumerator = g_file_enumerate_children (directory,
+ G_FILE_ATTRIBUTE_STANDARD_NAME","
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ cancellable,
+ &error);
- if (process == NULL)
+ if (enumerator == NULL)
+ IDE_GOTO (finish_subprocess);
+
+ while (NULL != (infoptr = g_file_enumerator_next_file (enumerator, cancellable, &error)))
{
- g_task_return_error (task, error);
- IDE_EXIT;
+ g_autoptr(GFileInfo) info = infoptr;
+ const gchar *name;
+ GFileType type;
+
+ name = g_file_info_get_name (info);
+ type = g_file_info_get_file_type (info);
+
+ if (g_hash_table_contains (ignored, name))
+ continue;
+
+ if (type == G_FILE_TYPE_DIRECTORY)
+ {
+ if (recursive)
+ {
+ g_ptr_array_add (directories, g_file_get_child (directory, name));
+ g_ptr_array_add (dest_directories, g_file_get_child (destination, name));
+ }
+ }
+ else if (type == G_FILE_TYPE_REGULAR)
+ {
+ g_string_append_printf (filenames, "%s\n", name);
+ }
}
- g_task_set_task_data (task, g_file_new_for_path (tags_file), g_object_unref);
+ g_output_stream_write_all (stdin_stream, filenames->str, filenames->len, NULL, NULL, NULL);
- ide_subprocess_wait_async (process,
- cancellable,
- ide_ctags_builder_process_wait_cb,
- g_object_ref (task));
+finish_subprocess:
+ g_output_stream_close (stdin_stream, NULL, NULL);
- IDE_EXIT;
-}
+ if (!ide_subprocess_wait_check (subprocess, NULL, &error))
+ {
+ g_warning ("%s", error->message);
+ return FALSE;
+ }
-void
-ide_ctags_builder_rebuild (IdeCtagsBuilder *self)
-{
- g_autoptr(GTask) task = NULL;
+ for (guint i = 0; i < directories->len; i++)
+ {
+ GFile *child = g_ptr_array_index (directories, i);
+ GFile *dest_child = g_ptr_array_index (dest_directories, i);
- g_return_if_fail (IDE_IS_CTAGS_BUILDER (self));
+ g_assert (G_IS_FILE (child));
+ g_assert (G_IS_FILE (dest_child));
- /* Make sure we aren't already in shutdown. */
- if (!ide_object_hold (IDE_OBJECT (self)))
- return;
+ if (!ide_ctags_builder_build (self, ctags, child, dest_child, recursive, cancellable))
+ return FALSE;
+ }
- task = g_task_new (self, NULL, ide_ctags_builder_build_cb, NULL);
- ide_thread_pool_push_task (IDE_THREAD_POOL_INDEXER, task, ide_ctags_builder_build_worker);
+ return TRUE;
}
static void
-ide_ctags_builder__ctags_path_changed (IdeCtagsBuilder *self,
- const gchar *key,
- GSettings *settings)
+ide_ctags_builder_build_worker (GTask *task,
+ gpointer source_object,
+ gpointer task_data_ptr,
+ GCancellable *cancellable)
{
- g_autofree gchar *ctags_path = NULL;
+ BuildTaskData *task_data = task_data_ptr;
+ IdeCtagsBuilder *self = source_object;
+ const gchar *ctags;
- g_assert (IDE_IS_CTAGS_BUILDER (self));
- g_assert (ide_str_equal0 (key, "ctags-path"));
- g_assert (G_IS_SETTINGS (settings));
+ IDE_ENTRY;
- ctags_path = g_settings_get_string (settings, "ctags-path");
- self->ctags_path = g_quark_from_string (ctags_path);
-}
+ g_assert (G_IS_TASK (task));
+ g_assert (IDE_IS_CTAGS_BUILDER (source_object));
+ g_assert (G_IS_FILE (task_data->directory));
-static void
-ide_ctags_builder_finalize (GObject *object)
-{
- IdeCtagsBuilder *self = (IdeCtagsBuilder *)object;
+ ctags = task_data->ctags;
+ if (!g_find_program_in_path (ctags))
+ ctags = "ctags";
- ide_clear_source (&self->build_timeout);
- g_clear_object (&self->settings);
+ ide_ctags_builder_build (self,
+ ctags,
+ task_data->directory,
+ task_data->destination,
+ task_data->recursive,
+ cancellable);
- G_OBJECT_CLASS (ide_ctags_builder_parent_class)->finalize (object);
+ g_task_return_boolean (task, TRUE);
- EGG_COUNTER_DEC (instances);
+ IDE_EXIT;
}
static void
-ide_ctags_builder_class_init (IdeCtagsBuilderClass *klass)
+ide_ctags_builder_build_async (IdeTagsBuilder *builder,
+ GFile *directory_or_file,
+ gboolean recursive,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-
- object_class->finalize = ide_ctags_builder_finalize;
-
- signals [TAGS_BUILT] =
- g_signal_new ("tags-built",
- G_TYPE_FROM_CLASS (klass),
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE,
- 1,
- G_TYPE_FILE);
-}
+ IdeCtagsBuilder *self = (IdeCtagsBuilder *)builder;
+ g_autoptr(GTask) task = NULL;
+ g_autoptr(GSettings) settings = NULL;
+ g_autofree gchar *destination_path = NULL;
+ g_autofree gchar *relative_path = NULL;
+ BuildTaskData *task_data;
+ IdeContext *context;
+ const gchar *project_id;
+ GFile *workdir;
-static void
-ide_ctags_builder_class_finalize (IdeCtagsBuilderClass *klass)
-{
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_CTAGS_BUILDER (self));
+ g_assert (G_IS_FILE (directory_or_file));
+
+ settings = g_settings_new ("org.gnome.builder.code-insight");
+
+ task_data = g_slice_new0 (BuildTaskData);
+ task_data->ctags = g_settings_get_string (settings, "ctags-path");
+ task_data->directory = g_object_ref (directory_or_file);
+ task_data->recursive = recursive;
+ task_data->ctags = g_strdup ("ctags");
+
+ /*
+ * The destination directory for the tags should match the hierarchy
+ * of the projects source tree, but be based in something like
+ * ~/.cache/gnome-builder/tags/$project_id/ so that they can be reused
+ * even between configuration changes. Primarily, we want to avoid
+ * putting things in the source tree.
+ */
+ context = ide_object_get_context (IDE_OBJECT (self));
+ project_id = ide_project_get_id (ide_context_get_project (context));
+ workdir = ide_vcs_get_working_directory (ide_context_get_vcs (context));
+ relative_path = g_file_get_relative_path (workdir, directory_or_file);
+ destination_path = g_build_filename (g_get_user_cache_dir (),
+ ide_get_program_name (),
+ "tags",
+ project_id,
+ relative_path,
+ NULL);
+ task_data->destination = g_file_new_for_path (destination_path);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_source_tag (task, ide_ctags_builder_build_async);
+ g_task_set_task_data (task, task_data, build_task_data_free);
+ ide_thread_pool_push_task (IDE_THREAD_POOL_INDEXER, task, ide_ctags_builder_build_worker);
+
+ IDE_EXIT;
}
-static void
-ide_ctags_builder_init (IdeCtagsBuilder *self)
+static gboolean
+ide_ctags_builder_build_finish (IdeTagsBuilder *builder,
+ GAsyncResult *result,
+ GError **error)
{
- g_autofree gchar *ctags_path = NULL;
+ gboolean ret;
- EGG_COUNTER_INC (instances);
+ IDE_ENTRY;
- self->settings = g_settings_new ("org.gnome.builder.code-insight");
+ g_return_val_if_fail (IDE_IS_CTAGS_BUILDER (builder), FALSE);
+ g_return_val_if_fail (G_IS_TASK (result), FALSE);
- g_signal_connect_object (self->settings,
- "changed::ctags-path",
- G_CALLBACK (ide_ctags_builder__ctags_path_changed),
- self,
- G_CONNECT_SWAPPED);
+ ret = g_task_propagate_boolean (G_TASK (result), error);
- ctags_path = g_settings_get_string (self->settings, "ctags-path");
- self->ctags_path = g_quark_from_string (ctags_path);
+ IDE_RETURN (ret);
}
-void
-_ide_ctags_builder_register_type (GTypeModule *module)
+static void
+tags_builder_iface_init (IdeTagsBuilderInterface *iface)
{
- ide_ctags_builder_register_type (module);
+ iface->build_async = ide_ctags_builder_build_async;
+ iface->build_finish = ide_ctags_builder_build_finish;
}
diff --git a/plugins/ctags/ide-ctags-builder.h b/plugins/ctags/ide-ctags-builder.h
index c07ba89..1440542 100644
--- a/plugins/ctags/ide-ctags-builder.h
+++ b/plugins/ctags/ide-ctags-builder.h
@@ -1,6 +1,6 @@
/* ide-ctags-builder.h
*
- * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ * Copyright (C) 2017 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
@@ -19,7 +19,7 @@
#ifndef IDE_CTAGS_BUILDER_H
#define IDE_CTAGS_BUILDER_H
-#include "ide-object.h"
+#include <ide.h>
G_BEGIN_DECLS
@@ -27,8 +27,7 @@ G_BEGIN_DECLS
G_DECLARE_FINAL_TYPE (IdeCtagsBuilder, ide_ctags_builder, IDE, CTAGS_BUILDER, IdeObject)
-IdeCtagsBuilder *ide_ctags_builder_new (void);
-void ide_ctags_builder_rebuild (IdeCtagsBuilder *self);
+IdeTagsBuilder *ide_ctags_builder_new (IdeContext *context);
G_END_DECLS
diff --git a/plugins/ctags/ide-ctags-index.c b/plugins/ctags/ide-ctags-index.c
index 1e97ac3..b337459 100644
--- a/plugins/ctags/ide-ctags-index.c
+++ b/plugins/ctags/ide-ctags-index.c
@@ -445,7 +445,7 @@ ide_ctags_index_init_async (GAsyncInitable *initable,
return;
}
- g_task_run_in_thread (task, ide_ctags_index_build_index);
+ ide_thread_pool_push_task (IDE_THREAD_POOL_INDEXER, task, ide_ctags_index_build_index);
}
static gboolean
@@ -677,3 +677,11 @@ ide_ctags_index_find_with_path (IdeCtagsIndex *self,
return ar;
}
+
+gboolean
+ide_ctags_index_get_is_empty (IdeCtagsIndex *self)
+{
+ g_return_val_if_fail (IDE_IS_CTAGS_INDEX (self), FALSE);
+
+ return self->index == NULL || self->index->len == 0;
+}
diff --git a/plugins/ctags/ide-ctags-index.h b/plugins/ctags/ide-ctags-index.h
index c21a70d..6d26aba 100644
--- a/plugins/ctags/ide-ctags-index.h
+++ b/plugins/ctags/ide-ctags-index.h
@@ -71,6 +71,7 @@ GPtrArray *ide_ctags_index_find_with_path(IdeCtagsIndex
gchar *ide_ctags_index_resolve_path (IdeCtagsIndex *self,
const gchar *path);
GFile *ide_ctags_index_get_file (IdeCtagsIndex *self);
+gboolean ide_ctags_index_get_is_empty (IdeCtagsIndex *self);
gsize ide_ctags_index_get_size (IdeCtagsIndex *self);
const gchar *ide_ctags_index_get_path_root (IdeCtagsIndex *self);
const IdeCtagsIndexEntry *ide_ctags_index_lookup (IdeCtagsIndex *self,
diff --git a/plugins/ctags/ide-ctags-service.c b/plugins/ctags/ide-ctags-service.c
index 8264883..fc7d4d9 100644
--- a/plugins/ctags/ide-ctags-service.c
+++ b/plugins/ctags/ide-ctags-service.c
@@ -37,10 +37,19 @@ struct _IdeCtagsService
IdeCtagsBuilder *builder;
GPtrArray *highlighters;
GPtrArray *completions;
+ GHashTable *build_timeout_by_dir;
- guint build_tags_timeout;
+ guint queued_miner_handler;
+ guint miner_active : 1;
+ guint needs_recursive_mine : 1;
};
+typedef struct
+{
+ gchar *path;
+ guint recursive;
+} MineInfo;
+
static void service_iface_init (IdeServiceInterface *iface);
G_DEFINE_DYNAMIC_TYPE_EXTENDED (IdeCtagsService, ide_ctags_service, IDE_TYPE_OBJECT, 0,
@@ -60,6 +69,11 @@ ide_ctags_service_build_index_init_cb (GObject *object,
if (!g_async_initable_init_finish (G_ASYNC_INITABLE (index), result, &error))
g_task_return_error (task, error);
+ else if (ide_ctags_index_get_is_empty (index))
+ g_task_return_new_error (task,
+ G_IO_ERROR,
+ G_IO_ERROR_NONE,
+ "tags file is empty");
else
g_task_return_pointer (task, g_object_ref (index), g_object_unref);
}
@@ -68,11 +82,15 @@ static guint64
get_file_mtime (GFile *file)
{
g_autoptr(GFileInfo) info = NULL;
+ g_autofree gchar *path = NULL;
if ((info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
G_FILE_QUERY_INFO_NONE, NULL, NULL)))
return g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ path = g_file_get_uri (file);
+ g_warning ("Failed to get mtime for %s", path);
+
return 0;
}
@@ -123,7 +141,6 @@ ide_ctags_service_build_index_cb (EggTaskCache *cache,
IdeCtagsService *self = user_data;
g_autoptr(IdeCtagsIndex) index = NULL;
GFile *file = (GFile *)key;
- g_autofree gchar *uri = NULL;
g_autofree gchar *path_root = NULL;
IDE_ENTRY;
@@ -136,8 +153,12 @@ ide_ctags_service_build_index_cb (EggTaskCache *cache,
path_root = resolve_path_root (self, file);
index = ide_ctags_index_new (file, path_root, get_file_mtime (file));
- uri = g_file_get_uri (file);
- g_debug ("Building ctags in memory index for %s", uri);
+#ifdef IDE_ENABLE_TRACE
+ {
+ g_autofree gchar *uri = g_file_get_uri (file);
+ IDE_TRACE_MSG ("Building ctags in memory index for %s", uri);
+ }
+#endif
g_async_initable_init_async (G_ASYNC_INITABLE (index),
G_PRIORITY_DEFAULT,
@@ -166,8 +187,12 @@ ide_ctags_service_tags_loaded_cb (GObject *object,
if (!(index = egg_task_cache_get_finish (cache, result, &error)))
{
- g_debug ("%s", error->message);
+ /* don't log if it was an empty file */
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NONE))
+ g_debug ("%s", error->message);
+
g_clear_error (&error);
+
IDE_EXIT;
}
@@ -192,20 +217,26 @@ static gboolean
file_is_newer (IdeCtagsIndex *index,
GFile *file)
{
+ guint64 file_mtime;
+ guint64 ctags_mtime;
+
g_assert (IDE_IS_CTAGS_INDEX (index));
g_assert (G_IS_FILE (file));
- return get_file_mtime (file) > ide_ctags_index_get_mtime (index);
+ file_mtime = get_file_mtime (file);
+ ctags_mtime = ide_ctags_index_get_mtime (index);
+
+ return file_mtime > ctags_mtime;
}
static gboolean
do_load (gpointer data)
{
+ IdeCtagsIndex *prev;
struct {
IdeCtagsService *self;
GFile *file;
} *pair = data;
- IdeCtagsIndex *prev;
if ((prev = egg_task_cache_peek (pair->self->indexes, pair->file)))
{
@@ -316,64 +347,103 @@ ide_ctags_service_miner (GTask *task,
gpointer task_data,
GCancellable *cancellable)
{
- g_autofree gchar *project_tags = NULL;
- g_autofree gchar *filename = NULL;
IdeCtagsService *self = source_object;
- IdeContext *context;
- IdeProject *project;
- IdeVcs *vcs;
- GFile *file;
+ GArray *mine_info = task_data;
+
+ IDE_ENTRY;
g_assert (G_IS_TASK (task));
g_assert (IDE_IS_CTAGS_SERVICE (self));
+ g_assert (mine_info != NULL);
- context = ide_object_get_context (IDE_OBJECT (self));
- vcs = ide_context_get_vcs (context);
- project = ide_context_get_project (context);
- filename = g_strconcat (ide_project_get_id (project), ".tags", NULL);
- project_tags = g_build_filename (g_get_user_cache_dir (),
- ide_get_program_name (),
- "tags",
- filename,
- NULL);
-
- /* mine ~/.cache/gnome-builder/tags/<name>.tags */
- file = g_file_new_for_path (project_tags);
- ide_ctags_service_load_tags (self, file);
- g_object_unref (file);
-
- /* mine the project tree */
- file = g_object_ref (ide_vcs_get_working_directory (vcs));
- ide_ctags_service_mine_directory (self, file, TRUE, cancellable);
- g_object_unref (file);
+ for (guint i = 0; i < mine_info->len; i++)
+ {
+ const MineInfo *info = &g_array_index (mine_info, MineInfo, i);
+ g_autoptr(GFile) file = g_file_new_for_path (info->path);
- /* mine ~/.tags */
- file = g_file_new_for_path (g_get_home_dir ());
- ide_ctags_service_mine_directory (self, file, FALSE, cancellable);
- g_object_unref (file);
+ ide_ctags_service_mine_directory (self, file, info->recursive, cancellable);
+ }
- /* mine /usr/include */
- file = g_file_new_for_path ("/usr/include");
- ide_ctags_service_mine_directory (self, file, TRUE, cancellable);
- g_object_unref (file);
+ self->miner_active = FALSE;
- ide_object_release (IDE_OBJECT (self));
+ IDE_EXIT;
}
static void
-ide_ctags_service_mine (IdeCtagsService *self)
+clear_mine_info (gpointer data)
+{
+ MineInfo *info = data;
+
+ g_free (info->path);
+}
+
+static gboolean
+ide_ctags_service_do_mine (gpointer data)
{
+ IdeCtagsService *self = data;
g_autoptr(GTask) task = NULL;
+ g_autoptr(GArray) mine_info = NULL;
+ g_autofree gchar *path = NULL;
+ IdeContext *context;
+ IdeProject *project;
+ MineInfo info;
+ GFile *workdir;
- g_return_if_fail (IDE_IS_CTAGS_SERVICE (self));
+ IDE_ENTRY;
+
+ g_assert (IDE_IS_CTAGS_SERVICE (self));
+
+ self->queued_miner_handler = 0;
+ self->miner_active = TRUE;
+
+ context = ide_object_get_context (IDE_OBJECT (self));
+ project = ide_context_get_project (context);
+ workdir = ide_vcs_get_working_directory (ide_context_get_vcs (context));
+
+ mine_info = g_array_new (FALSE, FALSE, sizeof (MineInfo));
+ g_array_set_clear_func (mine_info, clear_mine_info);
- /* prevent unloading until we release from worker thread */
- ide_object_hold (IDE_OBJECT (self));
+ /* mine: ~/.cache/gnome-builder/tags/$project_id */
+ info.path = g_build_filename (g_get_user_cache_dir (),
+ ide_get_program_name (),
+ "tags",
+ ide_project_get_id (project),
+ NULL);
+ info.recursive = TRUE;
+ g_array_append_val (mine_info, info);
+
+ /* mine: ~/.tags */
+ info.path = g_strdup (g_get_home_dir ());
+ info.recursive = FALSE;
+ g_array_append_val (mine_info, info);
+
+ /* mine the project tree */
+ info.path = g_file_get_path (workdir);
+ info.recursive = TRUE;
+ g_array_append_val (mine_info, info);
+
+ task = g_task_new (self, NULL, NULL, NULL);
+ g_task_set_source_tag (task, ide_ctags_service_do_mine);
+ g_task_set_task_data (task, g_steal_pointer (&mine_info), (GDestroyNotify)g_array_unref);
+ ide_thread_pool_push_task (IDE_THREAD_POOL_INDEXER, task, ide_ctags_service_miner);
- self->cancellable = g_cancellable_new ();
+ IDE_RETURN (G_SOURCE_REMOVE);
+}
+
+static void
+ide_ctags_service_queue_mine (IdeCtagsService *self)
+{
+ g_assert (IDE_IS_CTAGS_SERVICE (self));
- task = g_task_new (self, self->cancellable, NULL, NULL);
- g_task_run_in_thread (task, ide_ctags_service_miner);
+ if (self->queued_miner_handler == 0 && self->miner_active == FALSE)
+ {
+ self->queued_miner_handler =
+ g_timeout_add_full (250,
+ G_PRIORITY_DEFAULT,
+ ide_ctags_service_do_mine,
+ g_object_ref (self),
+ g_object_unref);
+ }
}
static void
@@ -407,64 +477,84 @@ build_system_tags_cb (GObject *object,
g_assert (IDE_IS_TAGS_BUILDER (builder));
- ide_ctags_service_mine (self);
+ ide_ctags_service_queue_mine (self);
}
static gboolean
-restart_miner (gpointer data)
+restart_miner (gpointer user_data)
{
- IdeCtagsService *self = data;
+ g_autofree gpointer *data = user_data;
+ g_autoptr(IdeCtagsService) self = data[0];
+ g_autoptr(GFile) directory = data[1];
+ g_autoptr(IdeTagsBuilder) tags_builder = NULL;
+ IdeBuildSystem *build_system;
IdeContext *context;
IDE_ENTRY;
g_assert (IDE_IS_CTAGS_SERVICE (self));
- self->build_tags_timeout = 0;
+ g_hash_table_remove (self->build_timeout_by_dir, directory);
context = ide_object_get_context (IDE_OBJECT (self));
+ build_system = ide_context_get_build_system (context);
- if (context != NULL)
- {
- IdeBuildSystem *build_system;
-
- build_system = ide_context_get_build_system (context);
+ if (IDE_IS_TAGS_BUILDER (build_system))
+ tags_builder = g_object_ref (IDE_TAGS_BUILDER (build_system));
+ else
+ tags_builder = ide_ctags_builder_new (context);
- if (IDE_IS_TAGS_BUILDER (build_system))
- {
- IdeVcs *vcs;
- GFile *workdir;
-
- vcs = ide_context_get_vcs (context);
- workdir = ide_vcs_get_working_directory (vcs);
- ide_tags_builder_build_async (IDE_TAGS_BUILDER (build_system), workdir, TRUE, NULL,
- build_system_tags_cb, g_object_ref (self));
- IDE_GOTO (finish);
- }
- else
- {
- ide_ctags_builder_rebuild (self->builder);
- }
- }
+ ide_tags_builder_build_async (tags_builder,
+ directory,
+ self->needs_recursive_mine,
+ NULL,
+ build_system_tags_cb,
+ g_object_ref (self));
-finish:
+ self->needs_recursive_mine = FALSE;
IDE_RETURN (G_SOURCE_REMOVE);
}
static void
+ide_ctags_service_queue_build_for_directory (IdeCtagsService *self,
+ GFile *directory)
+{
+ g_assert (IDE_IS_CTAGS_SERVICE (self));
+ g_assert (G_IS_FILE (directory));
+
+ if (!g_hash_table_lookup (self->build_timeout_by_dir, directory))
+ {
+ gpointer *data;
+ guint source_id;
+
+ data = g_new0 (gpointer, 2);
+ data[0] = g_object_ref (self);
+ data[1] = g_object_ref (directory);
+
+ source_id = g_timeout_add_seconds (5, restart_miner, data);
+
+ g_hash_table_insert (self->build_timeout_by_dir,
+ g_object_ref (directory),
+ GUINT_TO_POINTER (source_id));
+ }
+}
+
+static void
ide_ctags_service_buffer_saved (IdeCtagsService *self,
IdeBuffer *buffer,
IdeBufferManager *buffer_manager)
{
+ g_autoptr(GFile) parent = NULL;
+
IDE_ENTRY;
g_assert (IDE_IS_CTAGS_SERVICE (self));
g_assert (IDE_IS_BUFFER (buffer));
g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
- if (self->build_tags_timeout == 0)
- self->build_tags_timeout = g_timeout_add_seconds (5, restart_miner, self);
+ parent = g_file_get_parent (ide_file_get_file (ide_buffer_get_file (buffer)));
+ ide_ctags_service_queue_build_for_directory (self, parent);
IDE_EXIT;
}
@@ -475,6 +565,7 @@ ide_ctags_service_context_loaded (IdeService *service)
IdeBufferManager *buffer_manager;
IdeCtagsService *self = (IdeCtagsService *)service;
IdeContext *context;
+ GFile *workdir;
IDE_ENTRY;
@@ -482,6 +573,7 @@ ide_ctags_service_context_loaded (IdeService *service)
context = ide_object_get_context (IDE_OBJECT (self));
buffer_manager = ide_context_get_buffer_manager (context);
+ workdir = ide_vcs_get_working_directory (ide_context_get_vcs (context));
g_signal_connect_object (buffer_manager,
"buffer-saved",
@@ -489,7 +581,12 @@ ide_ctags_service_context_loaded (IdeService *service)
self,
G_CONNECT_SWAPPED);
- ide_ctags_service_mine (self);
+ /*
+ * Rebuild all ctags for the project at startup of the service.
+ * Then we do incrementals from there on out.
+ */
+ self->needs_recursive_mine = TRUE;
+ ide_ctags_service_queue_build_for_directory (self, workdir);
IDE_EXIT;
}
@@ -523,7 +620,6 @@ ide_ctags_service_stop (IdeService *service)
if (self->cancellable && !g_cancellable_is_cancelled (self->cancellable))
g_cancellable_cancel (self->cancellable);
- ide_clear_source (&self->build_tags_timeout);
g_clear_object (&self->cancellable);
g_clear_object (&self->builder);
}
@@ -535,11 +631,11 @@ ide_ctags_service_finalize (GObject *object)
IDE_ENTRY;
- ide_clear_source (&self->build_tags_timeout);
g_clear_object (&self->indexes);
g_clear_object (&self->cancellable);
g_clear_pointer (&self->highlighters, g_ptr_array_unref);
g_clear_pointer (&self->completions, g_ptr_array_unref);
+ g_clear_pointer (&self->build_timeout_by_dir, g_hash_table_unref);
G_OBJECT_CLASS (ide_ctags_service_parent_class)->finalize (object);
@@ -573,6 +669,10 @@ ide_ctags_service_init (IdeCtagsService *self)
self->highlighters = g_ptr_array_new ();
self->completions = g_ptr_array_new ();
+ self->build_timeout_by_dir = g_hash_table_new_full ((GHashFunc)g_file_hash,
+ (GEqualFunc)g_file_equal,
+ g_object_unref, NULL);
+
self->indexes = egg_task_cache_new ((GHashFunc)g_file_hash,
(GEqualFunc)g_file_equal,
g_object_ref,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]