ide-lsp-client.c 60 KB
Newer Older
1
/* ide-lsp-client.c
2
 *
3
 * Copyright 2016-2019 Christian Hergert <chergert@redhat.com>
4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 * 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/>.
17 18
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
19 20
 */

21
#define G_LOG_DOMAIN "ide-lsp-client"
22

23 24
#include "config.h"

25 26
#include <dazzle.h>
#include <dazzle.h>
27
#include <glib/gi18n.h>
28
#include <jsonrpc-glib.h>
29 30 31 32
#include <libide-code.h>
#include <libide-projects.h>
#include <libide-sourceview.h>
#include <libide-threading.h>
33
#include <unistd.h>
34

35
#include "ide-lsp-client.h"
36
#include "ide-lsp-enums.h"
37

38 39 40 41 42 43
typedef struct
{
  JsonrpcClient *client;
  GVariant      *id;
} AsyncCall;

44 45
typedef struct
{
46 47
  DzlSignalGroup *buffer_manager_signals;
  DzlSignalGroup *project_signals;
48 49
  JsonrpcClient  *rpc_client;
  GIOStream      *io_stream;
50
  GHashTable     *diagnostics_by_file;
51
  GPtrArray      *languages;
52
  GVariant       *server_capabilities;
53
  IdeLspTrace     trace;
54
} IdeLspClientPrivate;
55

56
G_DEFINE_TYPE_WITH_PRIVATE (IdeLspClient, ide_lsp_client, IDE_TYPE_OBJECT)
57

58 59 60 61 62 63
enum {
  FILE_CHANGE_TYPE_CREATED = 1,
  FILE_CHANGE_TYPE_CHANGED = 2,
  FILE_CHANGE_TYPE_DELETED = 3,
};

64 65 66 67 68 69 70
enum {
  SEVERITY_ERROR       = 1,
  SEVERITY_WARNING     = 2,
  SEVERITY_INFORMATION = 3,
  SEVERITY_HINT        = 4,
};

71 72 73 74 75 76
enum {
  TEXT_DOCUMENT_SYNC_NONE,
  TEXT_DOCUMENT_SYNC_FULL,
  TEXT_DOCUMENT_SYNC_INCREMENTAL,
};

77 78 79
enum {
  PROP_0,
  PROP_IO_STREAM,
80
  PROP_SERVER_CAPABILITIES,
81
  PROP_TRACE,
82 83 84
  N_PROPS
};

85
enum {
86
  INITIALIZED,
87
  LOAD_CONFIGURATION,
88
  NOTIFICATION,
89
  PUBLISHED_DIAGNOSTICS,
90
  SUPPORTS_LANGUAGE,
91 92 93
  N_SIGNALS
};

94
static GParamSpec *properties [N_PROPS];
95
static guint signals [N_SIGNALS];
96

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
static AsyncCall *
async_call_new (JsonrpcClient *client,
                GVariant      *id)
{
  AsyncCall *ac = g_atomic_rc_box_new0 (AsyncCall);
  ac->client = g_object_ref (client);
  ac->id = g_variant_ref (id);
  return ac;
}

static void
async_call_finalize (gpointer data)
{
  AsyncCall *ac = data;
  g_clear_object (&ac->client);
  g_clear_pointer (&ac->id, g_variant_unref);
}

static void
async_call_unref (gpointer data)
{
  g_atomic_rc_box_release_full (data, async_call_finalize);
}

G_DEFINE_AUTOPTR_CLEANUP_FUNC (AsyncCall, async_call_unref);

123
static gboolean
124
ide_lsp_client_supports_buffer (IdeLspClient *self,
Daniel Buch Hansen's avatar
Daniel Buch Hansen committed
125
                                IdeBuffer    *buffer)
126 127 128 129 130
{
  GtkSourceLanguage *language;
  const gchar *language_id = "text/plain";
  gboolean ret = FALSE;

131
  g_assert (IDE_IS_MAIN_THREAD ());
132
  g_assert (IDE_IS_LSP_CLIENT (self));
133 134 135 136 137 138 139 140 141 142 143
  g_assert (IDE_IS_BUFFER (buffer));

  language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer));
  if (language != NULL)
    language_id = gtk_source_language_get_id (language);

  g_signal_emit (self, signals [SUPPORTS_LANGUAGE], 0, language_id, &ret);

  return ret;
}

144
static void
145 146
ide_lsp_client_clear_diagnostics (IdeLspClient *self,
                                  const gchar  *uri)
