[gnome-builder] rust-analyzer: use rust-analyzer from SDKs when possible



commit 5fc678919ea7f487d1d2669004753a3f9f1f908d
Author: Christian Hergert <chergert redhat com>
Date:   Wed Mar 3 21:18:03 2021 -0800

    rust-analyzer: use rust-analyzer from SDKs when possible
    
    This is a fairly large rework of this code, so some things are better,
    some things fell through the cracks.
    
    First, what was removed:
    
     * Downloading and installing of rust-analyzer
    
    In most cases this is fine. Because with SDKs from Flathub, we will already
    have the rust-analyzer available now that SDK extensions are automatically
    resolved and installed.
    
    For cases where people are developing on their host, it's almost certain
    they already have rust-analyzer, so no need for us to manage installing
    that with all the annoyances that go with it.
    
    The RustAnalyzerService now defers the work of locating the rust-analyzer
    binary as well as creating a launcher with all the proper environment
    setup to the new RustAnalyzerPipelineAddin. This gets updated every time
    the pipeline changes so the RustAnalyzerService will track that and
    replace the launcher on the subprocess supervisor.
    
    When the subprocess supervisor spawns a new rust-analyzer, the LSP client
    is replaced with one talking to the new daemon over stdin/stdout.
    
    Initial startup of rust-analyzer was quite slow which appeared to be
    because of double compilation. There isn't an obvious way to specify how
    to use shared build directory when you're doing out of tree builds, but
    setting CARGO_TARGET_DIR and CARGO_HOME can help us when we're building
    GNOME application templates. So basically we just check to see if we're
    building with meson and override those.
    
    It's not clear to me if there is still double compilation going on, but
    at least I don't get a target/ directory in both builddir and the source
    tree now. So at least there is that.
    
    Our Cargo plugin alread sets some of these variables, so they can be
    inheritted by using a launcher from the build pipeline.
    
    Tested with a GNOME application template and Cargo template, both seemed
    to work fine.
    
    If you have time, please help test this before GNOME 40.
    
    Signed-off-by: Christian Hergert <chergert redhat com>

 src/plugins/rust-analyzer/meson.build              |   3 +-
 .../rust-analyzer-completion-provider.c            |   2 +-
 .../rust-analyzer-diagnostic-provider.c            |   8 +-
 .../rust-analyzer/rust-analyzer-formatter.c        |   6 +-
 .../rust-analyzer/rust-analyzer-highlighter.c      |   6 +-
 .../rust-analyzer/rust-analyzer-hover-provider.c   |   8 +-
 .../rust-analyzer/rust-analyzer-pipeline-addin.c   | 316 ++++++++++
 ...r-transfer.h => rust-analyzer-pipeline-addin.h} |  12 +-
 src/plugins/rust-analyzer/rust-analyzer-plugin.c   |  15 +-
 .../rust-analyzer/rust-analyzer-rename-provider.c  |   4 +-
 .../rust-analyzer/rust-analyzer-search-provider.c  |   2 +-
 src/plugins/rust-analyzer/rust-analyzer-service.c  | 656 +++++++++------------
 src/plugins/rust-analyzer/rust-analyzer-service.h  |  46 +-
 .../rust-analyzer/rust-analyzer-symbol-resolver.c  |   4 +-
 src/plugins/rust-analyzer/rust-analyzer-transfer.c | 201 -------
 .../rust-analyzer/rust-analyzer-workbench-addin.c  | 163 -----
 .../rust-analyzer/rust-analyzer-workbench-addin.h  |  31 -
 17 files changed, 677 insertions(+), 806 deletions(-)
---
diff --git a/src/plugins/rust-analyzer/meson.build b/src/plugins/rust-analyzer/meson.build
index 6fc0afd18..17f3a78d2 100644
--- a/src/plugins/rust-analyzer/meson.build
+++ b/src/plugins/rust-analyzer/meson.build
@@ -10,14 +10,13 @@ plugins_sources += files([
   'rust-analyzer-formatter.c',
   'rust-analyzer-highlighter.c',
   'rust-analyzer-hover-provider.c',
+  'rust-analyzer-pipeline-addin.c',
   'rust-analyzer-plugin.c',
   'rust-analyzer-preferences-addin.c',
   'rust-analyzer-rename-provider.c',
   'rust-analyzer-search-provider.c',
   'rust-analyzer-service.c',
   'rust-analyzer-symbol-resolver.c',
-  'rust-analyzer-transfer.c',
-  'rust-analyzer-workbench-addin.c',
 ])
 
 plugin_rust_analyzer_resources = gnome.compile_resources(
diff --git a/src/plugins/rust-analyzer/rust-analyzer-completion-provider.c 
b/src/plugins/rust-analyzer/rust-analyzer-completion-provider.c
index 49cf51a23..92f31a18e 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-completion-provider.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-completion-provider.c
@@ -52,7 +52,7 @@ rust_analyzer_completion_provider_load (IdeCompletionProvider *self,
   g_assert (RUST_IS_ANALYZER_COMPLETION_PROVIDER (self));
   g_assert (IDE_IS_CONTEXT (context));
 
-  service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE);
+  service = rust_analyzer_service_from_context (context);
   g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
   rust_analyzer_service_ensure_started (service);
 }
diff --git a/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.c 
b/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.c
index 880e2605a..55dc1391e 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.c
@@ -46,15 +46,15 @@ rust_analyzer_diagnostic_provider_init (RustAnalyzerDiagnosticProvider *self)
 static void
 rust_analyzer_diagnostic_provider_load (IdeDiagnosticProvider *self)
 {
-  RustAnalyzerService *service = NULL;
-  IdeContext *context = NULL;
+  RustAnalyzerService *service;
+  IdeContext *context;
 
   g_assert (RUST_IS_ANALYZER_DIAGNOSTIC_PROVIDER (self));
 
   context = ide_object_get_context (IDE_OBJECT (self));
-  service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE);
-  rust_analyzer_service_ensure_started (service);
+  service = rust_analyzer_service_from_context (context);
   g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
+  rust_analyzer_service_ensure_started (service);
 }
 
 static void
diff --git a/src/plugins/rust-analyzer/rust-analyzer-formatter.c 
b/src/plugins/rust-analyzer/rust-analyzer-formatter.c
index e713b6926..e8b441dfb 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-formatter.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-formatter.c
@@ -46,13 +46,13 @@ rust_analyzer_formatter_init (RustAnalyzerFormatter *self)
 static void
 rust_analyzer_formatter_load (IdeFormatter *self)
 {
-  IdeContext *context = NULL;
-  RustAnalyzerService *service = NULL;
+  RustAnalyzerService *service;
+  IdeContext *context;
 
   g_assert (RUST_IS_ANALYZER_FORMATTER (self));
 
   context = ide_object_get_context (IDE_OBJECT (self));
-  service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE);
+  service = rust_analyzer_service_from_context (context);
   g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
   rust_analyzer_service_ensure_started (service);
 }
diff --git a/src/plugins/rust-analyzer/rust-analyzer-highlighter.c 
b/src/plugins/rust-analyzer/rust-analyzer-highlighter.c
index 8e728aa2d..603f6b864 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-highlighter.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-highlighter.c
@@ -18,6 +18,10 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
+#define G_LOG_DOMAIN "rust-analyzer-highlighter"
+
+#include "config.h"
+
 #include "rust-analyzer-highlighter.h"
 #include "rust-analyzer-service.h"
 
