[gnome-builder] vala: add beginnings of vala support for Builder



commit b259bda547e2b8a99d1dbf7199e73a5fe1ec0cf2
Author: Christian Hergert <christian hergert me>
Date:   Fri Sep 25 22:34:03 2015 -0700

    vala: add beginnings of vala support for Builder
    
    There is plenty to do in terms of optimizing this, and code cleanup, so
    feel free to join in and help if you can. I'm by no means a Vala expert
    and I ran into a lot of problems along the way.
    
    What this includes:
    
     * A simple auto-indenter: There is a lot do do here to here to catch
       up with what the C indenter can do. We probably need to abstract
       everything into a two-pass system. One pass to get the context,
       a second pass to determine how to indent that context.
     * In process diagnostics like we do for clang. This isn't really a
       feature, it's more of an anti-feature. Longer term, we need to
       figure out a clean way to rip this all out of process (G-C-A, etc).
     * Auto-completion using libvala for type information. Mostly taken
       from Anjuta, but there is a lot of performance/cleanup work that
       can be done to make this feel good.
    
    TODO
    
     * Code cleanup
     * Extract packages from the project build system
     * Make autocompletion list creation not so stupid with regards to lists
     * Symbol tree support
     * goto definition, etc
     * A highlighter based on the AST

 configure.ac                                       |    2 +
 data/snippets/vala.snippets                        |    6 +
 libide/ide-buffer.c                                |    3 +-
 libide/ide-completion-provider.h                   |    5 +
 libide/ide-diagnostic-provider.c                   |    2 +-
 libide/ide-diagnostic-provider.h                   |    2 +
 libide/ide-diagnostic.c                            |   59 ++++-
 libide/ide-diagnostic.h                            |    9 +
 libide/ide-diagnostician.c                         |    4 +-
 libide/ide-diagnostics.c                           |    6 +-
 libide/ide-diagnostics.h                           |    1 +
 libide/ide-file.c                                  |   22 ++
 libide/ide-file.h                                  |    2 +
 libide/ide-internal.h                              |   12 -
 libide/ide-service.h                               |    2 +
 libide/ide-source-range.c                          |    4 +-
 libide/ide-source-range.h                          |   10 +-
 libide/ide-thread-pool.c                           |   87 ++++++-
 libide/ide-thread-pool.h                           |   18 +-
 libide/ide-unsaved-files.c                         |   23 ++
 libide/ide-unsaved-files.h                         |    2 +
 plugins/Makefile.am                                |    1 +
 plugins/clang/ide-clang-translation-unit.c         |   11 +-
 .../gnome-code-assistance.plugin                   |    2 +-
 .../ide-gca-diagnostic-provider.c                  |    8 +-
 plugins/vala-pack/Makefile.am                      |   55 ++++
 plugins/vala-pack/configure.ac                     |   20 ++
 plugins/vala-pack/ide-vala-completion-item.vala    |  107 +++++++
 .../vala-pack/ide-vala-completion-provider.vala    |  237 ++++++++++++++++
 plugins/vala-pack/ide-vala-completion.vala         |  189 +++++++++++++
 .../vala-pack/ide-vala-diagnostic-provider.vala    |   38 +++
 plugins/vala-pack/ide-vala-diagnostics.vala        |   61 ++++
 plugins/vala-pack/ide-vala-indenter.vala           |  184 ++++++++++++
 plugins/vala-pack/ide-vala-index.vala              |  291 ++++++++++++++++++++
 plugins/vala-pack/ide-vala-locator.vala            |  158 +++++++++++
 plugins/vala-pack/ide-vala-service.vala            |   87 ++++++
 plugins/vala-pack/ide-vala-source-file.vala        |  157 +++++++++++
 plugins/vala-pack/vala-pack-plugin.c               |   30 ++
 plugins/vala-pack/vala-pack.plugin                 |   12 +
 src/util/gb-string.c                               |   33 ++-
 src/util/gb-string.h                               |    2 +
 41 files changed, 1904 insertions(+), 60 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 4ed875f..9a5a229 100644
--- a/configure.ac
+++ b/configure.ac
@@ -249,6 +249,7 @@ m4_include([plugins/python-pack/configure.ac])
 m4_include([plugins/symbol-tree/configure.ac])
 m4_include([plugins/sysmon/configure.ac])
 m4_include([plugins/terminal/configure.ac])
+m4_include([plugins/vala-pack/configure.ac])
 m4_include([plugins/xml-pack/configure.ac])
 
 
@@ -493,6 +494,7 @@ echo "  Python Language Pack ................. : ${enable_python_pack_plugin}"
 echo "  System Monitor ....................... : ${enable_sysmon_plugin}"
 echo "  Symbol Tree .......................... : ${enable_symbol_tree_plugin}"
 echo "  Terminal ............................. : ${enable_terminal_plugin}"
+echo "  Vala Language Pack ................... : ${enable_vala_pack_plugin}"
 echo "  XML Language Pack .................... : ${enable_xml_pack_plugin}"
 echo ""
 echo "Experimental Plugins"
diff --git a/data/snippets/vala.snippets b/data/snippets/vala.snippets
index 2701556..6966b17 100644
--- a/data/snippets/vala.snippets
+++ b/data/snippets/vala.snippets
@@ -6,3 +6,9 @@ snippet class
                        $0
                }
        }
+snippet comment
+- scope vala
+- desc Insert a multiline comment.
+       /*
+        * $0
+        */
diff --git a/libide/ide-buffer.c b/libide/ide-buffer.c
index e3ddc35..c3ab1ba 100644
--- a/libide/ide-buffer.c
+++ b/libide/ide-buffer.c
@@ -404,7 +404,8 @@ ide_buffer_update_diagnostics (IdeBuffer      *self,
       IdeDiagnostic *diagnostic;
 
       diagnostic = ide_diagnostics_index (diagnostics, i);
-      ide_buffer_update_diagnostic (self, diagnostic);
+      if (diagnostic != NULL)
+        ide_buffer_update_diagnostic (self, diagnostic);
     }
 }
 
diff --git a/libide/ide-completion-provider.h b/libide/ide-completion-provider.h
index 485c527..4d99397 100644
--- a/libide/ide-completion-provider.h
+++ b/libide/ide-completion-provider.h
@@ -21,6 +21,8 @@
 
 #include <gtksourceview/gtksource.h>
 
+#include "ide-types.h"
+
 G_BEGIN_DECLS
 
 #define IDE_TYPE_COMPLETION_PROVIDER             (ide_completion_provider_get_type())
@@ -34,6 +36,9 @@ typedef struct _IdeCompletionProviderInterface IdeCompletionProviderInterface;
 struct _IdeCompletionProviderInterface
 {
   GtkSourceCompletionProviderIface parent_interface;
+
+  void (*set_context) (IdeCompletionProvider *self,
+                       IdeContext            *context);
 };
 
 GType ide_completion_provider_get_type (void);