147
{
148
  IdeLspClientPrivate *priv = ide_lsp_client_get_instance_private (self);
149 150
  g_autoptr(GFile) file = NULL;

151 152
  IDE_ENTRY;

153
  g_assert (IDE_IS_MAIN_THREAD ());
154
  g_assert (IDE_IS_LSP_CLIENT (self));
155 156
  g_assert (uri != NULL);

157
  IDE_TRACE_MSG ("Clearing diagnostics for %s", uri);
158

159
  file = g_file_new_for_uri (uri);
160
  g_hash_table_remove (priv->diagnostics_by_file, file);
161 162 163 164 165

  IDE_EXIT;
}

static void
166 167 168
ide_lsp_client_buffer_saved (IdeLspClient     *self,
                             IdeBuffer        *buffer,
                             IdeBufferManager *buffer_manager)
169
{
170
  g_autoptr(GVariant) params = NULL;
171
  g_autoptr(GBytes) content = NULL;
172
  g_autofree gchar *uri = NULL;
173
  const gchar *text;
174 175 176

  IDE_ENTRY;

177
  g_assert (IDE_IS_MAIN_THREAD ());
178
  g_assert (IDE_IS_LSP_CLIENT (self));
179 180 181
  g_assert (IDE_IS_BUFFER (buffer));
  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));

182
  if (!ide_lsp_client_supports_buffer (self, buffer))
183 184
    IDE_EXIT;

185
  uri = ide_buffer_dup_uri (buffer);
186 187
  content = ide_buffer_dup_content (buffer);
  text = (const gchar *)g_bytes_get_data (content, NULL);
188

189
  params = JSONRPC_MESSAGE_NEW (
190
    "textDocument", "{",
191
      "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
192
      "text", JSONRPC_MESSAGE_PUT_STRING (text),
193
    "}"
194 195
  );

196 197 198 199
  ide_lsp_client_send_notification_async (self,
                                          "textDocument/didSave",
                                          params,
                                          NULL, NULL, NULL);
200 201

  IDE_EXIT;
202 203
}

204 205 206 207 208 209
/*
 * TODO: This should all be delayed and buffered so we coalesce multiple
 *       events into a single dispatch.
 */

static void
210 211 212 213 214
ide_lsp_client_buffer_insert_text (IdeLspClient *self,
                                   GtkTextIter  *location,
                                   const gchar  *new_text,
                                   gint          len,
                                   IdeBuffer    *buffer)
215
{
216
  g_autoptr(GVariant) params = NULL;
217
  g_autofree gchar *uri = NULL;
218
  GVariant *capabilities = NULL;
219
  gint64 version;
220
  gint64 text_document_sync = TEXT_DOCUMENT_SYNC_NONE;
221

222 223
  IDE_ENTRY;

224
  g_assert (IDE_IS_MAIN_THREAD ());
225
  g_assert (IDE_IS_LSP_CLIENT (self));
226 227 228
  g_assert (location != NULL);
  g_assert (IDE_IS_BUFFER (buffer));

229 230 231 232 233 234 235 236 237 238 239
  capabilities = ide_lsp_client_get_server_capabilities (self);
  if (capabilities != NULL) {
    gint64 tds = 0;

    // for backwards compatibility reasons LS can stick to a number instead of the structure
    if (JSONRPC_MESSAGE_PARSE (capabilities, "textDocumentSync", JSONRPC_MESSAGE_GET_INT64 (&tds))
        | JSONRPC_MESSAGE_PARSE (capabilities, "textDocumentSync", "{", "change", JSONRPC_MESSAGE_GET_INT64 (&tds), "}"))
      {
        text_document_sync = tds;
      }
  }
240

241
  uri = ide_buffer_dup_uri (buffer);
Christian Hergert's avatar
Christian Hergert committed
242 243 244

  /* We get called before this change is registered */
  version = (gint64)ide_buffer_get_change_count (buffer) + 1;
245

246 247 248 249 250
  if (text_document_sync == TEXT_DOCUMENT_SYNC_INCREMENTAL)
    {
      g_autofree gchar *copy = NULL;
      gint line;
      gint column;
251

252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
      copy = g_strndup (new_text, len);

      line = gtk_text_iter_get_line (location);
      column = gtk_text_iter_get_line_offset (location);

      params = JSONRPC_MESSAGE_NEW (
        "textDocument", "{",
          "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
          "version", JSONRPC_MESSAGE_PUT_INT64 (version),
        "}",
        "contentChanges", "[",
          "{",
            "range", "{",
              "start", "{",
                "line", JSONRPC_MESSAGE_PUT_INT64 (line),
                "character", JSONRPC_MESSAGE_PUT_INT64 (column),
              "}",
              "end", "{",
                "line", JSONRPC_MESSAGE_PUT_INT64 (line),
                "character", JSONRPC_MESSAGE_PUT_INT64 (column),
              "}",
            "}",
            "rangeLength", JSONRPC_MESSAGE_PUT_INT64 (0),
            "text", JSONRPC_MESSAGE_PUT_STRING (copy),
276
          "}",
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
        "]");
    }
  else if (text_document_sync == TEXT_DOCUMENT_SYNC_FULL)
    {
      g_autoptr(GBytes) content = NULL;
      const gchar *text;
      g_autoptr(GString) str = NULL;

      content = ide_buffer_dup_content (buffer);
      text = (const gchar *)g_bytes_get_data (content, NULL);
      str = g_string_new (text);
      g_string_insert_len (str, gtk_text_iter_get_offset (location), new_text, len);

      params = JSONRPC_MESSAGE_NEW (
        "textDocument", "{",
          "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
          "version", JSONRPC_MESSAGE_PUT_INT64 (version),
294
        "}",
295 296 297 298 299 300 301
        "contentChanges", "[",
          "{",
            "text", JSONRPC_MESSAGE_PUT_STRING (str->str),
          "}",
        "]");
    }

302

Christian Hergert's avatar
Christian Hergert committed
303 304 305 306
  ide_lsp_client_send_notification_async (self,
                                          "textDocument/didChange",
                                          params,
                                          NULL, NULL, NULL);
307 308

  IDE_EXIT;
309 310 311
}