@@ -52,7 +56,7 @@ rust_analyzer_highlighter_load (IdeHighlighter *self)
   g_assert (RUST_IS_ANALYZER_HIGHLIGHTER (self));
 
   context = ide_object_get_context (IDE_OBJECT (self));
-  service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE);
+  service = rust_analyzer_service_from_context (context);
   g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
   rust_analyzer_service_ensure_started (service);
 }
diff --git a/src/plugins/rust-analyzer/rust-analyzer-hover-provider.c 
b/src/plugins/rust-analyzer/rust-analyzer-hover-provider.c
index dd3214fb4..00d194d0f 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-hover-provider.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-hover-provider.c
@@ -38,9 +38,13 @@ rust_analyzer_hover_provider_prepare (IdeLspHoverProvider *self)
 
   g_assert (RUST_IS_ANALYZER_HOVER_PROVIDER (self));
 
+  g_object_set (self,
+                "category", "Rust",
+                "priority", 200,
+                NULL);
+
   context = ide_object_get_context (IDE_OBJECT (self));
-  service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE);
-  g_object_set (self, "category", "Rust", "priority", 200, NULL);
+  service = rust_analyzer_service_from_context (context);
   g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
   rust_analyzer_service_ensure_started (service);
 }
diff --git a/src/plugins/rust-analyzer/rust-analyzer-pipeline-addin.c 
b/src/plugins/rust-analyzer/rust-analyzer-pipeline-addin.c
new file mode 100644
index 000000000..b0b412498
--- /dev/null
+++ b/src/plugins/rust-analyzer/rust-analyzer-pipeline-addin.c
@@ -0,0 +1,316 @@
+/* rust-analyzer-pipeline-addin.c
+ *
+ * Copyright 2021 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "rust-analyzer-pipeline-addin"
+
+#include "config.h"
+
+#include <libide-gui.h>
+
+#include "rust-analyzer-pipeline-addin.h"
+
+#if 0
+# define DEV_MODE
+#endif
+
+struct _RustAnalyzerPipelineAddin
+{
+  IdeObject    parent_instance;
+  IdePipeline *pipeline;
+  gchar       *path;
+  gchar       *cargo_home;
+};
+
+static void pipeline_addin_iface_init (IdePipelineAddinInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (RustAnalyzerPipelineAddin, rust_analyzer_pipeline_addin, IDE_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_PIPELINE_ADDIN, pipeline_addin_iface_init))
+
+static void
+rust_analyzer_pipeline_addin_class_init (RustAnalyzerPipelineAddinClass *klass)
+{
+}
+
+static void
+rust_analyzer_pipeline_addin_init (RustAnalyzerPipelineAddin *self)
+{
+}
+
+static gboolean
+is_meson_project (RustAnalyzerPipelineAddin *self)
+{
+  IdeBuildSystem *build_system;
+  IdeContext *context;
+
+  g_assert (RUST_IS_ANALYZER_PIPELINE_ADDIN (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  build_system = ide_build_system_from_context (context);
+
+  return strstr (G_OBJECT_TYPE_NAME (build_system), "Meson") != NULL;
+}
+
+static GFile *
+find_cargo_toml_from_file (GFile *file)
+{
+  g_autoptr(GFile) parent = g_file_get_parent (file);
+  g_autoptr(GFile) cargo_toml = g_file_get_child (parent, "Cargo.toml");
+  g_autofree gchar *name = g_file_get_basename (file);
+
+  if (g_strcmp0 (name, "Cargo.toml") == 0)
+    return g_steal_pointer (&parent);
+
+  if (g_file_query_exists (cargo_toml, NULL))
+    return g_steal_pointer (&cargo_toml);
+
+  if (parent != NULL)
+    return find_cargo_toml_from_file (parent);
+
+  return NULL;
+}
+
+static void
+rust_analyzer_pipeline_addin_discover_workdir_cb (GtkWidget *widget,
+                                                  gpointer   user_data)
+{
+  IdePage *page = (IdePage *)widget;
+  GFile **workdir = user_data;
+  g_autoptr(GFile) file = NULL;
+  g_autoptr(GFile) cargo_toml = NULL;
+
+  g_assert (IDE_IS_PAGE (page));
+  g_assert (workdir != NULL);
+  g_assert (*workdir == NULL || G_IS_FILE (*workdir));
+
+  if (!(file = ide_page_get_file_or_directory (page)) || !g_file_is_native (file))
+    return;
+
+  if ((cargo_toml = find_cargo_toml_from_file (file)))
+    {
+      g_autoptr(GFile) parent = g_file_get_parent (cargo_toml);
+
+      if (parent == NULL)
+        return;
+
+      if (*workdir == NULL || g_file_has_prefix (*workdir, parent))
+        g_set_object (workdir, parent);
+    }
+}
+
+void
+rust_analyzer_pipeline_addin_discover_workdir (RustAnalyzerPipelineAddin  *self,
+                                               gchar                     **src_workdir,
+                                               gchar                     **build_workdir)
+{
+  g_autoptr(IdeContext) context = NULL;
+  g_autofree gchar *relative = NULL;
+  g_autofree gchar *ret = NULL;
+  g_autoptr(GFile) project_workdir = NULL;
+  g_autoptr(GFile) workdir = NULL;
+  g_autoptr(GFile) cargo_toml = NULL;
+  IdeWorkbench *workbench;
+
+  IDE_ENTRY;
+
+  g_assert (RUST_IS_ANALYZER_PIPELINE_ADDIN (self));
+
+  if (!(context = ide_object_ref_context (IDE_OBJECT (self))) ||
+      !(workbench = ide_workbench_from_context (context)))
+    {
+      *src_workdir = NULL;
+      *build_workdir = NULL;
+      IDE_EXIT;
+    }
+
+  project_workdir = ide_context_ref_workdir (context);
+
+  /* Use project root as workdir if it contains Cargo.toml, otherwise
+   * try to look at open files and locate a workdir from the topmost
+   * directory containing a Cargo.toml.
+   */
+  cargo_toml = g_file_get_child (project_workdir, "Cargo.toml");
+  if (!g_file_query_exists (cargo_toml, NULL))
+    ide_workbench_foreach_page (workbench,
+                                rust_analyzer_pipeline_addin_discover_workdir_cb,
+                                &workdir);
+  if (workdir == NULL)
+    workdir = g_object_ref (project_workdir);
+
+  /* Now that we found what would be the workdir from the source
+   * tree, we want to translate that into the build tree so that
+   * we increase the chance that rust-analyzer will reuse artifacts
+   * from building the actual project.
+   *
+   * For example, it places a bunch of data in target/, but we don't
+   * want to polute the source tree with that, we want it to end up
+   * in the $builddir/target where meson/cargo would put it while
+   * actually building the project.
+   */
+  if (g_file_equal (project_workdir, workdir))
+    ret = g_strdup (ide_pipeline_get_builddir (self->pipeline));
+  else if ((relative = g_file_get_relative_path (project_workdir, workdir)))
+    ret = ide_pipeline_build_builddir_path (self->pipeline, relative, NULL);
+  else
+    ret = g_file_get_path (workdir);
+
+  *src_workdir = g_file_get_path (workdir);
+  *build_workdir = g_steal_pointer (&ret);
+
+  IDE_TRACE_MSG ("rust-analyzer workdir=%s builddir=%s", *src_workdir, *build_workdir);
+
+  IDE_EXIT;
+}
+
+IdeSubprocessLauncher *
+rust_analyzer_pipeline_addin_create_launcher (RustAnalyzerPipelineAddin *self)
+{
+  GSubprocessFlags flags = G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDIN_PIPE | 
G_SUBPROCESS_FLAGS_STDERR_SILENCE;
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  g_autofree gchar *src_workdir = NULL;
+  g_autofree gchar *build_workdir = NULL;
+  g_autofree gchar *cargo_target_dir = NULL;
+  g_autofree gchar *cargo_home = NULL;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail (RUST_IS_ANALYZER_PIPELINE_ADDIN (self), NULL);
+  g_return_val_if_fail (self->cargo_home == NULL || self->path != NULL, NULL);
+
+  if (self->path == NULL)
+    IDE_RETURN (NULL);
+
+  rust_analyzer_pipeline_addin_discover_workdir (self, &src_workdir, &build_workdir);
+  if (src_workdir == NULL || build_workdir == NULL)
+    IDE_RETURN (NULL);
+
+#ifdef DEV_MODE
+  flags &= ~G_SUBPROCESS_FLAGS_STDERR_SILENCE;
+#endif
+
+  /* If cargo_home is set, then we are executing on the host */
+  if (self->cargo_home != NULL)
+    {
+      g_debug ("Using rust-analyzer from home directory");
+
+      launcher = ide_subprocess_launcher_new (flags);
+      ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
+      ide_subprocess_launcher_set_clear_env (launcher, TRUE);
+      ide_subprocess_launcher_setenv (launcher, "CARGO_HOME", self->cargo_home, TRUE);
+    }
+  else
+    {
+      g_debug ("Using rust-analyzer from runtime");
+
+      if (!(launcher = ide_pipeline_create_launcher (self->pipeline, NULL)))
+        return NULL;
+
+      /* Unset CARGO_HOME if it's set by the runtime */
+      ide_subprocess_launcher_set_flags (launcher, flags);
+      ide_subprocess_launcher_set_clear_env (launcher, TRUE);
+    }
+
+  /* In Builder meson projects that use Cargo, we use target/cargo-home as
+   * a convention within the builddir. This is just convention, but it's the
+   * one thing we got right now to work off os.
+   */
+  if (is_meson_project (self))
+    {
+      cargo_target_dir = g_build_filename (build_workdir, "target", NULL);
+      cargo_home = g_build_filename (build_workdir, "cargo-home", NULL);
+    }
+  else
+    {
+      cargo_target_dir = g_strdup (build_workdir);
+      cargo_home = g_strdup (self->cargo_home);
+    }
+
+  if (cargo_home != NULL)
+    ide_subprocess_launcher_setenv (launcher, "CARGO_HOME", cargo_home, FALSE);
+  ide_subprocess_launcher_setenv (launcher, "CARGO_TARGET_DIR", cargo_target_dir, FALSE);
+
+#ifdef DEV_MODE
+  ide_subprocess_launcher_setenv (launcher, "RA_LOG", "rust_analyzer=info", TRUE);
+#endif
+
+  ide_subprocess_launcher_push_argv (launcher, self->path);
+  ide_subprocess_launcher_set_cwd (launcher, src_workdir);
+
+  return g_steal_pointer (&launcher);
+}
+
+static void
+set_path (RustAnalyzerPipelineAddin *self,
+          const gchar               *path,
+          const gchar               *cargo_home)
+{
+  if (g_strcmp0 (path, self->path) != 0)
+    {
+      g_free (self->path);
+      self->path = g_strdup (path);
+    }
+
+  if (g_strcmp0 (cargo_home, self->cargo_home) != 0)
+    {
+      g_free (self->cargo_home);
+      self->cargo_home = g_strdup (cargo_home);
+    }
+}
+
+static void
+rust_analyzer_pipeline_addin_prepare (IdePipelineAddin *addin,
+                                      IdePipeline      *pipeline)
+{
+  RustAnalyzerPipelineAddin *self = (RustAnalyzerPipelineAddin *)addin;
+  g_autoptr(GFile) cargo_home = NULL;
+  g_autoptr(GFile) file = NULL;
+
+  IDE_ENTRY;
+
+  g_assert (RUST_IS_ANALYZER_PIPELINE_ADDIN (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+
+  self->pipeline = pipeline;
+
+  if (ide_pipeline_contains_program_in_path (pipeline, "rust-analyzer", NULL))
+    {
+      set_path (self, "rust-analyzer", NULL);
+      IDE_EXIT;
+    }
+
+  cargo_home = g_file_new_build_filename (g_get_home_dir (), ".cargo", NULL);
+  file = g_file_get_child (cargo_home, "bin" G_DIR_SEPARATOR_S "rust-analyzer");
+
+  if (g_file_query_exists (file, NULL))
+    {
+      set_path (self, g_file_peek_path (file), g_file_peek_path (cargo_home));
+      IDE_EXIT;
+    }
+
+  set_path (self, NULL, NULL);
+
+  IDE_EXIT;
+}
+
+static void
+pipeline_addin_iface_init (IdePipelineAddinInterface *iface)
+{
+  iface->prepare = rust_analyzer_pipeline_addin_prepare;
+}
+
diff --git a/src/plugins/rust-analyzer/rust-analyzer-transfer.h 
b/src/plugins/rust-analyzer/rust-analyzer-pipeline-addin.h
similarity index 63%
rename from src/plugins/rust-analyzer/rust-analyzer-transfer.h
rename to src/plugins/rust-analyzer/rust-analyzer-pipeline-addin.h
index 7d45fccc2..4b0f016f4 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-transfer.h
+++ b/src/plugins/rust-analyzer/rust-analyzer-pipeline-addin.h
@@ -1,6 +1,6 @@
-/* rust-analyzer-transfer.h
+/* rust-analyzer-pipeline-addin.h
  *
- * Copyright 2020 Günther Wagner <info gunibert de>
+ * Copyright 2021 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
@@ -20,14 +20,14 @@
 
 #pragma once
 
-#include <libide-core.h>
+#include <libide-foundry.h>
 
 G_BEGIN_DECLS
 
-#define RUST_TYPE_ANALYZER_TRANSFER (rust_analyzer_transfer_get_type())
+#define RUST_TYPE_ANALYZER_PIPELINE_ADDIN (rust_analyzer_pipeline_addin_get_type())
 
-G_DECLARE_FINAL_TYPE (RustAnalyzerTransfer, rust_analyzer_transfer, RUST, ANALYZER_TRANSFER, IdeTransfer)
+G_DECLARE_FINAL_TYPE (RustAnalyzerPipelineAddin, rust_analyzer_pipeline_addin, RUST, 
ANALYZER_PIPELINE_ADDIN, IdeObject)
 
-RustAnalyzerTransfer *rust_analyzer_transfer_new (void);
+IdeSubprocessLauncher *rust_analyzer_pipeline_addin_create_launcher (RustAnalyzerPipelineAddin *self);
 
 G_END_DECLS
diff --git a/src/plugins/rust-analyzer/rust-analyzer-plugin.c 
b/src/plugins/rust-analyzer/rust-analyzer-plugin.c
index 043d41603..3b381d50b 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-plugin.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-plugin.c
@@ -18,26 +18,33 @@
  * SPDX-License-Identifier: GPL-3.0-or-later
  */
 
+#include "config.h"
+
 #include <libpeas/peas.h>
 #include <libide-lsp.h>
 #include <libide-gui.h>
+
 #include "rust-analyzer-completion-provider.h"
-#include "rust-analyzer-symbol-resolver.h"
 #include "rust-analyzer-diagnostic-provider.h"
 #include "rust-analyzer-formatter.h"
 #include "rust-analyzer-highlighter.h"
 #include "rust-analyzer-hover-provider.h"
-#include "rust-analyzer-workbench-addin.h"
+#include "rust-analyzer-pipeline-addin.h"
+#include "rust-analyzer-preferences-addin.h"
 #include "rust-analyzer-rename-provider.h"
 #include "rust-analyzer-search-provider.h"
-#include "rust-analyzer-preferences-addin.h"
+#include "rust-analyzer-service.h"
+#include "rust-analyzer-symbol-resolver.h"
 
 _IDE_EXTERN void
 _rust_analyzer_register_types (PeasObjectModule *module)
 {
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_WORKBENCH_ADDIN,
-                                              RUST_TYPE_ANALYZER_WORKBENCH_ADDIN);
+                                              RUST_TYPE_ANALYZER_SERVICE);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_PIPELINE_ADDIN,
+                                              RUST_TYPE_ANALYZER_PIPELINE_ADDIN);
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_COMPLETION_PROVIDER,
                                               RUST_TYPE_ANALYZER_COMPLETION_PROVIDER);
