1 /* rust-analyzer-service.c
2  *
3  * Copyright 2020 Günther Wagner <info@gunibert.de>
4  * Copyright 2021 Christian Hergert <chergert@redhat.com>
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  * SPDX-License-Identifier: GPL-3.0-or-later
20  */
21 
22 #define G_LOG_DOMAIN "rust-analyzer-service"
23 
24 #include "config.h"
25 
26 #include <dazzle.h>
27 #include <jsonrpc-glib.h>
28 
29 #include "rust-analyzer-pipeline-addin.h"
30 #include "rust-analyzer-service.h"
31 
32 struct _RustAnalyzerService
33 {
34   GObject                  parent_instance;
35   IdeWorkbench            *workbench;
36   IdeLspClient            *client;
37   IdeSubprocessSupervisor *supervisor;
38   DzlSignalGroup          *pipeline_signals;
39   GSettings               *settings;
40 };
41 
42 static void workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface);
43 
44 G_DEFINE_FINAL_TYPE_WITH_CODE (RustAnalyzerService, rust_analyzer_service, G_TYPE_OBJECT,
45                          G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, workbench_addin_iface_init))
46 
47 enum {
48   PROP_0,
49   PROP_CLIENT,
50   N_PROPS
51 };
52 
53 static GParamSpec *properties [N_PROPS];
54 
55 static void
rust_analyzer_service_pipeline_loaded_cb(RustAnalyzerService * self,IdePipeline * pipeline)56 rust_analyzer_service_pipeline_loaded_cb (RustAnalyzerService *self,
57                                           IdePipeline         *pipeline)
58 {
59   g_autoptr(IdeSubprocessLauncher) launcher = NULL;
60   IdePipelineAddin *addin;
61 
62   IDE_ENTRY;
63 
64   g_assert (RUST_IS_ANALYZER_SERVICE (self));
65   g_assert (IDE_IS_PIPELINE (pipeline));
66 
67   IDE_TRACE_MSG ("Pipeline loaded, attempting to locate rust-analyzer");
68 
69   ide_subprocess_supervisor_set_launcher (self->supervisor, NULL);
70   ide_subprocess_supervisor_stop (self->supervisor);
71 
72   if (!(addin = ide_pipeline_addin_find_by_module_name (pipeline, "rust-analyzer")) ||
73       !(launcher = rust_analyzer_pipeline_addin_create_launcher (RUST_ANALYZER_PIPELINE_ADDIN (addin))))
74     IDE_EXIT;
75 
76   ide_subprocess_supervisor_set_launcher (self->supervisor, launcher);
77   ide_subprocess_supervisor_start (self->supervisor);
78 
79   IDE_EXIT;
80 }
81 
82 static void
rust_analyzer_service_bind_pipeline(RustAnalyzerService * self,IdePipeline * pipeline,DzlSignalGroup * signal_group)83 rust_analyzer_service_bind_pipeline (RustAnalyzerService *self,
84                                      IdePipeline         *pipeline,
85                                      DzlSignalGroup      *signal_group)
86 {
87   IDE_ENTRY;
88 
89   g_assert (RUST_IS_ANALYZER_SERVICE (self));
90   g_assert (IDE_IS_PIPELINE (pipeline));
91   g_assert (DZL_IS_SIGNAL_GROUP (signal_group));
92 
93   if (ide_pipeline_is_ready (pipeline))
94     rust_analyzer_service_pipeline_loaded_cb (self, pipeline);
95 
96   IDE_EXIT;
97 }
98 
99 static void
rust_analyzer_service_lsp_initialized_cb(RustAnalyzerService * self,IdeLspClient * client)100 rust_analyzer_service_lsp_initialized_cb (RustAnalyzerService *self,
101                                           IdeLspClient        *client)
102 {
103   g_autoptr(GVariant) params = NULL;
104 
105   IDE_ENTRY;
106 
107   g_assert (RUST_IS_ANALYZER_SERVICE (self));
108   g_assert (IDE_IS_LSP_CLIENT (client));
109 
110   params = JSONRPC_MESSAGE_NEW ("settings", "");
111 
112   ide_lsp_client_send_notification_async (client,
113                                           "workspace/didChangeConfiguration",
114                                           params,
115                                           NULL, NULL, NULL);
116 
117   IDE_EXIT;
118 }
119 
120 static GVariant *
rust_analyzer_service_lsp_load_configuration_cb(RustAnalyzerService * self,IdeLspClient * client)121 rust_analyzer_service_lsp_load_configuration_cb (RustAnalyzerService *self,
122                                                  IdeLspClient        *client)
123 {
124   g_autoptr(GVariant) ret = NULL;
125   g_autofree gchar *command = NULL;
126 
127   IDE_ENTRY;
128 
129   g_assert (IDE_IS_LSP_CLIENT (client));
130   g_assert (RUST_IS_ANALYZER_SERVICE (self));
131 
132   command = g_settings_get_string (self->settings, "cargo-command");
133 
134   ret = JSONRPC_MESSAGE_NEW_ARRAY ("{",
135                                      "checkOnSave", "{",
136                                        "enable", JSONRPC_MESSAGE_PUT_BOOLEAN (command[0] != 0),
137                                        "command", JSONRPC_MESSAGE_PUT_STRING (command),
138                                      "}",
139                                    "}");
140 
141   IDE_RETURN (g_steal_pointer (&ret));
142 }
143 
144 static void
rust_analyzer_service_supervisor_spawned_cb(RustAnalyzerService * self,IdeSubprocess * subprocess,IdeSubprocessSupervisor * supervisor)145 rust_analyzer_service_supervisor_spawned_cb (RustAnalyzerService     *self,
146                                              IdeSubprocess           *subprocess,
147                                              IdeSubprocessSupervisor *supervisor)
148 {
149   g_autoptr(GIOStream) io_stream = NULL;
150   IdeSubprocessLauncher *launcher;
151   GOutputStream *output;
152   GInputStream *input;
153   const gchar *workdir;
154   IdeContext *context;
155 
156   IDE_ENTRY;
157 
158   g_assert (RUST_IS_ANALYZER_SERVICE (self));
159   g_assert (IDE_IS_SUBPROCESS (subprocess));
160   g_assert (IDE_IS_SUBPROCESS_SUPERVISOR (supervisor));
161 
162   input = ide_subprocess_get_stdout_pipe (subprocess);
163   output = ide_subprocess_get_stdin_pipe (subprocess);
164   io_stream = g_simple_io_stream_new (input, output);
165 
166   if (self->client != NULL)
167     {
168       ide_lsp_client_stop (self->client);
169       ide_object_destroy (IDE_OBJECT (self->client));
170       g_clear_object (&self->client);
171     }
172 
173   self->client = ide_lsp_client_new (io_stream);
174 
175   g_signal_connect_object (self->client,
176                            "load-configuration",
177                            G_CALLBACK (rust_analyzer_service_lsp_load_configuration_cb),
178                            self,
179                            G_CONNECT_SWAPPED);
180 
181   g_signal_connect_object (self->client,
182                            "initialized",
183                            G_CALLBACK (rust_analyzer_service_lsp_initialized_cb),
184                            self,
185                            G_CONNECT_SWAPPED);
186 
187   if ((launcher = ide_subprocess_supervisor_get_launcher (supervisor)) &&
188       (workdir = ide_subprocess_launcher_get_cwd (launcher)))
189     {
190       g_autoptr(GFile) file = g_file_new_for_path (workdir);
191       g_autofree gchar *root_uri = g_file_get_uri (file);
192 
193       ide_lsp_client_set_root_uri (self->client, root_uri);
194     }
195 
196   context = ide_workbench_get_context (self->workbench);
197   ide_lsp_client_add_language (self->client, "rust");
198   ide_object_append (IDE_OBJECT (context), IDE_OBJECT (self->client));
199 
200   g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
201 
202   ide_lsp_client_start (self->client);
203 
204   IDE_EXIT;
205 }
206 
207 static void
rust_analyzer_service_settings_changed_cb(RustAnalyzerService * self,const gchar * key,GSettings * settings)208 rust_analyzer_service_settings_changed_cb (RustAnalyzerService *self,
209                                            const gchar         *key,
210                                            GSettings           *settings)
211 {
212   IDE_ENTRY;
213 
214   g_assert (RUST_IS_ANALYZER_SERVICE (self));
215   g_assert (G_IS_SETTINGS (settings));
216 
217   if (self->client != NULL)
218     {
219       g_autoptr(GVariant) params = JSONRPC_MESSAGE_NEW ("settings", "");
220       ide_lsp_client_send_notification_async (self->client,
221                                               "workspace/didChangeConfiguration",
222                                               params,
223                                               NULL, NULL, NULL);
224     }
225 
226   IDE_EXIT;
227 }
228 
229 static void
rust_analyzer_service_finalize(GObject * object)230 rust_analyzer_service_finalize (GObject *object)
231 {
232   RustAnalyzerService *self = (RustAnalyzerService *)object;
233 
234   IDE_ENTRY;
235 
236   g_clear_object (&self->supervisor);
237   g_clear_object (&self->pipeline_signals);
238   g_clear_object (&self->client);
239   g_clear_object (&self->settings);
240 
241   G_OBJECT_CLASS (rust_analyzer_service_parent_class)->finalize (object);
242 
243   IDE_EXIT;
244 }
245 
246 static void
rust_analyzer_service_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)247 rust_analyzer_service_get_property (GObject    *object,
248                                     guint       prop_id,
249                                     GValue     *value,
250                                     GParamSpec *pspec)
251 {
252   RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object);
253 
254   switch (prop_id)
255     {
256     case PROP_CLIENT:
257       g_value_set_object (value, rust_analyzer_service_get_client (self));
258       break;
259 
260     default:
261       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
262     }
263 }
264 
265 static void
rust_analyzer_service_class_init(RustAnalyzerServiceClass * klass)266 rust_analyzer_service_class_init (RustAnalyzerServiceClass *klass)
267 {
268   GObjectClass *object_class = G_OBJECT_CLASS (klass);
269 
270   object_class->finalize = rust_analyzer_service_finalize;
271   object_class->get_property = rust_analyzer_service_get_property;
272 
273   properties [PROP_CLIENT] =
274     g_param_spec_object ("client",
275                          "Client",
276                          "The language server protocol client",
277                          IDE_TYPE_LSP_CLIENT,
278                          (G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
279 
280   g_object_class_install_properties (object_class, N_PROPS, properties);
281 }
282 
283 static void
rust_analyzer_service_init(RustAnalyzerService * self)284 rust_analyzer_service_init (RustAnalyzerService *self)
285 {
286   self->settings = g_settings_new ("org.gnome.builder.rust-analyzer");
287   g_signal_connect_object (self->settings,
288                            "changed",
289                            G_CALLBACK (rust_analyzer_service_settings_changed_cb),
290                            self,
291                            G_CONNECT_SWAPPED);
292 
293   self->supervisor = ide_subprocess_supervisor_new ();
294   g_signal_connect_object (self->supervisor,
295                            "spawned",
296                            G_CALLBACK (rust_analyzer_service_supervisor_spawned_cb),
297                            self,
298                            G_CONNECT_SWAPPED);
299 
300   self->pipeline_signals = dzl_signal_group_new (IDE_TYPE_PIPELINE);
301   dzl_signal_group_connect_object (self->pipeline_signals,
302                                    "loaded",
303                                    G_CALLBACK (rust_analyzer_service_pipeline_loaded_cb),
304                                    self,
305                                    G_CONNECT_SWAPPED);
306   g_signal_connect_object (self->pipeline_signals,
307                            "bind",
308                            G_CALLBACK (rust_analyzer_service_bind_pipeline),
309                            self,
310                            G_CONNECT_SWAPPED);
311 }
312 
313 static void
rust_analyzer_service_load(IdeWorkbenchAddin * addin,IdeWorkbench * workbench)314 rust_analyzer_service_load (IdeWorkbenchAddin *addin,
315                             IdeWorkbench      *workbench)
316 {
317   RustAnalyzerService *self = (RustAnalyzerService *)addin;
318 
319   IDE_ENTRY;
320 
321   g_assert (RUST_IS_ANALYZER_SERVICE (self));
322   g_assert (IDE_IS_WORKBENCH (workbench));
323 
324   self->workbench = workbench;
325 
326   IDE_EXIT;
327 }
328 
329 static void
rust_analyzer_service_unload(IdeWorkbenchAddin * addin,IdeWorkbench * workbench)330 rust_analyzer_service_unload (IdeWorkbenchAddin *addin,
331                               IdeWorkbench      *workbench)
332 {
333   RustAnalyzerService *self = (RustAnalyzerService *)addin;
334 
335   IDE_ENTRY;
336 
337   g_assert (RUST_IS_ANALYZER_SERVICE (self));
338   g_assert (IDE_IS_WORKBENCH (workbench));
339 
340   self->workbench = NULL;
341 
342   dzl_signal_group_set_target (self->pipeline_signals, NULL);
343 
344   if (self->client != NULL)
345     {
346       g_autoptr(IdeLspClient) client = g_steal_pointer (&self->client);
347       g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
348       ide_lsp_client_stop (client);
349       ide_object_destroy (IDE_OBJECT (client));
350     }
351 
352   if (self->supervisor != NULL)
353     {
354       ide_subprocess_supervisor_stop (self->supervisor);
355       g_clear_object (&self->supervisor);
356     }
357 
358   IDE_EXIT;
359 }
360 
361 static void
rust_analyzer_service_notify_pipeline_cb(RustAnalyzerService * self,GParamSpec * pspec,IdeBuildManager * build_manager)362 rust_analyzer_service_notify_pipeline_cb (RustAnalyzerService *self,
363                                           GParamSpec          *pspec,
364                                           IdeBuildManager     *build_manager)
365 {
366   IdePipeline *pipeline;
367 
368   IDE_ENTRY;
369 
370   g_assert (RUST_IS_ANALYZER_SERVICE (self));
371   g_assert (IDE_IS_BUILD_MANAGER (build_manager));
372 
373   pipeline = ide_build_manager_get_pipeline (build_manager);
374   dzl_signal_group_set_target (self->pipeline_signals, pipeline);
375 
376   IDE_EXIT;
377 }
378 
379 static void
rust_analyzer_service_project_loaded(IdeWorkbenchAddin * addin,IdeProjectInfo * project_info)380 rust_analyzer_service_project_loaded (IdeWorkbenchAddin *addin,
381                                       IdeProjectInfo    *project_info)
382 {
383   RustAnalyzerService *self = (RustAnalyzerService *)addin;
384   IdeBuildManager *build_manager;
385   IdeContext *context;
386 
387   IDE_ENTRY;
388 
389   g_assert (RUST_IS_ANALYZER_SERVICE (self));
390   g_assert (IDE_IS_WORKBENCH (self->workbench));
391   g_assert (IDE_IS_PROJECT_INFO (project_info));
392 
393   /* We only start things if we have a project loaded or else there isn't
394    * a whole lot we can do safely as too many subsystems will be in play
395    * which should only be loaded when a project is active.
396    */
397 
398   context = ide_workbench_get_context (self->workbench);
399   build_manager = ide_build_manager_from_context (context);
400   g_signal_connect_object (build_manager,
401                            "notify::pipeline",
402                            G_CALLBACK (rust_analyzer_service_notify_pipeline_cb),
403                            self,
404                            G_CONNECT_SWAPPED);
405   rust_analyzer_service_notify_pipeline_cb (self, NULL, build_manager);
406 
407   IDE_EXIT;
408 }
409 
410 static void
workbench_addin_iface_init(IdeWorkbenchAddinInterface * iface)411 workbench_addin_iface_init (IdeWorkbenchAddinInterface *iface)
412 {
413   iface->load = rust_analyzer_service_load;
414   iface->unload = rust_analyzer_service_unload;
415   iface->project_loaded = rust_analyzer_service_project_loaded;
416 }
417 
418 RustAnalyzerService *
rust_analyzer_service_from_context(IdeContext * context)419 rust_analyzer_service_from_context (IdeContext *context)
420 {
421   IdeWorkbenchAddin *addin;
422   IdeWorkbench *workbench;
423 
424   g_return_val_if_fail (IDE_IS_CONTEXT (context), NULL);
425 
426   workbench = ide_workbench_from_context (context);
427   addin = ide_workbench_addin_find_by_module_name (workbench, "rust-analyzer");
428 
429   return RUST_ANALYZER_SERVICE (addin);
430 }
431 
432 IdeLspClient *
rust_analyzer_service_get_client(RustAnalyzerService * self)433 rust_analyzer_service_get_client (RustAnalyzerService *self)
434 {
435   g_return_val_if_fail (RUST_IS_ANALYZER_SERVICE (self), NULL);
436 
437   return self->client;
438 }
439 
440 void
rust_analyzer_service_ensure_started(RustAnalyzerService * self)441 rust_analyzer_service_ensure_started (RustAnalyzerService *self)
442 {
443   IdeSubprocessLauncher *launcher;
444   IdePipeline *pipeline;
445   IdeContext *context;
446 
447   IDE_ENTRY;
448 
449   g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self));
450   g_return_if_fail (self->workbench != NULL);
451 
452   /* Ignore unless a project is loaded. Without a project loaded we
453    * dont have access to foundry subsystem.
454    */
455   context = ide_workbench_get_context (self->workbench);
456   if (!ide_context_has_project (context))
457     IDE_EXIT;
458 
459   /* Do nothing if the supervisor already has a launcher */
460   if ((launcher = ide_subprocess_supervisor_get_launcher (self->supervisor)))
461     IDE_EXIT;
462 
463   /* Try again (maybe new files opened) to see if we can get launcher
464    * using a discovered Cargo.toml.
465    */
466   if (!(pipeline = dzl_signal_group_get_target (self->pipeline_signals)) ||
467       !ide_pipeline_is_ready (pipeline))
468     IDE_EXIT;
469 
470   rust_analyzer_service_pipeline_loaded_cb (self, pipeline);
471 
472   IDE_EXIT;
473 }
474