static void
312
ide_lsp_client_buffer_delete_range (IdeLspClient *self,
Daniel Buch Hansen's avatar
Daniel Buch Hansen committed
313 314 315
                                    GtkTextIter  *begin_iter,
                                    GtkTextIter  *end_iter,
                                    IdeBuffer    *buffer)
316 317
{

318
  g_autoptr(GVariant) params = NULL;
319
  g_autofree gchar *uri = NULL;
320 321
  GtkTextIter copy_begin;
  GtkTextIter copy_end;
322 323 324 325 326 327 328
  struct {
    gint line;
    gint column;
  } begin, end;
  gint version;
  gint length;

329 330
  IDE_ENTRY;

331
  g_assert (IDE_IS_MAIN_THREAD ());
332
  g_assert (IDE_IS_LSP_CLIENT (self));
333 334 335 336
  g_assert (begin_iter != NULL);
  g_assert (end_iter != NULL);
  g_assert (IDE_IS_BUFFER (buffer));

337
  uri = ide_buffer_dup_uri (buffer);
Christian Hergert's avatar
Christian Hergert committed
338 339 340

  /* We get called before this change is registered */
  version = (gint)ide_buffer_get_change_count (buffer) + 1;
341

342 343 344
  copy_begin = *begin_iter;
  copy_end = *end_iter;
  gtk_text_iter_order (&copy_begin, &copy_end);
345

346 347
  begin.line = gtk_text_iter_get_line (&copy_begin);
  begin.column = gtk_text_iter_get_line_offset (&copy_begin);
348

349 350 351 352
  end.line = gtk_text_iter_get_line (&copy_end);
  end.column = gtk_text_iter_get_line_offset (&copy_end);

  length = gtk_text_iter_get_offset (&copy_end) - gtk_text_iter_get_offset (&copy_begin);
353

354
  params = JSONRPC_MESSAGE_NEW (
355
    "textDocument", "{",
356
      "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
357
      "version", JSONRPC_MESSAGE_PUT_INT64 (version),
358 359 360 361 362
    "}",
    "contentChanges", "[",
      "{",
        "range", "{",
          "start", "{",
363 364
            "line", JSONRPC_MESSAGE_PUT_INT64 (begin.line),
            "character", JSONRPC_MESSAGE_PUT_INT64 (begin.column),
365 366
          "}",
          "end", "{",
367 368
            "line", JSONRPC_MESSAGE_PUT_INT64 (end.line),
            "character", JSONRPC_MESSAGE_PUT_INT64 (end.column),
369 370
          "}",
        "}",
371
        "rangeLength", JSONRPC_MESSAGE_PUT_INT64 (length),
Christian Hergert's avatar
Christian Hergert committed
372
        "text", JSONRPC_MESSAGE_PUT_STRING (""),
373 374 375
      "}",
    "]");

Christian Hergert's avatar
Christian Hergert committed
376 377 378 379
  ide_lsp_client_send_notification_async (self,
                                          "textDocument/didChange",
                                          params,
                                          NULL, NULL, NULL);
380 381

  IDE_EXIT;
382 383
}

384
static void
Daniel Buch Hansen's avatar
Daniel Buch Hansen committed
385 386 387
ide_lsp_client_buffer_loaded (IdeLspClient     *self,
                              IdeBuffer        *buffer,
                              IdeBufferManager *buffer_manager)
