[gnome-builder/wip/chergert/langserv: 3/3] langserv: start prototyping IdeLangserverClient



commit b3528ac73b0105be8098b02f84b73d624517bf47
Author: Christian Hergert <chergert redhat com>
Date:   Thu Oct 20 19:59:35 2016 -0700

    langserv: start prototyping IdeLangserverClient
    
    This is work towards implementing a generic Language Server client that
    can be used by per-language implementations. The goal is to reduce how
    much code is necessary by a client to implement typical Language Server
    features.

 configure.ac                                       |    4 +
 libide/Makefile.am                                 |    4 +
 libide/langserv/ide-langserv-client.c              |  341 ++++++++++++++++++++
 libide/langserv/ide-langserv-client.h              |   51 +++
 libide/langserv/ide-langserv-diagnostic-provider.c |  246 ++++++++++++++
 libide/langserv/ide-langserv-diagnostic-provider.h |   49 +++
 plugins/rust-langserv/rust-langserv.plugin         |   13 +
 plugins/rust-langserv/rust_langserv_plugin.py      |  149 +++++++++
 8 files changed, 857 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index b315821..f9e9ced 100644
--- a/configure.ac
+++ b/configure.ac
@@ -190,6 +190,8 @@ m4_define([pygobject_required_version], [3.21.0])
 m4_define([libxml_required_version], [2.9.0])
 m4_define([pangoft2_required_version], [1.38.0])
 m4_define([peas_required_version], [1.18.0])
