[gnome-builder] lsp: code action support



commit 9fa9fb9b40629c71e440d07d7565ab7bc35568ff
Author: Georg Vienna <georg vienna himbarsoft com>
Date:   Thu Nov 11 20:55:58 2021 +0100

    lsp: code action support

 src/libide/code/ide-buffer.c                       | 142 ++++++++
 src/libide/code/ide-buffer.h                       |  10 +
 src/libide/code/ide-code-action-provider.c         | 116 +++++++
 src/libide/code/ide-code-action-provider.h         |  67 ++++
 src/libide/code/ide-code-action.c                  | 104 ++++++
 src/libide/code/ide-code-action.h                  |  65 ++++
 src/libide/code/libide-code.h                      |   2 +
 src/libide/code/meson.build                        |   4 +
 src/libide/lsp/ide-lsp-client.c                    |  18 +
 src/libide/lsp/ide-lsp-code-action-provider.c      | 296 ++++++++++++++++
 src/libide/lsp/ide-lsp-code-action-provider.h      |  49 +++
 src/libide/lsp/ide-lsp-code-action.c               | 374 +++++++++++++++++++++
 src/libide/lsp/ide-lsp-code-action.h               |  59 ++++
 src/libide/lsp/libide-lsp.h                        |   2 +
 src/libide/lsp/meson.build                         |   4 +
 src/libide/sourceview/ide-source-view.c            | 156 +++++++++
 .../jedi-language-server.plugin                    |   1 +
 .../jedi_language_server_plugin.py                 |   4 +
 src/plugins/rust-analyzer/meson.build              |   1 +
 .../rust-analyzer-code-action-provider.c           |  64 ++++
 .../rust-analyzer-code-action-provider.h           |  31 ++
 src/plugins/rust-analyzer/rust-analyzer-plugin.c   |   4 +
 src/plugins/rust-analyzer/rust-analyzer.plugin     |   1 +
 23 files changed, 1574 insertions(+)
---
diff --git a/src/libide/code/ide-buffer.c b/src/libide/code/ide-buffer.c
index 497496343..0c57854be 100644
--- a/src/libide/code/ide-buffer.c
+++ b/src/libide/code/ide-buffer.c
@@ -33,6 +33,7 @@
 #include "ide-buffer-addin-private.h"
 #include "ide-buffer-manager.h"
 #include "ide-buffer-private.h"
+#include "ide-code-action-provider.h"
 #include "ide-code-enums.h"
 #include "ide-diagnostic.h"
 #include "ide-diagnostics.h"
@@ -76,6 +77,7 @@ struct _IdeBuffer
   IdeExtensionSetAdapter *symbol_resolvers;
   IdeExtensionAdapter    *rename_provider;
   IdeExtensionAdapter    *formatter;
+  IdeExtensionAdapter    *code_action_provider;
   IdeBufferManager       *buffer_manager;
   IdeBufferChangeMonitor *change_monitor;
   GBytes                 *content;
@@ -386,6 +388,9 @@ ide_buffer_notify_language (IdeBuffer  *self,
 
   if (self->formatter)
     ide_extension_adapter_set_value (self->formatter, lang_id);
+
+  if (self->code_action_provider)
+    ide_extension_adapter_set_value (self->code_action_provider, lang_id);
 }
 
 static void
@@ -420,6 +425,7 @@ ide_buffer_dispose (GObject *object)
   ide_clear_and_destroy_object (&self->rename_provider);
   ide_clear_and_destroy_object (&self->symbol_resolvers);
   ide_clear_and_destroy_object (&self->formatter);
+  ide_clear_and_destroy_object (&self->code_action_provider);
   ide_clear_and_destroy_object (&self->highlight_engine);
   g_clear_object (&self->buffer_manager);
   ide_clear_and_destroy_object (&self->change_monitor);
@@ -1030,6 +1036,21 @@ ide_buffer_formatter_notify_extension (IdeBuffer           *self,
     ide_formatter_load (formatter);
 }
 