388
{
389
  g_autoptr(GVariant) params = NULL;
390
  g_autofree gchar *uri = NULL;
391 392 393 394 395
  g_autofree gchar *text = NULL;
  GtkSourceLanguage *language;
  const gchar *language_id;
  GtkTextIter begin;
  GtkTextIter end;
396
  gint64 version;
397

398 399
  IDE_ENTRY;

400
  g_assert (IDE_IS_MAIN_THREAD ());
401
  g_assert (IDE_IS_LSP_CLIENT (self));
402 403 404
  g_assert (IDE_IS_BUFFER (buffer));
  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));

405
  if (!ide_lsp_client_supports_buffer (self, buffer))
406 407
    IDE_EXIT;

408 409
  g_signal_connect_object (buffer,
                           "insert-text",
410
                           G_CALLBACK (ide_lsp_client_buffer_insert_text),
411 412 413 414 415
                           self,
                           G_CONNECT_SWAPPED);

  g_signal_connect_object (buffer,
                           "delete-range",
416
                           G_CALLBACK (ide_lsp_client_buffer_delete_range),
417 418 419
                           self,
                           G_CONNECT_SWAPPED);

420
  uri = ide_buffer_dup_uri (buffer);
421
  version = (gint64)ide_buffer_get_change_count (buffer);
422

423 424 425 426 427 428 429 430 431
  gtk_text_buffer_get_bounds (GTK_TEXT_BUFFER (buffer), &begin, &end);
  text = gtk_text_buffer_get_text (GTK_TEXT_BUFFER (buffer), &begin, &end, TRUE);

  language = gtk_source_buffer_get_language (GTK_SOURCE_BUFFER (buffer));
  if (language != NULL)
    language_id = gtk_source_language_get_id (language);
  else
    language_id = "text/plain";

432
  params = JSONRPC_MESSAGE_NEW (
433
    "textDocument", "{",
434
      "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
435 436
      "languageId", JSONRPC_MESSAGE_PUT_STRING (language_id),
      "text", JSONRPC_MESSAGE_PUT_STRING (text),
437
      "version", JSONRPC_MESSAGE_PUT_INT64 (version),
438 439
    "}"
  );
440

Christian Hergert's avatar
Christian Hergert committed
441 442 443 444
  ide_lsp_client_send_notification_async (self,
                                          "textDocument/didOpen",
                                          params,
                                          NULL, NULL, NULL);
445 446

  IDE_EXIT;
447 448 449
}

static void
Daniel Buch Hansen's avatar
Daniel Buch Hansen committed
450 451 452
ide_lsp_client_buffer_unloaded (IdeLspClient     *self,
                                IdeBuffer        *buffer,
                                IdeBufferManager *buffer_manager)
453
{
454
  g_autoptr(GVariant) params = NULL;
455
  g_autofree gchar *uri = NULL;
456

457 458
  IDE_ENTRY;

459
  g_assert (IDE_IS_MAIN_THREAD ());
460
  g_assert (IDE_IS_LSP_CLIENT (self));
461 462 463
  g_assert (IDE_IS_BUFFER (buffer));
  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));

464
  if (!ide_lsp_client_supports_buffer (self, buffer))
465 466
    IDE_EXIT;

467
  uri = ide_buffer_dup_uri (buffer);
468

469
  params = JSONRPC_MESSAGE_NEW (
470
    "textDocument", "{",
471
      "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
472 473
    "}"
  );
474

Christian Hergert's avatar
Christian Hergert committed
475 476 477 478
  ide_lsp_client_send_notification_async (self,
                                          "textDocument/didClose",
                                          params,
                                          NULL, NULL, NULL);
479 480

  IDE_EXIT;
481 482 483
}

static void
Daniel Buch Hansen's avatar
Daniel Buch Hansen committed
484 485 486
ide_lsp_client_buffer_manager_bind (IdeLspClient     *self,
                                    IdeBufferManager *buffer_manager,
                                    DzlSignalGroup   *signal_group)
487 488 489
{
  guint n_items;

490
  g_assert (IDE_IS_MAIN_THREAD ());
491
  g_assert (IDE_IS_LSP_CLIENT (self));
492
  g_assert (IDE_IS_BUFFER_MANAGER (buffer_manager));
493
  g_assert (DZL_IS_SIGNAL_GROUP (signal_group));
494 495 496 497 498 499 500 501

  n_items = g_list_model_get_n_items (G_LIST_MODEL (buffer_manager));

  for (guint i = 0; i < n_items; i++)
    {
      g_autoptr(IdeBuffer) buffer = NULL;

      buffer = g_list_model_get_item (G_LIST_MODEL (buffer_manager), i);
502
      ide_lsp_client_buffer_loaded (self, buffer, buffer_manager);
503 504 505 506
    }
}