diff --git a/libide/ide-diagnostic-provider.c b/libide/ide-diagnostic-provider.c
index 594140e..6434d60 100644
--- a/libide/ide-diagnostic-provider.c
+++ b/libide/ide-diagnostic-provider.c
@@ -54,7 +54,7 @@ ide_diagnostic_provider_diagnose_async  (IdeDiagnosticProvider *self,
  *
  * Completes an asynchronous call to ide_diagnostic_provider_diagnose_async().
  *
- * Returns: (transfer full): #IdeDiagnostics or %NULL and @error is set.
+ * Returns: (transfer full) (nullable): #IdeDiagnostics or %NULL and @error is set.
  */
 IdeDiagnostics *
 ide_diagnostic_provider_diagnose_finish (IdeDiagnosticProvider  *self,
diff --git a/libide/ide-diagnostic-provider.h b/libide/ide-diagnostic-provider.h
index 20dae99..517d21c 100644
--- a/libide/ide-diagnostic-provider.h
+++ b/libide/ide-diagnostic-provider.h
@@ -31,6 +31,8 @@ struct _IdeDiagnosticProviderInterface
 {
   GTypeInterface parent_interface;
 
+  void            (*set_context)     (IdeDiagnosticProvider  *self,
+                                      IdeContext             *context);
   void            (*diagnose_async)  (IdeDiagnosticProvider  *self,
                                       IdeFile                *file,
                                       GCancellable           *cancellable,
diff --git a/libide/ide-diagnostic.c b/libide/ide-diagnostic.c
index 65f69fb..ee93332 100644
--- a/libide/ide-diagnostic.c
+++ b/libide/ide-diagnostic.c
@@ -173,10 +173,23 @@ ide_diagnostic_get_location (IdeDiagnostic *self)
   return NULL;
 }
 
+/**
+ * ide_diagnostic_new:
+ * @severity: the severity of the diagnostic
+ * @text: the diagnostic message text
+ * @location: the location of the diagnostic
+ *
+ * Creates a new diagnostic.
+ *
+ * If you want to set a range for the diagnostic, see
+ * ide_diagnostic_add_range() or ide_diagnostic_take_range().
+ *
+ * Returns: (transfer full): An #IdeDiagnostic.
+ */
 IdeDiagnostic *
-_ide_diagnostic_new (IdeDiagnosticSeverity  severity,
-                     const gchar           *text,
-                     IdeSourceLocation     *location)
+ide_diagnostic_new (IdeDiagnosticSeverity  severity,
+                    const gchar           *text,
+                    IdeSourceLocation     *location)
 {
   IdeDiagnostic *ret;
 
@@ -191,9 +204,17 @@ _ide_diagnostic_new (IdeDiagnosticSeverity  severity,
   return ret;
 }
 
+/**
+ * ide_diagnostic_take_fixit:
+ * @self: A #IdeDiagnostic.
+ * @fixit: (transfer full): An #IdeFixit.
+ *
+ * Adds the suggested fixit to the diagnostic while transfering ownership
+ * of @fixit to @self.
+ */
 void
-_ide_diagnostic_take_fixit (IdeDiagnostic *self,
-                            IdeFixit      *fixit)
+ide_diagnostic_take_fixit (IdeDiagnostic *self,
+                           IdeFixit      *fixit)
 {
   g_return_if_fail (self);
   g_return_if_fail (fixit);
@@ -204,9 +225,19 @@ _ide_diagnostic_take_fixit (IdeDiagnostic *self,
   g_ptr_array_add (self->fixits, fixit);
 }
 
+/**
+ * ide_diagnostic_take_range:
+ * @self: A #IdeDiagnostic.
+ * @range: (transfer full): An #IdeSourceRange.
+ *
+ * Steals the ownership of @range and adds to the diagnostic.
+ *
+ * This saves multiple atomic references of @range which could be expensive
+ * if you are doing lots of diagnostics.
+ */
 void
-_ide_diagnostic_take_range (IdeDiagnostic  *self,
-                            IdeSourceRange *range)
+ide_diagnostic_take_range (IdeDiagnostic  *self,
+                           IdeSourceRange *range)
 {
   g_return_if_fail (self);
   g_return_if_fail (range);
@@ -217,14 +248,22 @@ _ide_diagnostic_take_range (IdeDiagnostic  *self,
   g_ptr_array_add (self->ranges, range);
 }
 
+/**
+ * ide_diagnostic_add_range:
+ * @self: An #IdeDiagnostic.
+ * @range: An #IdeSourceRange.
+ *
+ * Adds the range to the diagnostic. This allows diagnostic tools to highlight
+ * the errored text appropriately.
+ */
 void
-_ide_diagnostic_add_range (IdeDiagnostic  *self,
-                           IdeSourceRange *range)
+ide_diagnostic_add_range (IdeDiagnostic  *self,
+                          IdeSourceRange *range)
 {
   g_return_if_fail (self);
   g_return_if_fail (range);
 
-  _ide_diagnostic_take_range (self, ide_source_range_ref (range));
+  ide_diagnostic_take_range (self, ide_source_range_ref (range));
 }
 
 const gchar *
diff --git a/libide/ide-diagnostic.h b/libide/ide-diagnostic.h
index 4e93f85..7f5b5e7 100644
--- a/libide/ide-diagnostic.h
+++ b/libide/ide-diagnostic.h
@@ -49,6 +49,15 @@ gchar                 *ide_diagnostic_get_text_for_display (IdeDiagnostic *self)
 GType                  ide_diagnostic_get_type             (void);
 IdeDiagnostic         *ide_diagnostic_ref                  (IdeDiagnostic *self);
 void                   ide_diagnostic_unref                (IdeDiagnostic *self);
+IdeDiagnostic         *ide_diagnostic_new                  (IdeDiagnosticSeverity  severity,
+                                                            const gchar           *text,
+                                                            IdeSourceLocation     *location);
+void                   ide_diagnostic_add_range            (IdeDiagnostic         *self,
+                                                            IdeSourceRange        *range);
+void                   ide_diagnostic_take_fixit           (IdeDiagnostic         *self,
+                                                            IdeFixit              *fixit);
+void                   ide_diagnostic_take_range           (IdeDiagnostic         *self,
+                                                            IdeSourceRange        *range);
 
 const gchar           *ide_diagnostic_severity_to_string   (IdeDiagnosticSeverity severity);
 
diff --git a/libide/ide-diagnostician.c b/libide/ide-diagnostician.c
index efddc67..94a4f17 100644
--- a/libide/ide-diagnostician.c
+++ b/libide/ide-diagnostician.c
@@ -154,7 +154,7 @@ ide_diagnostician_diagnose_async (IdeDiagnostician    *self,
   if (count == 0)
     {
       g_task_return_pointer (task,
-                             _ide_diagnostics_new (NULL),
+                             ide_diagnostics_new (NULL),
                              (GDestroyNotify)ide_diagnostics_unref);
       return;
     }
@@ -165,7 +165,7 @@ ide_diagnostician_diagnose_async (IdeDiagnostician    *self,
   state->task = task;
   state->active = count;
   state->total = count;
-  state->diagnostics = _ide_diagnostics_new (NULL);
+  state->diagnostics = ide_diagnostics_new (NULL);
 
   g_task_set_task_data (task, state, diagnose_state_free);
 
diff --git a/libide/ide-diagnostics.c b/libide/ide-diagnostics.c
index b91468f..204d08b 100644
--- a/libide/ide-diagnostics.c
+++ b/libide/ide-diagnostics.c
@@ -34,8 +34,8 @@ struct _IdeDiagnostics
 };
 
 /**
- * _ide_diagnostics_new:
- * @ar: (transfer full) (nullable): an array of #IdeDiagnostic.
+ * ide_diagnostics_new:
+ * @ar: (transfer full) (element-type Ide.Diagnostic) (allow-none): an array of #IdeDiagnostic.
  *
  * Creates a new #IdeDiagnostics container structure for @ar.
  * Ownership of @ar is transfered to the resulting structure.
@@ -43,7 +43,7 @@ struct _IdeDiagnostics
  * Returns: (transfer full): A newly allocated #IdeDiagnostics.
  */
 IdeDiagnostics *
-_ide_diagnostics_new (GPtrArray *ar)
+ide_diagnostics_new (GPtrArray *ar)
 {
   IdeDiagnostics *ret;
 
diff --git a/libide/ide-diagnostics.h b/libide/ide-diagnostics.h
index 852f5f8..2429f09 100644
--- a/libide/ide-diagnostics.h
+++ b/libide/ide-diagnostics.h
@@ -33,6 +33,7 @@ IdeDiagnostic  *ide_diagnostics_index    (IdeDiagnostics *self,
                                           gsize           index);
 void            ide_diagnostics_merge    (IdeDiagnostics *self,
                                           IdeDiagnostics *other);
+IdeDiagnostics *ide_diagnostics_new      (GPtrArray      *ar);
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeDiagnostics, ide_diagnostics_unref)
 
diff --git a/libide/ide-file.c b/libide/ide-file.c
index 5a869cf..134b349 100644
--- a/libide/ide-file.c
+++ b/libide/ide-file.c
@@ -648,3 +648,25 @@ ide_file_find_other_finish (IdeFile       *self,
 
   return g_task_propagate_pointer (task, error);
 }
+
+/**
+ * ide_file_new:
+ * @context: (allow-none): An #IdeContext or %NULL.
+ * @file: a #GFile.
+ *
+ * Creates a new file.
+ *
+ * Returns: (transfer full): An #IdeFile.
+ */
+IdeFile *
+ide_file_new (IdeContext *context,
+              GFile      *file)
+{
+  g_return_val_if_fail (!context || IDE_IS_CONTEXT (context), NULL);
+  g_return_val_if_fail (G_IS_FILE (file), NULL);
+
+  return g_object_new (IDE_TYPE_FILE,
+                       "context", context,
+                       "file", file,
+                       NULL);
+}
diff --git a/libide/ide-file.h b/libide/ide-file.h
index 9da2f66..7bc100a 100644
--- a/libide/ide-file.h
+++ b/libide/ide-file.h
@@ -29,6 +29,8 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (IdeFile, ide_file, IDE, FILE, IdeObject)
 
+IdeFile           *ide_file_new                  (IdeContext           *context,
+                                                  GFile                *file);
 gboolean           ide_file_get_is_temporary     (IdeFile              *self);
 guint              ide_file_get_temporary_id     (IdeFile              *self);
 GtkSourceLanguage *ide_file_get_language         (IdeFile              *self);
diff --git a/libide/ide-internal.h b/libide/ide-internal.h
index dbaedcb..9023e18 100644
--- a/libide/ide-internal.h
+++ b/libide/ide-internal.h
@@ -67,16 +67,6 @@ void                _ide_buffer_manager_reclaim             (IdeBufferManager
 void                _ide_build_system_set_project_file      (IdeBuildSystem        *self,
                                                              GFile                 *project_file);
 gboolean            _ide_context_is_restoring               (IdeContext            *self);
-void                _ide_diagnostic_add_range               (IdeDiagnostic         *self,
-                                                             IdeSourceRange        *range);
-IdeDiagnostic      *_ide_diagnostic_new                     (IdeDiagnosticSeverity  severity,
-                                                             const gchar           *text,
-                                                             IdeSourceLocation     *location);
-void                _ide_diagnostic_take_fixit              (IdeDiagnostic         *diagnostic,
-                                                             IdeFixit              *fixit);
-void                _ide_diagnostic_take_range              (IdeDiagnostic         *self,
-                                                             IdeSourceRange        *range);
-IdeDiagnostics     *_ide_diagnostics_new                    (GPtrArray             *ar);
 const gchar        *_ide_file_get_content_type              (IdeFile               *self);
 GtkSourceFile      *_ide_file_set_content_type              (IdeFile               *self,
                                                              const gchar           *content_type);
@@ -95,8 +85,6 @@ IdeSettings        *_ide_settings_new                       (IdeContext
                                                              const gchar           *schema_id,
                                                              const gchar           *relative_path,
                                                              gboolean               ignore_project_settings);
-IdeSourceRange     *_ide_source_range_new                   (IdeSourceLocation     *begin,
-                                                             IdeSourceLocation     *end);
 GtkTextMark        *_ide_source_view_get_scroll_mark        (IdeSourceView         *self);
 gboolean            _ide_source_view_mode_do_event          (IdeSourceViewMode     *mode,
                                                              GdkEventKey           *event,
diff --git a/libide/ide-service.h b/libide/ide-service.h
index 1b759a1..2fd7448 100644
--- a/libide/ide-service.h
+++ b/libide/ide-service.h
@@ -35,6 +35,8 @@ struct _IdeServiceInterface
   const gchar *(*get_name)       (IdeService *service);
   void         (*start)          (IdeService *service);
   void         (*stop)           (IdeService *service);
+  void         (*set_context)    (IdeService *service,
+                                  IdeContext *context);
 };
 
 const gchar *ide_service_get_name (IdeService *self);
diff --git a/libide/ide-source-range.c b/libide/ide-source-range.c
index e185e2c..bc955aa 100644
--- a/libide/ide-source-range.c
+++ b/libide/ide-source-range.c
@@ -36,8 +36,8 @@ struct _IdeSourceRange
 };
 
 IdeSourceRange *
-_ide_source_range_new (IdeSourceLocation *begin,
-                       IdeSourceLocation *end)
+ide_source_range_new (IdeSourceLocation *begin,
+                      IdeSourceLocation *end)
 {
   IdeSourceRange *ret;
 
diff --git a/libide/ide-source-range.h b/libide/ide-source-range.h
index 7e4cf81..8c32579 100644
--- a/libide/ide-source-range.h
+++ b/libide/ide-source-range.h
@@ -26,10 +26,12 @@ G_BEGIN_DECLS
 #define IDE_TYPE_SOURCE_RANGE (ide_source_range_get_type())
 
 GType              ide_source_range_get_type  (void);
-IdeSourceRange    *ide_source_range_ref       (IdeSourceRange *self);
-void               ide_source_range_unref     (IdeSourceRange *self);
-IdeSourceLocation *ide_source_range_get_begin (IdeSourceRange *self);
-IdeSourceLocation *ide_source_range_get_end   (IdeSourceRange *self);
+IdeSourceRange    *ide_source_range_ref       (IdeSourceRange    *self);
+void               ide_source_range_unref     (IdeSourceRange    *self);
+IdeSourceLocation *ide_source_range_get_begin (IdeSourceRange    *self);
+IdeSourceLocation *ide_source_range_get_end   (IdeSourceRange    *self);
+IdeSourceRange    *ide_source_range_new       (IdeSourceLocation *begin,
+                                               IdeSourceLocation *end);
 
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (IdeSourceRange, ide_source_range_unref)
 
diff --git a/libide/ide-thread-pool.c b/libide/ide-thread-pool.c
index 3d75211..173c248 100644
--- a/libide/ide-thread-pool.c
+++ b/libide/ide-thread-pool.c
@@ -28,8 +28,17 @@
 
 typedef struct
 {
-  GTask           *task;
-  GTaskThreadFunc  func;
+  int type;
+  union {
+    struct {
+      GTask           *task;
+      GTaskThreadFunc  func;
+    } task;
+    struct {
+      IdeThreadFunc callback;
+      gpointer      data;
+    } func;
+  };
 } WorkItem;
 
 EGG_DEFINE_COUNTER (TotalTasks, "ThreadPool", "Total Tasks", "Total number of tasks processed.")
@@ -37,6 +46,11 @@ EGG_DEFINE_COUNTER (QueuedTasks, "ThreadPool", "Queued Tasks", "Current number o
 
 static GThreadPool *gThreadPools [IDE_THREAD_POOL_LAST];
 
+enum {
+  TYPE_TASK,
+  TYPE_FUNC,
+};
+
 static inline GThreadPool *
 ide_thread_pool_get_pool (IdeThreadPoolKind kind)
 {
@@ -75,8 +89,9 @@ ide_thread_pool_push_task (IdeThreadPoolKind  kind,
       WorkItem *work_item;
 
       work_item = g_slice_new0 (WorkItem);
-      work_item->task = g_object_ref (task);
-      work_item->func = func;
+      work_item->type = TYPE_TASK;
+      work_item->task.task = g_object_ref (task);
+      work_item->task.func = func;
 
       EGG_COUNTER_INC (QueuedTasks);
 
@@ -90,6 +105,52 @@ ide_thread_pool_push_task (IdeThreadPoolKind  kind,
   IDE_EXIT;
 }
 
+/**
+ * ide_thread_pool_push:
+ * @kind: the threadpool kind to use.
+ * @func: (scope async) (closure func_data): A function to call in the worker thread.
+ * @func_data: (transfer full): user data for @func.
+ *
+ * Runs the callback on the thread pool thread.
+ */
+void
+ide_thread_pool_push (IdeThreadPoolKind kind,
+                      IdeThreadFunc     func,
+                      gpointer          func_data)
+{
+  GThreadPool *pool;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (kind >= 0);
+  g_return_if_fail (kind < IDE_THREAD_POOL_LAST);
+  g_return_if_fail (func != NULL);
+
+  EGG_COUNTER_INC (TotalTasks);
+
+  pool = ide_thread_pool_get_pool (kind);
+
+  if (pool != NULL)
+    {
+      WorkItem *work_item;
+
+      work_item = g_slice_new0 (WorkItem);
+      work_item->type = TYPE_FUNC;
+      work_item->func.callback = func;
+      work_item->func.data = func_data;
+
+      EGG_COUNTER_INC (QueuedTasks);
+
+      g_thread_pool_push (pool, work_item, NULL);
+    }
+  else
+    {
+      g_critical ("No such thread pool %02x", kind);
+    }
+
+  IDE_EXIT;
+}
+
 static void
 ide_thread_pool_worker (gpointer data,
                         gpointer user_data)
@@ -103,13 +164,21 @@ ide_thread_pool_worker (gpointer data,
 
   EGG_COUNTER_DEC (QueuedTasks);
 
-  source_object = g_task_get_source_object (work_item->task);
-  task_data = g_task_get_task_data (work_item->task);
-  cancellable = g_task_get_cancellable (work_item->task);
+  if (work_item->type == TYPE_TASK)
+    {
+      source_object = g_task_get_source_object (work_item->task.task);
+      task_data = g_task_get_task_data (work_item->task.task);
+      cancellable = g_task_get_cancellable (work_item->task.task);
+
+      work_item->task.func (work_item->task.task, source_object, task_data, cancellable);
 
-  work_item->func (work_item->task, source_object, task_data, cancellable);
+      g_object_unref (work_item->task.task);
+    }
+  else if (work_item->type == TYPE_FUNC)
+    {
+      work_item->func.callback (work_item->func.data);
+    }
 
-  g_object_unref (work_item->task);
   g_slice_free (WorkItem, work_item);
 }
 
diff --git a/libide/ide-thread-pool.h b/libide/ide-thread-pool.h
index e34ef52..c11b6aa 100644
--- a/libide/ide-thread-pool.h
+++ b/libide/ide-thread-pool.h
@@ -23,6 +23,8 @@
 
 G_BEGIN_DECLS
 
+typedef struct _IdeThreadPool IdeThreadPool;
+
 typedef enum
 {
   IDE_THREAD_POOL_COMPILER,
@@ -30,9 +32,19 @@ typedef enum
   IDE_THREAD_POOL_LAST
 } IdeThreadPoolKind;
 
-void ide_thread_pool_push_task (IdeThreadPoolKind  kind,
-                                GTask             *task,
-                                GTaskThreadFunc    func);
+/**
+ * IdeThreadFunc:
+ * @user_data: (closure) (transfer full): The closure for the callback.
+ *
+ */
+typedef void (*IdeThreadFunc) (gpointer user_data);
+
+void     ide_thread_pool_push      (IdeThreadPoolKind     kind,
+                                    IdeThreadFunc         func,
+                                    gpointer              func_data);
+void     ide_thread_pool_push_task (IdeThreadPoolKind     kind,
+                                    GTask                *task,
+                                    GTaskThreadFunc       func);
 
 G_END_DECLS
 
diff --git a/libide/ide-unsaved-files.c b/libide/ide-unsaved-files.c
index c2af953..b1b5ecd 100644
--- a/libide/ide-unsaved-files.c
+++ b/libide/ide-unsaved-files.c
@@ -601,6 +601,29 @@ ide_unsaved_files_to_array (IdeUnsavedFiles *self)
   return ar;
 }
 
+gboolean
+ide_unsaved_files_contains (IdeUnsavedFiles *self,
+                            GFile           *file)
+{
+  IdeUnsavedFilesPrivate *priv = ide_unsaved_files_get_instance_private (self);
+  guint i;
+
+  g_return_val_if_fail (IDE_IS_UNSAVED_FILES (self), FALSE);
+  g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+  for (i = 0; i < priv->unsaved_files->len; i++)
+    {
+      UnsavedFile *uf;
+
+      uf = g_ptr_array_index (priv->unsaved_files, i);
+
+      if (g_file_equal (uf->file, file))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
 /**
  * ide_unsaved_files_get_unsaved_file:
  *
diff --git a/libide/ide-unsaved-files.h b/libide/ide-unsaved-files.h
index 595dd8a..c3336d8 100644
--- a/libide/ide-unsaved-files.h
+++ b/libide/ide-unsaved-files.h
@@ -56,6 +56,8 @@ gint64          ide_unsaved_files_get_sequence      (IdeUnsavedFiles      *files
 IdeUnsavedFile *ide_unsaved_files_get_unsaved_file  (IdeUnsavedFiles      *self,
                                                      GFile                *file);
 void            ide_unsaved_files_clear             (IdeUnsavedFiles      *self);
+gboolean        ide_unsaved_files_contains          (IdeUnsavedFiles      *self,
+                                                     GFile                *file);
 
 G_END_DECLS
 
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 46e85a7..1caa43c 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -18,6 +18,7 @@ SUBDIRS = \
        symbol-tree \
        sysmon \
        terminal \
+       vala-pack \
        xml-pack \
        $(NULL)
 
diff --git a/plugins/clang/ide-clang-translation-unit.c b/plugins/clang/ide-clang-translation-unit.c
index 8c8b2d2..02d1ce7 100644
--- a/plugins/clang/ide-clang-translation-unit.c
+++ b/plugins/clang/ide-clang-translation-unit.c
@@ -36,6 +36,7 @@
 #include "ide-project.h"
 #include "ide-ref-ptr.h"
 #include "ide-source-location.h"
+#include "ide-source-range.h"
 #include "ide-symbol.h"
 #include "ide-thread-pool.h"
 #include "ide-unsaved-file.h"
@@ -280,7 +281,7 @@ create_range (IdeClangTranslationUnit *self,
   end = create_location (self, project, workpath, cxend);
 
   if ((begin != NULL) && (end != NULL))
-    range = _ide_source_range_new (begin, end);
+    range = ide_source_range_new (begin, end);
 
   return range;
 }
@@ -352,7 +353,7 @@ create_diagnostic (IdeClangTranslationUnit *self,
 
   loc = create_location (self, project, workpath, cxloc);
 
-  diag = _ide_diagnostic_new (severity, spelling, loc);
+  diag = ide_diagnostic_new (severity, spelling, loc);
 
   num_ranges = clang_getDiagnosticNumRanges (cxdiag);
 
@@ -364,7 +365,7 @@ create_diagnostic (IdeClangTranslationUnit *self,
       cxrange = clang_getDiagnosticRange (cxdiag, i);
       range = create_range (self, project, workpath, cxrange);
       if (range != NULL)
-        _ide_diagnostic_take_range (diag, range);
+        ide_diagnostic_take_range (diag, range);
     }
 
   return diag;
@@ -441,7 +442,7 @@ ide_clang_translation_unit_get_diagnostics_for_file (IdeClangTranslationUnit *se
                   clang_disposeString (cxstr);
 
                   if (fixit != NULL)
-                    _ide_diagnostic_take_fixit (diag, fixit);
+                    ide_diagnostic_take_fixit (diag, fixit);
                 }
 
               g_ptr_array_add (diags, diag);
@@ -452,7 +453,7 @@ ide_clang_translation_unit_get_diagnostics_for_file (IdeClangTranslationUnit *se
 
       ide_project_reader_unlock (project);
 
-      g_hash_table_insert (self->diagnostics, g_object_ref (file), _ide_diagnostics_new (diags));
+      g_hash_table_insert (self->diagnostics, g_object_ref (file), ide_diagnostics_new (diags));
     }
 
   return g_hash_table_lookup (self->diagnostics, file);
diff --git a/plugins/gnome-code-assistance/gnome-code-assistance.plugin 
b/plugins/gnome-code-assistance/gnome-code-assistance.plugin
index 3f67dee..5d82967 100644
--- a/plugins/gnome-code-assistance/gnome-code-assistance.plugin
+++ b/plugins/gnome-code-assistance/gnome-code-assistance.plugin
@@ -6,4 +6,4 @@ Authors=Christian Hergert <christian hergert me>
 Copyright=Copyright © 2015 Christian Hergert
 Builtin=true
 Hidden=true
-X-Diagnostic-Provider-Languages=css,html,js,json,python,python3,ruby,sh,vala,xml
+X-Diagnostic-Provider-Languages=css,html,js,json,python,python3,ruby,sh,xml
diff --git a/plugins/gnome-code-assistance/ide-gca-diagnostic-provider.c 
b/plugins/gnome-code-assistance/ide-gca-diagnostic-provider.c
index 0cb02ca..fbf1461 100644
--- a/plugins/gnome-code-assistance/ide-gca-diagnostic-provider.c
+++ b/plugins/gnome-code-assistance/ide-gca-diagnostic-provider.c
@@ -144,7 +144,7 @@ variant_to_diagnostics (DiagnoseState *state,
 #endif
         }
 
-      diag = _ide_diagnostic_new (severity, d, NULL);
+      diag = ide_diagnostic_new (severity, d, NULL);
 
       while (g_variant_iter_next (c, "(x(xx)(xx))", &x1, &x2, &x3, &x4, &x5))
         {
@@ -164,8 +164,8 @@ variant_to_diagnostics (DiagnoseState *state,
           begin = ide_source_location_new (file, x2 - 1, x3 - 1, 0);
           end = ide_source_location_new (file, x4 - 1, x5 - 1, 0);
 
-          range = _ide_source_range_new (begin, end);
-          _ide_diagnostic_take_range (diag, range);
+          range = ide_source_range_new (begin, end);
+          ide_diagnostic_take_range (diag, range);
 
           ide_source_location_unref (begin);
           ide_source_location_unref (end);
@@ -174,7 +174,7 @@ variant_to_diagnostics (DiagnoseState *state,
       g_ptr_array_add (ar, diag);
     }
 
-  return _ide_diagnostics_new (ar);
+  return ide_diagnostics_new (ar);
 }
 
 static void
diff --git a/plugins/vala-pack/Makefile.am b/plugins/vala-pack/Makefile.am
new file mode 100644
index 0000000..67c3039
--- /dev/null
+++ b/plugins/vala-pack/Makefile.am
@@ -0,0 +1,55 @@
+if ENABLE_VALA_PACK_PLUGIN
+
+EXTRA_DIST = $(plugin_DATA)
+
+plugindir = $(libdir)/gnome-builder/plugins
+plugin_LTLIBRARIES = libvala-pack-plugin.la
+dist_plugin_DATA = vala-pack.plugin
+
+libvala_pack_plugin_la_SOURCES = \
+       ide-vala-service.vala \
+       ide-vala-completion.vala \
+       ide-vala-completion-item.vala \
+       ide-vala-completion-provider.vala \
+       ide-vala-diagnostics.vala \
+       ide-vala-diagnostic-provider.vala \
+       ide-vala-indenter.vala \
+       ide-vala-index.vala \
+       ide-vala-locator.vala \
+       ide-vala-source-file.vala \
+       vala-pack-plugin.c \
+       $(NULL)
+
+libvala_pack_plugin_la_VALAFLAGS = \
+       --target-glib=2.44 \
+       --thread \
+       --vapidir $(top_builddir)/libide \
+       --pkg gtksourceview-3.0 \
+       --pkg posix \
+       --pkg libvala-0.30 \
+       --pkg libide-1.0 \
+       -H ide-vala.h \
+       $(NULL)
+
+libvala_pack_plugin_la_CFLAGS = \
+       $(BUILDER_CFLAGS) \
+       $(VALA_CFLAGS) \
+       -I$(top_srcdir)/libide \
+       -I$(top_srcdir)/contrib/egg \
+       -I$(top_srcdir)/src/util \
+       $(NULL)
+
+libvala_pack_plugin_la_LDFLAGS = \
+       $(OPTIMIZE_LDFLAGS) \
+       $(VALA_LIBS) \
+       -avoid-version \
+       -module \
+       $(NULL)
+
+include $(top_srcdir)/plugins/Makefile.plugin
+
+GITIGNOREFILES = ide-vala.h
+
+endif
+
+-include $(top_srcdir)/git.mk
diff --git a/plugins/vala-pack/configure.ac b/plugins/vala-pack/configure.ac
new file mode 100644
index 0000000..273b267
--- /dev/null
+++ b/plugins/vala-pack/configure.ac
@@ -0,0 +1,20 @@
+# --enable-vala-pack-plugin=yes/no
+AC_ARG_ENABLE([enable-vala-pack-plugin],
+              [AS_HELP_STRING([--enable-vala-pack-plugin=@<:@auto/yes/no@:>@],
+                              [Build with support for vala enhancements such as auto completion.])],
+              [],
+              [enable_vala_pack_plugin=auto])
+
+AS_IF([test x$enable_vala_pack_plugin != xno],[
+       PKG_CHECK_MODULES(VALA, [libvala-0.30 >= 0.29.3],[enable_vala_pack_plugin=yes],[
+               AS_IF([test x$enable_vala_pack_plugin = yes],[
+                       AC_MSG_ERROR([Failed to locate vala. Please install the vala-devel package.])
+               ])
+       ])
+])
+
+# for if ENABLE_VALA_PACK_PLUGIN in Makefile.am
+AM_CONDITIONAL(ENABLE_VALA_PACK_PLUGIN, test x$enable_vala_pack_plugin != xno)
+
+# Ensure our makefile is generated by autoconf
+AC_CONFIG_FILES([plugins/vala-pack/Makefile])
diff --git a/plugins/vala-pack/ide-vala-completion-item.vala b/plugins/vala-pack/ide-vala-completion-item.vala
new file mode 100644
index 0000000..8e268d6
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-completion-item.vala
@@ -0,0 +1,107 @@
+/* ide-vala-completion-item.vala
+ *
+ * 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/>.
+ */
+
+using GLib;
+using Gtk;
+using Vala;
+
+namespace Ide
+{
+       public delegate string ValaCompletionMarkupFunc (string name);
+
+       public class ValaCompletionItem: GLib.Object, Gtk.SourceCompletionProposal
+       {
+               static uint hash_seed;
+
+               Vala.Symbol symbol;
+               ValaCompletionMarkupFunc? markup_func;
+
+               static construct {
+                       hash_seed = "IdeValaCompletionItem".hash ();
+               }
+
+               public ValaCompletionItem (Vala.Symbol symbol)
+               {
+                       this.symbol = symbol;
+               }
+
+               public void set_markup_func (owned ValaCompletionMarkupFunc? func)
+               {
+                       this.markup_func = func;
+               }
+
+               public unowned string? get_icon_name ()
+               {
+                       if (symbol is Vala.LocalVariable)
+                               return "lang-variable-symbolic";
+                       else if (symbol is Vala.Field)
+                               return "struct-field-symbolic";
+                       else if (symbol is Vala.Method || symbol is Vala.MethodCall)
+                               return "lang-function-symbolic";
+                       else if (symbol is Vala.Namespace)
+                               return "lang-include-symbolic";
+                       else if (symbol is Vala.MemberAccess)
+                               return "struct-field-symbolic";
+                       else if (symbol is Vala.Struct)
+                               return "lang-struct-symbolic";
+                       else if (symbol is Vala.Class)
+                               return "lang-class-symbolic";
+                       else if (symbol is Vala.Enum)
+                               return "lang-enum-symbolic";
+                       else if (symbol is Vala.EnumValue)
+                               return "lang-enum-value-symbolic";
+
+                       return null;
+               }
+
+               public bool matches (string? prefix_lower)
+               {
+                       if (prefix_lower == null || prefix_lower [0] == '\0')
+                               return true;
+
+                       return gb_str_simple_match (this.symbol.name, prefix_lower);
+               }
+
+               public string get_label () {
+                       return this.symbol.name;
+               }
+
+               public string get_markup () {
+                       if (this.markup_func != null)
+                               return this.markup_func (this.symbol.name);
+                       return this.symbol.name;
+               }
+
+               public string get_text () {
+                       return this.symbol.name;
+               }
+
+               public uint hash ()
+               {
+                       return this.symbol.name.hash () ^ hash_seed;
+               }
+
+               public unowned GLib.Icon? get_gicon () { return null; }
+               public unowned Gdk.Pixbuf? get_icon () { return null; }
+               public string? get_info () { return null; }
+       }
+
+       [CCode (cheader_filename = "gb-string.h", cname = "gb_str_simple_match")]
+       extern bool gb_str_simple_match (string? text, string? prefix_lower);
+}
+
diff --git a/plugins/vala-pack/ide-vala-completion-provider.vala 
b/plugins/vala-pack/ide-vala-completion-provider.vala
new file mode 100644
index 0000000..08ccf46
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-completion-provider.vala
@@ -0,0 +1,237 @@
+/* ide-vala-completion-provider.vala
+ *
+ * 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/>.
+ */
+
+using GLib;
+using Ide;
+using Vala;
+
+namespace Ide
+{
+       /*
+        * TODO:
+        *
+        *   There is some fun optimization work we can do here to work around the
+        *   list interface of GtkSourceCompletionContext. However, I'm not yet
+        *   sure how to do this in Vala. In C, of course, it's trivial.
+        *
+        *   The goal here is to reduce the overhead of sorting the GList of items
+        *   that needs to be sent to GtkSourceCompletionContext. We may want to
+        *   reorder them based on the input text or another better sorting
+        *   heuristic. But doing that on a malloc'd list is expensive and
+        *   annoying.
+        *
+        *   We could solve this by having an array of GList,pointer fields and
+        *   update the GList items after sorting. We could also just embed the
+        *   GList in the Ide.ValaCompletionitem (but I don't know how to do this
+        *   in Vala. I guess we could make a subclass for this too.
+        *
+        *   This way, when looking at our previous items, we sort using a
+        *   cacheline efficient array of items, and just update the pointers
+        *   at the same time or at the end.
+        *
+        *   For now, we'll do the dumb stupid slow thing.
+        */
+       public class ValaCompletionProvider: Ide.Object,
+                                            Gtk.SourceCompletionProvider,
+                                            Ide.CompletionProvider
+       {
+               ArrayList<Ide.ValaCompletionItem>? last_results;
+               string? last_line;
+               string? last_prefix;
+               int line = -1;
+               int column = -1;
+
+               public void populate (Gtk.SourceCompletionContext context)
+               {
+                       Gtk.TextIter iter;
+                       Gtk.TextIter begin;
+
+                       if (!context.get_iter (out iter)) {
+                               context.add_proposals (this, null, true);
+                               return;
+                       }
+
+                       begin = iter;
+                       begin.set_line_offset (0);
+                       var line = begin.get_slice (iter);
+
+                       /*
+                        * We might be able to reuse the results from our previous query if
+                        * the buffer is sufficiently similar. If so, possibly just
+                        * rearrange some things and redisplay those results.
+                        */
+                       if (can_replay (line)) {
+                               HashTable<void*,bool> dedup = new HashTable<void*,bool> (GLib.direct_hash, 
GLib.direct_equal);
+                               Gtk.TextIter stop = iter;
+
+                               /* Move to the just inserted character. */
+                               if (!stop.starts_line ())
+                                       stop.backward_char ();
+
+                               /*
+                                * Walk backwards to locate the first character after a stop
+                                * character (anything non-alphanumeric or _).
+                                */
+                               while (!stop.starts_line () &&
+                                          (stop.get_char ().isalnum () || stop.get_char () == '_') &&
+                                          stop.backward_char ()) {
+                                       /* Do nothing */
+                               }
+                               var ch = stop.get_char ();
+                               if (!ch.isalnum () && (ch != ')') && (stop.compare (iter) < 0))
+                                       stop.forward_char ();
+
+                               var prefix = stop.get_slice (iter).strip ();
+                               var downcase = prefix.down ();
+                               var results = new GLib.List<Gtk.SourceCompletionProposal> ();
+
+                               /* See the comment above about optimizing this. */
+                               foreach (var result in this.last_results) {
+                                       if (result.matches (downcase)) {
+                                               var hash = (void*)result.hash ();
+                                               if (dedup.contains ((void*)hash))
+                                                       continue;
+                                               results.prepend (result);
+                                               dedup.insert (hash, true);
+                                       }
+                               }
+
+                               this.last_prefix = prefix;
+
+                               results.reverse ();
+
+                               context.add_proposals (this, results, true);
+
+                               return;
+                       }
+
+                       this.line = -1;
+                       this.column = -1;
+
+                       var buffer = iter.get_buffer () as Ide.Buffer;
+                       var file = buffer.file;
+
+                       if (file == null || file.is_temporary) {
+                               context.add_proposals (this, null, true);
+                               return;
+                       }
+
+                       buffer.sync_to_unsaved_files ();
+
+                       var service = (this.get_context ()
+                                          .get_service_typed (typeof (Ide.ValaService)) as Ide.ValaService);
+                       var index = service.index;
+                       var unsaved_files = this.get_context ().get_unsaved_files ();
+
+                       var cancellable = new GLib.Cancellable ();
+                       context.cancelled.connect(() => {
+                               cancellable.cancel ();
+                       });
+
+                       index.code_complete.begin (file.file,
+                                                  iter.get_line () + 1,
+                                                  iter.get_line_offset () + 1,
+                                                  line,
+                                                  unsaved_files,
+                                                  null,
+                                                  (obj,res) => {
+                               int res_line = -1;
+                               int res_column = -1;
+
+                               var results = index.code_complete.end (res, out res_line , out res_column);
+
+                               if (res_line > 0 && res_column > 0) {
+                                       this.line = res_line - 1;
+                                       this.column = res_column - 1;
+                               }
+
+                               if (!cancellable.is_cancelled ()) {
+                                       /* TODO: fix more brain dead slow list conversion stuff */
+                                       var list = new GLib.List<Ide.ValaCompletionItem> ();
+                                       foreach (var item in results) {
+                                               list.prepend (item);
+                                               item.set_markup_func (this.markup_func);
+                                       }
+                                       context.add_proposals (this, list, true);
+                               }
+
+                               this.last_results = results;
+                               this.last_line = line;
+                       });
+               }
+
+               public bool match (Gtk.SourceCompletionContext context)
+               {
+                       Gtk.TextIter iter;
+
+                       if (!context.get_iter (out iter))
+                               return false;
+
+                       var buffer = iter.get_buffer () as Ide.Buffer;
+
+                       if (buffer.file == null || buffer.file.file == null) {
+                               return false;
+                       }
+
+                       /*
+                        * Only match if we were user requested or the character is not after
+                        * whitespace.
+                        */
+                       if (context.activation != Gtk.SourceCompletionActivation.USER_REQUESTED) {
+                               if (iter.starts_line () || !iter.backward_char () || iter.get_char ().isspace 
())
+                                       return false;
+                       }
+
+                       return true;
+               }
+
+               /*
+                * Check to see if this line can be replayed using the results from
+                * the previous query. We can do that if the characters that have
+                * changed are simply alphanumeric or _ (function or symbol name
+                * characters). We just need to massage the results appropriately.
+                */
+               bool can_replay (string? line)
+               {
+                       if (line == null || this.last_line == null)
+                               return false;
+
+                       if (!line.has_prefix (this.last_line))
+                               return false;
+
+                       var suffix = line.offset (this.last_line.length);
+
+                       while (suffix[0] != '\0') {
+                               var ch = suffix.get_char ();
+                               if (!ch.isalnum() && ch != '_')
+                                       return false;
+                               suffix = suffix.next_char ();
+                       }
+
+                       return true;
+               }
+
+               string markup_func (string name)
+               {
+                       return highlight_full (name, this.last_prefix, true, 1);
+               }
+       }
+
+       [CCode (cheader_filename = "gb-string.h", cname = "gb_str_highlight_full")]
+       extern unowned string? highlight_full (string haystack, string needle, bool insensitive, int type);
+}
diff --git a/plugins/vala-pack/ide-vala-completion.vala b/plugins/vala-pack/ide-vala-completion.vala
new file mode 100644
index 0000000..f7c98b1
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-completion.vala
@@ -0,0 +1,189 @@
+/*
+ * Some of the code below is taken from Anjuta and is licensed under the
+ * terms of the GPL v2. The original copyright is preserved.
+ *
+ * Copyright (C) 2008-2010 Abderrahim Kitouni
+ * 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 2 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/>.
+ */
+
+using GLib;
+using Gtk;
+using Vala;
+
+namespace Ide
+{
+       public class ValaCompletion: GLib.Object
+       {
+               static Regex member_access;
+               static Regex member_access_split;
+               static Regex function_call;
+
+               Vala.CodeContext context;
+               Vala.SourceLocation location;
+               string current_text;
+               Vala.Block? nearest;
+
+               static construct {
+                       try {
+                               member_access = new Regex("""((?:\w+(?:\s*\([^()]*\))?\.)*)(\w*)$""");
+                               member_access_split = new Regex ("""(\s*\([^()]*\))?\.""");
+                               function_call = new Regex("""(new 
)?((?:\w+(?:\s*\([^()]*\))?\.)*)(\w+)\s*\(([^(,)]+,)*([^(,)]*)$""");
+                       } catch (RegexError err) {
+                               critical("Regular expressions failed to compile : %s", err.message);
+                       }
+               }
+
+               public ValaCompletion (Vala.CodeContext context,
+                                      Vala.SourceLocation location,
+                                      string current_text,
+                                      Vala.Block? nearest)
+               {
+                       this.context = context;
+                       this.location = location;
+                       this.current_text = current_text;
+                       this.nearest = nearest;
+               }
+
+               public GLib.List<Vala.Symbol>? run (ref Vala.SourceLocation start_pos)
+               {
+                       MatchInfo match_info;
+
+                       if (!member_access.match (current_text, 0, out match_info))
+                               return null;
+                       else if (match_info.fetch(0).length < 2)
+                               return null;
+
+                       start_pos.line = this.location.line;
+                       start_pos.column = this.location.column - (int)match_info.fetch (2).length;
+
+                       var names = member_access_split.split (match_info.fetch (1));
+
+                       var syms = lookup_symbol (construct_member_access (names),
+                                                 match_info.fetch (2),
+                                                 true,
+                                                 nearest);
+
+                       return syms;
+               }
+
+               GLib.List<Vala.Symbol> lookup_symbol (Vala.Expression? inner, string name, bool prefix_match, 
Vala.Block? block)
+               {
+                       var matching_symbols = new GLib.List<Vala.Symbol> ();
+
+                       if (block == null)
+                               return matching_symbols;
+
+                       if (inner == null) {
+                               for (var sym = (Vala.Symbol) block; sym != null; sym = sym.parent_symbol) {
+                                       matching_symbols.concat (symbol_lookup_inherited (sym, name, 
prefix_match));
+                               }
+
+                               foreach (var ns in block.source_reference.file.current_using_directives) {
+                                       matching_symbols.concat (symbol_lookup_inherited 
(ns.namespace_symbol, name, prefix_match));
+                               }
+                       } else if (inner.symbol_reference != null) {
+                                       matching_symbols.concat (symbol_lookup_inherited 
(inner.symbol_reference, name, prefix_match));
+                       } else if (inner is Vala.MemberAccess) {
+                               var inner_ma = (Vala.MemberAccess) inner;
+                               var matching = lookup_symbol (inner_ma.inner, inner_ma.member_name, false, 
block);
+                               if (matching != null)
+                                       matching_symbols.concat (symbol_lookup_inherited (matching.data, 
name, prefix_match));
+                       } else if (inner is Vala.MethodCall) {
+                               var inner_inv = (Vala.MethodCall) inner;
+                               var inner_ma = inner_inv.call as Vala.MemberAccess;
+                               if (inner_ma != null) {
+                                       var matching = lookup_symbol (inner_ma.inner, inner_ma.member_name, 
false, block);
+                                       if (matching != null)
+                                               matching_symbols.concat (symbol_lookup_inherited 
(matching.data, name, prefix_match, true));
+                               }
+                       }
+
+                       return matching_symbols;
+               }
+
+               GLib.List<Vala.Symbol> symbol_lookup_inherited (Vala.Symbol? sym,
+                                                               string name,
+                                                               bool prefix_match,
+                                                               bool invocation = false)
+               {
+                       GLib.List<Vala.Symbol> result = null;
+
+                       // This may happen if we cannot find all the needed packages
+                       if (sym == null)
+                               return result;
+
+                       var symbol_table = sym.scope.get_symbol_table ();
+
+                       if (symbol_table != null) {
+                               foreach (string key in symbol_table.get_keys()) {
+                                       if (((prefix_match && key.has_prefix (name)) || key == name)) {
+                                               result.append (symbol_table[key]);
+                                       }
+                               }
+                       }
+
+                       if (invocation && sym is Vala.Method) {
+                               var func = (Vala.Method) sym;
+                               result.concat (symbol_lookup_inherited (func.return_type.data_type, name, 
prefix_match));
+                       } else if (sym is Vala.Class) {
+                               var cl = (Vala.Class) sym;
+                               foreach (var base_type in cl.get_base_types ()) {
+                                       result.concat (symbol_lookup_inherited (base_type.data_type, name, 
prefix_match));
+                               }
+                       } else if (sym is Vala.Struct) {
+                               var st = (Vala.Struct) sym;
+                               result.concat (symbol_lookup_inherited (st.base_type.data_type, name, 
prefix_match));
+                       } else if (sym is Vala.Interface) {
+                               var iface = (Vala.Interface) sym;
+                               foreach (var prerequisite in iface.get_prerequisites ()) {
+                                       result.concat (symbol_lookup_inherited (prerequisite.data_type, name, 
prefix_match));
+                               }
+                       } else if (sym is Vala.LocalVariable) {
+                               var variable = (Vala.LocalVariable) sym;
+                               result.concat (symbol_lookup_inherited (variable.variable_type.data_type, 
name, prefix_match));
+                       } else if (sym is Vala.Field) {
+                               var field = (Vala.Field) sym;
+                               result.concat (symbol_lookup_inherited (field.variable_type.data_type, name, 
prefix_match));
+                       } else if (sym is Vala.Property) {
+                               var prop = (Vala.Property) sym;
+                               result.concat (symbol_lookup_inherited (prop.property_type.data_type, name, 
prefix_match));
+                       } else if (sym is Vala.Parameter) {
+                               var fp = (Vala.Parameter) sym;
+                               result.concat (symbol_lookup_inherited (fp.variable_type.data_type, name, 
prefix_match));
+                       }
+
+                       return result;
+               }
+
+               Vala.Expression construct_member_access (string[] names)
+               {
+                       Vala.Expression expr = null;
+
+                       for (var i = 0; names[i] != null; i++) {
+                               if (names[i] != "") {
+                                       expr = new Vala.MemberAccess (expr, names[i]);
+                                       if (names[i+1] != null && names[i+1].chug ().has_prefix ("(")) {
+                                               expr = new Vala.MethodCall (expr);
+                                               i++;
+                                       }
+                               }
+                       }
+
+                       return expr;
+               }
+       }
+}
+
diff --git a/plugins/vala-pack/ide-vala-diagnostic-provider.vala 
b/plugins/vala-pack/ide-vala-diagnostic-provider.vala
new file mode 100644
index 0000000..5a66032
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-diagnostic-provider.vala
@@ -0,0 +1,38 @@
+/* ide-vala-diagnostic-provider.vala
+ *
+ * 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/>.
+ */
+
+using GLib;
+using Ide;
+using Vala;
+
+namespace Ide
+{
+       public class ValaDiagnosticProvider: Ide.Object, Ide.DiagnosticProvider
+       {
+               public async Ide.Diagnostics? diagnose_async (Ide.File file,
+                                                             GLib.Cancellable? cancellable)
+                       throws GLib.Error
+               {
+                       var context = this.get_context ();
+                       var service = (Ide.ValaService)context.get_service_typed (typeof (Ide.ValaService));
+                       yield service.index.parse_file (file.file, context.unsaved_files, cancellable);
+                       var results = yield service.index.get_diagnostics (file.file, cancellable);
+                       return results;
+               }
+       }
+}
diff --git a/plugins/vala-pack/ide-vala-diagnostics.vala b/plugins/vala-pack/ide-vala-diagnostics.vala
new file mode 100644
index 0000000..42daadf
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-diagnostics.vala
@@ -0,0 +1,61 @@
+/* ide-vala-diagnostics.vala
+ *
+ * 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/>.
+ */
+
+using GLib;
+using Vala;
+
+namespace Ide
+{
+       public class ValaDiagnostics: Vala.Report
+       {
+               public void clear ()
+               {
+                       this.errors = 0;
+                       this.warnings = 0;
+               }
+
+               void add (Vala.SourceReference?  source_reference,
+                         string                 message,
+                         Ide.DiagnosticSeverity severity)
+               {
+                       if (source_reference == null)
+                               return;
+
+                       if (source_reference.file is Ide.ValaSourceFile) {
+                               var file = (Ide.ValaSourceFile)source_reference.file;
+                               file.report (source_reference, message, severity);
+                       }
+               }
+
+               public override void note (Vala.SourceReference? source_reference, string message) {
+                       add (source_reference, message, Ide.DiagnosticSeverity.NOTE);
+               }
+
+               public override void depr (Vala.SourceReference? source_reference, string message) {
+                       add (source_reference, message, Ide.DiagnosticSeverity.DEPRECATED);
+               }
+
+               public override void warn (Vala.SourceReference? source_reference, string message) {
+                       add (source_reference, message, Ide.DiagnosticSeverity.WARNING);
+               }
+
+               public override void err (Vala.SourceReference? source_reference, string message) {
+                       add (source_reference, message, Ide.DiagnosticSeverity.ERROR);
+               }
+       }
+}
diff --git a/plugins/vala-pack/ide-vala-indenter.vala b/plugins/vala-pack/ide-vala-indenter.vala
new file mode 100644
index 0000000..95e29d5
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-indenter.vala
@@ -0,0 +1,184 @@
+/* ide-vala-indenter.vala
+ *
+ * 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/>.
+ */
+
+using Gtk;
+using Ide;
+
+namespace Ide
+{
+       public class ValaIndenter: Ide.Object, Ide.Indenter
+       {
+               public bool is_trigger (Gdk.EventKey evkey)
+               {
+                       switch (evkey.keyval) {
+
+                       /* newline indents */
+                       case Gdk.Key.Return:
+                       case Gdk.Key.KP_Enter:
+                               return true;
+
+                       /* close multiline comment */
+                       case Gdk.Key.slash:
+                               return true;
+
+                       default:
+                               return false;
+                       }
+               }
+
+               public string? format (Gtk.TextView text_view,
+                                      Gtk.TextIter begin,
+                                      Gtk.TextIter end,
+                                      out int      cursor_offset,
+                                      Gdk.EventKey evkey)
+               {
+                       Gtk.SourceView source_view = (text_view as Gtk.SourceView);
+                       bool was_newline = is_newline_keyval (evkey.keyval);
+                       Gtk.TextIter copy = end;
+
+                       cursor_offset = 0;
+
+                       /* Move us back to the just inserted character */
+                       copy.backward_char ();
+
+                       /* If we are in a comment, continue the indentation. */
+                       if (in_comment (text_view, copy)) {
+                               /* maybe close a multiline comment */
+                               if (copy.get_char () == '/') {
+                                       Gtk.TextIter close = copy;
+                                       if (close.backward_char () && close.get_char () == ' ' &&
+                                           close.backward_char () && close.get_char () == '*') {
+                                               begin.backward_char ();
+                                               begin.backward_char ();
+                                               return "/";
+                                       }
+                               }
+
+                               if (was_newline)
+                                       return indent_comment (text_view, copy);
+                       }
+
+                       if (is_newline_in_braces (copy)) {
+                               string prefix = copy_indent (text_view, copy);
+                               string indent;
+
+                               if (source_view.insert_spaces_instead_of_tabs)
+                                       indent = "    ";
+                               else
+                                       indent = "\t";
+
+                               cursor_offset = -prefix.length - 1;
+                               return (prefix + indent + "\n" + prefix);
+                       }
+
+                       if (was_newline)
+                               return copy_indent (text_view, copy);
+
+                       return null;
+               }
+
+               string? copy_indent (Gtk.TextView text_view,
+                                    Gtk.TextIter iter)
+               {
+                       Gtk.TextIter begin = iter;
+                       Gtk.TextIter end;
+
+                       begin.set_line_offset (0);
+                       end = begin;
+
+                       while (!end.ends_line () && end.get_char ().isspace () && end.forward_char ()) {
+                               /* Do nothing */
+                       }
+
+                       return begin.get_slice (end);
+               }
+
+               string get_line_text (Gtk.TextIter iter)
+               {
+                       Gtk.TextIter begin = iter;
+                       Gtk.TextIter end = iter;
+
+                       begin.set_line_offset (0);
+                       if (!end.ends_line ())
+                               end.forward_to_line_end ();
+
+                       return begin.get_slice (end);
+               }
+
+               string? indent_comment (Gtk.TextView text_view,
+                                       Gtk.TextIter iter)
+               {
+                       string line = get_line_text (iter).strip ();
+
+                       /* continue with another single line comment */
+                       if (line.has_prefix ("//"))
+                               return copy_indent (text_view, iter) + "// ";
+
+                       /* comment is closed, copy indent, possibly trimming extra space */
+                       if (line.has_suffix ("*/")) {
+                               if (line.has_prefix ("*")) {
+                                       var str = new GLib.StringBuilder (copy_indent (text_view, iter));
+                                       if (str.str.has_suffix (" "))
+                                               str.truncate (str.len - 1);
+                                       return str.str;
+                               }
+                       }
+
+                       if (line.has_prefix ("/*") && !line.has_suffix ("*/"))
+                               return copy_indent (text_view, iter) + " * ";
+                       else if (line.has_prefix ("*"))
+                               return copy_indent (text_view, iter) + "* ";
+
+                       return copy_indent (text_view, iter);
+               }
+
+               bool in_comment (Gtk.TextView text_view,
+                                Gtk.TextIter iter)
+               {
+                       Gtk.SourceBuffer buffer = text_view.buffer as Gtk.SourceBuffer;
+                       Gtk.TextIter copy = iter;
+
+                       copy.backward_char ();
+
+                       return buffer.iter_has_context_class (copy, "comment");
+               }
+
+               bool is_newline_keyval (uint keyval)
+               {
+                       switch (keyval) {
+                       case Gdk.Key.Return:
+                       case Gdk.Key.KP_Enter:
+                               return true;
+
+                       default:
+                               return false;
+                       }
+               }
+
+               bool is_newline_in_braces (Gtk.TextIter iter)
+               {
+                       Gtk.TextIter prev = iter;
+                       Gtk.TextIter next = iter;
+
+                       prev.backward_char ();
+                       next.forward_char ();
+
+                       return (prev.get_char () == '{') && (iter.get_char () == '\n') && (next.get_char () 
== '}');
+               }
+       }
+}
diff --git a/plugins/vala-pack/ide-vala-index.vala b/plugins/vala-pack/ide-vala-index.vala
new file mode 100644
index 0000000..d03526d
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-index.vala
@@ -0,0 +1,291 @@
+/* ide-vala-index.vala
+ *
+ * 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/>.
+ */
+
+/*
+ * The point of Ide.ValaIndex is somewhat analogous to Clang's CXIndex.
+ * It is the top-level complent for everything you can do with vala
+ * files for a particular context. Typically, you would have one index
+ * per project. Therefore, we use the singleton-per-project nature of
+ * Ide.Service (via Ide.ValaService) to keep an index-per-project.
+ */
+
+using GLib;
+using Gtk;
+using Ide;
+using Vala;
+
+namespace Ide
+{
+       public class ValaIndex: GLib.Object
+       {
+               Vala.CodeContext code_context;
+               Vala.Parser parser;
+               HashMap<GLib.File,Ide.ValaSourceFile> source_files;
+               Ide.ValaDiagnostics report;
+
+               public ValaIndex ()
+               {
+                       this.source_files = new HashMap<GLib.File,Ide.ValaSourceFile> (GLib.File.hash, 
(GLib.EqualFunc)GLib.File.equal);
+
+                       this.code_context = new Vala.CodeContext ();
+
+                       Vala.CodeContext.push (this.code_context);
+
+                       /*
+                        * TODO: Some of the following options could be extracted by parsing
+                        *       the contents of *_VALAFLAGS or AM_VALAFLAGS in automake.
+                        *       We need to do this in a somewhat build system agnostic fashion
+                        *       since there seems to a cargo cult of cmake/vala.
+                        */
+
+                       this.code_context.assert = true;
+                       this.code_context.checking = false;
+                       this.code_context.deprecated = false;
+                       this.code_context.hide_internal = false;
+                       this.code_context.experimental = false;
+                       this.code_context.experimental_non_null = false;
+                       this.code_context.gobject_tracing = false;
+                       this.code_context.nostdpkg = false;
+                       this.code_context.ccode_only = true;
+                       this.code_context.compile_only = true;
+                       this.code_context.use_header = false;
+                       this.code_context.includedir = null;
+                       this.code_context.basedir = Environment.get_current_dir ();
+                       this.code_context.directory = Environment.get_current_dir ();
+                       this.code_context.debug = false;
+                       this.code_context.thread = true;
+                       this.code_context.mem_profiler = false;
+                       this.code_context.save_temps = false;
+
+                       this.code_context.profile = Vala.Profile.GOBJECT;
+                       this.code_context.add_define ("GOBJECT");
+
+                       this.code_context.entry_point_name = null;
+
+                       this.code_context.run_output = false;
+
+                       for (var i = 2; i <= 30; i += 2) {
+                               this.code_context.add_define ("VALA_0_%d".printf (i));
+                       }
+
+                       for (var i = 16; i < GLib.Version.minor; i+= 2) {
+                               this.code_context.add_define ("GLIB_2_%d".printf (i));
+                       }
+
+                       this.code_context.add_external_package ("glib-2.0");
+                       this.code_context.add_external_package ("gobject-2.0");
+
+                       /* TODO: find packages from build system */
+                       this.code_context.add_external_package ("gio-2.0");
+                       this.code_context.add_external_package ("libvala-0.30");
+                       this.code_context.add_external_package ("libide-1.0");
+                       this.code_context.add_external_package ("gtksourceview-3.0");
+
+                       this.report = new Ide.ValaDiagnostics ();
+                       this.code_context.report = this.report;
+
+                       this.parser = new Vala.Parser ();
+                       this.parser.parse (this.code_context);
+
+                       this.code_context.check ();
+
+                       Vala.CodeContext.pop ();
+               }
+
+               public async void add_files (ArrayList<GLib.File> files,
+                                            GLib.Cancellable? cancellable)
+               {
+                       Ide.ThreadPool.push (Ide.ThreadPoolKind.COMPILER, () => {
+                               lock (this.code_context) {
+                                       Vala.CodeContext.push (this.code_context);
+
+                                       foreach (var file in files) {
+                                               var path = file.get_path ();
+                                               if (path == null)
+                                                       continue;
+
+                                               var source_file = new Ide.ValaSourceFile (this.code_context, 
Vala.SourceFileType.SOURCE, path, null, false);
+                                               this.code_context.add_source_file (source_file);
+
+                                               this.source_files[file] = source_file;
+                                       }
+
+                                       Vala.CodeContext.pop ();
+
+                                       GLib.Idle.add(add_files.callback);
+                               }
+                       });
+
+                       yield;
+               }
+
+               public async bool parse_file (GLib.File file,
+                                             Ide.UnsavedFiles? unsaved_files,
+                                             GLib.Cancellable? cancellable)
+                       throws GLib.Error
+               {
+                       GLib.GenericArray<UnsavedFile>? unsaved_files_copy = null;
+
+                       if (unsaved_files != null) {
+                               unsaved_files_copy = unsaved_files.to_array ();
+                       }
+
+                       Ide.ThreadPool.push (Ide.ThreadPoolKind.COMPILER, () => {
+                               if ((cancellable == null) || !cancellable.is_cancelled ()) {
+                                       lock (this.code_context) {
+                                               Vala.CodeContext.push (this.code_context);
+
+                                               this.apply_unsaved_files (unsaved_files_copy);
+                                               this.reparse ();
+                                               this.code_context.check ();
+
+                                               GLib.Idle.add(this.parse_file.callback);
+
+                                               Vala.CodeContext.pop ();
+                                       }
+                               }
+                       });
+
+                       yield;
+
+                       return true;
+               }
+
+               public async ArrayList<Ide.ValaCompletionItem> code_complete (GLib.File file,
+                                                                             int line,
+                                                                             int column,
+                                                                             string? line_text,
+                                                                             Ide.UnsavedFiles? unsaved_files,
+                                                                             out int result_line,
+                                                                             out int result_column,
+                                                                             GLib.Cancellable? cancellable)
+               {
+                       var unsaved_files_copy = unsaved_files.to_array ();
+                       var result = new ArrayList<Ide.ValaCompletionItem> ();
+
+                       Ide.ThreadPool.push (Ide.ThreadPoolKind.COMPILER, () => {
+                               if ((cancellable == null) || !cancellable.is_cancelled ()) {
+                                       lock (this.code_context) {
+                                               Vala.CodeContext.push (this.code_context);
+
+                                               this.apply_unsaved_files (unsaved_files_copy);
+                                               this.reparse ();
+                                               this.code_context.check ();
+
+                                               if (this.source_files.contains (file)) {
+                                                       var source_file = this.source_files [file];
+                                                       string? text = (line_text == null) ? 
source_file.get_source_line (line) : line_text;
+                                                       var locator = new Ide.ValaLocator ();
+                                                       var nearest = locator.locate (source_file, line, 
column);
+
+                                                       this.add_completions (source_file, ref line, ref 
column, text, nearest, result);
+                                               }
+
+                                               Vala.CodeContext.pop ();
+                                       }
+                               }
+
+                               GLib.Idle.add(code_complete.callback);
+                       });
+
+                       yield;
+
+                       result_line = line;
+                       result_column = column;
+
+                       return result;
+               }
+
+               public async Ide.Diagnostics? get_diagnostics (GLib.File file,
+                                                              GLib.Cancellable? cancellable = null)
+               {
+                       Ide.Diagnostics? diagnostics = null;
+
+                       Ide.ThreadPool.push (Ide.ThreadPoolKind.COMPILER, () => {
+                               if ((cancellable == null) || !cancellable.is_cancelled ()) {
+                                       lock (this.code_context) {
+                                               Vala.CodeContext.push (this.code_context);
+                                               if (this.source_files.contains (file)) {
+                                                       diagnostics = this.source_files[file].diagnose ();
+                                               }
+                                               Vala.CodeContext.pop ();
+                                       }
+                               }
+                               GLib.Idle.add(this.get_diagnostics.callback);
+                       });
+
+                       yield;
+
+                       return diagnostics;
+               }
+
+               void apply_unsaved_files (GLib.GenericArray<Ide.UnsavedFile> unsaved_files)
+               {
+                       foreach (var source_file in this.code_context.get_source_files ()) {
+                               if ((source_file.file_type == Vala.SourceFileType.SOURCE) &&
+                                   (source_file is Ide.ValaSourceFile)) {
+                                       (source_file as Ide.ValaSourceFile).sync (unsaved_files);
+                               }
+                       }
+               }
+
+               void reparse ()
+               {
+                       this.report.clear ();
+
+                       foreach (var source_file in this.code_context.get_source_files ()) {
+                               if (source_file.get_nodes ().size == 0) {
+                                       if (source_file is Ide.ValaSourceFile) {
+                                               this.parser.visit_source_file (source_file);
+                                               (source_file as Ide.ValaSourceFile).dirty = false;
+                                       } else {
+                                               this.parser.visit_source_file (source_file);
+                                       }
+                               }
+                       }
+               }
+
+               void add_completions (Ide.ValaSourceFile source_file,
+                                     ref int line,
+                                     ref int column,
+                                     string line_text,
+                                     Vala.Symbol? nearest,
+                                     ArrayList<Ide.ValaCompletionItem> results)
+               {
+                       if (!(nearest is Vala.Block))
+                               nearest = null;
+
+                       var block = (Vala.Block)nearest;
+                       Vala.SourceLocation cursor = Vala.SourceLocation (null, line, column);
+
+                       // TODO: our list building could use a lot of low-hanging optimizations.
+                       //       the list/array/list in particular.
+                       //       it would be nice to stay as an array as long as possible.
+
+                       var completion = new Ide.ValaCompletion (this.code_context, cursor, line_text, block);
+                       var list = completion.run (ref cursor);
+
+                       foreach (var symbol in list) {
+                               results.add (new Ide.ValaCompletionItem (symbol));
+                       }
+
+                       line = cursor.line;
+                       column = cursor.column;
+               }
+       }
+}
diff --git a/plugins/vala-pack/ide-vala-locator.vala b/plugins/vala-pack/ide-vala-locator.vala
new file mode 100644
index 0000000..a9c888a
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-locator.vala
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2008 Abderrahim Kitouni
+ * 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 2 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/>.
+ */
+
+/* Finds the innermost block containing the given location */
+namespace Ide {
+       public class ValaLocator: Vala.CodeVisitor {
+               struct Location {
+                       int line;
+                       int column;
+
+                       public Location (int line, int column) {
+                               this.line = line;
+                               this.column = column;
+                       }
+
+                       public bool inside (Vala.SourceReference src) {
+                               var begin = Location (src.begin.line, src.begin.column);
+                               var end = Location (src.end.line, src.end.column);
+
+                               return begin.before (this) && this.before(end);
+                       }
+
+                       public bool before (Location other) {
+                               if (line > other.line)
+                                       return false;
+                               else if (line == other.line && column > other.column)
+                                       return false;
+                               return true;
+                       }
+               }
+
+               Location location;
+               Vala.Symbol innermost;
+               Location innermost_begin;
+               Location innermost_end;
+
+               public Vala.Symbol locate (Vala.SourceFile file, int line, int column) {
+                       location = Location (line, column);
+                       innermost = null;
+                       file.accept_children(this);
+                       return innermost;
+               }
+
+               bool update_location (Vala.Symbol s) {
+                       if (!location.inside (s.source_reference))
+                               return false;
+
+                       var begin = Location (s.source_reference.begin.line, s.source_reference.begin.column);
+                       var end = Location (s.source_reference.end.line, s.source_reference.end.column);
+
+                       if (innermost == null || (innermost_begin.before(begin) && 
end.before(innermost_end))) {
+                                       innermost = s;
+                                       innermost_begin = begin;
+                                       innermost_end = end;
+                                       return true;
+                       }
+
+                       return false;
+               }
+
+               public override void visit_block (Vala.Block b) {
+                       if (update_location (b))
+                               b.accept_children(this);
+               }
+
+               public override void visit_namespace (Vala.Namespace ns) {
+                       update_location (ns);
+                       ns.accept_children(this);
+               }
+               public override void visit_class (Vala.Class cl) {
+                       /* the location of a class contains only its declaration, not its content */
+                       if (update_location (cl))
+                               return;
+                       cl.accept_children(this);
+               }
+               public override void visit_struct (Vala.Struct st) {
+                       if (update_location (st))
+                               return;
+                       st.accept_children(this);
+               }
+               public override void visit_interface (Vala.Interface iface) {
+                       if (update_location (iface))
+                               return;
+                       iface.accept_children(this);
+               }
+
+               public override void visit_method (Vala.Method m) {
+                       if (update_location (m))
+                               return;
+                       m.accept_children(this);
+               }
+               public override void visit_creation_method (Vala.CreationMethod m) {
+                       if (update_location (m))
+                               return;
+                       m.accept_children(this);
+               }
+               public override void visit_property (Vala.Property prop) {
+                       prop.accept_children(this);
+               }
+               public override void visit_property_accessor (Vala.PropertyAccessor acc) {
+                       acc.accept_children(this);
+               }
+               public override void visit_constructor (Vala.Constructor c) {
+                       c.accept_children(this);
+               }
+               public override void visit_destructor (Vala.Destructor d) {
+                       d.accept_children(this);
+               }
+               public override void visit_if_statement (Vala.IfStatement stmt) {
+                       stmt.accept_children(this);
+               }
+               public override void visit_switch_statement (Vala.SwitchStatement stmt) {
+                       stmt.accept_children(this);
+               }
+               public override void visit_switch_section (Vala.SwitchSection section) {
+                       visit_block (section);
+               }
+               public override void visit_while_statement (Vala.WhileStatement stmt) {
+                       stmt.accept_children(this);
+               }
+               public override void visit_do_statement (Vala.DoStatement stmt) {
+                       stmt.accept_children(this);
+               }
+               public override void visit_for_statement (Vala.ForStatement stmt) {
+                       stmt.accept_children(this);
+               }
+               public override void visit_foreach_statement (Vala.ForeachStatement stmt) {
+                       stmt.accept_children(this);
+               }
+               public override void visit_try_statement (Vala.TryStatement stmt) {
+                       stmt.accept_children(this);
+               }
+               public override void visit_catch_clause (Vala.CatchClause clause) {
+                       clause.accept_children(this);
+               }
+               public override void visit_lock_statement (Vala.LockStatement stmt) {
+                       stmt.accept_children(this);
+               }
+               public override void visit_lambda_expression (Vala.LambdaExpression expr) {
+                       expr.accept_children(this);
+               }
+       }
+}
diff --git a/plugins/vala-pack/ide-vala-service.vala b/plugins/vala-pack/ide-vala-service.vala
new file mode 100644
index 0000000..8bba1c0
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-service.vala
@@ -0,0 +1,87 @@
+/* ide-vala-service.vala
+ *
+ * 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/>.
+ */
+
+using GLib;
+using Ide;
+using Vala;
+
+namespace Ide
+{
+       public class ValaService: Ide.Object, Ide.Service
+       {
+               Ide.ValaIndex _index;
+
+               construct {
+                       this._index = new Ide.ValaIndex ();
+               }
+
+               public ValaIndex index {
+                       get { return this._index; }
+               }
+
+               public unowned string get_name () {
+                       return typeof (Ide.ValaService).name ();
+               }
+
+               public void start () {
+                       Ide.ThreadPool.push (Ide.ThreadPoolKind.INDEXER, () => {
+                               Ide.Vcs vcs = this.get_context ().get_vcs ();
+                               var files = new ArrayList<GLib.File> ();
+
+                               load_directory (vcs.get_working_directory (), null, files);
+
+                               if (files.size > 0) {
+                                       this._index.add_files.begin (files, null, () => {
+                                               debug ("Vala files registered");
+                                       });
+                               }
+                       });
+               }
+
+               public void stop () {
+               }
+
+               public void load_directory (GLib.File directory,
+                                           GLib.Cancellable? cancellable,
+                                           ArrayList<GLib.File> files)
+               {
+                       try {
+                               var enumerator = directory.enumerate_children 
(FileAttribute.STANDARD_NAME+","+FileAttribute.STANDARD_TYPE, 0, cancellable);
+                               var directories = new ArrayList<GLib.File> ();
+
+                               FileInfo file_info;
+                               while ((file_info = enumerator.next_file ()) != null) {
+                                       var name = file_info.get_name ();
+                                       if (file_info.get_file_type () == GLib.FileType.DIRECTORY) {
+                                               directories.add (directory.get_child (file_info.get_name ()));
+                                       } else if (name.has_suffix (".vala") || name.has_suffix (".vapi")) {
+                                               files.add (directory.get_child (file_info.get_name ()));
+                                       }
+                               }
+
+                               enumerator.close ();
+
+                               foreach (var child in directories) {
+                                       load_directory (child, cancellable, files);
+                               }
+                       } catch (GLib.Error err) {
+                               warning ("%s".printf (err.message));
+                       }
+               }
+       }
+}
diff --git a/plugins/vala-pack/ide-vala-source-file.vala b/plugins/vala-pack/ide-vala-source-file.vala
new file mode 100644
index 0000000..f0eff3e
--- /dev/null
+++ b/plugins/vala-pack/ide-vala-source-file.vala
@@ -0,0 +1,157 @@
+/* ide-vala-source-file.vala
+ *
+ * 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/>.
+ */
+
+/*
+ * Bits of the following file were inspired from Anjuta. It's original
+ * copyright is in tact below.
+ *
+ * Copyright (C) 2008-2010 Abderrahim Kitouni
+ *
+ * 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 2 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/>.
+ */
+
+using GLib;
+using Ide;
+using Vala;
+
+namespace Ide
+{
+       public class ValaSourceFile: Vala.SourceFile
+       {
+               ArrayList<Ide.Diagnostic> diagnostics;
+               Ide.File file;
+
+               public ValaSourceFile (Vala.CodeContext context,
+                                      Vala.SourceFileType type,
+                                      string filename,
+                                      string? content,
+                                      bool cmdline)
+               {
+                       base (context, type, filename, content, cmdline);
+
+                       this.file = new Ide.File (null, GLib.File.new_for_path (filename));
+                       this.diagnostics = new ArrayList<Ide.Diagnostic> ();
+
+                       this.add_default_namespace ();
+                       this.dirty = true;
+               }
+
+               public bool dirty { get; set; }
+
+               public GLib.File get_file ()
+               {
+                       return this.file.file;
+               }
+
+               public void reset ()
+               {
+                       this.diagnostics.clear ();
+
+                       /* Copy the node list since we will be mutating while iterating */
+                       var copy = new ArrayList<Vala.CodeNode> ();
+                       foreach (var node in this.get_nodes ()) {
+                               copy.add (node);
+                       }
+
+                       var entry_point = this.context.entry_point;
+
+                       foreach (var node in copy) {
+                               this.remove_node (node);
+
+                               if (node is Vala.Symbol) {
+                                       var symbol = (Vala.Symbol)node;
+                                       if (symbol.owner != null) {
+                                               symbol.owner.remove (symbol.name);
+                                       }
+                                       if (symbol == entry_point) {
+                                               this.context.entry_point = null;
+                                       }
+                               }
+                       }
+
+                       this.add_default_namespace ();
+                       this.dirty = true;
+               }
+
+               public void sync (GenericArray<Ide.UnsavedFile> unsaved_files)
+               {
+                       var gfile = this.file.file;
+                       unsaved_files.foreach((unsaved_file) => {
+                               if (unsaved_file.get_file ().equal (gfile)) {
+                                       var bytes = unsaved_file.get_content ();
+
+                                       if (bytes.get_data () != (uint8[]) this.content) {
+                                               this.content = (string)bytes.get_data ();
+                                               this.reset ();
+                                               return;
+                                       }
+                               }
+                       });
+               }
+
+               public void report (Vala.SourceReference source_reference,
+                                   string message,
+                                   Ide.DiagnosticSeverity severity)
+               {
+                       var begin = new Ide.SourceLocation (this.file,
+                                                           source_reference.begin.line - 1,
+                                                           source_reference.begin.column - 1,
+                                                           0);
+                       var end = new Ide.SourceLocation (this.file,
+                                                         source_reference.end.line - 1,
+                                                         source_reference.end.column - 1,
+                                                         0);
+
+                       var diag = new Ide.Diagnostic (severity, message, begin);
+                       diag.take_range (new Ide.SourceRange (begin, end));
+                       this.diagnostics.add (diag);
+               }
+
+               public Ide.Diagnostics? diagnose ()
+               {
+                       var ar = new GLib.GenericArray<Ide.Diagnostic> ();
+                       foreach (var diag in this.diagnostics) {
+                               ar.add (diag);
+                       }
+                       return new Ide.Diagnostics (ar);
+               }
+
+               void add_default_namespace ()
+               {
+                       this.current_using_directives = new ArrayList<Vala.UsingDirective> ();
+
+                       var unres = new Vala.UnresolvedSymbol (null, "GLib");
+                       var udir = new Vala.UsingDirective (unres);
+
+                       this.add_using_directive (udir);
+                       this.context.root.add_using_directive (udir);
+               }
+       }
+}
+
diff --git a/plugins/vala-pack/vala-pack-plugin.c b/plugins/vala-pack/vala-pack-plugin.c
new file mode 100644
index 0000000..c283d64
--- /dev/null
+++ b/plugins/vala-pack/vala-pack-plugin.c
@@ -0,0 +1,30 @@
+/* vala-pack-plugin.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/>.
+ */
+
+#include <libpeas/peas.h>
+
+#include "ide-vala.h"
+
+void
+peas_register_types (PeasObjectModule *module)
+{
+  peas_object_module_register_extension_type (module, IDE_TYPE_SERVICE, IDE_TYPE_VALA_SERVICE);
+  peas_object_module_register_extension_type (module, IDE_TYPE_INDENTER, IDE_TYPE_VALA_INDENTER);
+  peas_object_module_register_extension_type (module, IDE_TYPE_COMPLETION_PROVIDER, 
IDE_TYPE_VALA_COMPLETION_PROVIDER);
+  peas_object_module_register_extension_type (module, IDE_TYPE_DIAGNOSTIC_PROVIDER, 
IDE_TYPE_VALA_DIAGNOSTIC_PROVIDER);
+}
diff --git a/plugins/vala-pack/vala-pack.plugin b/plugins/vala-pack/vala-pack.plugin
new file mode 100644
index 0000000..50f1cf6
--- /dev/null
+++ b/plugins/vala-pack/vala-pack.plugin
@@ -0,0 +1,12 @@
+[Plugin]
+Module=vala-pack-plugin
+Name=Vala Language Pack
+Description=Provides support for the Vala programming language.
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2015 Christian Hergert
+Builtin=true
+Hidden=true
+X-Completion-Provider-Languages=vala
+X-Diagnostic-Provider-Languages=vala
+X-Indenter-Languages=vala
+X-Indenter-Languages-Priority=0
diff --git a/src/util/gb-string.c b/src/util/gb-string.c
index a3718fe..bea59aa 100644
--- a/src/util/gb-string.c
+++ b/src/util/gb-string.c
@@ -16,6 +16,8 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
+#include <string.h>
+
 #include "gb-string.h"
 
 gchar *
@@ -63,8 +65,6 @@ gb_str_highlight_full (const gchar     *str,
         }
     }
 
-#undef TOGGLE
-
   return g_string_free (ret, FALSE);
 }
 
@@ -72,5 +72,32 @@ gchar *
 gb_str_highlight (const gchar *str,
                   const gchar *match)
 {
-  return gb_str_highlight_full (str, match, FALSE, GB_HIGHLIGHT_UNDERLINE);
+  return gb_str_highlight_full (str, match, FALSE, GB_HIGHLIGHT_BOLD);
+}
+
+gboolean
+gb_str_simple_match (const gchar *haystack,
+                     const gchar *needle_down)
+{
+  if (gb_str_empty0 (haystack))
+    return FALSE;
+  else if (gb_str_empty0 (needle_down))
+    return TRUE;
+
+  for (; *needle_down; needle_down = g_utf8_next_char (needle_down))
+    {
+      gunichar ch = g_utf8_get_char (needle_down);
+      const gchar *tmp;
+
+      tmp = strchr (haystack, ch);
+      if (!tmp)
+        tmp = strchr (haystack, g_unichar_toupper (ch));
+
+      if (!tmp)
+        return FALSE;
+
+      haystack = tmp;
+    }
+
+  return TRUE;
 }
diff --git a/src/util/gb-string.h b/src/util/gb-string.h
index 11ae6a6..9673273 100644
--- a/src/util/gb-string.h
+++ b/src/util/gb-string.h
@@ -40,6 +40,8 @@ gchar *gb_str_highlight_full (const gchar     *str,
                               const gchar     *match,
                               gboolean         insensitive,
                               GbHighlightType  type);
+gboolean gb_str_simple_match (const gchar     *haystack,
+                              const gchar     *needle_down);
 
 G_END_DECLS
 


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