+static void
+ide_buffer_code_action_provider_notify_extension (IdeBuffer           *self,
+                                                  GParamSpec          *pspec,
+                                                  IdeExtensionAdapter *adapter)
+{
+  IdeCodeActionProvider *code_action_provider;
+
+  g_assert (IDE_IS_MAIN_THREAD ());
+  g_assert (IDE_IS_BUFFER (self));
+  g_assert (IDE_IS_EXTENSION_ADAPTER (adapter));
+
+  if ((code_action_provider = ide_extension_adapter_get_extension (adapter)))
+    ide_code_action_provider_load (code_action_provider);
+}
+
 static void
 ide_buffer_symbol_resolver_added (IdeExtensionSetAdapter *adapter,
                                   PeasPluginInfo         *plugin_info,
@@ -1142,6 +1163,20 @@ _ide_buffer_attach (IdeBuffer *self,
                            G_CONNECT_SWAPPED);
   ide_buffer_formatter_notify_extension (self, NULL, self->formatter);
 
+  /* Setup our code action provider, if any */
+  self->code_action_provider = ide_extension_adapter_new (parent,
+                                                          peas_engine_get_default (),
+                                                          IDE_TYPE_CODE_ACTION_PROVIDER,
+                                                          "Code-Action-Languages",
+                                                          ide_buffer_get_language_id (self));
+
+  g_signal_connect_object (self->code_action_provider,
+                           "notify::extension",
+                           G_CALLBACK (ide_buffer_code_action_provider_notify_extension),
+                           self,
+                           G_CONNECT_SWAPPED);
+  ide_buffer_code_action_provider_notify_extension (self, NULL, self->code_action_provider);
+
   /* Setup symbol resolvers */
   self->symbol_resolvers = ide_extension_set_adapter_new (parent,
                                                           peas_engine_get_default (),
@@ -2951,6 +2986,113 @@ ide_buffer_format_selection_finish (IdeBuffer     *self,
   IDE_RETURN (ret);
 }
 
+static void
+ide_buffer_query_code_action_cb(GObject      *object,
+                                GAsyncResult *result,
+                                gpointer user_data)
+{
+  IdeCodeActionProvider *code_action_provider = (IdeCodeActionProvider *)object;
+
+  g_autoptr(GError) error = NULL;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GPtrArray) code_actions = NULL;
+
+  g_assert(IDE_IS_CODE_ACTION_PROVIDER(object));
+  g_assert(G_IS_ASYNC_RESULT(result));
+  g_assert(IDE_IS_TASK(task));
+
+  code_actions = ide_code_action_provider_query_finish(code_action_provider, result, &error);
+
+  if (!code_actions)
+    ide_task_return_error(task, g_steal_pointer(&error));
+  else
+    ide_task_return_pointer(task, g_steal_pointer(&code_actions), g_ptr_array_unref);
+}
+
+/**
+ * ide_buffer_code_action_query_async:
+ * @self: an #IdeBuffer
+ * @cancellable: (nullable): a #GCancellable, or %NULL
+ * @callback: the callback upon completion
+ * @user_data: user data for @callback
+ *
+ * Queries for code actions in the current buffer.
+ *
+ * Since: 42.0
+ */
+void
+ide_buffer_code_action_query_async(IdeBuffer           *self,
+                                   GCancellable        *cancellable,
+                                   GAsyncReadyCallback callback,
+                                   gpointer user_data)
+{
+  g_autoptr(IdeTask)     task = NULL;
+  IdeCodeActionProvider *code_action_provider;
+
+  IDE_ENTRY;
+
+  g_return_if_fail(IDE_IS_MAIN_THREAD());
+  g_return_if_fail(IDE_IS_BUFFER(self));
+  g_return_if_fail(!cancellable || G_IS_CANCELLABLE(cancellable));
+
+  task = ide_task_new(self, cancellable, callback, user_data);
+  ide_task_set_source_tag(task, ide_buffer_code_action_query_async);
+
+  if (!(code_action_provider = ide_extension_adapter_get_extension(self->code_action_provider)))
+    {
+      const gchar *language_id = ide_buffer_get_language_id(self);
+
+      if (language_id == NULL)
+        language_id = "none";
+
+      ide_task_return_new_error(task,
+                                G_IO_ERROR,
+                                G_IO_ERROR_NOT_SUPPORTED,
+                                "No code action provider registered for language %s",
+                                language_id);
+
+      IDE_EXIT;
+    }
+
+  ide_code_action_provider_query_async(code_action_provider,
+                                       self,
+                                       cancellable,
+                                       ide_buffer_query_code_action_cb,
+                                       g_steal_pointer(&task));
+
+  IDE_EXIT;
+}
+
+/**
+ * ide_buffer_code_action_query_finish:
+ * @self: an #IdeBuffer
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to ide_buffer_query_code_action_async().
+ *
+ * Returns: (transfer full) (element-type IdeCodeAction): a #GPtrArray of #IdeCodeAction.
+ *
+ * Since: 42.0
+ */
+GPtrArray*
+ide_buffer_code_action_query_finish(IdeBuffer     *self,
+                                    GAsyncResult  *result,
+                                    GError       **error)
+{
+  GPtrArray* ret;
+
+  IDE_ENTRY;
+
+  g_return_val_if_fail(IDE_IS_MAIN_THREAD(), NULL);
+  g_return_val_if_fail(IDE_IS_BUFFER(self), NULL);
+  g_return_val_if_fail(IDE_IS_TASK(result), NULL);
+
+  ret = ide_task_propagate_pointer(IDE_TASK(result), error);
+
+  IDE_RETURN(ret);
+}
+
 /**
  * ide_buffer_get_insert_location:
  *
diff --git a/src/libide/code/ide-buffer.h b/src/libide/code/ide-buffer.h
index 56d9ed5d9..7c186076f 100644
--- a/src/libide/code/ide-buffer.h
+++ b/src/libide/code/ide-buffer.h
@@ -65,6 +65,16 @@ IDE_AVAILABLE_IN_3_32
 gboolean                ide_buffer_format_selection_finish       (IdeBuffer               *self,
                                                                   GAsyncResult            *result,
                                                                   GError                 **error);
+IDE_AVAILABLE_IN_42
+void                    ide_buffer_code_action_query_async       (IdeBuffer               *self,
+                                                                  GCancellable            *cancellable,
+                                                                  GAsyncReadyCallback      callback,
+                                                                  gpointer                 user_data);
+
+IDE_AVAILABLE_IN_42
+GPtrArray*              ide_buffer_code_action_query_finish      (IdeBuffer               *self,
+                                                                  GAsyncResult            *result,
+                                                                  GError                 **error);
 IDE_AVAILABLE_IN_3_32
 guint                   ide_buffer_get_change_count              (IdeBuffer               *self);
 IDE_AVAILABLE_IN_3_32
diff --git a/src/libide/code/ide-code-action-provider.c b/src/libide/code/ide-code-action-provider.c
new file mode 100644
index 000000000..a85673170
--- /dev/null
+++ b/src/libide/code/ide-code-action-provider.c
@@ -0,0 +1,116 @@
+/* ide-code-action-provider.c
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#define G_LOG_DOMAIN "ide-code-action-provider"
+
+#include "config.h"
+
+#include "ide-buffer.h"
+#include "ide-code-action-provider.h"
+
+G_DEFINE_INTERFACE (IdeCodeActionProvider, ide_code_action_provider, G_TYPE_OBJECT)
+
+static void
+ide_code_action_provider_real_query_async (IdeCodeActionProvider *self,
+                                           IdeBuffer             *buffer,
+                                           GCancellable          *cancellable,
+                                           GAsyncReadyCallback    callback,
+                                           gpointer               user_data)
+{
+  g_assert (IDE_IS_CODE_ACTION_PROVIDER (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self,
+                           callback,
+                           user_data,
+                           ide_code_action_provider_real_query_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "The operation is not supported");
+}
+
+static GPtrArray*
+ide_code_action_provider_real_query_finish (IdeCodeActionProvider  *self,
+                                            GAsyncResult           *result,
+                                            GError                **error)
+{
+  g_assert (IDE_IS_CODE_ACTION_PROVIDER (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_pointer (G_TASK (result), error);
+}
+
+static void
+ide_code_action_provider_default_init (IdeCodeActionProviderInterface *iface)
+{
+  iface->query_async = ide_code_action_provider_real_query_async;
+  iface->query_finish = ide_code_action_provider_real_query_finish;
+}
+
+void
+ide_code_action_provider_query_async (IdeCodeActionProvider *self,
+                                      IdeBuffer             *buffer,
+                                      GCancellable          *cancellable,
+                                      GAsyncReadyCallback    callback,
+                                      gpointer               user_data)
+{
+  g_return_if_fail (IDE_IS_CODE_ACTION_PROVIDER (self));
+  g_return_if_fail (IDE_IS_BUFFER (buffer));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_CODE_ACTION_PROVIDER_GET_IFACE (self)->query_async (self,
+                                                          buffer,
+                                                          cancellable,
+                                                          callback,
+                                                          user_data);
+}
+
+/**
+ * ide_code_action_provider_query_finish:
+ * @self: an #IdeBuffer
+ * @result: a #GAsyncResult
+ * @error: a location for a #GError, or %NULL
+ *
+ * Completes an asynchronous request to ide_code_action_provider_query_async().
+ *
+ * Returns: (transfer full) (element-type IdeCodeAction): a #GPtrArray of #IdeCodeAction.
+ *
+ * Since: 42.0
+ */
+GPtrArray*
+ide_code_action_provider_query_finish (IdeCodeActionProvider  *self,
+                                       GAsyncResult           *result,
+                                       GError                **error)
+{
+  g_return_val_if_fail (IDE_IS_CODE_ACTION_PROVIDER (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_CODE_ACTION_PROVIDER_GET_IFACE (self)->query_finish (self, result, error);
+}
+
+void
+ide_code_action_provider_load (IdeCodeActionProvider *self)
+{
+  g_return_if_fail (IDE_IS_CODE_ACTION_PROVIDER (self));
+
+  if (IDE_CODE_ACTION_PROVIDER_GET_IFACE (self)->load)
+    IDE_CODE_ACTION_PROVIDER_GET_IFACE (self)->load (self);
+}
diff --git a/src/libide/code/ide-code-action-provider.h b/src/libide/code/ide-code-action-provider.h
new file mode 100644
index 000000000..55bd6f87d
--- /dev/null
+++ b/src/libide/code/ide-code-action-provider.h
@@ -0,0 +1,67 @@
+/* ide-code-action-provider.h
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CODE_ACTION_PROVIDER (ide_code_action_provider_get_type())
+
+IDE_AVAILABLE_IN_42
+G_DECLARE_INTERFACE (IdeCodeActionProvider, ide_code_action_provider, IDE, CODE_ACTION_PROVIDER, IdeObject)
+
+struct _IdeCodeActionProviderInterface
+{
+  GTypeInterface parent;
+
+  void       (*load)               (IdeCodeActionProvider  *self);
+  void       (*query_async)        (IdeCodeActionProvider  *self,
+                                    IdeBuffer              *buffer,
+                                    GCancellable           *cancellable,
+                                    GAsyncReadyCallback     callback,
+                                    gpointer                user_data);
+  GPtrArray* (*query_finish)       (IdeCodeActionProvider  *self,
+                                    GAsyncResult           *result,
+                                    GError                **error);
+};
+
+IDE_AVAILABLE_IN_42
+void     ide_code_action_provider_load           (IdeCodeActionProvider *self);
+IDE_AVAILABLE_IN_42
+void     ide_code_action_provider_query_async    (IdeCodeActionProvider *self,
+                                                  IdeBuffer             *buffer,
+                                                  GCancellable          *cancellable,
+                                                  GAsyncReadyCallback    callback,
+                                                  gpointer               user_data);
+IDE_AVAILABLE_IN_42
+GPtrArray* ide_code_action_provider_query_finish (IdeCodeActionProvider *self,
+                                                  GAsyncResult          *result,
+                                                  GError               **error);
+
+G_END_DECLS
diff --git a/src/libide/code/ide-code-action.c b/src/libide/code/ide-code-action.c
new file mode 100644
index 000000000..7131d6044
--- /dev/null
+++ b/src/libide/code/ide-code-action.c
@@ -0,0 +1,104 @@
+/* ide-code-action.c
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#define G_LOG_DOMAIN "ide-code-action"
+
+#include "config.h"
+
+#include "ide-buffer.h"
+#include "ide-code-action.h"
+
+G_DEFINE_INTERFACE (IdeCodeAction, ide_code_action, G_TYPE_OBJECT)
+
+static const gchar*
+ide_code_action_real_get_title (IdeCodeAction        *self)
+{
+  g_assert (IDE_IS_CODE_ACTION (self));
+  return NULL;
+}
+
+static void
+ide_code_action_real_execute_async (IdeCodeAction       *self,
+                                    GCancellable        *cancellable,
+                                    GAsyncReadyCallback  callback,
+                                    gpointer             user_data)
+{
+  g_assert (IDE_IS_CODE_ACTION (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  g_task_report_new_error (self,
+                           callback,
+                           user_data,
+                           ide_code_action_real_execute_async,
+                           G_IO_ERROR,
+                           G_IO_ERROR_NOT_SUPPORTED,
+                           "The operation is not supported");
+}
+
+static gboolean
+ide_code_action_real_execute_finish (IdeCodeAction  *self,
+                                     GAsyncResult   *result,
+                                     GError        **error)
+{
+  g_assert (IDE_IS_CODE_ACTION (self));
+  g_assert (G_IS_TASK (result));
+
+  return g_task_propagate_boolean(G_TASK (result), error);
+}
+
+static void
+ide_code_action_default_init (IdeCodeActionInterface *iface)
+{
+  iface->get_title = ide_code_action_real_get_title;
+  iface->execute_async = ide_code_action_real_execute_async;
+  iface->execute_finish = ide_code_action_real_execute_finish;
+}
+
+const gchar*
+ide_code_action_get_title (IdeCodeAction *self)
+{
+  g_return_val_if_fail (IDE_IS_CODE_ACTION (self), NULL);
+
+  return IDE_CODE_ACTION_GET_IFACE (self)->get_title (self);
+}
+
+void
+ide_code_action_execute_async (IdeCodeAction       *self,
+                               GCancellable        *cancellable,
+                               GAsyncReadyCallback  callback,
+                               gpointer             user_data)
+{
+  g_return_if_fail (IDE_IS_CODE_ACTION (self));
+  g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  IDE_CODE_ACTION_GET_IFACE (self)->execute_async (self, cancellable, callback, user_data);
+}
+
+gboolean
+ide_code_action_execute_finish (IdeCodeAction  *self,
+                                GAsyncResult   *result,
+                                GError        **error)
+{
+  g_return_val_if_fail (IDE_IS_CODE_ACTION (self), FALSE);
+  g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
+
+  return IDE_CODE_ACTION_GET_IFACE (self)->execute_finish (self, result, error);
+}
+
diff --git a/src/libide/code/ide-code-action.h b/src/libide/code/ide-code-action.h
new file mode 100644
index 000000000..5848a9445
--- /dev/null
+++ b/src/libide/code/ide-code-action.h
@@ -0,0 +1,65 @@
+/* ide-code-action.h
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#pragma once
+
+#if !defined (IDE_CODE_INSIDE) && !defined (IDE_CODE_COMPILATION)
+# error "Only <libide-code.h> can be included directly."
+#endif
+
+#include <gtk/gtk.h>
+#include <libide-core.h>
+
+#include "ide-code-types.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_CODE_ACTION (ide_code_action_get_type())
+
+IDE_AVAILABLE_IN_42
+G_DECLARE_INTERFACE (IdeCodeAction, ide_code_action, IDE, CODE_ACTION, IdeObject)
+
+struct _IdeCodeActionInterface
+{
+  GTypeInterface parent;
+
+  const gchar* (*get_title)      (IdeCodeAction        *self);
+  void         (*execute_async)  (IdeCodeAction        *self,
+                                  GCancellable         *cancellable,
+                                  GAsyncReadyCallback   callback,
+                                  gpointer              user_data);
+  gboolean     (*execute_finish) (IdeCodeAction        *self,
+                                  GAsyncResult         *result,
+                                  GError              **error);
+};
+
+IDE_AVAILABLE_IN_42
+const gchar *ide_code_action_get_title      (IdeCodeAction        *self);
+IDE_AVAILABLE_IN_42
+void         ide_code_action_execute_async  (IdeCodeAction        *self,
+                                             GCancellable         *cancellable,
+                                             GAsyncReadyCallback   callback,
+                                             gpointer              user_data);
+IDE_AVAILABLE_IN_42
+gboolean     ide_code_action_execute_finish (IdeCodeAction        *self,
+                                             GAsyncResult          *result,
+                                             GError               **error);
+
+G_END_DECLS
diff --git a/src/libide/code/libide-code.h b/src/libide/code/libide-code.h
index e35570a3c..658ac27df 100644
--- a/src/libide/code/libide-code.h
+++ b/src/libide/code/libide-code.h
@@ -35,6 +35,8 @@ G_BEGIN_DECLS
 #include "ide-buffer-addin.h"
 #include "ide-buffer-change-monitor.h"
 #include "ide-buffer-manager.h"
+#include "ide-code-action.h"
+#include "ide-code-action-provider.h"
 #include "ide-code-index-entries.h"
 #include "ide-code-index-entry.h"
 #include "ide-code-indexer.h"
diff --git a/src/libide/code/meson.build b/src/libide/code/meson.build
index 1a4fc5d26..9a956e73e 100644
--- a/src/libide/code/meson.build
+++ b/src/libide/code/meson.build
@@ -23,6 +23,8 @@ libide_code_public_headers = [
   'ide-buffer-change-monitor.h',
   'ide-buffer.h',
   'ide-buffer-manager.h',
+  'ide-code-action.h',
+  'ide-code-action-provider.h',
   'ide-code-index-entries.h',
   'ide-code-index-entry.h',
   'ide-code-indexer.h',
@@ -84,6 +86,8 @@ libide_code_public_sources = [
   'ide-buffer-change-monitor.c',
   'ide-buffer-manager.c',
   'ide-code-global.c',
+  'ide-code-action.c',
+  'ide-code-action-provider.c',
   'ide-code-index-entries.c',
   'ide-code-index-entry.c',
   'ide-code-indexer.c',
diff --git a/src/libide/lsp/ide-lsp-client.c b/src/libide/lsp/ide-lsp-client.c
index bb09923e1..92cc475f1 100644
--- a/src/libide/lsp/ide-lsp-client.c
+++ b/src/libide/lsp/ide-lsp-client.c
@@ -1626,6 +1626,24 @@ ide_lsp_client_start (IdeLspClient *self)
             "]",
           "}",
         "}",
+        "codeAction", "{",
+          "dynamicRegistration", JSONRPC_MESSAGE_PUT_BOOLEAN (TRUE),
+          "isPreferredSupport", JSONRPC_MESSAGE_PUT_BOOLEAN (TRUE),
+          "codeActionLiteralSupport", "{",
+            "codeActionKind", "{",
+              "valueSet", "[",
+                "",
+                "quickfix",
+                "refactor",
+                "refactor.extract",
+                "refactor.inline",
+                "refactor.rewrite",
+                "source",
+                "source.organizeImports",
+              "]",
+            "}",
+          "}",
+        "}",
       "}",
       "window", "{",
         "workDoneProgress", JSONRPC_MESSAGE_PUT_BOOLEAN (TRUE),
diff --git a/src/libide/lsp/ide-lsp-code-action-provider.c b/src/libide/lsp/ide-lsp-code-action-provider.c
new file mode 100644
index 000000000..7f12aeed3
--- /dev/null
+++ b/src/libide/lsp/ide-lsp-code-action-provider.c
@@ -0,0 +1,296 @@
+/* ide-lsp-code-action-provider.c
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#define G_LOG_DOMAIN "ide-lsp-code-action-provider"
+
+#include "config.h"
+
+#include <jsonrpc-glib.h>
+
+#include <libide-code.h>
+#include <libide-threading.h>
+
+#include "ide-lsp-code-action.h"
+#include "ide-lsp-code-action-provider.h"
+#include "ide-lsp-workspace-edit.h"
+
+typedef struct
+{
+  IdeLspClient *client;
+} IdeLspCodeActionProviderPrivate;
+
+enum {
+  PROP_0,
+  PROP_CLIENT,
+  N_PROPS
+};
+
+static void code_action_provider_iface_init (IdeCodeActionProviderInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (IdeLspCodeActionProvider, ide_lsp_code_action_provider, IDE_TYPE_OBJECT,
+                         G_ADD_PRIVATE (IdeLspCodeActionProvider)
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_CODE_ACTION_PROVIDER, 
code_action_provider_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+/**
+ * ide_lsp_code_action_provider_get_client:
+ * @self: a #IdeLspCodeActionProvider
+ *
+ * Gets the client to use for the code action query.
+ *
+ * Returns: (transfer none): An #IdeLspClient or %NULL.
+ */
+IdeLspClient *
+ide_lsp_code_action_provider_get_client (IdeLspCodeActionProvider *self)
+{
+  IdeLspCodeActionProviderPrivate *priv = ide_lsp_code_action_provider_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_LSP_CODE_ACTION_PROVIDER (self), NULL);
+
+  return priv->client;
+}
+
+void
+ide_lsp_code_action_provider_set_client (IdeLspCodeActionProvider *self,
+                              IdeLspClient    *client)
+{
+  IdeLspCodeActionProviderPrivate *priv = ide_lsp_code_action_provider_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_LSP_CODE_ACTION_PROVIDER (self));
+
+  if (g_set_object (&priv->client, client))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
+}
+
+static void
+ide_lsp_code_action_provider_finalize (GObject *object)
+{
+  IdeLspCodeActionProvider *self = (IdeLspCodeActionProvider *)object;
+  IdeLspCodeActionProviderPrivate *priv = ide_lsp_code_action_provider_get_instance_private (self);
+
+  g_clear_object (&priv->client);
+
+  G_OBJECT_CLASS (ide_lsp_code_action_provider_parent_class)->finalize (object);
+}
+
+static void
+ide_lsp_code_action_provider_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  IdeLspCodeActionProvider *self = IDE_LSP_CODE_ACTION_PROVIDER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      g_value_set_object (value, ide_lsp_code_action_provider_get_client (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_lsp_code_action_provider_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+  IdeLspCodeActionProvider *self = IDE_LSP_CODE_ACTION_PROVIDER (object);
+
+  switch (prop_id)
+    {
+    case PROP_CLIENT:
+      ide_lsp_code_action_provider_set_client (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_lsp_code_action_provider_class_init (IdeLspCodeActionProviderClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_lsp_code_action_provider_finalize;
+  object_class->get_property = ide_lsp_code_action_provider_get_property;
+  object_class->set_property = ide_lsp_code_action_provider_set_property;
+
+  properties [PROP_CLIENT] =
+    g_param_spec_object ("client",
+                         "Client",
+                         "The client to communicate over",
+                         IDE_TYPE_LSP_CLIENT,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_lsp_code_action_provider_init (IdeLspCodeActionProvider *self)
+{
+}
+
+static void
+ide_lsp_code_action_provider_query_call_cb (GObject      *object,
+                                            GAsyncResult *result,
+                                            gpointer      user_data)
+{
+  IdeLspClient *client = (IdeLspClient *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GVariant) reply = NULL;
+  g_autoptr(GPtrArray) code_actions = NULL;
+  g_autoptr(GVariant) code_action = NULL;
+  IdeLspCodeActionProvider *self;
+  IdeBuffer *buffer;
+  GVariantIter iter;
+
+  g_return_if_fail (IDE_IS_LSP_CLIENT (client));
+  g_return_if_fail (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_lsp_client_call_finish (client, result, &reply, &error))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  self = ide_task_get_source_object (task);
+  buffer = ide_task_get_task_data (task);
+
+  g_assert (IDE_IS_LSP_CODE_ACTION_PROVIDER (self));
+  g_assert (IDE_IS_BUFFER (buffer));
+
+  code_actions = g_ptr_array_new_with_free_func (g_object_unref);
+
+  g_variant_iter_init (&iter, reply);
+
+  while (g_variant_iter_loop (&iter, "v", &code_action))
+    {
+      const gchar *title = NULL;
+      const gchar *command = NULL;
+      g_autoptr(GVariant) arguments = NULL;
+      g_autoptr(GVariant) edit = NULL;
+      g_autoptr(IdeLspWorkspaceEdit) workspace_edit = NULL;
+
+      if (!JSONRPC_MESSAGE_PARSE (code_action,
+        "title", JSONRPC_MESSAGE_GET_STRING (&title)
+      ))
+        {
+          IDE_TRACE_MSG ("Failed to extract command title from variant");
+          continue;
+        }
+
+      JSONRPC_MESSAGE_PARSE (code_action,
+        "command", "{",
+          "command", JSONRPC_MESSAGE_GET_STRING (&command),
+          "arguments", JSONRPC_MESSAGE_GET_VARIANT (&arguments),
+        "}"
+      );
+
+      if (JSONRPC_MESSAGE_PARSE (code_action,
+        "edit", JSONRPC_MESSAGE_GET_VARIANT (&edit)
+      ))
+        {
+          workspace_edit = ide_lsp_workspace_edit_new(edit);
+        }
+
+      g_ptr_array_add (code_actions, ide_lsp_code_action_new (client, title, command, arguments, 
workspace_edit));
+    }
+
+  ide_task_return_pointer(task, g_steal_pointer(&code_actions), g_ptr_array_unref);
+}
+
+static void
+ide_lsp_code_action_provider_query_async (IdeCodeActionProvider *code_action_provider,
+                                          IdeBuffer             *buffer,
+                                          GCancellable          *cancellable,
+                                          GAsyncReadyCallback    callback,
+                                          gpointer               user_data)
+{
+  IdeLspCodeActionProvider *self = (IdeLspCodeActionProvider *)code_action_provider;
+  IdeLspCodeActionProviderPrivate *priv = ide_lsp_code_action_provider_get_instance_private (self);
+  g_autoptr(GVariant) params = NULL;
+  g_autoptr(IdeTask) task = NULL;
+  IdeRange* selection = NULL;
+  IdeLocation* start = NULL;
+  IdeLocation* end = NULL;
+  g_autofree gchar *uri = NULL;
+
+  g_assert (IDE_IS_LSP_CODE_ACTION_PROVIDER (self));
+  g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+  task = ide_task_new (self, cancellable, callback, user_data);
+  ide_task_set_source_tag (task, ide_lsp_code_action_provider_query_async);
+  ide_task_set_task_data (task, g_object_ref (buffer), g_object_unref);
+  uri = ide_buffer_dup_uri (buffer);
+  selection = ide_buffer_get_selection_range (buffer);
+  start = ide_range_get_begin (selection);
+  end = ide_range_get_end (selection);
+
+  params = JSONRPC_MESSAGE_NEW (
+    "textDocument", "{",
+      "uri", JSONRPC_MESSAGE_PUT_STRING (uri),
+    "}",
+    "range", "{",
+          "start", "{",
+            "line", JSONRPC_MESSAGE_PUT_INT64 (ide_location_get_line (start)),
+            "character", JSONRPC_MESSAGE_PUT_INT64 (ide_location_get_line_offset (start)),
+          "}",
+          "end", "{",
+            "line", JSONRPC_MESSAGE_PUT_INT64 (ide_location_get_line (end)),
+            "character", JSONRPC_MESSAGE_PUT_INT64 (ide_location_get_line_offset (end)),
+          "}",
+        "}",
+    "context", "{",
+      "diagnostics", "[","]",
+    "}"
+  );
+
+  ide_lsp_client_call_async (priv->client,
+                             "textDocument/codeAction",
+                             params,
+                             cancellable,
+                             ide_lsp_code_action_provider_query_call_cb,
+                             g_steal_pointer (&task));
+}
+
+static GPtrArray*
+ide_lsp_code_action_provider_query_finish (IdeCodeActionProvider  *self,
+                                           GAsyncResult           *result,
+                                           GError                **error)
+{
+  g_assert (IDE_IS_CODE_ACTION_PROVIDER (self));
+  g_assert (IDE_IS_TASK (result));
+
+  return ide_task_propagate_pointer (IDE_TASK (result), error);
+}
+
+static void
+code_action_provider_iface_init (IdeCodeActionProviderInterface *iface)
+{
+  iface->query_async = ide_lsp_code_action_provider_query_async;
+  iface->query_finish = ide_lsp_code_action_provider_query_finish;
+}
diff --git a/src/libide/lsp/ide-lsp-code-action-provider.h b/src/libide/lsp/ide-lsp-code-action-provider.h
new file mode 100644
index 000000000..2454846bc
--- /dev/null
+++ b/src/libide/lsp/ide-lsp-code-action-provider.h
@@ -0,0 +1,49 @@
+/* ide-lsp-code-action-provider.h
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#pragma once
+
+#if !defined (IDE_LSP_INSIDE) && !defined (IDE_LSP_COMPILATION)
+# error "Only <libide-lsp.h> can be included directly."
+#endif
+
+#include <libide-code.h>
+
+#include "ide-lsp-client.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LSP_CODE_ACTION_PROVIDER (ide_lsp_code_action_provider_get_type())
+
+IDE_AVAILABLE_IN_42
+G_DECLARE_DERIVABLE_TYPE (IdeLspCodeActionProvider, ide_lsp_code_action_provider, IDE, 
LSP_CODE_ACTION_PROVIDER, IdeObject)
+
+struct _IdeLspCodeActionProviderClass
+{
+  IdeObjectClass parent_class;
+};
+
+IDE_AVAILABLE_IN_42
+void          ide_lsp_code_action_provider_set_client (IdeLspCodeActionProvider *self,
+                                                       IdeLspClient             *client);
+IDE_AVAILABLE_IN_42
+IdeLspClient *ide_lsp_code_action_provider_get_client (IdeLspCodeActionProvider *self);
+
+G_END_DECLS
diff --git a/src/libide/lsp/ide-lsp-code-action.c b/src/libide/lsp/ide-lsp-code-action.c
new file mode 100644
index 000000000..c00f9b06e
--- /dev/null
+++ b/src/libide/lsp/ide-lsp-code-action.c
@@ -0,0 +1,374 @@
+/* ide-lsp-code-action.c
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#define G_LOG_DOMAIN "ide-lsp-code-action"
+
+#include "config.h"
+
+#include <jsonrpc-glib.h>
+
+#include <libide-code.h>
+#include <libide-threading.h>
+
+#include "ide-lsp-code-action.h"
+
+typedef struct
+{
+  IdeLspClient        *client;
+  gchar               *title;
+
+  gchar               *command;
+  GVariant            *arguments;
+
+  IdeLspWorkspaceEdit *workspace_edit;
+} IdeLspCodeActionPrivate;
+
+enum {
+  PROP_0,
+  PROP_CLIENT,
+  PROP_TITLE,
+  N_PROPS
+};
+
+static void code_action_iface_init (IdeCodeActionInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (IdeLspCodeAction, ide_lsp_code_action, IDE_TYPE_OBJECT,
+                         G_ADD_PRIVATE (IdeLspCodeAction)
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_CODE_ACTION, code_action_iface_init))
+
+static GParamSpec *properties [N_PROPS];
+
+
+static const gchar *
+ide_lsp_code_action_get_title (IdeCodeAction *self)
+{
+  IdeLspCodeActionPrivate *priv = ide_lsp_code_action_get_instance_private (IDE_LSP_CODE_ACTION(self));
+
+  g_return_val_if_fail (IDE_IS_CODE_ACTION (self), NULL);
+
+  return priv->title;
+}
+
+static void
+ide_lsp_code_action_set_title (IdeLspCodeAction *self,
+                               const gchar      *title)
+{
+  IdeLspCodeActionPrivate *priv = ide_lsp_code_action_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_CODE_ACTION (self));
+
+  if (!ide_str_equal0 (priv->title, title))
+    {
+      g_free (priv->title);
+      priv->title = g_strdup (title);
+      g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_TITLE]);
+    }
+}
+
+/**
+ * ide_lsp_code_action_get_client:
+ * @self: a #IdeLspCodeAction
+ *
+ * Gets the client to use for the code action.
+ *
+ * Returns: (transfer none): An #IdeLspClient or %NULL.
+ */
+IdeLspClient *
+ide_lsp_code_action_get_client (IdeLspCodeAction *self)
+{
+  IdeLspCodeActionPrivate *priv = ide_lsp_code_action_get_instance_private (self);
+
+  g_return_val_if_fail (IDE_IS_LSP_CODE_ACTION (self), NULL);
+
+  return priv->client;
+}
+
+void
+ide_lsp_code_action_set_client (IdeLspCodeAction *self,
+                                IdeLspClient     *client)
+{
+  IdeLspCodeActionPrivate *priv = ide_lsp_code_action_get_instance_private (self);
+
+  g_return_if_fail (IDE_IS_LSP_CODE_ACTION (self));
+
+  if (g_set_object (&priv->client, client))
+    g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]);
+}
+
+static void
+ide_lsp_code_action_finalize (GObject *object)
+{
+  IdeLspCodeAction *self = (IdeLspCodeAction *)object;
+  IdeLspCodeActionPrivate *priv = ide_lsp_code_action_get_instance_private (self);
+
+  g_clear_object (&priv->client);
+
+  G_OBJECT_CLASS (ide_lsp_code_action_parent_class)->finalize (object);
+}
+
+static void
+ide_lsp_code_action_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+  IdeLspCodeAction *self = IDE_LSP_CODE_ACTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      g_value_set_string (value, ide_lsp_code_action_get_title (IDE_CODE_ACTION(self)));
+      break;
+    case PROP_CLIENT:
+      g_value_set_object (value, ide_lsp_code_action_get_client (self));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_lsp_code_action_set_property (GObject      *object,
+                                  guint         prop_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  IdeLspCodeAction *self = IDE_LSP_CODE_ACTION (object);
+
+  switch (prop_id)
+    {
+    case PROP_TITLE:
+      ide_lsp_code_action_set_title (self, g_value_get_string (value));
+      break;
+    case PROP_CLIENT:
+      ide_lsp_code_action_set_client (self, g_value_get_object (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+ide_lsp_code_action_class_init (IdeLspCodeActionClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = ide_lsp_code_action_finalize;
+  object_class->get_property = ide_lsp_code_action_get_property;
+  object_class->set_property = ide_lsp_code_action_set_property;
+
+  properties [PROP_TITLE] =
+    g_param_spec_string ("title",
+                         "Title",
+                         "The title of the code action",
+                         NULL,
+                         (G_PARAM_READWRITE |G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | 
G_PARAM_STATIC_STRINGS));
+
+  properties [PROP_CLIENT] =
+    g_param_spec_object ("client",
+                         "Client",
+                         "The client to communicate over",
+                         IDE_TYPE_LSP_CLIENT,
+                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+ide_lsp_code_action_init (IdeLspCodeAction *self)
+{
+}
+
+static void
+ide_lsp_code_action_execute_call_cb (GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  IdeLspClient *client = (IdeLspClient *)object;
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GVariant) reply = NULL;
+  IdeLspCodeAction *self;
+
+  g_return_if_fail (IDE_IS_LSP_CLIENT (client));
+  g_return_if_fail (G_IS_ASYNC_RESULT (result));
+
+  if (!ide_lsp_client_call_finish (client, result, &reply, &error))
+    {
+      ide_task_return_error (task, g_steal_pointer (&error));
+      return;
+    }
+
+  self = ide_task_get_source_object (task);
+
+  g_assert (IDE_IS_LSP_CODE_ACTION (self));
+
+  ide_task_return_boolean(task, TRUE);
+}
+
+static void
+ide_lsp_code_action_edits_applied(GObject      *object,
+                                  GAsyncResult *result,
+                                  gpointer user_data)
+{
+  IdeBufferManager *manager = (IdeBufferManager *)object;
+
+  g_autoptr(IdeTask) task = user_data;
+  g_autoptr(GError) error = NULL;
+  IdeLspCodeAction *self;
+  GCancellable* cancellable;
+  IdeLspCodeActionPrivate *priv;
+
+  IDE_ENTRY;
+
+  g_assert(IDE_IS_BUFFER_MANAGER(object));
+  g_assert(G_IS_ASYNC_RESULT(result));
+
+
+  if (!ide_buffer_manager_apply_edits_finish(manager, result, &error))
+    {
+      ide_task_return_error(task, g_steal_pointer(&error));
+      return;
+    }
+  self = ide_task_get_source_object(task);
+  cancellable = ide_task_get_cancellable(task);
+
+  priv = ide_lsp_code_action_get_instance_private(self);
+
+  // execute command if there is one
+  if (priv->command)
+    {
+      g_autoptr(GVariant) params = NULL;
+
+      params = JSONRPC_MESSAGE_NEW(
+        "command", JSONRPC_MESSAGE_PUT_STRING(priv->command),
+        "arguments", "{", JSONRPC_MESSAGE_PUT_VARIANT(priv->arguments), "}"
+      );
+
+      ide_lsp_client_call_async(priv->client,
+                                "workspace/executeCommand",
+                                params,
+                                cancellable,
+                                ide_lsp_code_action_execute_call_cb,
+                                g_steal_pointer(&task));
+    }
+  else
+    {
+      ide_task_return_boolean(task, TRUE);
+    }
+
+  IDE_EXIT;
+}
+
+static void
+ide_lsp_code_action_execute_async(IdeCodeAction       *code_action,
+                                  GCancellable        *cancellable,
+                                  GAsyncReadyCallback  callback,
+                                  gpointer             user_data)
+{
+  IdeLspCodeAction *self = (IdeLspCodeAction *)code_action;
+  IdeLspCodeActionPrivate *priv = ide_lsp_code_action_get_instance_private(self);
+
+  g_autoptr(IdeTask) task = NULL;
+  IdeContext* context;
+  IdeBufferManager* buffer_manager;
+
+  g_assert(IDE_IS_LSP_CODE_ACTION(self));
+  g_assert(!cancellable || G_IS_CANCELLABLE(cancellable));
+
+  task = ide_task_new(self, cancellable, callback, user_data);
+  ide_task_set_source_tag(task, ide_lsp_code_action_execute_async);
+
+  /* If a code action provides an edit and a command,
+   * first the edit is executed and then the command.
+   */
+  if (priv->workspace_edit)
+    {
+      g_autoptr(GPtrArray) edits = NULL;
+
+      edits = ide_lsp_workspace_edit_get_edits(priv->workspace_edit);
+
+      context = ide_object_ref_context(IDE_OBJECT(self));
+      buffer_manager = ide_buffer_manager_from_context(context);
+
+      ide_buffer_manager_apply_edits_async(buffer_manager,
+                                           IDE_PTR_ARRAY_STEAL_FULL(&edits),
+                                           cancellable,
+                                           ide_lsp_code_action_edits_applied,
+                                           g_steal_pointer(&task));
+    }
+  else
+    {
+      g_autoptr(GVariant) params = NULL;
+
+      params = JSONRPC_MESSAGE_NEW(
+        "command", JSONRPC_MESSAGE_PUT_STRING(priv->command),
+        "arguments", "{", JSONRPC_MESSAGE_PUT_VARIANT(priv->arguments), "}"
+      );
+
+      ide_lsp_client_call_async(priv->client,
+                                "workspace/executeCommand",
+                                params,
+                                cancellable,
+                                ide_lsp_code_action_execute_call_cb,
+                                g_steal_pointer(&task));
+    }
+}
+
+static gboolean
+ide_lsp_code_action_execute_finish (IdeCodeAction  *self,
+                                 GAsyncResult  *result,
+                                 GError       **error)
+{
+  g_assert (IDE_IS_CODE_ACTION (self));
+  g_assert (IDE_IS_TASK (result));
+
+  return ide_task_propagate_boolean (IDE_TASK (result), error);
+}
+
+static void
+code_action_iface_init (IdeCodeActionInterface *iface)
+{
+  iface->get_title = ide_lsp_code_action_get_title;
+  iface->execute_async = ide_lsp_code_action_execute_async;
+  iface->execute_finish = ide_lsp_code_action_execute_finish;
+}
+
+IdeLspCodeAction *
+ide_lsp_code_action_new(IdeLspClient        *client,
+                        const gchar         *title,
+                        const gchar         *command,
+                        GVariant            *arguments,
+                        IdeLspWorkspaceEdit *workspace_edit)
+{
+  IdeLspCodeAction* code_action = g_object_new(IDE_TYPE_LSP_CODE_ACTION,
+                                               "client", client,
+                                               "title", title,
+                                               NULL);
+  IdeLspCodeActionPrivate *priv = ide_lsp_code_action_get_instance_private(code_action);
+
+  priv->command = g_strdup(command);
+  priv->arguments = g_variant_ref(arguments);
+  priv->workspace_edit = g_object_ref(workspace_edit);
+  return code_action;
+}
+
diff --git a/src/libide/lsp/ide-lsp-code-action.h b/src/libide/lsp/ide-lsp-code-action.h
new file mode 100644
index 000000000..3a9d1b8a4
--- /dev/null
+++ b/src/libide/lsp/ide-lsp-code-action.h
@@ -0,0 +1,59 @@
+/* ide-lsp-code-action.h
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#pragma once
+
+#if !defined (IDE_LSP_INSIDE) && !defined (IDE_LSP_COMPILATION)
+# error "Only <libide-lsp.h> can be included directly."
+#endif
+
+#include <libide-code.h>
+
+#include "ide-lsp-client.h"
+#include "ide-lsp-workspace-edit.h"
+
+G_BEGIN_DECLS
+
+#define IDE_TYPE_LSP_CODE_ACTION (ide_lsp_code_action_get_type())
+
+IDE_AVAILABLE_IN_42
+G_DECLARE_DERIVABLE_TYPE (IdeLspCodeAction, ide_lsp_code_action, IDE, LSP_CODE_ACTION, IdeObject)
+
+struct _IdeLspCodeActionClass
+{
+  IdeObjectClass parent_class;
+};
+
+IDE_AVAILABLE_IN_42
+IdeLspCodeAction *ide_lsp_code_action_new        (IdeLspClient        *client,
+                                                  const gchar         *title,
+                                                  const gchar         *command,
+                                                  GVariant            *arguments,
+                                                  IdeLspWorkspaceEdit *workspace_edit);
+
+IDE_AVAILABLE_IN_42
+void              ide_lsp_code_action_set_client (IdeLspCodeAction    *self,
+                                                  IdeLspClient        *client);
+IDE_AVAILABLE_IN_42
+IdeLspClient     *ide_lsp_code_action_get_client (IdeLspCodeAction    *self);
+
+
+
+G_END_DECLS
diff --git a/src/libide/lsp/libide-lsp.h b/src/libide/lsp/libide-lsp.h
index 5f08b2410..61e06bb35 100644
--- a/src/libide/lsp/libide-lsp.h
+++ b/src/libide/lsp/libide-lsp.h
@@ -27,6 +27,8 @@
 #include "ide-lsp-types.h"
 
 #include "ide-lsp-client.h"
+#include "ide-lsp-code-action.h"
+#include "ide-lsp-code-action-provider.h"
 #include "ide-lsp-completion-item.h"
 #include "ide-lsp-completion-provider.h"
 #include "ide-lsp-completion-results.h"
diff --git a/src/libide/lsp/meson.build b/src/libide/lsp/meson.build
index f365940d8..3e0841d1f 100644
--- a/src/libide/lsp/meson.build
+++ b/src/libide/lsp/meson.build
@@ -9,6 +9,8 @@ libide_include_directories += include_directories('.')
 libide_lsp_public_headers = [
   'libide-lsp.h',
   'ide-lsp-client.h',
+  'ide-lsp-code-action.h',
+  'ide-lsp-code-action-provider.h',
   'ide-lsp-completion-item.h',
   'ide-lsp-completion-provider.h',
   'ide-lsp-completion-results.h',
@@ -39,6 +41,8 @@ install_headers(libide_lsp_public_headers, subdir: libide_lsp_header_subdir)
 
 libide_lsp_public_sources = [
   'ide-lsp-client.c',
+  'ide-lsp-code-action.c',
+  'ide-lsp-code-action-provider.c',
   'ide-lsp-completion-item.c',
   'ide-lsp-completion-provider.c',
   'ide-lsp-completion-results.c',
diff --git a/src/libide/sourceview/ide-source-view.c b/src/libide/sourceview/ide-source-view.c
index f7f38febd..c16abad31 100644
--- a/src/libide/sourceview/ide-source-view.c
+++ b/src/libide/sourceview/ide-source-view.c
@@ -229,6 +229,7 @@ enum {
   END_USER_ACTION,
   FOCUS_LOCATION,
   FORMAT_SELECTION,
+  QUERY_CODE_ACTION,
   FIND_REFERENCES,
   GOTO_DEFINITION,
   HIDE_COMPLETION,
@@ -5096,6 +5097,149 @@ ide_source_view_real_format_selection (IdeSourceView *self)
   IDE_EXIT;
 }
 
+static void
+ide_source_view_code_action_execute_cb (GObject      *object,
+                                        GAsyncResult *result,
+                                        gpointer      user_data)
+{
+  IDE_TRACE_MSG ("ide_source_view_code_action_execute_cb done");
+}
+
+static void
+execute_code_action_cb (IdeCodeAction *code_action)
+{
+  ide_code_action_execute_async(code_action,
+                                NULL,
+                                ide_source_view_code_action_execute_cb,
+                                g_object_ref (code_action));
+}
+
+static void
+popup_menu_detach (GtkWidget *attach_widget,
+                              GtkMenu   *menu)
+{
+}
+
+static void
+ide_source_view_code_action_query_cb(GObject      *object,
+                                     GAsyncResult *result,
+                                     gpointer      user_data)
+{
+  IdeBuffer *buffer = (IdeBuffer *)object;
+
+  g_autoptr(IdeSourceView) self = user_data;
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GPtrArray) code_actions = NULL;
+
+  IDE_ENTRY;
+
+  g_assert(IDE_IS_SOURCE_VIEW(self));
+  g_assert(G_IS_ASYNC_RESULT(result));
+
+  code_actions = ide_buffer_code_action_query_finish(buffer, result, &error);
+
+  if (!code_actions)
+    {
+      if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
+        g_warning("%s", error->message);
+      IDE_EXIT;
+    }
+
+  if (code_actions->len)
+    {
+      IdeContext* context;
+      GtkTextView* text_view;
+      GtkTextIter iter;
+      GdkRectangle iter_location;
+      GdkRectangle visible_rect;
+      gboolean is_visible;
+      GtkWidget* popup_menu;
+
+      context = ide_buffer_ref_context(buffer);
+
+      popup_menu = gtk_menu_new();
+
+      gtk_style_context_add_class(gtk_widget_get_style_context(popup_menu),
+                                  GTK_STYLE_CLASS_CONTEXT_MENU);
+
+
+      gtk_menu_attach_to_widget(GTK_MENU(popup_menu),
+                                GTK_WIDGET(self),
+                                popup_menu_detach);
+
+      for (gsize i = 0; i < code_actions->len; i++)
+        {
+          IdeCodeAction* code_action;
+          GtkWidget* menu_item;
+
+          code_action = g_ptr_array_index(code_actions, i);
+          ide_object_append(IDE_OBJECT(context), IDE_OBJECT(code_action));
+          menu_item = gtk_menu_item_new_with_label(ide_code_action_get_title(code_action));
+
+          g_signal_connect_swapped(menu_item,
+                                   "activate",
+                                   G_CALLBACK(execute_code_action_cb),
+                                   code_action);
+
+
+          gtk_widget_show(menu_item);
+          gtk_menu_shell_append(GTK_MENU_SHELL(popup_menu), menu_item);
+        }
+
+      gtk_menu_shell_select_first(GTK_MENU_SHELL(popup_menu), FALSE);
+
+      text_view = GTK_TEXT_VIEW(self);
+
+      gtk_text_buffer_get_iter_at_mark(gtk_text_view_get_buffer(text_view),
+                                       &iter,
+                                       gtk_text_buffer_get_insert(gtk_text_view_get_buffer(text_view)));
+
+      gtk_text_view_get_iter_location(text_view, &iter, &iter_location);
+      gtk_text_view_get_visible_rect(text_view, &visible_rect);
+
+      is_visible = (iter_location.x + iter_location.width > visible_rect.x &&
+                    iter_location.x < visible_rect.x + visible_rect.width &&
+                    iter_location.y + iter_location.height > visible_rect.y &&
+                    iter_location.y < visible_rect.y + visible_rect.height);
+
+      if (is_visible)
+        {
+          gtk_text_view_buffer_to_window_coords(text_view,
+                                                GTK_TEXT_WINDOW_WIDGET,
+                                                iter_location.x,
+                                                iter_location.y,
+                                                &iter_location.x,
+                                                &iter_location.y);
+
+          gtk_menu_popup_at_rect(GTK_MENU(popup_menu),
+                                 gtk_widget_get_window(GTK_WIDGET(text_view)),
+                                 &iter_location,
+                                 GDK_GRAVITY_SOUTH_EAST,
+                                 GDK_GRAVITY_NORTH_WEST,
+                                 NULL);
+        }
+    }
+
+  IDE_EXIT;
+}
+
+static void
+ide_source_view_real_code_action_query (IdeSourceView *self)
+{
+  IdeSourceViewPrivate *priv = ide_source_view_get_instance_private (self);
+
+  IDE_ENTRY;
+
+  g_assert (IDE_IS_SOURCE_VIEW (self));
+
+  ide_buffer_code_action_query_async (priv->buffer,
+                                      NULL,
+                                      ide_source_view_code_action_query_cb,
+                                      g_object_ref (self));
+
+  IDE_EXIT;
+}
+
 static void
 ide_source_view_real_find_references_jump (IdeSourceView *self,
                                            GtkListBoxRow *row,
@@ -6138,6 +6282,13 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
                                 G_CALLBACK (ide_source_view_real_format_selection),
                                 NULL, NULL, NULL, G_TYPE_NONE, 0);
 
+  signals [QUERY_CODE_ACTION] =
+    g_signal_new_class_handler ("query-code-action",
+                                G_TYPE_FROM_CLASS (klass),
+                                G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+                                G_CALLBACK (ide_source_view_real_code_action_query),
+                                NULL, NULL, NULL, G_TYPE_NONE, 0);
+
   signals [GOTO_DEFINITION] =
     g_signal_new ("goto-definition",
                   G_TYPE_FROM_CLASS (klass),
@@ -6569,6 +6720,11 @@ ide_source_view_class_init (IdeSourceViewClass *klass)
                                 GDK_CONTROL_MASK | GDK_SHIFT_MASK,
                                 "begin-rename", 0);
 
+  gtk_binding_entry_add_signal (binding_set,
+                                GDK_KEY_q,
+                                GDK_CONTROL_MASK | GDK_SHIFT_MASK,
+                                "query-code-action", 0);
+
   gtk_binding_entry_add_signal (binding_set,
                                 GDK_KEY_space,
                                 GDK_CONTROL_MASK | GDK_SHIFT_MASK,
diff --git a/src/plugins/jedi-language-server/jedi-language-server.plugin 
b/src/plugins/jedi-language-server/jedi-language-server.plugin
index b6a7e7acf..af841444e 100644
--- a/src/plugins/jedi-language-server/jedi-language-server.plugin
+++ b/src/plugins/jedi-language-server/jedi-language-server.plugin
@@ -14,3 +14,4 @@ X-Highlighter-Languages=python,python3
 X-Hover-Provider-Languages=python,python3
 X-Rename-Provider-Languages=python,python3
 X-Formatter-Languages=python,python3
+X-Code-Action-Languages=python,python3
diff --git a/src/plugins/jedi-language-server/jedi_language_server_plugin.py 
b/src/plugins/jedi-language-server/jedi_language_server_plugin.py
index fddcef0a1..8c7b6c07f 100644
--- a/src/plugins/jedi-language-server/jedi_language_server_plugin.py
+++ b/src/plugins/jedi-language-server/jedi_language_server_plugin.py
@@ -142,3 +142,7 @@ class JediRenameProvider(Ide.LspRenameProvider, Ide.RenameProvider):
     def do_load(self):
         JediService.bind_client(self)
 
+class JediCodeActionProvider(Ide.LspCodeActionProvider, Ide.CodeActionProvider):
+    def do_load(self):
+        JediService.bind_client(self)
+
diff --git a/src/plugins/rust-analyzer/meson.build b/src/plugins/rust-analyzer/meson.build
index 17f3a78d2..ca3e2ba1a 100644
--- a/src/plugins/rust-analyzer/meson.build
+++ b/src/plugins/rust-analyzer/meson.build
@@ -5,6 +5,7 @@ plugins_deps += [
 ]
 
 plugins_sources += files([
+  'rust-analyzer-code-action-provider.c',
   'rust-analyzer-completion-provider.c',
   'rust-analyzer-diagnostic-provider.c',
   'rust-analyzer-formatter.c',
diff --git a/src/plugins/rust-analyzer/rust-analyzer-code-action-provider.c 
b/src/plugins/rust-analyzer/rust-analyzer-code-action-provider.c
new file mode 100644
index 000000000..4f4600137
--- /dev/null
+++ b/src/plugins/rust-analyzer/rust-analyzer-code-action-provider.c
@@ -0,0 +1,64 @@
+/* rust-analyzer-code-action-provider.c
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#include "rust-analyzer-code-action-provider.h"
+#include "rust-analyzer-service.h"
+
+struct _RustAnalyzerCodeActionProvider
+{
+  IdeLspCodeActionProvider parent_instance;
+};
+
+static void provider_iface_init (IdeCodeActionProviderInterface *iface);
+
+G_DEFINE_FINAL_TYPE_WITH_CODE (RustAnalyzerCodeActionProvider,
+                         rust_analyzer_code_action_provider,
+                         IDE_TYPE_LSP_CODE_ACTION_PROVIDER,
+                         G_IMPLEMENT_INTERFACE (IDE_TYPE_CODE_ACTION_PROVIDER, provider_iface_init))
+
+static void
+rust_analyzer_code_action_provider_class_init (RustAnalyzerCodeActionProviderClass *klass)
+{
+}
+
+static void
+rust_analyzer_code_action_provider_init (RustAnalyzerCodeActionProvider *self)
+{
+}
+
+static void
+rust_analyzer_code_action_provider_load (IdeCodeActionProvider *self)
+{
+  RustAnalyzerService *service;
+  IdeContext *context;
+
+  g_assert (RUST_IS_ANALYZER_CODE_ACTION_PROVIDER (self));
+
+  context = ide_object_get_context (IDE_OBJECT (self));
+  service = rust_analyzer_service_from_context (context);
+  g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE);
+  rust_analyzer_service_ensure_started (service);
+}
+
+static void
+provider_iface_init (IdeCodeActionProviderInterface *iface)
+{
+  iface->load = rust_analyzer_code_action_provider_load;
+}
diff --git a/src/plugins/rust-analyzer/rust-analyzer-code-action-provider.h 
b/src/plugins/rust-analyzer/rust-analyzer-code-action-provider.h
new file mode 100644
index 000000000..f7f974b49
--- /dev/null
+++ b/src/plugins/rust-analyzer/rust-analyzer-code-action-provider.h
@@ -0,0 +1,31 @@
+/* rust-analyzer-code-action-provider.h
+ *
+ * Copyright 2021 Georg Vienna <georg vienna himbarsoft com>
+ *
+ * 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
+ */
+
+#pragma once
+
+#include <libide-lsp.h>
+
+G_BEGIN_DECLS
+
+#define RUST_TYPE_ANALYZER_CODE_ACTION_PROVIDER (rust_analyzer_code_action_provider_get_type())
+
+G_DECLARE_FINAL_TYPE (RustAnalyzerCodeActionProvider, rust_analyzer_code_action_provider, RUST, 
ANALYZER_CODE_ACTION_PROVIDER, IdeLspCodeActionProvider)
+
+G_END_DECLS
diff --git a/src/plugins/rust-analyzer/rust-analyzer-plugin.c 
b/src/plugins/rust-analyzer/rust-analyzer-plugin.c
index d37e7d7f1..61a5e35f0 100644
--- a/src/plugins/rust-analyzer/rust-analyzer-plugin.c
+++ b/src/plugins/rust-analyzer/rust-analyzer-plugin.c
@@ -24,6 +24,7 @@
 #include <libide-lsp.h>
 #include <libide-gui.h>
 
+#include "rust-analyzer-code-action-provider.h"
 #include "rust-analyzer-completion-provider.h"
 #include "rust-analyzer-diagnostic-provider.h"
 #include "rust-analyzer-formatter.h"
@@ -69,6 +70,9 @@ _rust_analyzer_register_types (PeasObjectModule *module)
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_SEARCH_PROVIDER,
                                               RUST_TYPE_ANALYZER_SEARCH_PROVIDER);
+  peas_object_module_register_extension_type (module,
+                                              IDE_TYPE_CODE_ACTION_PROVIDER,
+                                              RUST_TYPE_ANALYZER_CODE_ACTION_PROVIDER);
   peas_object_module_register_extension_type (module,
                                               IDE_TYPE_PREFERENCES_ADDIN,
                                               RUST_TYPE_ANALYZER_PREFERENCES_ADDIN);
diff --git a/src/plugins/rust-analyzer/rust-analyzer.plugin b/src/plugins/rust-analyzer/rust-analyzer.plugin
index 53e86e0c7..c554f8365 100644
--- a/src/plugins/rust-analyzer/rust-analyzer.plugin
+++ b/src/plugins/rust-analyzer/rust-analyzer.plugin
@@ -13,3 +13,4 @@ X-Formatter-Languages=rust
 X-Highlighter-Languages=rust
 X-Hover-Provider-Languages=rust
 X-Rename-Provider-Languages=rust
+X-Code-Action-Languages=rust


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]