diff --git a/src/plugins/rust-analyzer/rust-analyzer-rename-provider.c 
b/src/plugins/rust-analyzer/rust-analyzer-rename-provider.c
index fe2330a1d..edc251ae3 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-rename-provider.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-rename-provider.c
@@ -52,9 +52,9 @@ rust_analyzer_rename_provider_load (IdeRenameProvider *self)
   g_assert (RUST_IS_ANALYZER_RENAME_PROVIDER (self));
 
   context = ide_object_get_context (IDE_OBJECT (self));
-  service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE);
-  rust_analyzer_service_ensure_started (service);
+  service = rust_analyzer_service_from_context (context);
   g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
+  rust_analyzer_service_ensure_started (service);
 }
 
 static void
diff --git a/src/plugins/rust-analyzer/rust-analyzer-search-provider.c 
b/src/plugins/rust-analyzer/rust-analyzer-search-provider.c
index 838339489..97e195df3 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-search-provider.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-search-provider.c
@@ -57,7 +57,7 @@ rust_analyzer_search_provider_load (IdeSearchProvider *self,
   g_assert (context != NULL);
   g_assert (IDE_IS_CONTEXT (context));
 
-  service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE);
+  service = rust_analyzer_service_from_context (context);
   g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
   rust_analyzer_service_ensure_started (service);
 
