[gnome-builder] lsp: code action support
- From: Christian Hergert <chergert src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-builder] lsp: code action support
- Date: Thu, 11 Nov 2021 21:15:58 +0000 (UTC)
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]