ide-lsp-hover-provider.c 14.3 KB
Newer Older
1
/* ide-lsp-hover-provider.c
2
 *
3
 * Copyright 2018-2019 Christian Hergert <chergert@redhat.com>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
 *
 * 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
 */

21
#define G_LOG_DOMAIN "ide-lsp-hover-provider"
22

23 24
#include "config.h"

25
#include <jsonrpc-glib.h>
26 27 28
#include <libide-code.h>
#include <libide-sourceview.h>
#include <libide-threading.h>
29

30
#include "ide-lsp-hover-provider.h"
31 32

/**
33 34
 * SECTION:ide-lsp-hover-provider
 * @title: IdeLspHoverProvider
35 36
 * @short_description: Interactive hover integration for language servers
 *
37
 * The #IdeLspHoverProvider provides integration with language servers
38 39 40
 * that support hover requests. This can display markup in the interactive
 * tooltip that is displayed in the editor.
 *
41
 * Since: 3.30
42 43 44 45
 */

typedef struct
{
46
  IdeLspClient *client;
47 48
  gchar *category;
  gint priority;
49
} IdeLspHoverProviderPrivate;
50 51 52

static void hover_provider_iface_init (IdeHoverProviderInterface *iface);

53 54
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (IdeLspHoverProvider,
                                  ide_lsp_hover_provider,
55
                                  IDE_TYPE_OBJECT,
56
                                  G_ADD_PRIVATE (IdeLspHoverProvider)
57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
                                  G_IMPLEMENT_INTERFACE (IDE_TYPE_HOVER_PROVIDER, hover_provider_iface_init))

enum {
  PROP_0,
  PROP_CATEGORY,
  PROP_CLIENT,
  PROP_PRIORITY,
  N_PROPS
};

static GParamSpec *properties [N_PROPS];

static IdeMarkedContent *
parse_marked_string (GVariant *v)
{
72
  g_autoptr(GString) gstr = g_string_new (NULL);
73 74 75 76 77 78 79 80 81 82 83 84
  g_autoptr(GVariant) child = NULL;
  GVariant *item;
  GVariantIter iter;

  g_assert (v != NULL);

  /*
   * @v can be (MarkedString | MarkedString[] | MarkupContent)
   *
   * MarkedString is (string | { language: string, value: string })
   */

85 86 87 88 89 90 91 92 93 94 95
  if (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING))
    {
      gsize len = 0;
      const gchar *str = g_variant_get_string (v, &len);

      if (str && *str == '\0')
        return NULL;

      return ide_marked_content_new_from_data (str, len, IDE_MARKED_KIND_PLAINTEXT);
    }

96 97 98 99
  if (g_variant_is_of_type (v, G_VARIANT_TYPE_VARIANT))
    v = child = g_variant_get_variant (v);


100
  if (g_variant_is_of_type (v, G_VARIANT_TYPE_DICTIONARY))
101
    {
102
      const gchar *value = "";
103

104 105 106 107 108 109 110 111
      g_variant_lookup (v, "value", "&s", &value);
      if (!ide_str_empty0 (value))
        g_string_append_printf (gstr, "%s", value);
    }
  else
    {
      g_variant_iter_init (&iter, v);
      if ((item = g_variant_iter_next_value (&iter)))
112
        {
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
          GVariant *asv = item;
          g_autoptr(GVariant) child2 = NULL;

          if (g_variant_is_of_type (item, G_VARIANT_TYPE_VARIANT))
            asv = child2 = g_variant_get_variant (item);

          if (g_variant_is_of_type (asv, G_VARIANT_TYPE_STRING))
            g_string_append (gstr, g_variant_get_string (asv, NULL));
          else if (g_variant_is_of_type (asv, G_VARIANT_TYPE_VARDICT))
            {
              const gchar *lang = "";
              const gchar *value = "";

              g_variant_lookup (asv, "language", "&s", &lang);
              g_variant_lookup (asv, "value", "&s", &value);

    #if 0
              if (!ide_str_empty0 (lang) && !ide_str_empty0 (value))
                g_string_append_printf (str, "```%s\n%s\n```", lang, value);
              else if (!ide_str_empty0 (value))
                g_string_append (str, value);
    #else
              if (!ide_str_empty0 (value))
                g_string_append_printf (gstr, "```\n%s\n```", value);
    #endif
            }

          g_variant_unref (item);
141 142
        }
    }
