[gnome-builder] ctags: add helper to build ctags indexes on disk



commit 0ae5810a2d997a56637e29865d4b438c80801588
Author: Christian Hergert <christian hergert me>
Date:   Sat May 16 20:13:11 2015 -0700

    ctags: add helper to build ctags indexes on disk

 libide/Makefile.am               |    2 +
 libide/ctags/ide-ctags-builder.c |  354 ++++++++++++++++++++++++++++++++++++++
 libide/ctags/ide-ctags-builder.h |   34 ++++
 3 files changed, 390 insertions(+), 0 deletions(-)
---
diff --git a/libide/Makefile.am b/libide/Makefile.am
index bfdae37..b0e2b8c 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -223,6 +223,8 @@ libide_1_0_la_SOURCES = \
        clang/ide-clang-symbol-resolver.h \
        clang/ide-clang-translation-unit.c \
        clang/ide-clang-translation-unit.h \
+       ctags/ide-ctags-builder.c \
+       ctags/ide-ctags-builder.h \
        ctags/ide-ctags-completion-item.c \
        ctags/ide-ctags-completion-item.h \
        ctags/ide-ctags-completion-provider.c \
diff --git a/libide/ctags/ide-ctags-builder.c b/libide/ctags/ide-ctags-builder.c
new file mode 100644
index 0000000..e7fdeff
--- /dev/null
+++ b/libide/ctags/ide-ctags-builder.c
@@ -0,0 +1,354 @@
+/* ide-ctags-builder.c
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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-ctags-builder"
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "egg-counter.h"
+
+#include "ide-buffer.h"
+#include "ide-buffer-manager.h"
+#include "ide-context.h"
+#include "ide-ctags-builder.h"
+#include "ide-debug.h"
+#include "ide-global.h"
+#include "ide-project.h"
+#include "ide-thread-pool.h"
+#include "ide-vcs.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;
+  guint      build_timeout;
+  guint      is_building : 1;
+};
+
+enum {
+  TAGS_BUILT,
+  LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (IdeCtagsBuilder, ide_ctags_builder, IDE_TYPE_OBJECT)
+
+static guint gSignals [LAST_SIGNAL];
+
+IdeCtagsBuilder *
+ide_ctags_builder_new (void)
+{
+  return g_object_new (IDE_TYPE_CTAGS_BUILDER, NULL);
+}
+
+static void
+ide_ctags_builder_build_cb (GObject      *object,
+                            GAsyncResult *result,
+                            gpointer      user_data)
+{
+  IdeCtagsBuilder *self = (IdeCtagsBuilder *)object;
+  GTask *task = (GTask *)result;
+  GFile *file;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_CTAGS_BUILDER (self));
+  g_assert (G_IS_TASK (task));
+
+  if (g_task_propagate_boolean (task, &error))
+    {
+      file = g_task_get_task_data (task);
+      g_assert (G_IS_FILE (file));
+      g_signal_emit (self, gSignals [TAGS_BUILT], 0, file);
+    }
+  else
+    {
+      g_warning ("%s", error->message);
+      g_clear_error (&error);
+    }
+
+  self->is_building = FALSE;
+
+  IDE_EXIT;
+}
+
+static void
+ide_ctags_builder_process_wait_cb (GObject      *object,
+                                   GAsyncResult *result,
+                                   gpointer      user_data)
+{
+  GSubprocess *process = (GSubprocess *)object;
+  g_autoptr(GTask) task = user_data;
+  GError *error = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (G_IS_SUBPROCESS (process));
+  g_assert (G_IS_TASK (task));
+
+  if (!g_subprocess_wait_finish (process, result, &error))
+    g_task_return_error (task, error);
+  else
+    g_task_return_boolean (task, TRUE);
+
+  IDE_EXIT;
+}
+
+static void
+ide_ctags_builder_build_worker (GTask        *task,
+                                gpointer      source_object,
+                                gpointer      task_data,
+                                GCancellable *cancellable)
+{
+  IdeCtagsBuilder *self = source_object;
+  g_autoptr(GFile) workdir = NULL;
+  g_autoptr(GSubprocessLauncher) launcher = NULL;
+  g_autoptr(GSubprocess) process = NULL;
+  g_autoptr(GPtrArray) argv = NULL;
+  g_autofree gchar *tags_file = NULL;
+  g_autofree gchar *workpath = NULL;
+  g_autofree gchar *options_path = NULL;
+  g_autofree gchar *tagsdir = NULL;
+  IdeContext *context;
+  IdeProject *project;
+  GError *error = NULL;
+  IdeVcs *vcs;
+
+  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));
+
+  /*
+   * 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_file = g_build_filename (g_get_user_cache_dir (),
+                                ide_get_program_name (),
+                                ide_project_get_id (project),
+                                "tags",
+                                NULL);
+  options_path = g_build_filename (g_get_user_config_dir (),
+                                   ide_get_program_name (),
+                                   "ctags.conf",
+                                   NULL);
+  ide_object_release (IDE_OBJECT (self));
+
+  /*
+   * 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.");
+      return;
+    }
+
+  /* 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);
+
+  /* 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);
+
+  argv = g_ptr_array_new_with_free_func (g_free);
+  g_ptr_array_add (argv, g_strdup ("ctags"));
+  g_ptr_array_add (argv, g_strdup ("-f"));
+  g_ptr_array_add (argv, g_strdup ("-"));
+  g_ptr_array_add (argv, g_strdup ("--recurse=yes"));
+  g_ptr_array_add (argv, g_strdup ("--tag-relative=no"));
+  g_ptr_array_add (argv, g_strdup ("--exclude=.git"));
+  g_ptr_array_add (argv, g_strdup ("--exclude=.bzr"));
+  g_ptr_array_add (argv, g_strdup ("--exclude=.svn"));
+  g_ptr_array_add (argv, g_strdup ("--sort=yes"));
+  g_ptr_array_add (argv, g_strdup ("--languages=all"));
+  g_ptr_array_add (argv, g_strdup ("--file-scope=yes"));
+  if (g_file_test (options_path, G_FILE_TEST_IS_REGULAR))
+    g_ptr_array_add (argv, g_strdup_printf ("--options=%s", options_path));
+  g_ptr_array_add (argv, g_strdup ("."));
+  g_ptr_array_add (argv, NULL);
+
+#ifdef IDE_ENABLE_TRACE
+  {
+    g_autofree gchar *msg = g_strjoinv (" ", (gchar **)argv->pdata);
+    IDE_TRACE_MSG ("%s", msg);
+  }
+#endif
+
+  /*
+   * Create our arguments to launch the ctags generation process.
+   */
+  launcher = g_subprocess_launcher_new (G_SUBPROCESS_FLAGS_NONE);
+  g_subprocess_launcher_set_cwd (launcher, workpath);
+  g_subprocess_launcher_set_stdout_file_path (launcher, tags_file);
+  process = g_subprocess_launcher_spawnv (launcher, (const gchar * const *)argv->pdata, &error);
+
+  EGG_COUNTER_INC (parse_count);
+
+  if (process == NULL)
+    {
+      g_task_return_error (task, error);
+      return;
+    }
+
+  g_task_set_task_data (task, g_file_new_for_path (tags_file), g_object_unref);
+
+  g_subprocess_wait_async (process,
+                           cancellable,
+                           ide_ctags_builder_process_wait_cb,
+                           g_object_ref (task));
+}
+
+static void
+ide_ctags_builder_do_build (IdeCtagsBuilder *self)
+{
+  g_autoptr(GTask) task = NULL;
+
+  g_assert (IDE_IS_CTAGS_BUILDER (self));
+
+  /* Make sure we aren't already in shutdown. */
+  if (!ide_object_hold (IDE_OBJECT (self)))
+    return;
+
+  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);
+}
+
+static gboolean
+ide_ctags_builder_build_timeout (gpointer data)
+{
+  IdeCtagsBuilder *self = data;
+
+  g_assert (IDE_IS_CTAGS_BUILDER (self));
+
+  self->build_timeout = 0;
+
+  if (self->is_building == FALSE)
+    {
+      self->is_building = TRUE;
+      ide_ctags_builder_do_build (self);
+    }
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+ide_ctags_builder__buffer_saved_cb (IdeCtagsBuilder  *self,
+                                    IdeBuffer        *buffer,
+                                    IdeBufferManager *buffer_manager)
+{
+  g_assert (IDE_IS_CTAGS_BUILDER (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+
+  if (self->build_timeout != 0)
+    {
+      g_source_remove (self->build_timeout);
+      self->build_timeout = 0;
+    }
+
+  /*
+   * TODO: We will need to make ctags code insight check a few keys,
+   *       such as symbol resolving, autocompletion, highlight, etc.
+   */
+  if (!g_settings_get_boolean (self->settings, "ctags-autocompletion"))
+    return;
+
+  self->build_timeout = g_timeout_add_seconds (BUILD_CTAGS_DELAY_SECONDS,
+                                               ide_ctags_builder_build_timeout,
+                                               self);
+}
+
+static void
+ide_ctags_builder_constructed (GObject *object)
+{
+  IdeCtagsBuilder *self = (IdeCtagsBuilder *)object;
+  IdeBufferManager *buffer_manager;
+  IdeContext *context;
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  buffer_manager = ide_context_get_buffer_manager (context);
+
+  g_signal_connect_object (buffer_manager,
+                           "buffer-saved",
+                           G_CALLBACK (ide_ctags_builder__buffer_saved_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  G_OBJECT_CLASS (ide_ctags_builder_parent_class)->constructed (object);
+}
+
+static void
+ide_ctags_builder_finalize (GObject *object)
+{
+  IdeCtagsBuilder *self = (IdeCtagsBuilder *)object;
+
+  if (self->build_timeout)
+    {
+      g_source_remove (self->build_timeout);
+      self->build_timeout = 0;
+    }
+
+  g_clear_object (&self->settings);
+
+  G_OBJECT_CLASS (ide_ctags_builder_parent_class)->finalize (object);
+
+  EGG_COUNTER_DEC (instances);
+}
+
+static void
+ide_ctags_builder_class_init (IdeCtagsBuilderClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = ide_ctags_builder_constructed;
+  object_class->finalize = ide_ctags_builder_finalize;
+
+  gSignals [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);
+}
+
+static void
+ide_ctags_builder_init (IdeCtagsBuilder *self)
+{
+  EGG_COUNTER_INC (instances);
+
+  self->settings = g_settings_new ("org.gnome.builder.code-insight");
+}
diff --git a/libide/ctags/ide-ctags-builder.h b/libide/ctags/ide-ctags-builder.h
new file mode 100644
index 0000000..fe25e2a
--- /dev/null
+++ b/libide/ctags/ide-ctags-builder.h
@@ -0,0 +1,34 @@
+/* ide-ctags-builder.h
+ *
+ * Copyright (C) 2015 Christian Hergert <christian hergert me>
+ *
+ * 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/>.
+ */
+
+#ifndef IDE_CTAGS_BUILDER_H
+#define IDE_CTAGS_BUILDER_H
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CTAGS_BUILDER (ide_ctags_builder_get_type())
+
+G_DECLARE_FINAL_TYPE (IdeCtagsBuilder, ide_ctags_builder, IDE, CTAGS_BUILDER, IdeObject)
+
+IdeCtagsBuilder *ide_ctags_builder_new (void);
+
+G_END_DECLS
+
+#endif /* IDE_CTAGS_BUILDER_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]