1 /* ide-gca-diagnostic-provider.c
2 *
3 * Copyright 2015-2019 Christian Hergert <christian@hergert.me>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21 #define G_LOG_DOMAIN "ide-gca-diagnostic-provider"
22
23 #include <glib/gi18n.h>
24
25 #include "gca-diagnostics.h"
26 #include "ide-gca-diagnostic-provider.h"
27 #include "ide-gca-service.h"
28
29 #include "gca-structs.h"
30
31 struct _IdeGcaDiagnosticProvider
32 {
33 IdeObject parent_instance;
34 GHashTable *document_cache;
35 };
36
37 typedef struct
38 {
39 IdeTask *task; /* Integrity check backpointer */
40 IdeUnsavedFile *unsaved_file;
41 GFile *file;
42 gchar *language_id;
43 } DiagnoseState;
44
45 static void diagnostic_provider_iface_init (IdeDiagnosticProviderInterface *iface);
46
47 G_DEFINE_TYPE_EXTENDED (IdeGcaDiagnosticProvider, ide_gca_diagnostic_provider, IDE_TYPE_OBJECT, G_TYPE_FLAG_FINAL,
48 G_IMPLEMENT_INTERFACE (IDE_TYPE_DIAGNOSTIC_PROVIDER,
49 diagnostic_provider_iface_init))
50
51 static GSettings *gca_settings;
52
53 static void
diagnose_state_free(gpointer data)54 diagnose_state_free (gpointer data)
55 {
56 DiagnoseState *state = data;
57
58 if (state)
59 {
60 g_clear_object (&state->file);
61 g_free (state->language_id);
62 g_clear_pointer (&state->unsaved_file, ide_unsaved_file_unref);
63 g_slice_free (DiagnoseState, state);
64 }
65 }
66
67 static IdeDiagnosticSeverity
get_severity(guint val)68 get_severity (guint val)
69 {
70 switch (val)
71 {
72 case GCA_SEVERITY_INFO:
73 return IDE_DIAGNOSTIC_NOTE;
74
75 case GCA_SEVERITY_WARNING:
76 return IDE_DIAGNOSTIC_WARNING;
77
78 case GCA_SEVERITY_DEPRECATED:
79 return IDE_DIAGNOSTIC_DEPRECATED;
80
81 case GCA_SEVERITY_ERROR:
82 return IDE_DIAGNOSTIC_ERROR;
83
84 case GCA_SEVERITY_FATAL:
85 return IDE_DIAGNOSTIC_FATAL;
86
87 case GCA_SEVERITY_NONE:
88 default:
89 return IDE_DIAGNOSTIC_IGNORED;
90 break;
91 }
92 }
93
94 static IdeDiagnostics *
variant_to_diagnostics(DiagnoseState * state,GVariant * variant)95 variant_to_diagnostics (DiagnoseState *state,
96 GVariant *variant)
97 {
98
99 g_autoptr(GPtrArray) ar = NULL;
100 GVariantIter iter;
101 GVariantIter *b;
102 GVariantIter *c;
103 gchar *d = NULL;
104 guint a;
105
106 IDE_PROBE;
107
108 g_assert (variant);
109
110 ar = g_ptr_array_new_with_free_func (g_object_unref);
111
112 g_variant_iter_init (&iter, variant);
113
114 while (g_variant_iter_loop (&iter, "(ua((x(xx)(xx))s)a(x(xx)(xx))s)",
115 &a, &b, &c, &d))
116 {
117 IdeDiagnosticSeverity severity;
118 IdeDiagnostic *diag;
119 gint64 x1, x2, x3, x4, x5;
120 gchar *e;
121
122 severity = get_severity (a);
123
124 while (g_variant_iter_next (b, "((x(xx)(xx))s)",
125 &x1, &x2, &x3, &x4, &x5, &e))
126 {
127 /*
128 * TODO: Add fixits back after we plumb them into IdeDiagnostic.
129 */
130 #if 0
131 GcaFixit fixit = {{ 0 }};
132
133 fixit.range.file = x1;
134 fixit.range.begin.line = x2 - 1;
135 fixit.range.begin.column = x3 - 1;
136 fixit.range.end.line = x4 - 1;
137 fixit.range.end.column = x5 - 1;
138 fixit.value = g_strdup (e);
139
140 g_array_append_val (diag.fixits, fixit);
141 #endif
142 }
143
144 diag = ide_diagnostic_new (severity, d, NULL);
145
146 while (g_variant_iter_next (c, "(x(xx)(xx))", &x1, &x2, &x3, &x4, &x5))
147 {
148 g_autoptr(IdeRange) range = NULL;
149 g_autoptr(IdeLocation) begin = NULL;
150 g_autoptr(IdeLocation) end = NULL;
151
152 /*
153 * FIXME:
154 *
155 * Not always true, but we can cheat for now and claim it is within
156 * the file we just parsed.
157 */
158
159 begin = ide_location_new (state->file, x2 - 1, x3 - 1);
160 end = ide_location_new (state->file, x4 - 1, x5 - 1);
161
162 range = ide_range_new (begin, end);
163 ide_diagnostic_take_range (diag, g_steal_pointer (&range));
164 }
165
166 g_ptr_array_add (ar, g_steal_pointer (&diag));
167 }
168
169 return ide_diagnostics_new_from_array (ar);
170 }
171
172 static void
diagnostics_cb(GObject * object,GAsyncResult * result,gpointer user_data)173 diagnostics_cb (GObject *object,
174 GAsyncResult *result,
175 gpointer user_data)
176 {
177 GcaDiagnostics *proxy = (GcaDiagnostics *)object;
178 g_autoptr(IdeTask) task = user_data;
179 g_autoptr(GVariant) var = NULL;
180 g_autoptr(GError) error = NULL;
181 IdeDiagnostics *diagnostics;
182 DiagnoseState *state;
183
184 IDE_ENTRY;
185
186 g_assert (IDE_IS_TASK (task));
187 g_assert (G_IS_ASYNC_RESULT (result));
188
189 if (!gca_diagnostics_call_diagnostics_finish (proxy, &var, result, &error))
190 {
191 IDE_TRACE_MSG ("%s", error->message);
192 ide_task_return_error (task, g_steal_pointer (&error));
193 IDE_EXIT;
194 }
195
196 state = ide_task_get_task_data (task);
197 g_assert (state->task == task);
198
199 diagnostics = variant_to_diagnostics (state, var);
200
201 ide_task_return_pointer (task, diagnostics, g_object_unref);
202
203 IDE_EXIT;
204 }
205
206 static void
get_diag_proxy_cb(GObject * object,GAsyncResult * result,gpointer user_data)207 get_diag_proxy_cb (GObject *object,
208 GAsyncResult *result,
209 gpointer user_data)
210 {
211 g_autoptr(IdeTask) task = user_data;
212 g_autoptr(GError) error = NULL;
213 IdeGcaDiagnosticProvider *self;
214 GcaDiagnostics *proxy;
215 const gchar *path;
216
217 IDE_ENTRY;
218
219 g_assert (IDE_IS_TASK (task));
220 g_assert (G_IS_ASYNC_RESULT (result));
221
222 self = ide_task_get_source_object (task);
223
224 proxy = gca_diagnostics_proxy_new_finish (result, &error);
225
226 if (!proxy)
227 {
228 ide_task_return_error (task, g_steal_pointer (&error));
229 IDE_EXIT;
230 }
231
232 path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (proxy));
233 g_hash_table_replace (self->document_cache, g_strdup (path), proxy);
234
235 gca_diagnostics_call_diagnostics (proxy,
236 ide_task_get_cancellable (task),
237 diagnostics_cb,
238 g_object_ref (task));
239
240 IDE_EXIT;
241 }
242
243 static void
parse_cb(GObject * object,GAsyncResult * result,gpointer user_data)244 parse_cb (GObject *object,
245 GAsyncResult *result,
246 gpointer user_data)
247 {
248 GcaService *proxy = (GcaService *)object;
249 IdeGcaDiagnosticProvider *self;
250 g_autoptr(IdeTask) task = user_data;
251 g_autoptr(GError) error = NULL;
252 g_autofree gchar *document_path = NULL;
253 GcaDiagnostics *doc_proxy;
254 DiagnoseState *state;
255 gboolean ret;
256
257 IDE_ENTRY;
258
259 g_assert (GCA_IS_SERVICE (proxy));
260 g_assert (IDE_IS_TASK (task));
261
262 self = ide_task_get_source_object (task);
263 state = ide_task_get_task_data (task);
264
265 ret = gca_service_call_parse_finish (proxy, &document_path, result, &error);
266
267 if (!ret)
268 {
269 if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
270 {
271 ide_task_return_pointer (task,
272 ide_diagnostics_new (),
273 g_object_unref);
274 }
275 else
276 {
277 IDE_TRACE_MSG ("%s", error->message);
278 ide_task_return_error (task, g_steal_pointer (&error));
279 }
280
281 IDE_EXIT;
282 }
283
284 doc_proxy = g_hash_table_lookup (self->document_cache, document_path);
285
286 if (!doc_proxy)
287 {
288 g_autofree gchar *well_known_name = NULL;
289 GDBusConnection *conn;
290
291 well_known_name = g_strdup_printf ("org.gnome.CodeAssist.v1.%s",
292 state->language_id);
293 conn = g_dbus_proxy_get_connection (G_DBUS_PROXY (proxy));
294
295 gca_diagnostics_proxy_new (conn,
296 G_DBUS_PROXY_FLAGS_NONE,
297 well_known_name,
298 document_path,
299 ide_task_get_cancellable (task),
300 get_diag_proxy_cb,
301 g_object_ref (task));
302 IDE_EXIT;
303 }
304
305 gca_diagnostics_call_diagnostics (doc_proxy,
306 ide_task_get_cancellable (task),
307 diagnostics_cb,
308 g_object_ref (task));
309
310 IDE_EXIT;
311 }
312
313 static GVariant *
get_parse_options(void)314 get_parse_options (void)
315 {
316 if (G_UNLIKELY (gca_settings == NULL))
317 gca_settings = g_settings_new ("org.gnome.builder.gnome-code-assistance");
318
319 if (g_settings_get_boolean (gca_settings, "enable-pylint"))
320 {
321 GVariantBuilder builder;
322
323 g_variant_builder_init (&builder, G_VARIANT_TYPE_ARRAY);
324 g_variant_builder_add (&builder, "{sv}", "pylint", g_variant_new_boolean (TRUE));
325 return g_variant_builder_end (&builder);
326 }
327
328 return g_variant_new ("a{sv}", 0);
329 }
330
331 static void
get_proxy_cb(GObject * object,GAsyncResult * result,gpointer user_data)332 get_proxy_cb (GObject *object,
333 GAsyncResult *result,
334 gpointer user_data)
335 {
336 g_autoptr(IdeTask) task = user_data;
337 g_autoptr(GVariant) options = NULL;
338 IdeGcaService *service = (IdeGcaService *)object;
339 g_autofree gchar *path = NULL;
340 g_autoptr(GError) error = NULL;
341 DiagnoseState *state;
342 const gchar *temp_path;
343 GcaService *proxy;
344 GVariant *cursor = NULL;
345
346 IDE_ENTRY;
347
348 g_assert (IDE_IS_TASK (task));
349 g_assert (IDE_IS_GCA_SERVICE (service));
350
351 state = ide_task_get_task_data (task);
352 g_assert (state->task == task);
353
354 proxy = ide_gca_service_get_proxy_finish (service, result, &error);
355
356 if (!proxy)
357 {
358 ide_task_return_error (task, g_steal_pointer (&error));
359 IDE_GOTO (cleanup);
360 }
361
362 temp_path = path = g_file_get_path (state->file);
363
364 if (!path)
365 {
366 ide_task_return_new_error (task,
367 G_IO_ERROR,
368 G_IO_ERROR_NOT_SUPPORTED,
369 _("Code assistance requires a local file."));
370 IDE_GOTO (cleanup);
371 }
372
373 if (state->unsaved_file)
374 {
375 if (!ide_unsaved_file_persist (state->unsaved_file,
376 ide_task_get_cancellable (task),
377 &error))
378 {
379 ide_task_return_error (task, g_steal_pointer (&error));
380 IDE_GOTO (cleanup);
381 }
382
383 temp_path = ide_unsaved_file_get_temp_path (state->unsaved_file);
384 }
385
386 /* TODO: Plumb support for cursors down to this level? */
387 cursor = g_variant_new ("(xx)", (gint64)0, (gint64)0);
388 options = g_variant_ref_sink (get_parse_options ());
389
390 gca_service_call_parse (proxy,
391 path,
392 temp_path,
393 cursor,
394 options,
395 ide_task_get_cancellable (task),
396 parse_cb,
397 g_object_ref (task));
398
399 cleanup:
400 g_clear_object (&proxy);
401
402 IDE_EXIT;
403 }
404
405 static void
ide_gca_diagnostic_provider_diagnose_async(IdeDiagnosticProvider * provider,GFile * file,GBytes * contents,const gchar * language_id,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)406 ide_gca_diagnostic_provider_diagnose_async (IdeDiagnosticProvider *provider,
407 GFile *file,
408 GBytes *contents,
409 const gchar *language_id,
410 GCancellable *cancellable,
411 GAsyncReadyCallback callback,
412 gpointer user_data)
413 {
414 IdeGcaDiagnosticProvider *self = (IdeGcaDiagnosticProvider *)provider;
415 g_autoptr(IdeTask) task = NULL;
416 IdeGcaService *service;
417 DiagnoseState *state;
418 IdeUnsavedFiles *files;
419 IdeContext *context;
420
421 IDE_ENTRY;
422
423 g_return_if_fail (IDE_IS_GCA_DIAGNOSTIC_PROVIDER (self));
424
425 task = ide_task_new (self, cancellable, callback, user_data);
426 ide_task_set_source_tag (task, ide_gca_diagnostic_provider_diagnose_async);
427
428 if (language_id == NULL)
429 {
430 ide_task_return_new_error (task,
431 G_IO_ERROR,
432 G_IO_ERROR_NOT_SUPPORTED,
433 "No language specified, code assistance not supported.");
434 IDE_EXIT;
435 }
436
437 context = ide_object_get_context (IDE_OBJECT (provider));
438 service = ide_gca_service_from_context (context);
439 files = ide_unsaved_files_from_context (context);
440
441 state = g_slice_new0 (DiagnoseState);
442 state->task = task;
443 state->language_id = g_strdup (language_id);
444 state->file = g_object_ref (file);
445 state->unsaved_file = ide_unsaved_files_get_unsaved_file (files, file);
446
447 ide_task_set_task_data (task, state, diagnose_state_free);
448
449 ide_gca_service_get_proxy_async (service,
450 language_id,
451 cancellable,
452 get_proxy_cb,
453 g_steal_pointer (&task));
454
455 IDE_EXIT;
456 }
457
458 static IdeDiagnostics *
ide_gca_diagnostic_provider_diagnose_finish(IdeDiagnosticProvider * self,GAsyncResult * result,GError ** error)459 ide_gca_diagnostic_provider_diagnose_finish (IdeDiagnosticProvider *self,
460 GAsyncResult *result,
461 GError **error)
462 {
463 IdeTask *task = (IdeTask *)result;
464 IdeDiagnostics *ret;
465
466 IDE_ENTRY;
467
468 g_return_val_if_fail (IDE_IS_GCA_DIAGNOSTIC_PROVIDER (self), NULL);
469 g_return_val_if_fail (IDE_IS_TASK (task), NULL);
470
471 ret = ide_task_propagate_pointer (task, error);
472
473 IDE_RETURN (ret);
474 }
475
476 static void
ide_gca_diagnostic_provider_finalize(GObject * object)477 ide_gca_diagnostic_provider_finalize (GObject *object)
478 {
479 IdeGcaDiagnosticProvider *self = (IdeGcaDiagnosticProvider *)object;
480
481 g_clear_pointer (&self->document_cache, g_hash_table_unref);
482
483 G_OBJECT_CLASS (ide_gca_diagnostic_provider_parent_class)->finalize (object);
484 }
485
486 static void
diagnostic_provider_iface_init(IdeDiagnosticProviderInterface * iface)487 diagnostic_provider_iface_init (IdeDiagnosticProviderInterface *iface)
488 {
489 iface->diagnose_async = ide_gca_diagnostic_provider_diagnose_async;
490 iface->diagnose_finish = ide_gca_diagnostic_provider_diagnose_finish;
491 }
492
493 static void
ide_gca_diagnostic_provider_class_init(IdeGcaDiagnosticProviderClass * klass)494 ide_gca_diagnostic_provider_class_init (IdeGcaDiagnosticProviderClass *klass)
495 {
496 GObjectClass *object_class = G_OBJECT_CLASS (klass);
497
498 object_class->finalize = ide_gca_diagnostic_provider_finalize;
499 }
500
501 static void
ide_gca_diagnostic_provider_init(IdeGcaDiagnosticProvider * self)502 ide_gca_diagnostic_provider_init (IdeGcaDiagnosticProvider *self)
503 {
504 self->document_cache = g_hash_table_new_full (g_str_hash, g_str_equal,
505 g_free, g_object_unref);
506 }
507