+m4_define([json_glib_required_version], [1.2.0])
+m4_define([jsonrpc_glib_required_version], [0.1.0])
 
 PKG_CHECK_MODULES(EGG,      [glib-2.0 >= glib_required_version
                              gmodule-2.0 >= glib_required_version
@@ -205,6 +207,8 @@ PKG_CHECK_MODULES(LIBIDE,   [gio-2.0 >= glib_required_version
                              gio-unix-2.0 >= glib_required_version
                              gtk+-3.0 >= gtk_required_version
                              gtksourceview-3.0 >= gtksourceview_required_version
+                             json-glib-1.0 >= json_glib_required_version
+                             jsonrpc-glib-0 >= jsonrpc_glib_required_version
                              libpeas-1.0 >= peas_required_version
                              libxml-2.0 >= libxml_required_version
                              pangoft2 >= pangoft2_required_version])
diff --git a/libide/Makefile.am b/libide/Makefile.am
index 2d6faa7..7174fc9 100644
--- a/libide/Makefile.am
+++ b/libide/Makefile.am
@@ -69,6 +69,8 @@ libide_1_0_la_public_headers =                            \
        highlighting/ide-highlighter.h                    \
        history/ide-back-forward-item.h                   \
        history/ide-back-forward-list.h                   \
+       langserv/ide-langserv-client.h                    \
+       langserv/ide-langserv-diagnostic-provider.h       \
        local/ide-local-device.h                          \
        logging/ide-log.h                                 \
        plugins/ide-extension-adapter.h                   \
@@ -238,6 +240,8 @@ libide_1_0_la_public_sources =                            \
        ide-object.c                                      \
        ide-service.c                                     \
        ide.c                                             \
+       langserv/ide-langserv-client.c                    \
+       langserv/ide-langserv-diagnostic-provider.c       \
        local/ide-local-device.c                          \
        logging/ide-log.c                                 \
        plugins/ide-extension-adapter.c                   \
diff --git a/libide/langserv/ide-langserv-client.c b/libide/langserv/ide-langserv-client.c
new file mode 100644
index 0000000..90b036a
--- /dev/null
+++ b/libide/langserv/ide-langserv-client.c
@@ -0,0 +1,341 @@
+/* ide-langserv-client.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-langserv-client"
+
+#include <egg-signal-group.h>
+#include <jsonrpc-glib.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+
+#include "buffers/ide-buffer.h"
+#include "buffers/ide-buffer-manager.h"
+#include "langserv/ide-langserv-client.h"
+
+typedef struct
+{
+  EggSignalGroup *buffer_manager_signals;
+  JsonrpcClient  *rpc_client;
+  GIOStream      *io_stream;
+} IdeLangservClientPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeLangservClient, ide_langserv_client, IDE_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_IO_STREAM,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static JsonNode *
+create_text_document (IdeBuffer *buffer)
+{
+  g_autoptr(JsonNode) ret = NULL;
+  g_autoptr(JsonObject) text_document = NULL;
+  g_autofree gchar *uri = NULL;
+  IdeFile *file;
+  GFile *gfile;
+
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  file = ide_buffer_get_file (buffer);
+  gfile = ide_file_get_file (file);
+  uri = g_file_get_uri (gfile);
+
+  text_document = json_object_new ();
+  json_object_set_string_member (text_document, "uri", uri);
+
+  ret = json_node_new (JSON_NODE_OBJECT);
+  json_node_set_object (ret, g_steal_pointer (&text_document));
+
+  return g_steal_pointer (&ret);
+}
+
+static void
+ide_langserv_client_buffer_loaded (IdeLangservClient *self,
+                                   IdeBuffer         *buffer,
+                                   IdeBufferManager  *buffer_manager)
+{
+  IdeLangservClientPrivate *priv = ide_langserv_client_get_instance_private (self);
+  g_autoptr(JsonObject) object = NULL;
+  g_autoptr(JsonNode) params = NULL;
+
+  g_assert (IDE_IS_LANGSERV_CLIENT (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+
+  object = json_object_new ();
+  json_object_set_member (object, "textDocument", create_text_document (buffer));
+
+  params = json_node_new (JSON_NODE_OBJECT);
+  json_node_set_object (params, g_steal_pointer (&object));
+
+  jsonrpc_client_notification_async (priv->rpc_client, "textDocument/didOpen", params, NULL, NULL, NULL);
+}
+
+static void
+ide_langserv_client_buffer_unloaded (IdeLangservClient *self,
+                                     IdeBuffer         *buffer,
+                                     IdeBufferManager  *buffer_manager)
+{
+  IdeLangservClientPrivate *priv = ide_langserv_client_get_instance_private (self);
+  g_autoptr(JsonObject) object = NULL;
+  g_autoptr(JsonNode) params = NULL;
+
+  g_assert (IDE_IS_LANGSERV_CLIENT (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+
+  object = json_object_new ();
+  json_object_set_member (object, "textDocument", create_text_document (buffer));
+
+  params = json_node_new (JSON_NODE_OBJECT);
+  json_node_set_object (params, g_steal_pointer (&object));
+
+  jsonrpc_client_notification_async (priv->rpc_client, "textDocument/didClose", params, NULL, NULL, NULL);
+}
+
+static void
+ide_langserv_client_buffer_manager_bind (IdeLangservClient *self,
+                                         IdeBufferManager  *buffer_manager,
+                                         EggSignalGroup    *signal_group)
+{
+  guint n_items;
+
+  g_assert (IDE_IS_LANGSERV_CLIENT (self));
+  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
+  g_assert (EGG_IS_SIGNAL_GROUP (signal_group));
+
+  n_items = g_list_model_get_n_items (G_LIST_MODEL (buffer_manager));
+
+  for (guint i = 0; i < n_items; i++)
+    {
+      g_autoptr(IdeBuffer) buffer = NULL;
+
+      buffer = g_list_model_get_item (G_LIST_MODEL (buffer_manager), i);
+      ide_langserv_client_buffer_loaded (self, buffer, buffer_manager);
+    }
+}
+
+static void
+ide_langserv_client_buffer_manager_unbind (IdeLangservClient *self,
+                                           EggSignalGroup    *signal_group)
+{
+  g_assert (IDE_IS_LANGSERV_CLIENT (self));
+  g_assert (EGG_IS_SIGNAL_GROUP (signal_group));
+
+  /* TODO: We need to track everything we've notified so that we
+   *       can notify the peer to release its resources.
+   */
+}
+
+static void
+ide_langserv_client_finalize (GObject *object)
+{
+  IdeLangservClient *self = (IdeLangservClient *)object;
+  IdeLangservClientPrivate *priv = ide_langserv_client_get_instance_private (self);
+
+  g_clear_object (&priv->rpc_client);
+  g_clear_object (&priv->buffer_manager_signals);
+
+  G_OBJECT_CLASS (ide_langserv_client_parent_class)->finalize (object);
+}
+
+static void
+ide_langserv_client_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  IdeLangservClient *self = IDE_LANGSERV_CLIENT (object);
+  IdeLangservClientPrivate *priv = ide_langserv_client_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_IO_STREAM:
+      g_value_set_object (value, priv->io_stream);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_langserv_client_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  IdeLangservClient *self = IDE_LANGSERV_CLIENT (object);
+  IdeLangservClientPrivate *priv = ide_langserv_client_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_IO_STREAM:
+      priv->io_stream = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_langserv_client_class_init (IdeLangservClientClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_langserv_client_finalize;
+  object_class->get_property = ide_langserv_client_get_property;
+  object_class->set_property = ide_langserv_client_set_property;
+
+  properties [PROP_IO_STREAM] =
+    g_param_spec_object ("io-stream",
+                         "IO Stream",
+                         "The GIOStream to communicate over",
+                         G_TYPE_IO_STREAM,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_langserv_client_init (IdeLangservClient *self)
+{
+  IdeLangservClientPrivate *priv = ide_langserv_client_get_instance_private (self);
+
+  priv->buffer_manager_signals = egg_signal_group_new (IDE_TYPE_BUFFER_MANAGER);
+
+  egg_signal_group_connect_object (priv->buffer_manager_signals,
+                                   "buffer-loaded",
+                                   G_CALLBACK (ide_langserv_client_buffer_loaded),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  egg_signal_group_connect_object (priv->buffer_manager_signals,
+                                   "buffer-unloaded",
+                                   G_CALLBACK (ide_langserv_client_buffer_unloaded),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->buffer_manager_signals,
+                           "bind",
+                           G_CALLBACK (ide_langserv_client_buffer_manager_bind),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->buffer_manager_signals,
+                           "unbind",
+                           G_CALLBACK (ide_langserv_client_buffer_manager_unbind),
+                           self,
+                           G_CONNECT_SWAPPED);
+}
+
+static void
+ide_langserv_client_notification (IdeLangservClient *self,
+                                  const gchar       *method,
+                                  JsonNode          *params,
+                                  JsonrpcClient     *rpc_client)
+{
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_LANGSERV_CLIENT (self));
+  g_assert (method != NULL);
+  g_assert (params != NULL);
+  g_assert (rpc_client != NULL);
+
+  IDE_TRACE_MSG ("Notification: %s", method);
+
+  IDE_EXIT;
+}
+
+IdeLangservClient *
+ide_langserv_client_new (IdeContext *context,
+                         GIOStream  *io_stream)
+{
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  return g_object_new (IDE_TYPE_LANGSERV_CLIENT,
+                       "context", context,
+                       "io-stream", io_stream,
+                       NULL);
+}
+
+void
+ide_langserv_client_start (IdeLangservClient *self)
+{
+  IdeLangservClientPrivate *priv = ide_langserv_client_get_instance_private (self);
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_LANGSERV_CLIENT (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+
+  if (G_IS_IO_STREAM (priv->io_stream) && IDE_IS_CONTEXT (context))
+    {
+      IdeBufferManager *buffer_manager = NULL;
+
+      priv->rpc_client = jsonrpc_client_new (priv->io_stream);
+
+      g_signal_connect_object (priv->rpc_client,
+                               "notification",
+                               G_CALLBACK (ide_langserv_client_notification),
+                               self,
+                               G_CONNECT_SWAPPED);
+
+      /*
+       * The first thing we need to do is initialize the client with information
+       * about our project. So that we will perform asynchronously here. It will
+       * also start our read loop.
+       */
+
+      buffer_manager = ide_context_get_buffer_manager (context);
+      egg_signal_group_set_target (priv->buffer_manager_signals, buffer_manager);
+    }
+  else
+    {
+      g_warning ("Cannot start %s due to misconfiguration.",
+                 G_OBJECT_TYPE_NAME (self));
+    }
+
+  IDE_EXIT;
+}
+
+void
+ide_langserv_client_stop (IdeLangservClient *self)
+{
+  IdeLangservClientPrivate *priv = ide_langserv_client_get_instance_private (self);
+
+  IDE_ENTRY;
+
+  g_return_if_fail (IDE_IS_LANGSERV_CLIENT (self));
+
+  if (priv->rpc_client != NULL)
+    {
+      jsonrpc_client_close_async (priv->rpc_client, NULL, NULL, NULL);
+      g_clear_object (&priv->rpc_client);
+    }
+
+  IDE_EXIT;
+}
diff --git a/libide/langserv/ide-langserv-client.h b/libide/langserv/ide-langserv-client.h
new file mode 100644
index 0000000..98950b5
--- /dev/null
+++ b/libide/langserv/ide-langserv-client.h
@@ -0,0 +1,51 @@
+/* ide-langserv-client.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_LANGSERV_CLIENT_H
+#define IDE_LANGSERV_CLIENT_H
+
+#include "ide-object.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LANGSERV_CLIENT (ide_langserv_client_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeLangservClient, ide_langserv_client, IDE, LANGSERV_CLIENT, IdeObject)
+
+struct _IdeLangservClientClass
+{
+  IdeObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+  gpointer _reserved5;
+  gpointer _reserved6;
+  gpointer _reserved7;
+  gpointer _reserved8;
+};
+
+IdeLangservClient *ide_langserv_client_new          (IdeContext        *context,
+                                                     GIOStream         *io_stream);
+void               ide_langserv_client_start        (IdeLangservClient *self);
+void               ide_langserv_client_stop         (IdeLangservClient *self);
+
+G_END_DECLS
+
+#endif /* IDE_LANGSERV_CLIENT_H */
diff --git a/libide/langserv/ide-langserv-diagnostic-provider.c 
b/libide/langserv/ide-langserv-diagnostic-provider.c
new file mode 100644
index 0000000..c7b5714
--- /dev/null
+++ b/libide/langserv/ide-langserv-diagnostic-provider.c
@@ -0,0 +1,246 @@
+/* ide-langserv-diagnostic-provider.c
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "ide-langserv-diagnostic-provider"
+
+#include <egg-signal-group.h>
+#include <json-glib/json-glib.h>
+
+#include "ide-context.h"
+#include "ide-debug.h"
+
+#include "buffers/ide-unsaved-file.h"
+#include "buffers/ide-unsaved-files.h"
+#include "files/ide-file.h"
+#include "langserv/ide-langserv-diagnostic-provider.h"
+
+typedef struct
+{
+  IdeLangservClient *client;
+  EggSignalGroup *signals;
+} IdeLangservDiagnosticProviderPrivate;
+
+static void diagnostic_provider_iface_init (IdeDiagnosticProviderInterface *iface);
+
+G_DEFINE_TYPE_EXTENDED (IdeLangservDiagnosticProvider, ide_langserv_diagnostic_provider, IDE_TYPE_OBJECT, 0,
+                        G_ADD_PRIVATE (IdeLangservDiagnosticProvider)
+                        G_IMPLEMENT_INTERFACE (IDE_TYPE_DIAGNOSTIC_PROVIDER, diagnostic_provider_iface_init))
+
+enum {
+  PROP_0,
+  PROP_CLIENT,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_langserv_diagnostic_provider_finalize (GObject *object)
+{
+  IdeLangservDiagnosticProvider *self = (IdeLangservDiagnosticProvider *)object;
+  IdeLangservDiagnosticProviderPrivate *priv = ide_langserv_diagnostic_provider_get_instance_private (self);
+
+  g_clear_object (&priv->client);
+  g_clear_object (&priv->signals);
+
+  G_OBJECT_CLASS (ide_langserv_diagnostic_provider_parent_class)->finalize (object);
+}
+
+static void
+ide_langserv_diagnostic_provider_get_property (GObject    *object,
+                                               guint       prop_id,
+                                               GValue     *value,
+                                               GParamSpec *pspec)
+{
+  IdeLangservDiagnosticProvider *self = IDE_LANGSERV_DIAGNOSTIC_PROVIDER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      g_value_set_object (value, ide_langserv_diagnostic_provider_get_client (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_langserv_diagnostic_provider_set_property (GObject      *object,
+                                               guint         prop_id,
+                                               const GValue *value,
+                                               GParamSpec   *pspec)
+{
+  IdeLangservDiagnosticProvider *self = IDE_LANGSERV_DIAGNOSTIC_PROVIDER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      ide_langserv_diagnostic_provider_set_client (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_langserv_diagnostic_provider_class_init (IdeLangservDiagnosticProviderClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_langserv_diagnostic_provider_finalize;
+  object_class->get_property = ide_langserv_diagnostic_provider_get_property;
+  object_class->set_property = ide_langserv_diagnostic_provider_set_property;
+
+  properties [PROP_CLIENT] =
+    g_param_spec_object ("client",
+                         "Client",
+                         "Client",
+                         IDE_TYPE_LANGSERV_CLIENT,
+                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_langserv_diagnostic_provider_init (IdeLangservDiagnosticProvider *self)
+{
+  IdeLangservDiagnosticProviderPrivate *priv = ide_langserv_diagnostic_provider_get_instance_private (self);
+
+  priv->signals = egg_signal_group_new (IDE_TYPE_LANGSERV_CLIENT);;
+
+}
+
+static void
+ide_langserv_diagnostic_provider_diagnose_cb (GObject      *object,
+                                              GAsyncResult *result,
+                                              gpointer      user_data)
+{
+  IdeLangservDiagnosticProvider *self = (IdeLangservDiagnosticProvider *)object;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_LANGSERV_CLIENT (self));
+  g_assert (G_IS_ASYNC_RESULT (result));
+
+  IDE_EXIT;
+}
+
+static void
+ide_langserv_diagnostic_provider_diagnose_async (IdeDiagnosticProvider *provider,
+                                                 IdeFile               *file,
+                                                 GCancellable          *cancellable,
+                                                 GAsyncReadyCallback    callback,
+                                                 gpointer               user_data)
+{
+  IdeLangservDiagnosticProvider *self = (IdeLangservDiagnosticProvider *)provider;
+  IdeLangservDiagnosticProviderPrivate *priv = ide_langserv_diagnostic_provider_get_instance_private (self);
+  g_autoptr(JsonObject) object = NULL;
+  g_autoptr(GTask) task = NULL;
+  IdeUnsavedFiles *unsaved_files;
+  IdeContext *context;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_LANGSERV_DIAGNOSTIC_PROVIDER (self));
+  g_assert (IDE_IS_FILE (file));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = g_task_new (self, cancellable, callback, user_data);
+  g_task_set_source_tag (task, ide_langserv_diagnostic_provider_diagnose_async);
+
+  if (priv->client == NULL)
+    {
+      g_task_return_new_error (task,
+                               G_IO_ERROR,
+                               G_IO_ERROR_NOT_SUPPORTED,
+                               "Improperly configured %s is missing IdeLangservClient",
+                               G_OBJECT_TYPE_NAME (self));
+      return;
+    }
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  unsaved_files = ide_context_get_unsaved_files (context);
+
+  IDE_EXIT;
+}
+
+static IdeDiagnostics *
+ide_langserv_diagnostic_provider_diagnose_finish (IdeDiagnosticProvider  *provider,
+                                                  GAsyncResult           *result,
+                                                  GError                **error)
+{
+  IdeLangservDiagnosticProvider *self = (IdeLangservDiagnosticProvider *)provider;
+  IdeDiagnostics *ret;
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_LANGSERV_DIAGNOSTIC_PROVIDER (self));
+  g_assert (G_IS_TASK (result));
+
+  ret = g_task_propagate_pointer (G_TASK (result), error);
+
+  IDE_RETURN (ret);
+}
+
+static void
+diagnostic_provider_iface_init (IdeDiagnosticProviderInterface *iface)
+{
+  iface->diagnose_async = ide_langserv_diagnostic_provider_diagnose_async;
+  iface->diagnose_finish = ide_langserv_diagnostic_provider_diagnose_finish;
+}
+
+IdeLangservDiagnosticProvider *
+ide_langserv_diagnostic_provider_new (void)
+{
+  return g_object_new (IDE_TYPE_LANGSERV_DIAGNOSTIC_PROVIDER, NULL);
+}
+
+/**
+ * ide_langserv_diagnostic_provider_get_client:
+ *
+ * Gets the client used by diagnostic provider.
+ *
+ * Returns: (nullable) (transfer none): An #IdeLangservClient or %NULL.
+ */
+IdeLangservClient *
+ide_langserv_diagnostic_provider_get_client (IdeLangservDiagnosticProvider *self)
+{
+  IdeLangservDiagnosticProviderPrivate *priv = ide_langserv_diagnostic_provider_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_LANGSERV_DIAGNOSTIC_PROVIDER (self), NULL);
+
+  return priv->client;
+}
+
+void
+ide_langserv_diagnostic_provider_set_client (IdeLangservDiagnosticProvider *self,
+                                             IdeLangservClient             *client)
+{
+  IdeLangservDiagnosticProviderPrivate *priv = ide_langserv_diagnostic_provider_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_LANGSERV_DIAGNOSTIC_PROVIDER (self));
+  g_return_if_fail (!client || IDE_IS_LANGSERV_CLIENT (client));
+
+  if (g_set_object (&priv->client, client))
+    {
+      egg_signal_group_set_target (priv->signals, client);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
+    }
+}
diff --git a/libide/langserv/ide-langserv-diagnostic-provider.h 
b/libide/langserv/ide-langserv-diagnostic-provider.h
new file mode 100644
index 0000000..4553782
--- /dev/null
+++ b/libide/langserv/ide-langserv-diagnostic-provider.h
@@ -0,0 +1,49 @@
+/* ide-langserv-diagnostic-provider.h
+ *
+ * Copyright (C) 2016 Christian Hergert <chergert redhat com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef IDE_LANGSERV_DIAGNOSTIC_PROVIDER_H
+#define IDE_LANGSERV_DIAGNOSTIC_PROVIDER_H
+
+#include "ide-object.h"
+
+#include "diagnostics/ide-diagnostic-provider.h"
+#include "langserv/ide-langserv-client.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LANGSERV_DIAGNOSTIC_PROVIDER (ide_langserv_diagnostic_provider_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (IdeLangservDiagnosticProvider, ide_langserv_diagnostic_provider, IDE, 
LANGSERV_DIAGNOSTIC_PROVIDER, IdeObject)
+
+struct _IdeLangservDiagnosticProviderClass
+{
+  IdeObjectClass parent_class;
+
+  gpointer _reserved1;
+  gpointer _reserved2;
+  gpointer _reserved3;
+  gpointer _reserved4;
+};
+
+IdeLangservClient *ide_langserv_diagnostic_provider_get_client (IdeLangservDiagnosticProvider *self);
+void               ide_langserv_diagnostic_provider_set_client (IdeLangservDiagnosticProvider *self,
+                                                                IdeLangservClient             *client);
+
+G_END_DECLS
+
+#endif /* IDE_LANGSERV_DIAGNOSTIC_PROVIDER_H */
diff --git a/plugins/rust-langserv/rust-langserv.plugin b/plugins/rust-langserv/rust-langserv.plugin
new file mode 100644
index 0000000..87f0264
--- /dev/null
+++ b/plugins/rust-langserv/rust-langserv.plugin
@@ -0,0 +1,13 @@
+[Plugin]
+Module=rust_langserv_plugin
+Loader=python3
+Name=Rust Language Server Integration
+Description=Provides auto-completion, diagnostics, and other IDE features
+Authors=Christian Hergert <christian hergert me>
+Copyright=Copyright © 2016 Christian Hergert
+Builtin=true
+X-Completion-Provider-Languages=rust
+X-Diagnostic-Provider-Languages=rust
+X-Highlighter-Languages=rust
+X-Symbol-Resolver-Provider-Languages=rust
+
diff --git a/plugins/rust-langserv/rust_langserv_plugin.py b/plugins/rust-langserv/rust_langserv_plugin.py
new file mode 100644
index 0000000..4f78c3f
--- /dev/null
+++ b/plugins/rust-langserv/rust_langserv_plugin.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python
+
+# rust_langserv_plugin.py
+#
+# Copyright (C) 2016 Christian Hergert <chergert redhat com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# 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/>.
+
+"""
+This plugin provides integration with the Rust Language Server.
+It builds off the generic language service components in libide
+by bridging them to our supervised Rust Language Server.
+"""
+
+import gi
+import os
+
+gi.require_version('Ide', '1.0')
+gi.require_version('GtkSource', '3.0')
+
+from gi.repository import GLib
+from gi.repository import Gio
+from gi.repository import GObject
+from gi.repository import GtkSource
+from gi.repository import Ide
+
+class RustService(Ide.Service):
+    client = None
+
+    __gsignals__ = {
+        # This signal is emitted whenever the `rls` has restarted. This happens
+        # at the start of the process as well as when the rls process crashes
+        # and we need to reload things.
+        'client-changed': (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (Ide.LangservClient,)),
+    }
+
+    def do_start(self):
+        """
+        Start the rust service which provides communication with the
+        Rust Language Server. We supervise our own instance of the
+        language server and restart it as necessary using the
+        Ide.SubprocessSupervisor.
+
+        Various extension points (diagnostics, symbol providers, etc) use
+        the RustService to access the rust components they need.
+        """
+        # Setup a launcher to spawn the rust language server
+        launcher = self._create_launcher()
+        launcher.setenv("SYS_ROOT", self._discover_sysroot())
+
+        # If rls was installed with Cargo, try to discover that
+        # to save the user having to update PATH.
+        path_to_rls = os.path.expanduser("~/.cargo/bin/rls")
+        if not os.path.exists(path_to_rls):
+            path_to_rls = "rls"
+
+        # Setup our Argv. We need to let RLS know we want to communicate
+        # with http. It will start a server on port 9000 by default.
+        launcher.push_args([path_to_rls, "--http"])
+
+        # Spawn our peer process and monitor it for
+        # crashes. We may need to restart it occasionally.
+        supervisor = Ide.SubprocessSupervisor()
+        supervisor.connect('spawned', self._rls_spawned)
+        supervisor.set_launcher(launcher)
+        supervisor.start()
+
+    def _rls_spawned(self, subprocess):
+        """
+        This callback is executed when the `rls` process is spawned.
+        We can use the stdin/stdout to create a channel for our
+        LangservClient.
+        """
+        stdin = subprocess.get_stdin_stream()
+        stdout = subprocess.get_stdout_stream()
+        io_stream = Gio.SimpleStream.new(stdout, stdin)
+
+        self.client = Ide.LangservClient.new(self.get_context(), io_stream)
+        self.client.start()
+
+    def _create_launcher(self):
+        """
+        Creates a launcher to be used by the rust service. This needs
+        to be run on the host because we do not currently bundle rust
+        inside our flatpak.
+
+        In the future, we might be able to rely on the runtime for
+        the tooling. Maybe even the program if flatpak-builder has
+        prebuilt our dependencies.
+        """
+        launcher = Ide.SubprocessLauncher()
+        launcher.set_flags(Gio.SubprocessFlags.STDIN_PIPE |
+                           Gio.SubprocessFlags.STDOUT_PIPE |
+                           Gio.SubprocessFlags.STDERR_SILENCE)
+        launcher.set_cwd(GLib.get_home_dir())
+        launcher.set_run_on_host(True)
+        return launcher
+
+    def _discover_sysroot(self):
+        """
+        The Rust Language Server needs to know where the sysroot is of
+        the Rust installation we are using. This is simple enough to
+        get, by using `rust --print sysroot` as the rust-language-server
+        documentation suggests.
+        """
+        launcher = self._create_launcher()()
+        launcher.push_args(['rustc', '--print', 'sysroot'])
+        subprocess = launcher.spawn_sync()
+        stdout = subprocess.communicate_utf8()
+        return stdout.strip()
+
+    @classmethod
+    def bind_client(klass, provider):
+        """
+        This helper tracks changes to our client as it might happen when
+        our `rls` process has crashed.
+        """
+        self = context.get_service_typed(type(RustService))
+        self.connect('client-changed': lambda client: provider.set_client(provider))
+        if self.client:
+            provider.set_client(self.client)
+
+class RustCompletionProvider(Ide.LangservCompletionProvider):
+    def do_set_context(self, context):
+        RustService.bind_client(self);
+
+class RustDiagnosticProvider(Ide.LangservDiagnosticProvider):
+    def do_set_context(self, context):
+        RustService.bind_client(self);
+
+class RustSymbolResolver(Ide.LangservSymbolResolver):
+    def do_set_context(self, context):
+        RustService.bind_client(self);
+
+class RustHighlighter(Ide.LangservHighlighter):
+    def do_set_context(self, context):
+        RustService.bind_client(self);
+


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