diff --git a/src/plugins/rust-analyzer/rust-analyzer-service.c 
b/src/plugins/rust-analyzer/rust-analyzer-service.c
index 17f1dc0fd..4e1924717 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-service.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-service.c
@@ -1,6 +1,7 @@
 /* rust-analyzer-service.c
  *
  * Copyright 2020 Günther Wagner <info gunibert de>
+ * Copyright 2021 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
@@ -20,178 +21,120 @@
 
 #define G_LOG_DOMAIN "rust-analyzer-service"
 
-#include "rust-analyzer-service.h"
-#include "rust-analyzer-transfer.h"
-#include <gio/gunixinputstream.h>
-#include <gio/gunixoutputstream.h>
-#include <glib-unix.h>
-#include <libide-core.h>
+#include "config.h"
+
+#include <dazzle.h>
 #include <jsonrpc-glib.h>
-#include <glib/gi18n.h>
-#include <libide-search.h>
-#include <libide-io.h>
-#include <libide-editor.h>
-#include <libide-gui.h>
-#include "rust-analyzer-search-provider.h"
+
+#include "rust-analyzer-pipeline-addin.h"
+#include "rust-analyzer-service.h"
 
 struct _RustAnalyzerService
 {
-  IdeObject parent_instance;
-  IdeLspClient  *client;
+  GObject                  parent_instance;
+  IdeWorkbench            *workbench;
+  IdeLspClient            *client;
   IdeSubprocessSupervisor *supervisor;
-  GFileMonitor *cargo_monitor;
-  RustAnalyzerSearchProvider *search_provider;
-  GSettings *settings;
-  char *cargo_command;
-  char *rust_analyzer_path;
-
-  ServiceState state;
+  DzlSignalGroup          *pipeline_signals;
+  GSettings               *settings;
 };
 
-G_DEFINE_TYPE (RustAnalyzerService, rust_analyzer_service, IDE_TYPE_OBJECT)
+static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (RustAnalyzerService, rust_analyzer_service, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
 
 enum {
   PROP_0,
   PROP_CLIENT,
-  PROP_CARGO_COMMAND,
   N_PROPS
 };
 
 static GParamSpec *properties [N_PROPS];
 
-RustAnalyzerService *
-rust_analyzer_service_new (void)
-{
-  return g_object_new (RUST_TYPE_ANALYZER_SERVICE, NULL);
-}
-
 static void
-_cargo_toml_changed_cb (GFileMonitor      *monitor,
-                        GFile             *file,
-                        GFile             *other_file,
-                        GFileMonitorEvent  event_type,
-                        gpointer           user_data)
+rust_analyzer_service_pipeline_loaded_cb (RustAnalyzerService *self,
+                                          IdePipeline         *pipeline)
 {
-  RustAnalyzerService *self = RUST_ANALYZER_SERVICE (user_data);
+  g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  IdePipelineAddin *addin;
 
-  g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self));
-
-  if (self->supervisor != NULL)
-    {
-      IdeSubprocess *subprocess = ide_subprocess_supervisor_get_subprocess (self->supervisor);
-      if (subprocess != NULL)
-        ide_subprocess_force_exit (subprocess);
-    }
-}
-
-static IdeSearchEngine *
-_get_search_engine (RustAnalyzerService *self)
-{
-  IdeContext *context = NULL;
+  IDE_ENTRY;
 
   g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
 
-  context = ide_object_get_context (IDE_OBJECT (self));
-  return ide_object_get_child_typed (IDE_OBJECT (context), IDE_TYPE_SEARCH_ENGINE);
-}
-
-static GFile *
-rust_analyzer_service_get_current_file (RustAnalyzerService *self)
-{
-  g_autoptr(IdeContext) context = NULL;
-  IdeWorkbench *workbench = NULL;
-  IdeWorkspace *workspace = NULL;
-  IdeSurface *surface = NULL;
-  IdePage *page = NULL;
+  IDE_TRACE_MSG ("Pipeline loaded, attempting to locate rust-analyzer");
 
-  IDE_ENTRY;
+  ide_subprocess_supervisor_set_launcher (self->supervisor, NULL);
+  ide_subprocess_supervisor_stop (self->supervisor);
 
-  g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  if (!(addin = ide_pipeline_addin_find_by_module_name (pipeline, "rust-analyzer")) ||
+      !(launcher = rust_analyzer_pipeline_addin_create_launcher (RUST_ANALYZER_PIPELINE_ADDIN (addin))))
+    IDE_EXIT;
 
-  context = ide_object_ref_context (IDE_OBJECT (self));
-  workbench = ide_workbench_from_context (context);
-  workspace = ide_workbench_get_current_workspace (workbench);
-  surface = ide_workspace_get_surface_by_name (workspace, "editor");
-  page = ide_editor_surface_get_active_page (IDE_EDITOR_SURFACE (surface));
+  ide_subprocess_supervisor_set_launcher (self->supervisor, launcher);
+  ide_subprocess_supervisor_start (self->supervisor);
 
-  if (!IDE_IS_EDITOR_PAGE (page))
-    return NULL;
-
-  IDE_RETURN (g_object_ref (ide_editor_page_get_file (IDE_EDITOR_PAGE (page))));
+  IDE_EXIT;
 }
 
-static gboolean
-rust_analyzer_service_search_cargo_root (RustAnalyzerService *self,
-                                         GFile               *dir)
+static void
+rust_analyzer_service_bind_pipeline (RustAnalyzerService *self,
+                                     IdePipeline         *pipeline,
+                                     DzlSignalGroup      *signal_group)
 {
-  g_autoptr(GFile) cargofile = NULL;
-
   IDE_ENTRY;
 
   g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (IDE_IS_PIPELINE (pipeline));
+  g_assert (DZL_IS_SIGNAL_GROUP (signal_group));
 
-  cargofile = g_file_get_child (dir, "Cargo.toml");
+  if (ide_pipeline_is_ready (pipeline))
+    rust_analyzer_service_pipeline_loaded_cb (self, pipeline);
 
-  if (g_file_query_exists (cargofile, NULL))
-    IDE_RETURN (TRUE);
-
-  IDE_RETURN (FALSE);
+  IDE_EXIT;
 }
 
-static GFile *
-rust_analyzer_service_determine_workdir (RustAnalyzerService *self)
+static void
+rust_analyzer_service_lsp_initialized_cb (RustAnalyzerService *self,
+                                          IdeLspClient        *client)
 {
-  g_autoptr(GFile) workdir = NULL;
-  g_autoptr(IdeContext) context = NULL;
+  g_autoptr(GVariant) params = NULL;
+
+  IDE_ENTRY;
 
   g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (IDE_IS_LSP_CLIENT (client));
 
-  /* Search workbench root first */
-  context = ide_object_ref_context (IDE_OBJECT (self));
-  workdir = ide_context_ref_workdir (context);
-  if (rust_analyzer_service_search_cargo_root (self, workdir) == FALSE)
-    {
-      /* Search now from the current opened file upwards */
-      g_autoptr(GFile) current_file = NULL;
-      g_autoptr(GFile) parent = NULL;
-
-      current_file = rust_analyzer_service_get_current_file (self);
-      if (current_file == NULL)
-        goto end;
-      parent = g_file_get_parent (current_file);
-
-      while (!g_file_equal (workdir, parent))
-        {
-          GFile *prev_parent = NULL;
-          if (rust_analyzer_service_search_cargo_root (self, parent))
-            {
-              return g_steal_pointer (&parent);
-            }
-          prev_parent = parent;
-          parent = g_file_get_parent (parent);
-          g_object_unref (prev_parent);
-        }
-    }
+  params = JSONRPC_MESSAGE_NEW ("settings", "");
 