static void
Christian Hergert's avatar
Christian Hergert committed
507 508
ide_lsp_client_buffer_manager_unbind (IdeLspClient   *self,
                                      DzlSignalGroup *signal_group)
509
{
510
  g_assert (IDE_IS_LSP_CLIENT (self));
511
  g_assert (DZL_IS_SIGNAL_GROUP (signal_group));
512 513 514 515 516 517

  /* TODO: We need to track everything we've notified so that we
   *       can notify the peer to release its resources.
   */
}

518
static void
519
ide_lsp_client_project_file_trashed (IdeLspClient *self,
Daniel Buch Hansen's avatar
Daniel Buch Hansen committed
520 521
                                     GFile        *file,
                                     IdeProject   *project)
522
{
523
  g_autoptr(GVariant) params = NULL;
524 525 526 527
  g_autofree gchar *uri = NULL;

  IDE_ENTRY;

528
  g_assert (IDE_IS_MAIN_THREAD ());
529
  g_assert (IDE_IS_LSP_CLIENT (self));
530 531 532 533 534
  g_assert (G_IS_FILE (file));
  g_assert (IDE_IS_PROJECT (project));

  uri = g_file_get_uri (file);

535
  params = JSONRPC_MESSAGE_NEW (
536
    "changes", "[",
537
      "{",
538
        "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
539
        "type", JSONRPC_MESSAGE_PUT_INT64 (FILE_CHANGE_TYPE_DELETED),
540 541 542 543
      "}",
    "]"
  );

544
  ide_lsp_client_send_notification_async (self, "workspace/didChangeWatchedFiles",
545
                                               params, NULL, NULL, NULL);
546

547
  ide_lsp_client_clear_diagnostics (self, uri);
548 549 550 551 552

  IDE_EXIT;
}

static void
553 554 555 556
ide_lsp_client_project_file_renamed (IdeLspClient *self,
                                     GFile        *src,
                                     GFile        *dst,
                                     IdeProject   *project)
557
{
558
  g_autoptr(GVariant) params = NULL;
559 560 561 562 563
  g_autofree gchar *src_uri = NULL;
  g_autofree gchar *dst_uri = NULL;

  IDE_ENTRY;

564
  g_assert (IDE_IS_MAIN_THREAD ());
565
  g_assert (IDE_IS_LSP_CLIENT (self));
566 567 568 569 570 571 572
  g_assert (G_IS_FILE (src));
  g_assert (G_IS_FILE (dst));
  g_assert (IDE_IS_PROJECT (project));

  src_uri = g_file_get_uri (src);
  dst_uri = g_file_get_uri (dst);

573
  params = JSONRPC_MESSAGE_NEW (
574 575
    "changes", "["
      "{",
576
        "uri", JSONRPC_MESSAGE_PUT_STRING (src_uri),
577
        "type", JSONRPC_MESSAGE_PUT_INT64 (FILE_CHANGE_TYPE_DELETED),
578 579
      "}",
      "{",
580
        "uri", JSONRPC_MESSAGE_PUT_STRING (dst_uri),
581
        "type", JSONRPC_MESSAGE_PUT_INT64 (FILE_CHANGE_TYPE_CREATED),
582 583 584 585
      "}",
    "]"
  );

586
  ide_lsp_client_send_notification_async (self, "workspace/didChangeWatchedFiles",
587
                                               params, NULL, NULL, NULL);
588

589
  ide_lsp_client_clear_diagnostics (self, src_uri);
590 591 592 593

  IDE_EXIT;
}

594
static IdeDiagnostics *
595 596 597
ide_lsp_client_translate_diagnostics (IdeLspClient *self,
                                      GFile        *file,
                                      GVariantIter *diagnostics)
