[gnome-builder] lsp: Add IdeLspService



commit 1cd1ee8caba3548bdf3f22d0b056fc51cde80a34
Author: James Westman <james jwestman net>
Date:   Tue Dec 7 23:23:05 2021 -0600

    lsp: Add IdeLspService
    
    This is a base class containing much of the boilerplate for language
    server plugins. It has a "client" property that can be bound using
    "ide_lsp_service_class_bind_client".

 src/libide/lsp/ide-lsp-service.c | 407 +++++++++++++++++++++++++++++++++++++++
 src/libide/lsp/ide-lsp-service.h |  64 ++++++
 src/libide/lsp/libide-lsp.h      |   1 +
 src/libide/lsp/meson.build       |   2 +
 4 files changed, 474 insertions(+)
---
diff --git a/src/libide/lsp/ide-lsp-service.c b/src/libide/lsp/ide-lsp-service.c
new file mode 100644
index 000000000..e06b4a5bc
--- /dev/null
+++ b/src/libide/lsp/ide-lsp-service.c
@@ -0,0 +1,407 @@
+/* ide-lsp-service.c
+ *
+ * Copyright 2021 James Westman <james jwestman net>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "ide-lsp-service"
+
+#include "config.h"
+#include "ide-lsp-service.h"
+
+typedef struct
+{
+  IdeSubprocessSupervisor *supervisor;
+  IdeLspClient *client;
+  guint has_started : 1;
+  guint inherit_stderr : 1;
+} IdeLspServicePrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (IdeLspService, ide_lsp_service, IDE_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_CLIENT,
+  PROP_SUPERVISOR,
+  PROP_INHERIT_STDERR,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+ide_lsp_service_get_property (GObject    *object,
+                              guint       prop_id,
+                              GValue     *value,
+                              GParamSpec *pspec)
+{
+  IdeLspService *self = IDE_LSP_SERVICE (object);
+  IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      g_value_set_object (value, priv->client);
+      break;
+
+    case PROP_SUPERVISOR:
+      g_value_set_object (value, priv->supervisor);
+      break;
+
+    case PROP_INHERIT_STDERR:
+      g_value_set_boolean (value, priv->inherit_stderr);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_lsp_service_set_property (GObject      *object,
+                              guint         prop_id,
+                              const GValue *value,
+                              GParamSpec   *pspec)
+{
+  IdeLspService *self = IDE_LSP_SERVICE (object);
+
+  switch (prop_id)
+    {
+    case PROP_INHERIT_STDERR:
+      ide_lsp_service_set_inherit_stderr (self, g_value_get_boolean (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_lsp_service_destroy (IdeObject *object)
+{
+  IdeLspService *self = (IdeLspService *)object;
+  IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
+
+  ide_lsp_service_stop (self);
+
+  g_clear_object (&priv->supervisor);
+  g_clear_object (&priv->client);
+
+  IDE_OBJECT_CLASS (ide_lsp_service_parent_class)->destroy (object);
+}
+
+G_NORETURN static void
+ide_lsp_service_real_configure_client (IdeLspService *self,
+                                       IdeLspClient  *client)
+{
+  g_assert_not_reached ();
+}
+
+static void
+ide_lsp_service_real_configure_launcher (IdeLspService         *self,
+                                         IdeSubprocessLauncher *client)
+{
+}
+
+static void
+ide_lsp_service_real_configure_supervisor (IdeLspService           *self,
+                                           IdeSubprocessSupervisor *client)
+{
+}
+
+static void
+ide_lsp_service_class_init (IdeLspServiceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  IdeObjectClass *ide_object_class = IDE_OBJECT_CLASS (klass);
+  IdeLspServiceClass *service_class = IDE_LSP_SERVICE_CLASS (klass);
+
+  object_class->get_property = ide_lsp_service_get_property;
+  object_class->set_property = ide_lsp_service_set_property;
+
+  ide_object_class->destroy = ide_lsp_service_destroy;
+
+  service_class->configure_client = ide_lsp_service_real_configure_client;
+  service_class->configure_launcher = ide_lsp_service_real_configure_launcher;
+  service_class->configure_supervisor = ide_lsp_service_real_configure_supervisor;
+
+  /**
+   * IdeLspService:client:
+   *
+   * The [class@LspClient] provided by the service, or %NULL if it has not been started yet.
+   */
+  properties[PROP_CLIENT] =
+    g_param_spec_object ("client",
+                         "Client",
+                         "Client",
+                         IDE_TYPE_LSP_CLIENT,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * IdeLspService:supervisor:
+   *
+   * The [class@SubprocessSupervisor] that manages the language server process, or %NULL if the
+   * service is not running.
+   */
+  properties[PROP_SUPERVISOR] =
+    g_param_spec_object ("supervisor",
+                         "Supervisor",
+                         "Supervisor",
+                         IDE_TYPE_LSP_CLIENT,
+                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+  /**
+   * IdeLspService:inherit-stderr:
+   *
+   * If inherit-stderr is enabled, the language server process's stderr is passed through to Builder's.
+   */
+  properties[PROP_INHERIT_STDERR] =
+    g_param_spec_boolean ("inherit-stderr",
+                          "Inherit stderr",
+                          "Inherit stderr",
+                          FALSE,
+                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_lsp_service_init (IdeLspService *self)
+{
+}
+
+/**
+ * ide_lsp_service_get_inherit_stderr:
+ * @self: a [class@LspService]
+ *
+ * Gets whether the language server process's stderr output should be passed to Builder's.
+ *
+ * Returns: %TRUE if the subprocess inherits stderr, otherwise %FALSE
+ */
+gboolean
+ide_lsp_service_get_inherit_stderr (IdeLspService *self)
+{
+  IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_MAIN_THREAD (), FALSE);
+  g_return_val_if_fail (IDE_IS_LSP_SERVICE (self), FALSE);
+
+  return priv->inherit_stderr;
+}
+
+/**
+ * ide_lsp_service_set_inherit_stderr:
+ * @self: a [class@LspService]
+ * @inherit_stderr: %TRUE to enable stderr, %FALSE to disable it
+ *
+ * Gets whether the language server process's stderr output should be passed to Builder's.
+ */
+void
+ide_lsp_service_set_inherit_stderr (IdeLspService *self,
+                                    gboolean       inherit_stderr)
+{
+  IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_LSP_SERVICE (self));
+
+  if (priv->inherit_stderr == !!inherit_stderr)
+    return;
+
+  priv->inherit_stderr = !!inherit_stderr;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_INHERIT_STDERR]);
+}
+
+static void
+on_supervisor_spawned (IdeLspService           *self,
+                       IdeSubprocess           *subprocess,
+                       IdeSubprocessSupervisor *supervisor)
+{
+  IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
+  IdeLspServiceClass *klass = IDE_LSP_SERVICE_GET_CLASS (self);
+  g_autoptr(GIOStream) iostream = NULL;
+  g_autoptr(IdeLspClient) client = NULL;
+  g_autoptr(GInputStream) to_stdout = NULL;
+  g_autoptr(GOutputStream) to_stdin = NULL;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_LSP_SERVICE (self));
+  g_assert (IDE_IS_SUBPROCESS (subprocess));
+  g_assert (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
+
+  to_stdin = ide_subprocess_get_stdin_pipe (subprocess);
+  to_stdout = ide_subprocess_get_stdout_pipe (subprocess);
+  iostream = g_simple_io_stream_new (to_stdout, to_stdin);
+
+  if (priv->client != NULL)
+    {
+      ide_lsp_client_stop (priv->client);
+      ide_object_destroy (IDE_OBJECT (priv->client));
+    }
+
+  client = ide_lsp_client_new (iostream);
+  ide_object_append (IDE_OBJECT (self), IDE_OBJECT (client));
+
+  klass->configure_client (self, client);
+
+  ide_lsp_client_start (client);
+
+  priv->client = g_steal_pointer (&client);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CLIENT]);
+}
+
+static void
+ensure_started (IdeLspService *self,
+                IdeContext    *context)
+{
+  IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
+  IdeLspServiceClass *klass = IDE_LSP_SERVICE_GET_CLASS (self);
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  GSubprocessFlags flags;
+  g_autoptr(GFile) workdir = NULL;
+  g_autofree char *workdir_path = NULL;
+  g_autoptr(IdeSubprocessSupervisor) supervisor = NULL;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_LSP_SERVICE (self));
+  g_assert (IDE_IS_CONTEXT (context));
+
+  if (priv->has_started)
+    return;
+
+  priv->has_started = TRUE;
+
+  g_assert (priv->supervisor == NULL);
+  g_assert (priv->client == NULL);
+
+  flags = G_SUBPROCESS_FLAGS_STDIN_PIPE | G_SUBPROCESS_FLAGS_STDOUT_PIPE;
+  if (!priv->inherit_stderr)
+    flags |= G_SUBPROCESS_FLAGS_STDERR_SILENCE;
+  launcher = ide_subprocess_launcher_new (flags);
+
+  ide_subprocess_launcher_set_clear_env (launcher, FALSE);
+
+  workdir = ide_context_ref_workdir (context);
+  workdir_path = g_file_get_path (workdir);
+  ide_subprocess_launcher_set_cwd (launcher, workdir_path);
+
+  klass->configure_launcher (self, launcher);
+
+  supervisor = ide_subprocess_supervisor_new ();
+  ide_subprocess_supervisor_set_launcher (supervisor, launcher);
+  g_signal_connect_object (supervisor,
+                           "spawned",
+                           G_CALLBACK (on_supervisor_spawned),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  klass->configure_supervisor (self, supervisor);
+
+  ide_subprocess_supervisor_start (supervisor);
+
+  priv->supervisor = g_steal_pointer (&supervisor);
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SUPERVISOR]);
+}
+
+/**
+ * ide_lsp_service_stop:
+ * @self: a [class@LspService]
+ *
+ * Stops the service and its associated process, if any. This will set [property@LspService:client]
+ * to %NULL.
+ */
+void
+ide_lsp_service_stop (IdeLspService *self)
+{
+  gboolean notify_client = FALSE;
+  gboolean notify_supervisor = FALSE;
+  IdeLspServicePrivate *priv = ide_lsp_service_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_LSP_SERVICE (self));
+
+  if (priv->client != NULL)
+    {
+      ide_lsp_client_stop (priv->client);
+      ide_object_destroy (IDE_OBJECT (priv->client));
+      priv->client = NULL;
+      notify_client = TRUE;
+    }
+
+  if (priv->supervisor != NULL)
+    {
+      ide_subprocess_supervisor_stop (priv->supervisor);
+      g_clear_object (&priv->supervisor);
+      notify_supervisor = TRUE;
+    }
+
+  priv->has_started = FALSE;
+
+  if (notify_client)
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_CLIENT]);
+  if (notify_supervisor)
+    g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SUPERVISOR]);
+}
+
+/**
+ * ide_lsp_service_restart:
+ * @self: a [class@LspService]
+ *
+ * Restarts the service and its associated process.
+ */
+void
+ide_lsp_service_restart (IdeLspService *self)
+{
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_LSP_SERVICE (self));
+  g_return_if_fail (!ide_object_in_destruction (IDE_OBJECT (self)));
+
+  ide_lsp_service_stop (self);
+  ensure_started (self, ide_object_get_context (IDE_OBJECT (self)));
+}
+
+/**
+ * ide_lsp_service_class_bind_client:
+ * @klass: a [class@LspService] class structure
+ * @provider: an [class@Object]
+ *
+ * Binds the "client" property of @property to its context's instance of @klass. If the language
+ * server is not running yet, it will be started.
+ */
+void
+ide_lsp_service_class_bind_client (IdeLspServiceClass *klass,
+                                   IdeObject          *provider)
+{
+  IdeContext *context;
+  g_autoptr(IdeLspService) service = NULL;
+  GParamSpec *pspec;
+
+  g_return_if_fail (IDE_IS_MAIN_THREAD ());
+  g_return_if_fail (IDE_IS_LSP_SERVICE_CLASS (klass));
+  g_return_if_fail (IDE_IS_OBJECT (provider));
+
+  pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (provider), "client");
+  g_return_if_fail (pspec != NULL && g_type_is_a (pspec->value_type, IDE_TYPE_LSP_CLIENT));
+
+  context = ide_object_get_context (provider);
+  g_return_if_fail (IDE_IS_CONTEXT (context));
+  service = ide_object_ensure_child_typed (IDE_OBJECT (context), G_OBJECT_CLASS_TYPE (klass));
+
+  ensure_started (service, context);
+
+  g_object_bind_property (service, "client", provider, "client", G_BINDING_SYNC_CREATE);
+}
diff --git a/src/libide/lsp/ide-lsp-service.h b/src/libide/lsp/ide-lsp-service.h
new file mode 100644
index 000000000..2b8654d76
--- /dev/null
+++ b/src/libide/lsp/ide-lsp-service.h
@@ -0,0 +1,64 @@
+/* ide-lsp-service.h
+ *
+ * Copyright 2021 James Westman <james jwestman net>
+ *
+ * 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#if !defined (IDE_LSP_INSIDE) && !defined (IDE_LSP_COMPILATION)
+# error "Only <libide-lsp.h> can be included directly."
+#endif
+
+#include <libide-code.h>
+#include "ide-lsp-client.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LSP_SERVICE (ide_lsp_service_get_type())
+
+IDE_AVAILABLE_IN_42
+G_DECLARE_DERIVABLE_TYPE (IdeLspService, ide_lsp_service, IDE, LSP_SERVICE, IdeObject)
+
+struct _IdeLspServiceClass
+{
+  IdeObjectClass parent_class;
+
+  void (*configure_launcher)   (IdeLspService         *self,
+                                IdeSubprocessLauncher *launcher);
+  void (*configure_supervisor) (IdeLspService           *self,
+                                IdeSubprocessSupervisor *supervisor);
+  void (*configure_client)     (IdeLspService *self,
+                                IdeLspClient  *client);
+};
+
+IDE_AVAILABLE_IN_42
+void     ide_lsp_service_class_bind_client  (IdeLspServiceClass *klass,
+                                             IdeObject          *provider);
+
+IDE_AVAILABLE_IN_42
+void     ide_lsp_service_set_inherit_stderr (IdeLspService *self,
+                                             gboolean       dev_mode);
+IDE_AVAILABLE_IN_42
+gboolean ide_lsp_service_get_inherit_stderr (IdeLspService *self);
+
+IDE_AVAILABLE_IN_42
+void     ide_lsp_service_stop               (IdeLspService * self);
+IDE_AVAILABLE_IN_42
+void     ide_lsp_service_restart            (IdeLspService *self);
+
+G_END_DECLS
diff --git a/src/libide/lsp/libide-lsp.h b/src/libide/lsp/libide-lsp.h
index 61e06bb35..8779ea553 100644
--- a/src/libide/lsp/libide-lsp.h
+++ b/src/libide/lsp/libide-lsp.h
@@ -39,6 +39,7 @@
 #include "ide-lsp-hover-provider.h"
 #include "ide-lsp-rename-provider.h"
 #include "ide-lsp-search-provider.h"
+#include "ide-lsp-service.h"
 #include "ide-lsp-symbol-node.h"
 #include "ide-lsp-symbol-resolver.h"
 #include "ide-lsp-symbol-tree.h"
diff --git a/src/libide/lsp/meson.build b/src/libide/lsp/meson.build
index 3e0841d1f..7257f8bf7 100644
--- a/src/libide/lsp/meson.build
+++ b/src/libide/lsp/meson.build
@@ -20,6 +20,7 @@ libide_lsp_public_headers = [
   'ide-lsp-hover-provider.h',
   'ide-lsp-rename-provider.h',
   'ide-lsp-search-provider.h',
+  'ide-lsp-service.h',
   'ide-lsp-symbol-node.h',
   'ide-lsp-symbol-resolver.h',
   'ide-lsp-symbol-tree.h',
@@ -53,6 +54,7 @@ libide_lsp_public_sources = [
   'ide-lsp-rename-provider.c',
   'ide-lsp-search-provider.c',
   'ide-lsp-search-result.c',
+  'ide-lsp-service.c',
   'ide-lsp-symbol-node.c',
   'ide-lsp-symbol-resolver.c',
   'ide-lsp-symbol-tree.c',


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