diff --git a/doc/help/plugins/langserv.rst b/doc/help/plugins/langserv.rst index ed99dbe5ddab8cd96669a9fc1d99b5368eb155d0..63692234fe68c0fc433eec0473ab3a9343022e14 100644 --- a/doc/help/plugins/langserv.rst +++ b/doc/help/plugins/langserv.rst @@ -1,3 +1,77 @@ ############################ Integrating Language Servers ############################ + +In order to integrate a language server with **GNOME Builder** you have to create an ``Ide.SubprocessLauncher`` +to startup the language server. + +This subprocess should be restarted if it breaks therefore we have to wrap this +``Ide.SubprocessLauncher`` in a ``Ide.SubprocessSupervisor`` to monitor the +external process. After the subprocess is started we connect ``stdin`` and ``stdout`` +to our ``Ide.LspClient`` for dispatching of messages between client and server. + +.. code-block:: python3 + + class LSPService(Ide.Object): + _has_started = False + _client = None + + @GObject.Property(type=Ide.LspClient) + def client(self): + return self._client = value + + @client.setter + def client(self, value): + self._client = value + self.notify('client') + + def start(self): + if not self._has_started: + self._has_started = True + launcher = Ide.SubprocessLauncher() + launcher.set_flags(Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE) + launcher.push_argv("my_language_server_executable") + + supervisor = Ide.SubprocessSupervisor() + supervisor.connect('spawned', lsp_spawned) + supervisor.set_launcher(launcher) + supervisor.start() + + def lsp_spawned(self, supervisor, subprocess): + stdin = subprocess.get_stdin_pipe() + stdout = subprocess.get_stdout_pipe() + io_stream = Gio.SimpleIOStream.new(stdout, stdin) + + client = Ide.LspClient.new(io_stream) + self.append(client) + client.add_language('my_language') + client.start() + self.client(client) + + +As a language server handles several parts of an IDE we have to create according +extensions for code completion, diagnostics or hover content. As we want to make +sure that the corresponding service is only started once we use the builtin object +system (``Ide.Context.ensure_child_type(type)``. + +.. code-block:: python3 + + class MyLspCompletionProvider(Ide.LspCompletionProvider, Ide.CompletionProvider): + + def do_load(self, context): + service = context.ensure_child_typed(LSPService) + service.start() + service.bind_property('client', self, 'client', GObject.BindingFlags.SYNC_CREATE) + + def do_get_priority(self, context): + return 0 + +.. code-block:: python3 + + class MyLspDiagnosticProvider(Ide.LspDiagnosticProvider, Ide.DiagnosticProvider): + + def do_load(self): + context = self.get_context() + service = context.ensure_child_typed(LSPService) + service.start() + service.bind_property('client', self, 'client', GObject.BindingFlags.SYNC_CREATE) diff --git a/meson_options.txt b/meson_options.txt index 496fbb6f17dd11b301fe87d888fcb826830b82b6..0a103b57e97823817906a6eabd3d7a9c68ab0a27 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -65,6 +65,7 @@ option('plugin_qemu', type: 'boolean') option('plugin_quick_highlight', type: 'boolean') option('plugin_retab', type: 'boolean') option('plugin_rls', type: 'boolean') +option('plugin_rust_analyzer', type: 'boolean') option('plugin_rustup', type: 'boolean') option('plugin_shellcmd', type: 'boolean') option('plugin_spellcheck', type: 'boolean') diff --git a/src/libide/lsp/ide-lsp-client.c b/src/libide/lsp/ide-lsp-client.c index 927a8ea05d69954adf23dc3e2612bf1a9c8133b4..fb4e8560509dca38411d457f3486badd162c2ce0 100644 --- a/src/libide/lsp/ide-lsp-client.c +++ b/src/libide/lsp/ide-lsp-client.c @@ -68,6 +68,12 @@ enum { SEVERITY_HINT = 4, }; +enum { + TEXT_DOCUMENT_SYNC_NONE, + TEXT_DOCUMENT_SYNC_FULL, + TEXT_DOCUMENT_SYNC_INCREMENTAL, +}; + enum { PROP_0, PROP_IO_STREAM, @@ -208,10 +214,9 @@ ide_lsp_client_buffer_insert_text (IdeLspClient *self, { g_autoptr(GVariant) params = NULL; g_autofree gchar *uri = NULL; - g_autofree gchar *copy = NULL; + GVariant *capabilities = NULL; gint64 version; - gint line; - gint column; + gint64 text_document_sync = TEXT_DOCUMENT_SYNC_NONE; IDE_ENTRY; @@ -220,37 +225,79 @@ ide_lsp_client_buffer_insert_text (IdeLspClient *self, g_assert (location != NULL); g_assert (IDE_IS_BUFFER (buffer)); - copy = g_strndup (new_text, len); + capabilities = ide_lsp_client_get_server_capabilities (self); + if (capabilities != NULL) { + gint64 tds = 0; + + // for backwards compatibility reasons LS can stick to a number instead of the structure + if (JSONRPC_MESSAGE_PARSE (capabilities, "textDocumentSync", JSONRPC_MESSAGE_GET_INT64 (&tds)) + | JSONRPC_MESSAGE_PARSE (capabilities, "textDocumentSync", "{", "change", JSONRPC_MESSAGE_GET_INT64 (&tds), "}")) + { + text_document_sync = tds; + } + } uri = ide_buffer_dup_uri (buffer); /* We get called before this change is registered */ version = (gint64)ide_buffer_get_change_count (buffer) + 1; - line = gtk_text_iter_get_line (location); - column = gtk_text_iter_get_line_offset (location); + if (text_document_sync == TEXT_DOCUMENT_SYNC_INCREMENTAL) + { + g_autofree gchar *copy = NULL; + gint line; + gint column; - params = JSONRPC_MESSAGE_NEW ( - "textDocument", "{", - "uri", JSONRPC_MESSAGE_PUT_STRING (uri), - "version", JSONRPC_MESSAGE_PUT_INT64 (version), - "}", - "contentChanges", "[", - "{", - "range", "{", - "start", "{", - "line", JSONRPC_MESSAGE_PUT_INT64 (line), - "character", JSONRPC_MESSAGE_PUT_INT64 (column), - "}", - "end", "{", - "line", JSONRPC_MESSAGE_PUT_INT64 (line), - "character", JSONRPC_MESSAGE_PUT_INT64 (column), + copy = g_strndup (new_text, len); + + line = gtk_text_iter_get_line (location); + column = gtk_text_iter_get_line_offset (location); + + params = JSONRPC_MESSAGE_NEW ( + "textDocument", "{", + "uri", JSONRPC_MESSAGE_PUT_STRING (uri), + "version", JSONRPC_MESSAGE_PUT_INT64 (version), + "}", + "contentChanges", "[", + "{", + "range", "{", + "start", "{", + "line", JSONRPC_MESSAGE_PUT_INT64 (line), + "character", JSONRPC_MESSAGE_PUT_INT64 (column), + "}", + "end", "{", + "line", JSONRPC_MESSAGE_PUT_INT64 (line), + "character", JSONRPC_MESSAGE_PUT_INT64 (column), + "}", + "}", + "rangeLength", JSONRPC_MESSAGE_PUT_INT64 (0), + "text", JSONRPC_MESSAGE_PUT_STRING (copy), "}", + "]"); + } + else if (text_document_sync == TEXT_DOCUMENT_SYNC_FULL) + { + g_autoptr(GBytes) content = NULL; + const gchar *text; + g_autoptr(GString) str = NULL; + + content = ide_buffer_dup_content (buffer); + text = (const gchar *)g_bytes_get_data (content, NULL); + str = g_string_new (text); + g_string_insert_len (str, gtk_text_iter_get_offset (location), new_text, len); + + params = JSONRPC_MESSAGE_NEW ( + "textDocument", "{", + "uri", JSONRPC_MESSAGE_PUT_STRING (uri), + "version", JSONRPC_MESSAGE_PUT_INT64 (version), "}", - "rangeLength", JSONRPC_MESSAGE_PUT_INT64 (0), - "text", JSONRPC_MESSAGE_PUT_STRING (copy), - "}", - "]"); + "contentChanges", "[", + "{", + "text", JSONRPC_MESSAGE_PUT_STRING (str->str), + "}", + "]"); + } + ide_lsp_client_send_notification_async (self, "textDocument/didChange", diff --git a/src/libide/lsp/ide-lsp-completion-item.c b/src/libide/lsp/ide-lsp-completion-item.c index 1af194671a00540c5f2c4c9c590ecde70f277c68..6d69acb014282dd7dbbf12c7b7a970e3680e90dc 100644 --- a/src/libide/lsp/ide-lsp-completion-item.c +++ b/src/libide/lsp/ide-lsp-completion-item.c @@ -139,6 +139,7 @@ ide_lsp_completion_item_get_snippet (IdeLspCompletionItem *self) g_autoptr(IdeSnippet) snippet = NULL; g_autoptr(IdeSnippetChunk) plainchunk = NULL; const gchar *snippet_text = NULL; + const gchar *snippet_new_text = NULL; const gchar *text; gint64 format = 0; @@ -147,10 +148,21 @@ ide_lsp_completion_item_get_snippet (IdeLspCompletionItem *self) text = self->label; if (JSONRPC_MESSAGE_PARSE (self->variant, - "insertTextFormat", JSONRPC_MESSAGE_GET_INT64 (&format), - "insertText", JSONRPC_MESSAGE_GET_STRING (&snippet_text))) + "insertTextFormat", JSONRPC_MESSAGE_GET_INT64 (&format))) { - if (format == 2 && snippet_text != NULL) + JSONRPC_MESSAGE_PARSE (self->variant, "insertText", JSONRPC_MESSAGE_GET_STRING (&snippet_text)); + JSONRPC_MESSAGE_PARSE (self->variant, "textEdit", "{", "newText", JSONRPC_MESSAGE_GET_STRING (&snippet_new_text), "}"); + if (format == 2 && snippet_new_text != NULL) + { + g_autoptr(GError) error = NULL; + + if ((snippet = ide_snippet_parser_parse_one (snippet_new_text, -1, &error))) + return g_steal_pointer (&snippet); + + g_warning ("Failed to parse snippet: %s: %s", + error->message, snippet_text); + } + else if (format == 2 && snippet_text != NULL) { g_autoptr(GError) error = NULL; diff --git a/src/libide/lsp/ide-lsp-formatter.c b/src/libide/lsp/ide-lsp-formatter.c index 0a5c4c244b7472c9ccfa62eeae5dbfdca63c1e2b..2f006c2dda41861a9e305dae48558f3550e7470c 100644 --- a/src/libide/lsp/ide-lsp-formatter.c +++ b/src/libide/lsp/ide-lsp-formatter.c @@ -356,7 +356,7 @@ ide_lsp_formatter_format_range_async (IdeFormatter *formatter, g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); task = ide_task_new (self, cancellable, callback, user_data); - ide_task_set_source_tag (task, ide_lsp_formatter_format_async); + ide_task_set_source_tag (task, ide_lsp_formatter_format_range_async); ide_task_set_task_data (task, g_object_ref (buffer), g_object_unref); if (gtk_text_iter_compare (begin, end) > 0) diff --git a/src/plugins/meson.build b/src/plugins/meson.build index 5690b5ffae12b36b223e262e72f6204525748a25..bee20b6fd32eba648937e9dc84137dcced2be5fe 100644 --- a/src/plugins/meson.build +++ b/src/plugins/meson.build @@ -106,6 +106,7 @@ subdir('recent') subdir('restore-cursor') subdir('retab') subdir('rls') +subdir('rust-analyzer') subdir('rustup') subdir('shellcmd') subdir('snippets') @@ -185,6 +186,7 @@ status += [ 'Quick Highlight ....... : @0@'.format(get_option('plugin_quick_highlight')), 'Retab ................. : @0@'.format(get_option('plugin_retab')), 'RLS ................... : @0@'.format(get_option('plugin_rls')), + 'Rust Analyzer ......... : @0@'.format(get_option('plugin_rust_analyzer')), 'Rustup ................ : @0@'.format(get_option('plugin_rustup')), 'Spellcheck ............ : @0@'.format(get_option('plugin_spellcheck')), 'Stylelint ............. : @0@'.format(get_option('plugin_stylelint')), diff --git a/src/plugins/rust-analyzer/meson.build b/src/plugins/rust-analyzer/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..492ed9844e52dfe1e587690814b0626670567760 --- /dev/null +++ b/src/plugins/rust-analyzer/meson.build @@ -0,0 +1,22 @@ +if get_option('plugin_rust_analyzer') + +plugins_sources += files([ + 'rust-analyzer.c', + 'rust-analyzer-service.c', + 'rust-analyzer-completion-provider.c', + 'rust-analyzer-symbol-resolver.c', + 'rust-analyzer-diagnostic-provider.c', + 'rust-analyzer-formatter.c', + 'rust-analyzer-transfer.c', + 'rust-analyzer-workbench-addin.c', +]) + +plugin_rust_analyzer_resources = gnome.compile_resources( + 'rust-analyzer-resources', + 'rust-analyzer.gresource.xml', + c_name: 'rust_analyzer' +) + +plugins_sources += plugin_rust_analyzer_resources + +endif diff --git a/src/plugins/rust-analyzer/rust-analyzer-completion-provider.c b/src/plugins/rust-analyzer/rust-analyzer-completion-provider.c new file mode 100644 index 0000000000000000000000000000000000000000..6b88d081c0a34d42ecf8c9a8e5c6b35a3b45bccb --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-completion-provider.c @@ -0,0 +1,67 @@ +/* rust-analyzer-completion-provider.c + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "rust-analyzer-completion-provider.h" +#include "rust-analyzer-service.h" + +struct _RustAnalyzerCompletionProvider +{ + IdeLspCompletionProvider parent_instance; +}; + +static void provider_iface_init (IdeCompletionProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (RustAnalyzerCompletionProvider, + rust_analyzer_completion_provider, + IDE_TYPE_LSP_COMPLETION_PROVIDER, + G_IMPLEMENT_INTERFACE (IDE_TYPE_COMPLETION_PROVIDER, provider_iface_init)) + +static void +rust_analyzer_completion_provider_class_init (RustAnalyzerCompletionProviderClass *klass) +{ +} + +static void +rust_analyzer_completion_provider_init (RustAnalyzerCompletionProvider *self) +{ +} + +static void +rust_analyzer_completion_provider_load (IdeCompletionProvider *self, + IdeContext *context) +{ + RustAnalyzerService *service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE); + g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE); + rust_analyzer_service_ensure_started (service); +} + +static gint +rust_analyzer_completion_provider_get_priority (IdeCompletionProvider *provider, + IdeCompletionContext *context) +{ + return -1000; +} + +static void +provider_iface_init (IdeCompletionProviderInterface *iface) +{ + iface->load = rust_analyzer_completion_provider_load; + iface->get_priority = rust_analyzer_completion_provider_get_priority; +} diff --git a/src/plugins/rust-analyzer/rust-analyzer-completion-provider.h b/src/plugins/rust-analyzer/rust-analyzer-completion-provider.h new file mode 100644 index 0000000000000000000000000000000000000000..f06faaff5e52faa2df4fdefa83b0212897f915b0 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-completion-provider.h @@ -0,0 +1,32 @@ +/* rust-analyzer-completion-provider.h + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define RUST_TYPE_ANALYZER_COMPLETION_PROVIDER (rust_analyzer_completion_provider_get_type()) + +G_DECLARE_FINAL_TYPE (RustAnalyzerCompletionProvider, rust_analyzer_completion_provider, RUST, ANALYZER_COMPLETION_PROVIDER, IdeLspCompletionProvider) + +G_END_DECLS diff --git a/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.c b/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.c new file mode 100644 index 0000000000000000000000000000000000000000..dd5e16c781b5bd4693a1fb9d44c0767f63e7bc73 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.c @@ -0,0 +1,59 @@ +/* rust-analyzer-diagnostic-provider.c + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "rust-analyzer-diagnostic-provider.h" +#include "rust-analyzer-service.h" + +struct _RustAnalyzerDiagnosticProvider +{ + IdeLspDiagnosticProvider parent_instance; +}; + +static void provider_iface_init (IdeDiagnosticProviderInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (RustAnalyzerDiagnosticProvider, + rust_analyzer_diagnostic_provider, + IDE_TYPE_LSP_DIAGNOSTIC_PROVIDER, + G_IMPLEMENT_INTERFACE (IDE_TYPE_DIAGNOSTIC_PROVIDER, provider_iface_init)) + +static void +rust_analyzer_diagnostic_provider_class_init (RustAnalyzerDiagnosticProviderClass *klass) +{ +} + +static void +rust_analyzer_diagnostic_provider_init (RustAnalyzerDiagnosticProvider *self) +{ +} + +static void +rust_analyzer_diagnostic_provider_load (IdeDiagnosticProvider *self) +{ + IdeContext *context = ide_object_get_context (IDE_OBJECT (self)); + RustAnalyzerService *service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE); + rust_analyzer_service_ensure_started (service); + g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE); +} + +static void +provider_iface_init (IdeDiagnosticProviderInterface *iface) +{ + iface->load = rust_analyzer_diagnostic_provider_load; +} diff --git a/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.h b/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.h new file mode 100644 index 0000000000000000000000000000000000000000..5da85ef38781732e54182f75d7e1d178d9821f2f --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.h @@ -0,0 +1,31 @@ +/* rust-analyzer-diagnostic-provider.h + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define RUST_TYPE_ANALYZER_DIAGNOSTIC_PROVIDER (rust_analyzer_diagnostic_provider_get_type()) + +G_DECLARE_FINAL_TYPE (RustAnalyzerDiagnosticProvider, rust_analyzer_diagnostic_provider, RUST, ANALYZER_DIAGNOSTIC_PROVIDER, IdeLspDiagnosticProvider) + +G_END_DECLS diff --git a/src/plugins/rust-analyzer/rust-analyzer-formatter.c b/src/plugins/rust-analyzer/rust-analyzer-formatter.c new file mode 100644 index 0000000000000000000000000000000000000000..8ddee6981e1a08d37f67001ce2a7b05c53c14152 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-formatter.c @@ -0,0 +1,59 @@ +/* rust-analyzer-formatter.c + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "rust-analyzer-formatter.h" +#include "rust-analyzer-service.h" + +struct _RustAnalyzerFormatter +{ + IdeLspFormatter parent_instance; +}; + +static void provider_iface_init (IdeFormatterInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (RustAnalyzerFormatter, + rust_analyzer_formatter, + IDE_TYPE_LSP_FORMATTER, + G_IMPLEMENT_INTERFACE (IDE_TYPE_FORMATTER, provider_iface_init)) + +static void +rust_analyzer_formatter_class_init (RustAnalyzerFormatterClass *klass) +{ +} + +static void +rust_analyzer_formatter_init (RustAnalyzerFormatter *self) +{ +} + +static void +rust_analyzer_formatter_load (IdeFormatter *self) +{ + IdeContext *context = ide_object_get_context (IDE_OBJECT (self)); + RustAnalyzerService *service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE); + g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE); + rust_analyzer_service_ensure_started (service); +} + +static void +provider_iface_init (IdeFormatterInterface *iface) +{ + iface->load = rust_analyzer_formatter_load; +} diff --git a/src/plugins/rust-analyzer/rust-analyzer-formatter.h b/src/plugins/rust-analyzer/rust-analyzer-formatter.h new file mode 100644 index 0000000000000000000000000000000000000000..cd1640631106f8beae25895dd0646727c3ddd156 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-formatter.h @@ -0,0 +1,31 @@ +/* rust-analyzer-formatter.h + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define RUST_TYPE_ANALYZER_FORMATTER (rust_analyzer_formatter_get_type()) + +G_DECLARE_FINAL_TYPE (RustAnalyzerFormatter, rust_analyzer_formatter, RUST, ANALYZER_FORMATTER, IdeLspFormatter) + +G_END_DECLS diff --git a/src/plugins/rust-analyzer/rust-analyzer-service.c b/src/plugins/rust-analyzer/rust-analyzer-service.c new file mode 100644 index 0000000000000000000000000000000000000000..96c28630546feda50ed6444fd887e7e4abad108e --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-service.c @@ -0,0 +1,400 @@ +/* rust-analyzer-service.c + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "rust-analyzer-service" + +#include "rust-analyzer-service.h" +#include "rust-analyzer-transfer.h" +#include +#include +#include +#include +#include + +struct _RustAnalyzerService +{ + IdeObject parent_instance; + IdeLspClient *client; + IdeSubprocessSupervisor *supervisor; + GFileMonitor *cargo_monitor; + + ServiceState state; +}; + +G_DEFINE_TYPE (RustAnalyzerService, rust_analyzer_service, IDE_TYPE_OBJECT) + +enum { + PROP_0, + PROP_CLIENT, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +RustAnalyzerService * +rust_analyzer_service_new (void) +{ + return g_object_new (RUST_TYPE_ANALYZER_SERVICE, NULL); +} + +static void +_cargo_toml_changed_cb (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + gpointer user_data) +{ + RustAnalyzerService *self = RUST_ANALYZER_SERVICE (user_data); + + if (self->supervisor != NULL) + { + IdeSubprocess *subprocess = ide_subprocess_supervisor_get_subprocess (self->supervisor); + if (subprocess != NULL) + ide_subprocess_force_exit (subprocess); + } +} + +static void +rust_analyzer_service_finalize (GObject *object) +{ + RustAnalyzerService *self = (RustAnalyzerService *)object; + + g_clear_object (&self->client); + + G_OBJECT_CLASS (rust_analyzer_service_parent_class)->finalize (object); +} + +static void +rust_analyzer_service_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object); + + switch (prop_id) + { + case PROP_CLIENT: + g_value_set_object (value, rust_analyzer_service_get_client (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +rust_analyzer_service_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object); + + switch (prop_id) + { + case PROP_CLIENT: + rust_analyzer_service_set_client (self, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +rust_analyzer_service_set_parent (IdeObject *object, + IdeObject *parent) +{ + RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object); + + IdeContext *context = NULL; + g_autoptr(GFile) workdir = NULL; + g_autoptr(GFile) cargo_toml = NULL; + + g_return_if_fail (RUST_IS_ANALYZER_SERVICE (object)); + g_return_if_fail (parent != NULL); + + context = ide_object_get_context (object); + workdir = ide_context_ref_workdir (context); + cargo_toml = g_file_get_child (workdir, "Cargo.toml"); + + if (g_file_query_exists (cargo_toml, NULL)) + { + GError *error = NULL; + + if (self->cargo_monitor != NULL) + return; + + self->cargo_monitor = g_file_monitor (cargo_toml, G_FILE_MONITOR_NONE, NULL, &error); + if (error != NULL) + { + g_warning ("%s", error->message); + return; + } + g_file_monitor_set_rate_limit (self->cargo_monitor, 5 * 1000); // 5 Seconds + g_signal_connect (self->cargo_monitor, "changed", G_CALLBACK (_cargo_toml_changed_cb), self); + } + +} + +static void +rust_analyzer_service_destroy (IdeObject *object) +{ + RustAnalyzerService *self = RUST_ANALYZER_SERVICE (object); + + if (self->supervisor != NULL) + { + g_autoptr(IdeSubprocessSupervisor) supervisor = g_steal_pointer (&self->supervisor); + + ide_subprocess_supervisor_stop (supervisor); + } + + IDE_OBJECT_CLASS (rust_analyzer_service_parent_class)->destroy (object); +} + +static void +rust_analyzer_service_class_init (RustAnalyzerServiceClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + IdeObjectClass *i_class = IDE_OBJECT_CLASS (klass); + + object_class->finalize = rust_analyzer_service_finalize; + object_class->get_property = rust_analyzer_service_get_property; + object_class->set_property = rust_analyzer_service_set_property; + + i_class->parent_set = rust_analyzer_service_set_parent; + i_class->destroy = rust_analyzer_service_destroy; + + properties [PROP_CLIENT] = + g_param_spec_object ("client", + "Client", + "The Language Server client", + IDE_TYPE_LSP_CLIENT, + (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +rust_analyzer_service_init (RustAnalyzerService *self) +{ + self->client = NULL; + self->state = RUST_ANALYZER_SERVICE_INIT; +} + +IdeLspClient * +rust_analyzer_service_get_client (RustAnalyzerService *self) +{ + g_return_val_if_fail (RUST_IS_ANALYZER_SERVICE (self), NULL); + + return self->client; +} + +void +rust_analyzer_service_set_client (RustAnalyzerService *self, + IdeLspClient *client) +{ + g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self)); + g_return_if_fail (!client || IDE_IS_LSP_CLIENT (client)); + + if (g_set_object (&self->client, client)) + { + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_CLIENT]); + } +} + +static void +_handle_notification (IdeLspClient *client, + gchar *method, + GVariant *params) +{ + g_autoptr(IdeNotification) notification = NULL; + IdeNotifications *notifications = NULL; + IdeContext *context = NULL; + const gchar *message = NULL; + const gchar *token = NULL; + const gchar *kind = NULL; + + if (!ide_str_equal0 (method, "$/progress")) + return; + + JSONRPC_MESSAGE_PARSE (params, "token", JSONRPC_MESSAGE_GET_STRING (&token)); + + if (!ide_str_equal0 (token, "rustAnalyzer/startup")) + return; + + JSONRPC_MESSAGE_PARSE (params, "value", "{", "kind", JSONRPC_MESSAGE_GET_STRING (&kind), "message", JSONRPC_MESSAGE_GET_STRING (&message), "}"); + + context = ide_object_get_context (IDE_OBJECT (client)); + notifications = ide_object_get_child_typed (IDE_OBJECT (context), IDE_TYPE_NOTIFICATIONS); + notification = ide_notifications_find_by_id (notifications, "org.gnome-builder.rust-analyzer.startup"); + + if (notification == NULL) + { + notification = ide_notification_new (); + ide_notification_set_id (notification, "org.gnome-builder.rust-analyzer.startup"); + ide_notification_set_title (notification, message); + ide_notification_set_has_progress (notification, TRUE); + ide_notification_set_progress_is_imprecise (notification, TRUE); + ide_notification_set_icon_name (notification, "system-run-symbolic"); + ide_notification_attach (notification, IDE_OBJECT (context)); + } + else + { + ide_notification_set_title (notification, message); + if (ide_str_equal0 (kind, "end")) + { + ide_notification_set_has_progress (notification, FALSE); + ide_notification_set_icon_name (notification, NULL); + ide_notification_withdraw_in_seconds (notification, 3); + } + } +} + +void +rust_analyzer_service_lsp_started (IdeSubprocessSupervisor *supervisor, + IdeSubprocess *subprocess, + gpointer user_data) +{ + g_autoptr(GIOStream) io_stream = NULL; + GInputStream *input; + GOutputStream *output; + IdeLspClient *client = NULL; + + RustAnalyzerService *self = RUST_ANALYZER_SERVICE (user_data); + + input = ide_subprocess_get_stdout_pipe (subprocess); + output = ide_subprocess_get_stdin_pipe (subprocess); + io_stream = g_simple_io_stream_new (input, output); + + if (self->client != NULL) + { + ide_lsp_client_stop (self->client); + ide_object_destroy (IDE_OBJECT (self->client)); + } + + client = ide_lsp_client_new (io_stream); + g_signal_connect (client, "notification", G_CALLBACK (_handle_notification), NULL); + rust_analyzer_service_set_client (self, client); + ide_object_append (IDE_OBJECT (self), IDE_OBJECT (client)); + ide_lsp_client_add_language (client, "rust"); + ide_lsp_client_start (client); +} + +static gboolean +rust_analyzer_service_check_rust_analyzer_bin (RustAnalyzerService *self) +{ + // Check if `rust-analyzer` can be found on PATH or if there is an executable + // in typical location + g_autoptr(GFile) rust_analyzer_bin_file = NULL; + g_autofree gchar *rust_analyzer_bin = NULL; + g_autoptr(GFileInfo) file_info = NULL; + + rust_analyzer_bin = g_find_program_in_path ("rust-analyzer"); + if (rust_analyzer_bin == NULL) + { + g_autofree gchar *path = NULL; + const gchar *homedir = g_get_home_dir (); + + path = g_build_path (G_DIR_SEPARATOR_S, homedir, ".cargo", "bin", "rust-analyzer", NULL); + rust_analyzer_bin_file = g_file_new_for_path (path); + } + else + { + rust_analyzer_bin_file = g_file_new_for_path (rust_analyzer_bin); + } + + if (!g_file_query_exists (rust_analyzer_bin_file, NULL)) + { + return FALSE; + } + + file_info = g_file_query_info (rust_analyzer_bin_file, + "*", + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (ide_str_equal0 ("application/x-sharedlib", g_file_info_get_content_type (file_info))) + return TRUE; + + return FALSE; +} + +void +rust_analyzer_service_ensure_started (RustAnalyzerService *self) +{ + if (self->state == RUST_ANALYZER_SERVICE_INIT) + { + if (!rust_analyzer_service_check_rust_analyzer_bin (self)) + { + g_autoptr(IdeNotification) notification = NULL; + IdeContext *context = NULL; + + self->state = RUST_ANALYZER_SERVICE_OFFER_DOWNLOAD; + + notification = ide_notification_new (); + ide_notification_set_id (notification, "org.gnome-builder.rust-analyzer"); + ide_notification_set_title (notification, "Your computer is missing the Rust Analyzer Language Server"); + ide_notification_set_body (notification, "The Language Server is necessary to provide IDE features like completion or diagnostic"); + ide_notification_set_icon_name (notification, "dialog-warning-symbolic"); + ide_notification_add_button (notification, "Install Language Server", NULL, "win.install-rust-analyzer"); + ide_notification_set_urgent (notification, TRUE); + context = ide_object_get_context (IDE_OBJECT (self)); + ide_notification_attach (notification, IDE_OBJECT (context)); + } + else + self->state = RUST_ANALYZER_SERVICE_READY; + } + else if (self->state == RUST_ANALYZER_SERVICE_READY) + { + g_autofree gchar *newpath = NULL; + g_autoptr(IdeSubprocessLauncher) launcher = NULL; + IdeContext *context = NULL; + GFile *workdir = NULL; + const gchar *oldpath = NULL; + + launcher = ide_subprocess_launcher_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE | G_SUBPROCESS_FLAGS_STDIN_PIPE); + ide_subprocess_launcher_set_run_on_host (launcher, TRUE); + ide_subprocess_launcher_set_clear_env (launcher, TRUE); + + context = ide_object_get_context (IDE_OBJECT (self)); + workdir = ide_context_ref_workdir (context); + ide_subprocess_launcher_set_cwd (launcher, g_file_get_path (workdir)); + oldpath = g_getenv ("PATH"); + newpath = g_strdup_printf ("%s/%s:%s", g_get_home_dir (), ".cargo/bin", oldpath); + ide_subprocess_launcher_setenv (launcher, "PATH", newpath, TRUE); + + ide_subprocess_launcher_push_argv (launcher, "rust-analyzer"); + + self->supervisor = ide_subprocess_supervisor_new (); + g_signal_connect (self->supervisor, "spawned", G_CALLBACK (rust_analyzer_service_lsp_started), self); + ide_subprocess_supervisor_set_launcher (self->supervisor, launcher); + ide_subprocess_supervisor_start (self->supervisor); + self->state = RUST_ANALYZER_SERVICE_LSP_STARTED; + } +} + +void +rust_analyzer_service_set_state (RustAnalyzerService *self, + ServiceState state) +{ + g_return_if_fail (RUST_IS_ANALYZER_SERVICE (self)); + + self->state = state; +} diff --git a/src/plugins/rust-analyzer/rust-analyzer-service.h b/src/plugins/rust-analyzer/rust-analyzer-service.h new file mode 100644 index 0000000000000000000000000000000000000000..6c3ffae6609bcf31a506d36c22f5474f60ff41f3 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-service.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define RUST_TYPE_ANALYZER_SERVICE (rust_analyzer_service_get_type()) + +G_DECLARE_FINAL_TYPE (RustAnalyzerService, rust_analyzer_service, RUST, ANALYZER_SERVICE, IdeObject) + +typedef enum { + RUST_ANALYZER_SERVICE_INIT, + RUST_ANALYZER_SERVICE_OFFER_DOWNLOAD, + RUST_ANALYZER_SERVICE_READY, + RUST_ANALYZER_SERVICE_LSP_STARTED, +} ServiceState; + +RustAnalyzerService *rust_analyzer_service_new (void); +IdeLspClient *rust_analyzer_service_get_client (RustAnalyzerService *self); +void rust_analyzer_service_set_client (RustAnalyzerService *self, + IdeLspClient *client); +void rust_analyzer_service_ensure_started (RustAnalyzerService *self); +void rust_analyzer_service_set_state (RustAnalyzerService *self, + ServiceState state); + +G_END_DECLS diff --git a/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.c b/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.c new file mode 100644 index 0000000000000000000000000000000000000000..980138b8921f3c08ea7cd431dfe3f41cdff1be3c --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.c @@ -0,0 +1,59 @@ +/* rust-analyzer-symbol-resolver.c + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "rust-analyzer-symbol-resolver.h" +#include "rust-analyzer-service.h" + +struct _RustAnalyzerSymbolResolver +{ + IdeLspSymbolResolver parent_instance; +}; + +static void symbol_iface_init (IdeSymbolResolverInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (RustAnalyzerSymbolResolver, + rust_analyzer_symbol_resolver, + IDE_TYPE_LSP_SYMBOL_RESOLVER, + G_IMPLEMENT_INTERFACE (IDE_TYPE_SYMBOL_RESOLVER, symbol_iface_init)) + +static void +rust_analyzer_symbol_resolver_class_init (RustAnalyzerSymbolResolverClass *klass) +{ +} + +static void +rust_analyzer_symbol_resolver_init (RustAnalyzerSymbolResolver *self) +{ +} + +static void +rust_analyzer_symbol_resolver_load (IdeSymbolResolver *self) +{ + IdeContext *context = ide_object_get_context (IDE_OBJECT (self)); + RustAnalyzerService *service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE); + rust_analyzer_service_ensure_started (service); + g_object_bind_property (service, "client", self, "client", G_BINDING_SYNC_CREATE); +} + +static void +symbol_iface_init (IdeSymbolResolverInterface *iface) +{ + iface->load = rust_analyzer_symbol_resolver_load; +} diff --git a/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.h b/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.h new file mode 100644 index 0000000000000000000000000000000000000000..12d8bd507bec41ae060da2bd390bd62cd969d552 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.h @@ -0,0 +1,31 @@ +/* rust-analyzer-symbol-resolver.h + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define RUST_TYPE_ANALYZER_SYMBOL_RESOLVER (rust_analyzer_symbol_resolver_get_type()) + +G_DECLARE_FINAL_TYPE (RustAnalyzerSymbolResolver, rust_analyzer_symbol_resolver, RUST, ANALYZER_SYMBOL_RESOLVER, IdeLspSymbolResolver) + +G_END_DECLS diff --git a/src/plugins/rust-analyzer/rust-analyzer-transfer.c b/src/plugins/rust-analyzer/rust-analyzer-transfer.c new file mode 100644 index 0000000000000000000000000000000000000000..4ccae579e10375df1ea5829cef6a330208b2ce5f --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-transfer.c @@ -0,0 +1,177 @@ +/* rust-analyzer-transfer.c + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "rust-analyzer-transfer.h" +#include +#include +#include +#include +#include +#include + +struct _RustAnalyzerTransfer +{ + IdeTransfer parent_instance; +}; + +G_DEFINE_TYPE (RustAnalyzerTransfer, rust_analyzer_transfer, IDE_TYPE_TRANSFER) + +RustAnalyzerTransfer * +rust_analyzer_transfer_new (void) +{ + return g_object_new (RUST_TYPE_ANALYZER_TRANSFER, NULL); +} + +typedef struct { + gchar buffer[6*1024]; + gsize count; + guint64 total_bytes; + gchar *filepath; + GOutputStream *filestream; + IdeTransfer *transfer; + IdeTask *task; +} DownloadData; + +static void +_downloaded_chunk (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autofree gchar *statusmsg = NULL; + g_autoptr(GError) error = NULL; + GInputStream *stream = G_INPUT_STREAM (source_object); + DownloadData *data = user_data; + + gsize count = g_input_stream_read_finish (stream, result, &error); + if (error != NULL) + { + ide_task_return_error (data->task, g_steal_pointer (&error)); + return; + } + + if (count == 0) + { + g_output_stream_close (data->filestream, NULL, NULL); + g_input_stream_close (stream, NULL, NULL); + ide_task_return_boolean (data->task, TRUE); + g_object_unref (data->task); + g_chmod (data->filepath, S_IRWXU); + g_free (data->filepath); + g_slice_free (DownloadData, data); + return; + } + + data->count += count; + statusmsg = g_strdup_printf ("%.2f MB / %.2f MB", data->count / 1048576., data->total_bytes / 1048576.); + ide_transfer_set_status (data->transfer, statusmsg); + ide_transfer_set_progress (data->transfer, (gdouble) data->count / data->total_bytes); + + g_output_stream_write_all (data->filestream, &data->buffer, count, NULL, ide_task_get_cancellable (data->task), &error); + if (error != NULL) + { + ide_task_return_error (data->task, g_steal_pointer (&error)); + return; + } + g_input_stream_read_async (stream, &data->buffer, sizeof (data->buffer), G_PRIORITY_DEFAULT, ide_task_get_cancellable (data->task), _downloaded_chunk, data); +} + +static void +_download_lsp (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + g_autoptr(IdeTask) task = IDE_TASK (user_data); + g_autoptr(GFile) file = NULL; + g_autoptr(GError) error = NULL; + SoupRequest *request = SOUP_REQUEST (source_object); + GInputStream *stream = NULL; + DownloadData *data; + + stream = soup_request_send_finish (request, result, NULL); + + data = g_slice_new0 (DownloadData); + data->filepath = g_build_filename (g_get_home_dir (), ".cargo", "bin", "rust-analyzer", NULL); + file = g_file_new_for_path (data->filepath); + data->transfer = IDE_TRANSFER (ide_task_get_task_data (task)); + data->filestream = G_OUTPUT_STREAM (g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, ide_task_get_cancellable (data->task), &error)); + if (data->filestream == NULL) + { + ide_task_return_error (task, g_steal_pointer (&error)); + return; + } + data->total_bytes = soup_request_get_content_length (request); + data->task = g_steal_pointer (&task); + + g_input_stream_read_async (stream, &data->buffer, sizeof (data->buffer), G_PRIORITY_DEFAULT, ide_task_get_cancellable (data->task), _downloaded_chunk, data); +} + +static void +rust_analyzer_transfer_execute_async (IdeTransfer *transfer, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + RustAnalyzerTransfer *self = RUST_ANALYZER_TRANSFER (transfer); + g_autoptr(IdeTask) task = NULL; + g_autoptr(SoupSession) session = NULL; + g_autoptr(SoupRequest) request = NULL; + g_autoptr(GError) error = NULL; + + g_assert (!cancellable || G_IS_CANCELLABLE (cancellable)); + + task = ide_task_new (self, cancellable, callback, user_data); + ide_task_set_source_tag (task, rust_analyzer_transfer_execute_async); + ide_task_set_task_data (task, transfer, g_object_unref); + + session = soup_session_new (); + + request = soup_session_request (session, "https://github.com/rust-analyzer/rust-analyzer/releases/download/nightly/rust-analyzer-linux", &error); + if (request == NULL) + ide_task_return_error (task, g_steal_pointer (&error)); + else + soup_request_send_async (request, NULL, _download_lsp, g_steal_pointer (&task)); +} + +static gboolean +rust_analyzer_transfer_execute_finish (IdeTransfer *transfer, + GAsyncResult *result, + GError **error) +{ + gboolean ret; + + ret = ide_task_propagate_boolean (IDE_TASK (result), error); + + return ret; +} + +static void +rust_analyzer_transfer_class_init (RustAnalyzerTransferClass *klass) +{ + IdeTransferClass *transfer_class = IDE_TRANSFER_CLASS (klass); + + transfer_class->execute_async = rust_analyzer_transfer_execute_async; + transfer_class->execute_finish = rust_analyzer_transfer_execute_finish; +} + +static void +rust_analyzer_transfer_init (RustAnalyzerTransfer *self) +{ + ide_transfer_set_title (IDE_TRANSFER (self), "Installing Rust Analyzer..."); +} diff --git a/src/plugins/rust-analyzer/rust-analyzer-transfer.h b/src/plugins/rust-analyzer/rust-analyzer-transfer.h new file mode 100644 index 0000000000000000000000000000000000000000..7d45fccc2e8354d0279390bc951cefbc800693f8 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-transfer.h @@ -0,0 +1,33 @@ +/* rust-analyzer-transfer.h + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define RUST_TYPE_ANALYZER_TRANSFER (rust_analyzer_transfer_get_type()) + +G_DECLARE_FINAL_TYPE (RustAnalyzerTransfer, rust_analyzer_transfer, RUST, ANALYZER_TRANSFER, IdeTransfer) + +RustAnalyzerTransfer *rust_analyzer_transfer_new (void); + +G_END_DECLS diff --git a/src/plugins/rust-analyzer/rust-analyzer-workbench-addin.c b/src/plugins/rust-analyzer/rust-analyzer-workbench-addin.c new file mode 100644 index 0000000000000000000000000000000000000000..375f38aadfd515f2df4a7eec0e855cd6c15e3c2c --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-workbench-addin.c @@ -0,0 +1,139 @@ +/* rust-analyzer-workbench-addin.c + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include "rust-analyzer-workbench-addin.h" +#include +#include +#include "rust-analyzer-transfer.h" +#include "rust-analyzer-service.h" + +struct _RustAnalyzerWorkbenchAddin +{ + IdeObject parent_instance; +}; + +static void provider_iface_init (IdeWorkbenchAddinInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (RustAnalyzerWorkbenchAddin, rust_analyzer_workbench_addin, IDE_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (IDE_TYPE_WORKBENCH_ADDIN, provider_iface_init)) + +static void +rust_analyzer_workbench_addin_class_init (RustAnalyzerWorkbenchAddinClass *klass) +{ +} + +static void +rust_analyzer_workbench_addin_init (RustAnalyzerWorkbenchAddin *self) +{ +} + +static void +rust_analyzer_workbench_addin_load (IdeWorkbenchAddin *addin, + IdeWorkbench *workbench) +{ +} + +static void +rust_analyzer_workbench_addin_unload (IdeWorkbenchAddin *addin, + IdeWorkbench *workbench) +{ +} + +static void +rust_analyzer_service_downloaded_lsp (GObject *object, + GAsyncResult *res, + gpointer user_data) +{ + RustAnalyzerService *service = NULL; + IdeContext *context = IDE_CONTEXT (user_data); + + g_return_if_fail (IDE_IS_CONTEXT (context)); + + service = ide_object_ensure_child_typed (IDE_OBJECT (context), RUST_TYPE_ANALYZER_SERVICE); + rust_analyzer_service_set_state (service, RUST_ANALYZER_SERVICE_READY); + rust_analyzer_service_ensure_started (service); +} + +static void +rust_analyzer_workbench_addin_remove_lsp (IdeTransfer *transfer, + gpointer user_data) +{ + g_autofree gchar *rust_analyzer_path = NULL; + g_autoptr(GFile) rust_analyzer_bin = NULL; + + rust_analyzer_path = g_build_filename (g_get_home_dir (), ".cargo", "bin", "rust-analyzer", NULL); + rust_analyzer_bin = g_file_new_for_path (rust_analyzer_path); + g_file_trash (rust_analyzer_bin, NULL, NULL); +} + +static void +rust_analyzer_workbench_addin_install_rust_analyzer (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + g_autoptr(RustAnalyzerTransfer) transfer = NULL; + IdeNotifications *notifications = NULL; + IdeNotification *notification = NULL; + IdeTransferManager *transfer_manager = NULL; + + IdeContext *context = IDE_CONTEXT (user_data); + + notifications = ide_object_get_child_typed (IDE_OBJECT (context), IDE_TYPE_NOTIFICATIONS); + notification = ide_notifications_find_by_id (notifications, "org.gnome-builder.rust-analyzer"); + + if (notification != NULL) + ide_notification_withdraw (notification); + + transfer_manager = ide_transfer_manager_get_default (); + transfer = rust_analyzer_transfer_new (); + g_signal_connect (transfer, "cancelled", G_CALLBACK (rust_analyzer_workbench_addin_remove_lsp), NULL); + + notification = ide_transfer_create_notification (IDE_TRANSFER (transfer)); + ide_notification_attach (notification, IDE_OBJECT (context)); + + ide_transfer_manager_execute_async (transfer_manager, + IDE_TRANSFER (transfer), + g_cancellable_new (), + rust_analyzer_service_downloaded_lsp, + context); +} + +static void +rust_analyzer_workbench_addin_workspace_added (IdeWorkbenchAddin *addin, + IdeWorkspace *workspace) +{ + GSimpleAction *install_rust_analyzer = NULL; + + install_rust_analyzer = g_simple_action_new ("install-rust-analyzer", NULL); + g_simple_action_set_enabled (install_rust_analyzer, TRUE); + g_signal_connect (install_rust_analyzer, + "activate", + G_CALLBACK (rust_analyzer_workbench_addin_install_rust_analyzer), + ide_workspace_get_context (workspace)); + g_action_map_add_action (G_ACTION_MAP (workspace), G_ACTION (install_rust_analyzer)); +} + +static void +provider_iface_init (IdeWorkbenchAddinInterface *iface) +{ + iface->load = rust_analyzer_workbench_addin_load; + iface->unload = rust_analyzer_workbench_addin_unload; + iface->workspace_added = rust_analyzer_workbench_addin_workspace_added; +} diff --git a/src/plugins/rust-analyzer/rust-analyzer-workbench-addin.h b/src/plugins/rust-analyzer/rust-analyzer-workbench-addin.h new file mode 100644 index 0000000000000000000000000000000000000000..f5eb0befd3a0875ae767e72b2597cea1f4fb25d0 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-workbench-addin.h @@ -0,0 +1,31 @@ +/* rust-analyzer-workbench-addin.h + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define RUST_TYPE_ANALYZER_WORKBENCH_ADDIN (rust_analyzer_workbench_addin_get_type()) + +G_DECLARE_FINAL_TYPE (RustAnalyzerWorkbenchAddin, rust_analyzer_workbench_addin, RUST, ANALYZER_WORKBENCH_ADDIN, IdeObject) + +G_END_DECLS diff --git a/src/plugins/rust-analyzer/rust-analyzer.c b/src/plugins/rust-analyzer/rust-analyzer.c new file mode 100644 index 0000000000000000000000000000000000000000..6fbbfc10b5089d5dcc9e12dfc44e453559b6d2b5 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer.c @@ -0,0 +1,48 @@ +/* rust-analyzer.c + * + * Copyright 2020 Günther Wagner + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include "rust-analyzer-completion-provider.h" +#include "rust-analyzer-symbol-resolver.h" +#include "rust-analyzer-diagnostic-provider.h" +#include "rust-analyzer-formatter.h" +#include "rust-analyzer-workbench-addin.h" + +_IDE_EXTERN void +_rust_analyzer_register_types (PeasObjectModule *module) +{ + peas_object_module_register_extension_type (module, + IDE_TYPE_WORKBENCH_ADDIN, + RUST_TYPE_ANALYZER_WORKBENCH_ADDIN); + peas_object_module_register_extension_type (module, + IDE_TYPE_COMPLETION_PROVIDER, + RUST_TYPE_ANALYZER_COMPLETION_PROVIDER); + peas_object_module_register_extension_type (module, + IDE_TYPE_SYMBOL_RESOLVER, + RUST_TYPE_ANALYZER_SYMBOL_RESOLVER); + peas_object_module_register_extension_type (module, + IDE_TYPE_DIAGNOSTIC_PROVIDER, + RUST_TYPE_ANALYZER_DIAGNOSTIC_PROVIDER); + peas_object_module_register_extension_type (module, + IDE_TYPE_FORMATTER, + RUST_TYPE_ANALYZER_FORMATTER); +} diff --git a/src/plugins/rust-analyzer/rust-analyzer.gresource.xml b/src/plugins/rust-analyzer/rust-analyzer.gresource.xml new file mode 100644 index 0000000000000000000000000000000000000000..79bbf331a3ed545f18791629f3beb496ac3f87fb --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer.gresource.xml @@ -0,0 +1,6 @@ + + + + rust-analyzer.plugin + + diff --git a/src/plugins/rust-analyzer/rust-analyzer.plugin b/src/plugins/rust-analyzer/rust-analyzer.plugin new file mode 100644 index 0000000000000000000000000000000000000000..5a1ee93265e2d5c011ccf42fe62a99d2d495c618 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer.plugin @@ -0,0 +1,15 @@ +[Plugin] +Authors=Günther Wagner +Builtin=true +Copyright=Copyright © 2020 Günther Wagner +Description=Provides auto-completion, diagnostics, and other IDE features +Module=rust-analyzer +Embedded=_rust_analyzer_register_types +Name=Rust Analyzer Language Server Integration +X-Completion-Provider-Languages=rust +X-Diagnostic-Provider-Languages=rust +X-Symbol-Resolver-Languages=rust +X-Formatter-Languages=rust +#X-Highlighter-Languages=rust +#X-Hover-Provider-Languages=rust +#X-Rename-Provider-Languages=rust diff --git a/src/plugins/vim/keybindings/vim.css b/src/plugins/vim/keybindings/vim.css index 134f6cf38619dbff34b0df82746f5f30fc28d460..2914d913f354c61893674a6f616c7bfcdfcbcc9e 100644 --- a/src/plugins/vim/keybindings/vim.css +++ b/src/plugins/vim/keybindings/vim.css @@ -1725,6 +1725,7 @@ /* cycle "tabs" */ bind "t" { "action" ("frame", "previous-page", "") }; bind "t" { "action" ("frame", "next-page", "") }; + bind "q" { "format-selection" () }; } @binding-set builder-vim-source-view-normal-g-u