598 599
{
  g_autoptr(GPtrArray) ar = NULL;
600
  g_autoptr(IdeDiagnostics) ret = NULL;
601
  GVariant *value;
602

603
  g_assert (IDE_IS_MAIN_THREAD ());
604
  g_assert (IDE_IS_LSP_CLIENT (self));
605 606
  g_assert (diagnostics != NULL);

607
  ar = g_ptr_array_new_with_free_func (g_object_unref);
608

609
  while (g_variant_iter_loop (diagnostics, "v", &value))
610
    {
611 612
      g_autoptr(IdeLocation) begin_loc = NULL;
      g_autoptr(IdeLocation) end_loc = NULL;
613
      g_autoptr(IdeDiagnostic) diag = NULL;
614
      g_autoptr(GVariant) range = NULL;
615 616
      const gchar *message = NULL;
      const gchar *source = NULL;
617
      gint64 severity = 0;
618 619
      gboolean success;
      struct {
620 621
        gint64 line;
        gint64 column;
622 623 624
      } begin, end;

      /* Mandatory fields */
625 626 627
      if (!JSONRPC_MESSAGE_PARSE (value,
                                  "range", JSONRPC_MESSAGE_GET_VARIANT (&range),
                                  "message", JSONRPC_MESSAGE_GET_STRING (&message)))
628 629 630
        continue;

      /* Optional Fields */
631
      JSONRPC_MESSAGE_PARSE (value, "severity", JSONRPC_MESSAGE_GET_INT64 (&severity));
632
      JSONRPC_MESSAGE_PARSE (value, "source", JSONRPC_MESSAGE_GET_STRING (&source));
633 634

      /* Extract location information */
635
      success = JSONRPC_MESSAGE_PARSE (range,
636
        "start", "{",
637 638
          "line", JSONRPC_MESSAGE_GET_INT64 (&begin.line),
          "character", JSONRPC_MESSAGE_GET_INT64 (&begin.column),
639 640
        "}",
        "end", "{",
641 642
          "line", JSONRPC_MESSAGE_GET_INT64 (&end.line),
          "character", JSONRPC_MESSAGE_GET_INT64 (&end.column),
643 644 645 646 647 648
        "}"
      );

      if (!success)
        continue;

649 650
      begin_loc = ide_location_new (file, begin.line, begin.column);
      end_loc = ide_location_new (file, end.line, end.column);
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669

      switch (severity)
        {
        case SEVERITY_ERROR:
          severity = IDE_DIAGNOSTIC_ERROR;
          break;

        case SEVERITY_WARNING:
          severity = IDE_DIAGNOSTIC_WARNING;
          break;

        case SEVERITY_INFORMATION:
        case SEVERITY_HINT:
        default:
          severity = IDE_DIAGNOSTIC_NOTE;
          break;
        }

      diag = ide_diagnostic_new (severity, message, begin_loc);
670
      ide_diagnostic_take_range (diag, ide_range_new (begin_loc, end_loc));
671 672 673 674

      g_ptr_array_add (ar, g_steal_pointer (&diag));
    }

675 676 677 678 679 680 681 682 683
  ret = ide_diagnostics_new ();

  if (ar != NULL)
    {
      for (guint i = 0; i < ar->len; i++)
        ide_diagnostics_add (ret, g_ptr_array_index (ar, i));
    }

  return g_steal_pointer (&ret);
684 685
}

686
static void
687 688
ide_lsp_client_text_document_publish_diagnostics (IdeLspClient *self,
                                                  GVariant     *params)
689
{
690
  IdeLspClientPrivate *priv = ide_lsp_client_get_instance_private (self);
691
  g_autoptr(GVariantIter) json_diagnostics = NULL;
692 693 694 695 696
  const gchar *uri = NULL;
  gboolean success;

  IDE_ENTRY;

697
  g_assert (IDE_IS_MAIN_THREAD ());
698
  g_assert (IDE_IS_LSP_CLIENT (self));
699 700
  g_assert (params != NULL);

701 702 703
  success = JSONRPC_MESSAGE_PARSE (params,
    "uri", JSONRPC_MESSAGE_GET_STRING (&uri),
    "diagnostics", JSONRPC_MESSAGE_GET_ITER (&json_diagnostics)
704 705
  );

706 707 708
  if (success)
    {
      g_autoptr(GFile) file = NULL;
709
      g_autoptr(IdeDiagnostics) diagnostics = NULL;
710

711
      file = g_file_new_for_uri (uri);
712

713
      diagnostics = ide_lsp_client_translate_diagnostics (self, file, json_diagnostics);
714

715 716 717
      IDE_TRACE_MSG ("%"G_GSIZE_FORMAT" diagnostics received for %s",
                     diagnostics ? ide_diagnostics_get_size (diagnostics) : 0,
                     uri);
718

719 720 721 722 723
      /*
       * Insert the diagnostics into our cache before emit any signals
       * so that we have up to date information incase the signal causes
       * a callback to query back.
       */
724
      g_hash_table_insert (priv->diagnostics_by_file,
725
                           g_object_ref (file),
726
                           g_object_ref (diagnostics));
727 728

      g_signal_emit (self, signals [PUBLISHED_DIAGNOSTICS], 0, file, diagnostics);
729
    }
730 731 732 733 734

  IDE_EXIT;
}

static void
735 736 737
ide_lsp_client_real_notification (IdeLspClient *self,
                                  const gchar  *method,
                                  GVariant     *params)