-end:
-  return g_steal_pointer (&workdir);
+  ide_lsp_client_send_notification_async (client,
+                                          "workspace/didChangeConfiguration",
+                                          params,
+                                          NULL, NULL, NULL);
+
+  IDE_EXIT;
 }
 
 static GVariant *
-rust_analyzer_service_load_configuration (IdeLspClient *client,
-                                          gpointer      user_data)
+rust_analyzer_service_lsp_load_configuration_cb (RustAnalyzerService *self,
+                                                 IdeLspClient        *client)
 {
-  RustAnalyzerService *self = (RustAnalyzerService *)user_data;
-  GVariant *ret = NULL;
+  g_autoptr(GVariant) ret = NULL;
+  g_autofree gchar *command = NULL;
 
   IDE_ENTRY;
 
   g_assert (IDE_IS_LSP_CLIENT (client));
   g_assert (RUST_IS_ANALYZER_SERVICE (self));
 
+  command = g_settings_get_string (self->settings, "cargo-command");
+
   ret = JSONRPC_MESSAGE_NEW_ARRAY ("{",
                                      "checkOnSave", "{",
-                                       "command", JSONRPC_MESSAGE_PUT_STRING (self->cargo_command),
+                                       "enable", JSONRPC_MESSAGE_PUT_BOOLEAN (command[0] != 0),
+                                       "command", JSONRPC_MESSAGE_PUT_STRING (command),
                                      "}",
                                    "}");
 
@@ -199,133 +142,140 @@ rust_analyzer_service_load_configuration (IdeLspClient *client,
 }
 
 static void
-rust_analyzer_service_get_property (GObject    *object,
-                                    guint       prop_id,
-                                    GValue     *value,
-                                    GParamSpec *pspec)
+rust_analyzer_service_supervisor_spawned_cb (RustAnalyzerService     *self,
+                                             IdeSubprocess           *subprocess,
+                                             IdeSubprocessSupervisor *supervisor)
 {
-  RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object);
+  g_autoptr(GIOStream) io_stream = NULL;
+  IdeSubprocessLauncher *launcher;
+  GOutputStream *output;
+  GInputStream *input;
+  const gchar *workdir;
+  IdeContext *context;
 
-  switch (prop_id)
+  IDE_ENTRY;
+
+  g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (IDE_IS_SUBPROCESS (subprocess));
+  g_assert (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
+
+  input = ide_subprocess_get_stdout_pipe (subprocess);
+  output = ide_subprocess_get_stdin_pipe (subprocess);
+  io_stream = g_simple_io_stream_new (input, output);
+
+  if (self->client != NULL)
     {
-    case PROP_CLIENT:
-      g_value_set_object (value, rust_analyzer_service_get_client (self));
-      break;
-    case PROP_CARGO_COMMAND:
-      g_value_set_string (value, rust_analyzer_service_get_cargo_command (self));
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      ide_lsp_client_stop (self->client);
+      ide_object_destroy (IDE_OBJECT (self->client));
+      g_clear_object (&self->client);
+    }
+
+  self->client = ide_lsp_client_new (io_stream);
+
+  g_signal_connect_object (self->client,
+                           "load-configuration",
+                           G_CALLBACK (rust_analyzer_service_lsp_load_configuration_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (self->client,
+                           "initialized",
+                           G_CALLBACK (rust_analyzer_service_lsp_initialized_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  if ((launcher = ide_subprocess_supervisor_get_launcher (supervisor)) &&
+      (workdir = ide_subprocess_launcher_get_cwd (launcher)))
+    {
+      g_autoptr(GFile) file = g_file_new_for_path (workdir);
+      g_autofree gchar *root_uri = g_file_get_uri (file);
+
+      ide_lsp_client_set_root_uri (self->client, root_uri);
     }
+
+  context = ide_workbench_get_context (self->workbench);
+  ide_lsp_client_add_language (self->client, "rust");
+  ide_object_append (IDE_OBJECT (context), IDE_OBJECT (self->client));
+
+  g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
+
+  ide_lsp_client_start (self->client);
+
+  IDE_EXIT;
 }
 
 static void
-rust_analyzer_service_set_property (GObject      *object,
-                                    guint         prop_id,
-                                    const GValue *value,
-                                    GParamSpec   *pspec)
+rust_analyzer_service_settings_changed_cb (RustAnalyzerService *self,
+                                           const gchar         *key,
+                                           GSettings           *settings)
 {
-  RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object);
+  IDE_ENTRY;
 
-  switch (prop_id)
+  g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (G_IS_SETTINGS (settings));
+
+  if (self->client != NULL)
     {
-    case PROP_CLIENT:
-      rust_analyzer_service_set_client (self, g_value_get_object (value));
-      break;
-    case PROP_CARGO_COMMAND:
-      rust_analyzer_service_set_cargo_command (self, g_value_dup_string (value));
-      break;
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      g_autoptr(GVariant) params = JSONRPC_MESSAGE_NEW ("settings", "");
+      ide_lsp_client_send_notification_async (self->client,
+                                              "workspace/didChangeConfiguration",
+                                              params,
+                                              NULL, NULL, NULL);
     }
+
+  IDE_EXIT;
 }
 
 static void
-rust_analyzer_service_set_parent (IdeObject *object,
-                                  IdeObject *parent)
+rust_analyzer_service_finalize (GObject *object)
 {
-  RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object);
-
-  IdeContext *context = NULL;
-  g_autoptr(GFile) workdir = NULL;
-  g_autoptr(GFile) cargo_toml = NULL;
-
-  g_return_if_fail (RUST_IS_ANALYZER_SERVICE (object));
+  RustAnalyzerService *self = (RustAnalyzerService *)object;
 
-  if (parent == NULL)
-    return;
+  IDE_ENTRY;
 
-  context = ide_object_get_context (object);
-  workdir = ide_context_ref_workdir (context);
-  cargo_toml = g_file_get_child (workdir, "Cargo.toml");
+  g_clear_object (&self->supervisor);
+  g_clear_object (&self->pipeline_signals);
+  g_clear_object (&self->client);
+  g_clear_object (&self->settings);
 
-  if (g_file_query_exists (cargo_toml, NULL))
-    {
-      GError *error = NULL;
-
-      if (self->cargo_monitor != NULL)
-        return;
-
-      self->cargo_monitor = g_file_monitor (cargo_toml, G_FILE_MONITOR_NONE, NULL, &error);
-      if (error != NULL)
-        {
-          g_warning ("%s", error->message);
-          return;
-        }
-      g_file_monitor_set_rate_limit (self->cargo_monitor, 5 * 1000); // 5 Seconds
-      g_signal_connect (self->cargo_monitor, "changed", G_CALLBACK (_cargo_toml_changed_cb), self);
-    }
+  G_OBJECT_CLASS (rust_analyzer_service_parent_class)->finalize (object);
 
+  IDE_EXIT;
 }
 
 static void
-rust_analyzer_service_destroy (IdeObject *object)
+rust_analyzer_service_get_property (GObject    *object,
+                                    guint       prop_id,
+                                    GValue     *value,
+                                    GParamSpec *pspec)
 {
   RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object);
-  IdeSearchEngine *search_engine = NULL;
 
-  if (self->supervisor != NULL)
+  switch (prop_id)
     {
-      g_autoptr(IdeSubprocessSupervisor) supervisor = g_steal_pointer (&self->supervisor);
+    case PROP_CLIENT:
+      g_value_set_object (value, rust_analyzer_service_get_client (self));
+      break;
 
-      ide_subprocess_supervisor_stop (supervisor);
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
     }
-
-  g_clear_object (&self->client);
-
-  search_engine = _get_search_engine (self);
-  if (search_engine != NULL)
-    ide_search_engine_remove_provider (search_engine, IDE_SEARCH_PROVIDER (self->search_provider));
-  g_clear_object (&self->search_provider);
-
-  IDE_OBJECT_CLASS (rust_analyzer_service_parent_class)->destroy (object);
 }
 
 static void
 rust_analyzer_service_class_init (RustAnalyzerServiceClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  IdeObjectClass *i_class = IDE_OBJECT_CLASS (klass);
 
+  object_class->finalize = rust_analyzer_service_finalize;
   object_class->get_property = rust_analyzer_service_get_property;
-  object_class->set_property = rust_analyzer_service_set_property;
-
-  i_class->parent_set = rust_analyzer_service_set_parent;
-  i_class->destroy = rust_analyzer_service_destroy;
 
   properties [PROP_CLIENT] =
     g_param_spec_object ("client",
                          "Client",
-                         "The Language Server client",
+                         "The language server protocol client",
                          IDE_TYPE_LSP_CLIENT,
-                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
-
-  properties [PROP_CARGO_COMMAND] =
-    g_param_spec_string ("cargo-command",
-                         "Cargo-command",
-                         "The used cargo command for rust-analyzer",
-                         "check",
-                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+                         (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_properties (object_class, N_PROPS, properties);
 }
@@ -333,213 +283,191 @@ rust_analyzer_service_class_init (RustAnalyzerServiceClass *klass)
 static void
 rust_analyzer_service_init (RustAnalyzerService *self)
 {
-  self->client = NULL;
-  self->state = RUST_ANALYZER_SERVICE_INIT;
   self->settings = g_settings_new ("org.gnome.builder.rust-analyzer");
-  g_settings_bind (self->settings, "cargo-command", self, "cargo-command", G_SETTINGS_BIND_DEFAULT);
+  g_signal_connect_object (self->settings,
+                           "changed",
+                           G_CALLBACK (rust_analyzer_service_settings_changed_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  self->supervisor = ide_subprocess_supervisor_new ();
+  g_signal_connect_object (self->supervisor,
+                           "spawned",
+                           G_CALLBACK (rust_analyzer_service_supervisor_spawned_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  self->pipeline_signals = dzl_signal_group_new (IDE_TYPE_PIPELINE);
+  dzl_signal_group_connect_object (self->pipeline_signals,
+                                   "loaded",
+                                   G_CALLBACK (rust_analyzer_service_pipeline_loaded_cb),
+                                   self,
+                                   G_CONNECT_SWAPPED);
+  g_signal_connect_object (self->pipeline_signals,
+                           "bind",
+                           G_CALLBACK (rust_analyzer_service_bind_pipeline),
+                           self,
+                           G_CONNECT_SWAPPED);
 }
 
-IdeLspClient *
-rust_analyzer_service_get_client (RustAnalyzerService *self)
+static void
+rust_analyzer_service_load (IdeWorkbenchAddin *addin,
+                            IdeWorkbench      *workbench)
 {
-  g_return_val_if_fail (RUST_IS_ANALYZER_SERVICE (self), NULL);
+  RustAnalyzerService *self = (RustAnalyzerService *)addin;
 
-  return self->client;
-}
+  IDE_ENTRY;
 
-void
-rust_analyzer_service_set_client (RustAnalyzerService *self,
-                                  IdeLspClient        *client)
-{
-  g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self));
-  g_return_if_fail (!client || IDE_IS_LSP_CLIENT (client));
+  g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
 
-  if (g_set_object (&self->client, client))
-    {
-      g_signal_connect_object (self->client,
-                               "load-configuration",
-                               G_CALLBACK (rust_analyzer_service_load_configuration),
-                               self,
-                               0);
-      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
-    }
+  self->workbench = workbench;
+
+  IDE_EXIT;
 }
 
 static void
-rust_analyzer_service_lsp_initialized (IdeLspClient *client,
-                                       gpointer      user_data)
+rust_analyzer_service_unload (IdeWorkbenchAddin *addin,
+                              IdeWorkbench      *workbench)
 {
-  RustAnalyzerService *self = (RustAnalyzerService *) user_data;
-  g_autoptr(GVariant) params = NULL;
+  RustAnalyzerService *self = (RustAnalyzerService *)addin;
 
-  g_assert (IDE_IS_LSP_CLIENT (client));
-  g_assert (RUST_IS_ANALYZER_SERVICE (self));
-
-  params = JSONRPC_MESSAGE_NEW ("settings", "");
-  ide_lsp_client_send_notification_async (client, "workspace/didChangeConfiguration", params, NULL, NULL, 
NULL);
-}
+  IDE_ENTRY;
 
-void
-rust_analyzer_service_lsp_started (IdeSubprocessSupervisor *supervisor,
-                                   IdeSubprocess           *subprocess,
-                                   gpointer                 user_data)
-{
-  RustAnalyzerService *self = user_data;
-  g_autoptr(GIOStream) io_stream = NULL;
-  g_autoptr(GFile) workdir = NULL;
-  g_autofree gchar *root_uri = NULL;
-  GInputStream *input;
-  GOutputStream *output;
-  IdeLspClient *client = NULL;
+  g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (IDE_IS_WORKBENCH (workbench));
 
-  g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self));
-  g_return_if_fail (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
-  g_return_if_fail (IDE_IS_SUBPROCESS (subprocess));
+  self->workbench = NULL;
 
-  input = ide_subprocess_get_stdout_pipe (subprocess);
-  output = ide_subprocess_get_stdin_pipe (subprocess);
-  io_stream = g_simple_io_stream_new (input, output);
+  dzl_signal_group_set_target (self->pipeline_signals, NULL);
 
   if (self->client != NULL)
     {
-      ide_lsp_client_stop (self->client);
-      ide_object_destroy (IDE_OBJECT (self->client));
+      g_autoptr(IdeLspClient) client = g_steal_pointer (&self->client);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
+      ide_lsp_client_stop (client);
+      ide_object_destroy (IDE_OBJECT (client));
+    }
+
+  if (self->supervisor != NULL)
+    {
+      ide_subprocess_supervisor_stop (self->supervisor);
+      g_clear_object (&self->supervisor);
     }
 
-  client = ide_lsp_client_new (io_stream);
-  g_signal_connect (client, "initialized", G_CALLBACK (rust_analyzer_service_lsp_initialized), self);
-  rust_analyzer_service_set_client (self, client);
-  ide_object_append (IDE_OBJECT (self), IDE_OBJECT (client));
-  ide_lsp_client_add_language (client, "rust");
-  workdir = rust_analyzer_service_determine_workdir (self);
-  root_uri = g_file_get_uri (workdir);
-  ide_lsp_client_set_root_uri (client, root_uri);
-  ide_lsp_client_start (client);
+  IDE_EXIT;
 }
 
-static gboolean
-rust_analyzer_service_check_rust_analyzer_bin (RustAnalyzerService *self)
+static void
+rust_analyzer_service_notify_pipeline_cb (RustAnalyzerService *self,
+                                          GParamSpec          *pspec,
+                                          IdeBuildManager     *build_manager)
 {
-  /* Check if `rust-analyzer` can be found on PATH or if there is an executable
-   * in typical location
-   */
-  g_autoptr(GFile) rust_analyzer_bin_file = NULL;
-  g_autofree gchar *rust_analyzer_bin = NULL;
-  g_autoptr(GFileInfo) file_info = NULL;
-
-  g_return_val_if_fail (RUST_IS_ANALYZER_SERVICE (self), FALSE);
-
-  if ((rust_analyzer_bin = g_find_program_in_path ("rust-analyzer")))
-    rust_analyzer_bin_file = g_file_new_for_path (rust_analyzer_bin);
-  else
-    rust_analyzer_bin_file = g_file_new_build_filename (g_get_home_dir (),
-                                                        ".cargo",
-                                                        "bin",
-                                                        "rust-analyzer",
-                                                        NULL);
-
-  if (!g_file_query_exists (rust_analyzer_bin_file, NULL))
-    return FALSE;
-
-  file_info = g_file_query_info (rust_analyzer_bin_file,
-                                 G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE,
-                                 G_FILE_QUERY_INFO_NONE,
-                                 NULL, NULL);
-
-  if (file_info != NULL &&
-      g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE))
-    {
-      g_clear_pointer (&self->rust_analyzer_path, g_free);
-      self->rust_analyzer_path = g_file_get_path (rust_analyzer_bin_file);
-      return TRUE;
-    }
+  IdePipeline *pipeline;
+
+  IDE_ENTRY;
+
+  g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (IDE_IS_BUILD_MANAGER (build_manager));
+
+  pipeline = ide_build_manager_get_pipeline (build_manager);
+  dzl_signal_group_set_target (self->pipeline_signals, pipeline);
 
-  return FALSE;
+  IDE_EXIT;
 }
 
-void
-rust_analyzer_service_ensure_started (RustAnalyzerService *self)
+static void
+rust_analyzer_service_project_loaded (IdeWorkbenchAddin *addin,
+                                      IdeProjectInfo    *project_info)
 {
-  g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self));
+  RustAnalyzerService *self = (RustAnalyzerService *)addin;
+  IdeBuildManager *build_manager;
+  IdeContext *context;
 
-  if (self->state == RUST_ANALYZER_SERVICE_INIT)
-    {
-      if (!rust_analyzer_service_check_rust_analyzer_bin (self))
-        {
-          g_autoptr(IdeNotification) notification = NULL;
-          IdeContext *context = NULL;
-
-          self->state = RUST_ANALYZER_SERVICE_OFFER_DOWNLOAD;
-
-          notification = ide_notification_new ();
-          ide_notification_set_id (notification, "org.gnome-builder.rust-analyzer");
-          ide_notification_set_title (notification, _("Rust Analyzer is missing from your computer"));
-          ide_notification_set_body (notification, _("Support for diagnostics and auto-completion may be 
limited until it is installed."));
-          ide_notification_set_icon_name (notification, "dialog-warning-symbolic");
-          ide_notification_add_button (notification, _("Install Rust Analyzer"), NULL, 
"win.install-rust-analyzer");
-          ide_notification_set_urgent (notification, TRUE);
-          context = ide_object_get_context (IDE_OBJECT (self));
-          ide_notification_attach (notification, IDE_OBJECT (context));
-        }
-      else
-          self->state = RUST_ANALYZER_SERVICE_READY;
-    }
-  else if (self->state == RUST_ANALYZER_SERVICE_READY)
-    {
-      g_autoptr(GFile) workdir = NULL;
-      g_autofree gchar *root_path = NULL;
-      g_autoptr(IdeSubprocessLauncher) launcher = NULL;
+  IDE_ENTRY;
 
-      g_assert (self->rust_analyzer_path != NULL);
+  g_assert (RUST_IS_ANALYZER_SERVICE (self));
+  g_assert (IDE_IS_WORKBENCH (self->workbench));
+  g_assert (IDE_IS_PROJECT_INFO (project_info));
 
-      launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE | 
G_SUBPROCESS_FLAGS_STDIN_PIPE);
-      ide_subprocess_launcher_set_run_on_host (launcher, TRUE);
-      ide_subprocess_launcher_set_clear_env (launcher, TRUE);
+  /* We only start things if we have a project loaded or else there isn't
+   * a whole lot we can do safely as too many subsystems will be in play
+   * which should only be loaded when a project is active.
+   */
 
-      workdir = rust_analyzer_service_determine_workdir (self);
-      root_path = g_file_get_path (workdir);
-      ide_subprocess_launcher_set_cwd (launcher, root_path);
+  context = ide_workbench_get_context (self->workbench);
+  build_manager = ide_build_manager_from_context (context);
+  g_signal_connect_object (build_manager,
+                           "notify::pipeline",
+                           G_CALLBACK (rust_analyzer_service_notify_pipeline_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+  rust_analyzer_service_notify_pipeline_cb (self, NULL, build_manager);
 
-      ide_subprocess_launcher_push_argv (launcher, self->rust_analyzer_path);
+  IDE_EXIT;
+}
 
-      self->supervisor = ide_subprocess_supervisor_new ();
-      g_signal_connect (self->supervisor, "spawned", G_CALLBACK (rust_analyzer_service_lsp_started), self);
-      ide_subprocess_supervisor_set_launcher (self->supervisor, launcher);
-      ide_subprocess_supervisor_start (self->supervisor);
-      self->state = RUST_ANALYZER_SERVICE_LSP_STARTED;
-    }
+static void
+workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
+{
+  iface->load = rust_analyzer_service_load;
+  iface->unload = rust_analyzer_service_unload;
+  iface->project_loaded = rust_analyzer_service_project_loaded;
 }
 
-void
-rust_analyzer_service_set_state (RustAnalyzerService *self,
-                                 ServiceState         state)
+RustAnalyzerService *
+rust_analyzer_service_from_context (IdeContext *context)
 {
-  g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self));
+  IdeWorkbenchAddin *addin;
+  IdeWorkbench *workbench;
+
+  g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
+
+  workbench = ide_workbench_from_context (context);
+  addin = ide_workbench_addin_find_by_module_name (workbench, "rust-analyzer");
 
-  self->state = state;
+  return RUST_ANALYZER_SERVICE (addin);
 }
 
-gchar *
-rust_analyzer_service_get_cargo_command (RustAnalyzerService *self)
+IdeLspClient *
+rust_analyzer_service_get_client (RustAnalyzerService *self)
 {
   g_return_val_if_fail (RUST_IS_ANALYZER_SERVICE (self), NULL);
 
-  return self->cargo_command;
+  return self->client;
 }
 
 void
-rust_analyzer_service_set_cargo_command (RustAnalyzerService *self,
-                                         const gchar         *cargo_command)
+rust_analyzer_service_ensure_started (RustAnalyzerService *self)
 {
-  g_autoptr(GVariant) params = NULL;
+  IdeSubprocessLauncher *launcher;
+  IdePipeline *pipeline;
+  IdeContext *context;
+
+  IDE_ENTRY;
 
   g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self));
-  g_return_if_fail (cargo_command != NULL);
+  g_return_if_fail (self->workbench != NULL);
+
+  /* Ignore unless a project is loaded. Without a project loaded we
+   * dont have access to foundry subsystem.
+   */
+  context = ide_workbench_get_context (self->workbench);
+  if (!ide_context_has_project (context))
+    IDE_EXIT;
 
-  g_clear_pointer (&self->cargo_command, g_free);
-  self->cargo_command = g_strdup (cargo_command);
+  /* Do nothing if the supervisor already has a launcher */
+  if ((launcher = ide_subprocess_supervisor_get_launcher (self->supervisor)))
+    IDE_EXIT;
 
-  params = JSONRPC_MESSAGE_NEW ("settings", "");
-  if (self->client != NULL)
-    ide_lsp_client_send_notification_async (self->client, "workspace/didChangeConfiguration", params, NULL, 
NULL, NULL);
+  /* Try again (maybe new files opened) to see if we can get launcher
+   * using a discovered Cargo.toml.
+   */
+  if (!(pipeline = dzl_signal_group_get_target (self->pipeline_signals)) ||
+      !ide_pipeline_is_ready (pipeline))
+    IDE_EXIT;
+
+  rust_analyzer_service_pipeline_loaded_cb (self, pipeline);
+
+  IDE_EXIT;
 }
diff --git a/src/plugins/rust-analyzer/rust-analyzer-service.h 
b/src/plugins/rust-analyzer/rust-analyzer-service.h
index eb4b9541b..da497dd88 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-service.h
+++ b/src/plugins/rust-analyzer/rust-analyzer-service.h
@@ -1,30 +1,38 @@
+/* rust-analyzer-service.h
+ *
+ * Copyright 2020 Günther Wagner <info gunibert de>
+ * Copyright 2021 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/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
 #pragma once
 
-#include <libide-core.h>
+#include <libide-foundry.h>
+#include <libide-gui.h>
 #include <libide-lsp.h>
 
 G_BEGIN_DECLS
 
 #define RUST_TYPE_ANALYZER_SERVICE (rust_analyzer_service_get_type())
 
-G_DECLARE_FINAL_TYPE (RustAnalyzerService, rust_analyzer_service, RUST, ANALYZER_SERVICE, IdeObject)
-
-typedef enum {
-  RUST_ANALYZER_SERVICE_INIT,
-  RUST_ANALYZER_SERVICE_OFFER_DOWNLOAD,
-  RUST_ANALYZER_SERVICE_READY,
-  RUST_ANALYZER_SERVICE_LSP_STARTED,
-} ServiceState;
+G_DECLARE_FINAL_TYPE (RustAnalyzerService, rust_analyzer_service, RUST, ANALYZER_SERVICE, GObject)
 
-RustAnalyzerService *rust_analyzer_service_new               (void);
-IdeLspClient        *rust_analyzer_service_get_client        (RustAnalyzerService *self);
-void                 rust_analyzer_service_set_client        (RustAnalyzerService *self,
-                                                              IdeLspClient        *client);
-gchar               *rust_analyzer_service_get_cargo_command (RustAnalyzerService *self);
-void                 rust_analyzer_service_set_cargo_command (RustAnalyzerService *self,
-                                                              const gchar         *cargo_command);
-void                 rust_analyzer_service_ensure_started    (RustAnalyzerService *self);
-void                 rust_analyzer_service_set_state         (RustAnalyzerService *self,
-                                                              ServiceState         state);
+RustAnalyzerService *rust_analyzer_service_from_context   (IdeContext          *context);
+IdeLspClient        *rust_analyzer_service_get_client     (RustAnalyzerService *self);
+void                 rust_analyzer_service_ensure_started (RustAnalyzerService *self);
 
 G_END_DECLS
diff --git a/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.c 
b/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.c
index f0fc10e19..92466fbfe 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.c
@@ -52,9 +52,9 @@ rust_analyzer_symbol_resolver_load (IdeSymbolResolver *self)
   g_assert (RUST_IS_ANALYZER_SYMBOL_RESOLVER (self));
 
   context = ide_object_get_context (IDE_OBJECT (self));
-  service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE);
-  rust_analyzer_service_ensure_started (service);
+  service = rust_analyzer_service_from_context (context);
   g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
+  rust_analyzer_service_ensure_started (service);
 }
 
 static void


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