[gnome-builder/wip/chergert/langserv] langserv: start prototyping IdeLangserverClient
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder/wip/chergert/langserv] langserv: start prototyping IdeLangserverClient
- Date: Fri, 21 Oct 2016 03:07:30 +0000 (UTC)
commit 27dcd2427eb4053edc2d939519e61d86e743a1f5
Author: Christian Hergert <chergert redhat com>
Date: Thu Oct 20 20:07:17 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..cd8fdaa
--- /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]