738 739 740
{
  IDE_ENTRY;

741
  g_assert (IDE_IS_MAIN_THREAD ());
742
  g_assert (IDE_IS_LSP_CLIENT (self));
743 744
  g_assert (method != NULL);

745 746 747
  if (params != NULL)
    {
      if (g_str_equal (method, "textDocument/publishDiagnostics"))
Günther Wagner's avatar
Günther Wagner committed
748 749 750
        {
          ide_lsp_client_text_document_publish_diagnostics (self, params);
        }
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773
      else if (g_str_equal (method, "$/progress"))
        {
          const gchar *token = NULL;
          const gchar *message = NULL;
          const gchar *title = NULL;
          const gchar *kind = NULL;
          IdeContext *context;
          IdeNotifications *notifications;
          IdeNotification *notification;

          JSONRPC_MESSAGE_PARSE (params, "token", JSONRPC_MESSAGE_GET_STRING (&token),
                                         "value", "{",
                                           "kind", JSONRPC_MESSAGE_GET_STRING (&kind),
                                         "}");
          JSONRPC_MESSAGE_PARSE (params, "value", "{",
                                           "title", JSONRPC_MESSAGE_GET_STRING (&title),
                                         "}");
          JSONRPC_MESSAGE_PARSE (params, "value", "{",
                                           "message", JSONRPC_MESSAGE_GET_STRING (&message),
                                         "}");
          context = ide_object_get_context (IDE_OBJECT (self));
          notifications = ide_object_get_child_typed (IDE_OBJECT (context), IDE_TYPE_NOTIFICATIONS);

Günther Wagner's avatar
Günther Wagner committed
774
          if (ide_str_equal0 (kind, "begin"))
775 776 777 778 779 780
            {
              notification = ide_notification_new ();
              ide_notification_set_id (notification, token);
              ide_notification_set_has_progress (notification, TRUE);
              ide_notification_set_progress_is_imprecise (notification, TRUE);
              ide_notification_set_title (notification, title);
Günther Wagner's avatar
Günther Wagner committed
781
              ide_notification_set_body (notification, message != NULL ? message : title);
782 783 784 785 786 787
              ide_notification_attach (notification, IDE_OBJECT (context));
            }
          else
            {
              notification = ide_notifications_find_by_id (notifications, token);
              if (message != NULL && notification != NULL)
Günther Wagner's avatar
Günther Wagner committed
788
                ide_notification_set_body (notification, message);
789 790
            }

Günther Wagner's avatar
Günther Wagner committed
791
          if (ide_str_equal0 (kind, "end") && notification != NULL)
792 793
            ide_notification_withdraw_in_seconds (notification, 3);
        }
794
    }
795 796 797 798 799

  IDE_EXIT;
}

static void
800 801 802 803
ide_lsp_client_send_notification (IdeLspClient  *self,
                                  const gchar   *method,
                                  GVariant      *params,
                                  JsonrpcClient *rpc_client)
804 805 806 807 808
{
  GQuark detail;

  IDE_ENTRY;

809
  g_assert (IDE_IS_MAIN_THREAD ());
810
  g_assert (IDE_IS_LSP_CLIENT (self));
811
  g_assert (method != NULL);
812
  g_assert (JSONRPC_IS_CLIENT (rpc_client));
813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828

  IDE_TRACE_MSG ("Notification: %s", method);

  /*
   * To avoid leaking quarks we do not create a quark for the string unless
   * it already exists. This should be fine in practice because we only need
   * the quark if there is a caller that has registered for it. And the callers
   * registering for it will necessarily create the quark.
   */
  detail = g_quark_try_string (method);

  g_signal_emit (self, signals [NOTIFICATION], detail, method, params);

  IDE_EXIT;
}

829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964
static void
ide_lsp_client_apply_edit_cb (GObject      *object,
                              GAsyncResult *result,
                              gpointer      user_data)
{
  IdeBufferManager *bufmgr = (IdeBufferManager *)object;
  g_autoptr(AsyncCall) call = user_data;
  g_autoptr(GError) error = NULL;
  g_autoptr(GVariant) reply = NULL;

  IDE_ENTRY;

  g_assert (IDE_IS_BUFFER_MANAGER (bufmgr));
  g_assert (G_IS_ASYNC_RESULT (result));
  g_assert (call != NULL);
  g_assert (JSONRPC_IS_CLIENT (call->client));
  g_assert (call->id != NULL);

  if (ide_buffer_manager_apply_edits_finish (bufmgr, result, &error))
    reply = JSONRPC_MESSAGE_NEW ("applied", JSONRPC_MESSAGE_PUT_BOOLEAN (TRUE));
  else
    reply = JSONRPC_MESSAGE_NEW ("applied", JSONRPC_MESSAGE_PUT_BOOLEAN (FALSE),
                                 "failureReason", JSONRPC_MESSAGE_PUT_STRING (error->message));

  jsonrpc_client_reply_async (call->client,
                              call->id,
                              reply,
                              NULL, NULL, NULL);

  IDE_EXIT;
}