143 144
  if (gstr->len)
    return ide_marked_content_new_from_data (gstr->str, gstr->len, IDE_MARKED_KIND_MARKDOWN);
145 146 147 148 149

  return NULL;
}

static void
150
ide_lsp_hover_provider_dispose (GObject *object)
151
{
152 153
  IdeLspHoverProvider *self = (IdeLspHoverProvider *)object;
  IdeLspHoverProviderPrivate *priv = ide_lsp_hover_provider_get_instance_private (self);
154 155 156 157 158 159

  IDE_ENTRY;

  g_clear_object (&priv->client);
  g_clear_pointer (&priv->category, g_free);

160
  G_OBJECT_CLASS (ide_lsp_hover_provider_parent_class)->dispose (object);
161 162 163 164 165

  IDE_EXIT;
}

static void
166
ide_lsp_hover_provider_get_property (GObject    *object,
167 168 169 170
                                          guint       prop_id,
                                          GValue     *value,
                                          GParamSpec *pspec)
{
171 172
  IdeLspHoverProvider *self = IDE_LSP_HOVER_PROVIDER (object);
  IdeLspHoverProviderPrivate *priv = ide_lsp_hover_provider_get_instance_private (self);
173 174 175 176 177 178 179 180

  switch (prop_id)
    {
    case PROP_CATEGORY:
      g_value_set_string (value, priv->category);
      break;

    case PROP_CLIENT:
181
      g_value_set_object (value, ide_lsp_hover_provider_get_client (self));
182 183 184 185 186 187 188 189 190 191 192 193
      break;

    case PROP_PRIORITY:
      g_value_set_int (value, priv->priority);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
194
ide_lsp_hover_provider_set_property (GObject      *object,
195 196 197 198
                                          guint         prop_id,
                                          const GValue *value,
                                          GParamSpec   *pspec)
{
199 200
  IdeLspHoverProvider *self = IDE_LSP_HOVER_PROVIDER (object);
  IdeLspHoverProviderPrivate *priv = ide_lsp_hover_provider_get_instance_private (self);
201 202 203 204 205 206 207 208 209

  switch (prop_id)
    {
    case PROP_CATEGORY:
      g_free (priv->category);
      priv->category = g_value_dup_string (value);
      break;

    case PROP_CLIENT:
210
      ide_lsp_hover_provider_set_client (self, g_value_get_object (value));
211 212 213 214 215 216 217 218 219 220 221 222
      break;

    case PROP_PRIORITY:
      priv->priority = g_value_get_int (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}

static void
223
ide_lsp_hover_provider_class_init (IdeLspHoverProviderClass *klass)
224 225 226
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

227 228 229
  object_class->dispose = ide_lsp_hover_provider_dispose;
  object_class->get_property = ide_lsp_hover_provider_get_property;
  object_class->set_property = ide_lsp_hover_provider_set_property;
230 231

  /**
232
   * IdeLspHoverProvider:client:
233
   *
234
   * The "client" property is the #IdeLspClient that should be used to
235 236
   * communicate with the Language Server peer process.
   *
237
   * Since: 3.30
238 239 240 241 242
   */
  properties [PROP_CLIENT] =
    g_param_spec_object ("client",
                         "Client",
                         "The client to communicate with",
243
                         IDE_TYPE_LSP_CLIENT,
244 245 246
                         (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS));

  /**
247
   * IdeLspHoverProvider:category:
248 249 250 251
   *
   * The "category" property is the category name to use when displaying
   * the hover contents.
   *
252
   * Since: 3.30
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
   */
  properties [PROP_CATEGORY] =
    g_param_spec_string ("category",
                         "Category",
                         "The category to display in the hover popover",
                         NULL,
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties [PROP_PRIORITY] =
    g_param_spec_int ("priority",
                      "Priority",
                      "Priority for hover content",
                      G_MININT,
                      G_MAXINT,
                      0,
                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, N_PROPS, properties);
}

static void
274
ide_lsp_hover_provider_init (IdeLspHoverProvider *self)
275 276 277 278
{
}

static void
279
ide_lsp_hover_provider_hover_cb (GObject      *object,
280 281 282
                                      GAsyncResult *result,
                                      gpointer      user_data)
{
283 284 285
  IdeLspClient *client = (IdeLspClient *)object;
  IdeLspHoverProvider *self;
  IdeLspHoverProviderPrivate *priv;
286 287 288 289 290 291 292 293 294
  g_autoptr(GVariant) reply = NULL;
  g_autoptr(GVariant) contents = NULL;
  g_autoptr(IdeMarkedContent) marked = NULL;
  g_autoptr(IdeTask) task = user_data;
  g_autoptr(GError) error = NULL;
  IdeHoverContext *context;

  IDE_ENTRY;

295
  g_assert (IDE_IS_LSP_CLIENT (client));
296 297 298 299
  g_assert (G_IS_ASYNC_RESULT (result));
  g_assert (IDE_IS_TASK (task));

  self = ide_task_get_source_object (task);
300
  priv = ide_lsp_hover_provider_get_instance_private (self);
301

302
  g_assert (IDE_IS_LSP_HOVER_PROVIDER (self));
303

304
  if (!ide_lsp_client_call_finish (client, result, &reply, &error))
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
    {
      ide_task_return_error (task, g_steal_pointer (&error));
      IDE_EXIT;
    }

  if (!g_variant_is_of_type (reply, G_VARIANT_TYPE_VARDICT) ||
      !(contents = g_variant_lookup_value (reply, "contents", NULL)))
    {
      ide_task_return_new_error (task,
                                 G_IO_ERROR,
                                 G_IO_ERROR_INVALID_DATA,
                                 "Expected 'contents' in reply");
      IDE_EXIT;
    }

  if (!(marked = parse_marked_string (contents)))
    {
      ide_task_return_new_error (task,
                                 G_IO_ERROR,
                                 G_IO_ERROR_INVALID_DATA,
                                 "Unusable contents from language server");
      IDE_EXIT;
    }

  context = ide_task_get_task_data (task);

  g_assert (context != NULL);
  g_assert (IDE_IS_HOVER_CONTEXT (context));

  ide_hover_context_add_content (context,
                                 priv->priority,
                                 priv->category,
                                 marked);

  ide_task_return_boolean (task, TRUE);

  IDE_EXIT;
}

static void
345
ide_lsp_hover_provider_hover_async (IdeHoverProvider    *provider,
346 347 348 349 350 351
                                         IdeHoverContext     *context,
                                         const GtkTextIter   *iter,
                                         GCancellable        *cancellable,
                                         GAsyncReadyCallback  callback,
                                         gpointer             user_data)
{
352 353
  IdeLspHoverProvider *self = (IdeLspHoverProvider *)provider;
  IdeLspHoverProviderPrivate *priv = ide_lsp_hover_provider_get_instance_private (self);
354 355 356 357 358 359 360 361 362 363
  g_autoptr(IdeTask) task = NULL;
  g_autoptr(GVariant) params = NULL;
  g_autofree gchar *uri = NULL;
  IdeBuffer *buffer;
  gint line;
  gint column;

  IDE_ENTRY;

  g_assert (IDE_IS_MAIN_THREAD ());
364
  g_assert (IDE_IS_LSP_HOVER_PROVIDER (self));
365 366 367 368 369 370
  g_assert (IDE_IS_HOVER_CONTEXT (context));
  g_assert (iter != NULL);
  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));

  task = ide_task_new (self, cancellable, callback, user_data);
  ide_task_set_task_data (task, g_object_ref (context), g_object_unref);
371
  ide_task_set_source_tag (task, ide_lsp_hover_provider_hover_async);
372 373 374 375 376 377 378 379 380 381 382

  if (priv->client == NULL)
    {
      ide_task_return_new_error (task,
                                 G_IO_ERROR,
                                 G_IO_ERROR_NOT_CONNECTED,
                                 "No client to deliver request");
      return;
    }

  buffer = IDE_BUFFER (gtk_text_iter_get_buffer (iter));
383
  uri = ide_buffer_dup_uri (buffer);
384 385 386 387 388 389 390 391 392 393 394 395 396
  line = gtk_text_iter_get_line (iter);
  column = gtk_text_iter_get_line_offset (iter);

  params = JSONRPC_MESSAGE_NEW (
    "textDocument", "{",
      "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
    "}",
    "position", "{",
      "line", JSONRPC_MESSAGE_PUT_INT32 (line),
      "character", JSONRPC_MESSAGE_PUT_INT32 (column),
    "}"
  );

397
  g_assert (IDE_IS_LSP_CLIENT (priv->client));
398

399
  ide_lsp_client_call_async (priv->client,
400 401 402
                                  "textDocument/hover",
                                  params,
                                  cancellable,
403
                                  ide_lsp_hover_provider_hover_cb,
404 405 406 407 408 409
                                  g_steal_pointer (&task));

  IDE_EXIT;
}

static gboolean
410
ide_lsp_hover_provider_hover_finish (IdeHoverProvider  *provider,
411 412 413 414 415 416 417
                                          GAsyncResult      *result,
                                          GError           **error)
{
  gboolean ret;

  IDE_ENTRY;

418
  g_assert (IDE_IS_LSP_HOVER_PROVIDER (provider));
419 420 421 422 423 424 425 426
  g_assert (IDE_IS_TASK (result));

  ret = ide_task_propagate_boolean (IDE_TASK (result), error);

  IDE_RETURN (ret);
}

static void
427
ide_lsp_hover_provider_real_load (IdeHoverProvider *provider,
428 429
                                       IdeSourceView    *view)
{
430
  IdeLspHoverProvider *self = (IdeLspHoverProvider *)provider;
431 432 433 434

  IDE_ENTRY;

  g_assert (IDE_IS_MAIN_THREAD ());
435
  g_assert (IDE_IS_LSP_HOVER_PROVIDER (self));
436

437 438
  if (IDE_LSP_HOVER_PROVIDER_GET_CLASS (self)->prepare)
    IDE_LSP_HOVER_PROVIDER_GET_CLASS (self)->prepare (self);
439 440

  IDE_EXIT;
441 442 443 444 445
}

static void
hover_provider_iface_init (IdeHoverProviderInterface *iface)
{
446 447 448
  iface->load = ide_lsp_hover_provider_real_load;
  iface->hover_async = ide_lsp_hover_provider_hover_async;
  iface->hover_finish = ide_lsp_hover_provider_hover_finish;
449 450 451
}

/**
452 453
 * ide_lsp_hover_provider_get_client:
 * @self: an #IdeLspHoverProvider
454 455 456
 *
 * Gets the client that is used for communication.
 *
457
 * Returns: (transfer none) (nullable): an #IdeLspClient or %NULL
458
 *
459
 * Since: 3.30
460
 */
461 462
IdeLspClient *
ide_lsp_hover_provider_get_client (IdeLspHoverProvider *self)
463
{
464
  IdeLspHoverProviderPrivate *priv = ide_lsp_hover_provider_get_instance_private (self);
465

466
  g_return_val_if_fail (IDE_IS_LSP_HOVER_PROVIDER (self), NULL);
467 468 469 470 471

  return priv->client;
}

/**
472 473 474
 * ide_lsp_hover_provider_set_client:
 * @self: an #IdeLspHoverProvider
 * @client: an #IdeLspClient
475 476 477
 *
 * Sets the client to be used to query for hover information.
 *
478
 * Since: 3.30
479 480
 */
void
481 482
ide_lsp_hover_provider_set_client (IdeLspHoverProvider *self,
                                        IdeLspClient        *client)
483
{
484
  IdeLspHoverProviderPrivate *priv = ide_lsp_hover_provider_get_instance_private (self);
485

486 487
  g_return_if_fail (IDE_IS_LSP_HOVER_PROVIDER (self));
  g_return_if_fail (!client || IDE_IS_LSP_CLIENT (client));
488 489 490 491

  if (g_set_object (&priv->client, client))
    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
}