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