static gboolean
ide_lsp_client_handle_apply_edit (IdeLspClient  *self,
                                  JsonrpcClient *client,
                                  GVariant      *id,
                                  GVariant      *params)
{
  g_autoptr(GVariant) parent = NULL;
  g_autoptr(GVariant) changes = NULL;
  g_autoptr(GPtrArray) edits = NULL;

  IDE_ENTRY;

  g_assert (IDE_IS_LSP_CLIENT (self));
  g_assert (JSONRPC_IS_CLIENT (client));
  g_assert (id != NULL);
  g_assert (params != NULL);

  if (!(parent = g_variant_lookup_value (params, "edit", G_VARIANT_TYPE_VARDICT)))
    IDE_GOTO (invalid_params);

  edits = g_ptr_array_new_with_free_func (g_object_unref);

#if 0
  /* We'd prefer to support this, but do not currently */
  if (JSONRPC_MESSAGE_PARSE (edit, "documentChanges", JSONRPC_MESSAGE_GET_VARIANT (&changes)))
    {
    }
#endif

  if (JSONRPC_MESSAGE_PARSE (parent, "changes", JSONRPC_MESSAGE_GET_VARIANT (&changes)))
    {
      if (g_variant_is_of_type (changes, G_VARIANT_TYPE_VARDICT))
        {
          GVariantIter iter;
          GVariant *value;
          gchar *uri;

          g_variant_iter_init (&iter, changes);
          while (g_variant_iter_loop (&iter, "{sv}", &uri, &value))
            {
              GVariantIter edit_iter;
              GVariant *item;
              struct {
                gint64 line;
                gint64 column;
              } begin, end;

              g_variant_iter_init (&edit_iter, value);
              while (g_variant_iter_loop (&edit_iter, "v", &item))
                {
                  const gchar *new_text = NULL;
                  gboolean r;

                  r = JSONRPC_MESSAGE_PARSE (item,
                    "range", "{",
                      "start", "{",
                        "line", JSONRPC_MESSAGE_GET_INT64 (&begin.line),
                        "character", JSONRPC_MESSAGE_GET_INT64 (&begin.column),
                      "}",
                      "end", "{",
                        "line", JSONRPC_MESSAGE_GET_INT64 (&end.line),
                        "character", JSONRPC_MESSAGE_GET_INT64 (&end.column),
                      "}",
                    "}",
                    "newText", JSONRPC_MESSAGE_GET_STRING (&new_text)
                  );

                  if (r)
                    {
                      g_autoptr(IdeLocation) begin_loc = NULL;
                      g_autoptr(IdeLocation) end_loc = NULL;
                      g_autoptr(IdeRange) range = NULL;
                      g_autoptr(GFile) file = NULL;

                      file = g_file_new_for_uri (uri);
                      begin_loc = ide_location_new (file, begin.line, begin.column);
                      end_loc = ide_location_new (file, end.line, end.column);
                      range = ide_range_new (begin_loc, end_loc);

                      g_ptr_array_add (edits, ide_text_edit_new (range, new_text));
                    }
                }
            }
        }
    }

  if (edits->len > 0)
    {
      g_autoptr(IdeContext) context = ide_object_ref_context (IDE_OBJECT (self));
      IdeBufferManager *bufmgr = ide_buffer_manager_from_context (context);

      ide_buffer_manager_apply_edits_async (bufmgr,
                                            IDE_PTR_ARRAY_STEAL_FULL (&edits),
                                            NULL,
                                            ide_lsp_client_apply_edit_cb,
                                            async_call_new (client, id));

      IDE_RETURN (TRUE);
    }

invalid_params:
  IDE_RETURN (FALSE);
}

965 966 967 968 969 970 971 972 973 974 975 976 977 978
static gboolean
ide_lsp_client_handle_call (IdeLspClient  *self,
                            const gchar   *method,
                            GVariant      *id,
                            GVariant      *params,
                            JsonrpcClient *client)
{
  IDE_ENTRY;

  g_assert (IDE_IS_LSP_CLIENT (self));
  g_assert (method != NULL);
  g_assert (id != NULL);
  g_assert (JSONRPC_IS_CLIENT (client));

979 980
  IDE_TRACE_MSG ("Received remote call for method \"%s\"", method);

981 982 983 984 985 986 987 988
  if (strcmp (method, "workspace/configuration") == 0)
    {
      g_autoptr(GVariant) config = NULL;

      g_signal_emit (self, signals [LOAD_CONFIGURATION], 0, &config);

      if (config != NULL)
        {
989 990 991
          /* Ensure we didn't get anything floating */
          g_variant_take_ref (config);