From 7a5fe28a9705d48e157ae3fece8e2b83ff443c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Sat, 25 Apr 2020 20:10:45 +0200 Subject: [PATCH 01/10] rust-analyzer: added plugin for LSP client --- meson_options.txt | 3 +- src/plugins/meson.build | 2 + src/plugins/rust-analyzer/meson.build | 13 + .../rust-analyzer/rust-analyzer.plugin | 16 ++ .../rust-analyzer/rust_analyzer_plugin.py | 254 ++++++++++++++++++ 5 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 src/plugins/rust-analyzer/meson.build create mode 100644 src/plugins/rust-analyzer/rust-analyzer.plugin create mode 100644 src/plugins/rust-analyzer/rust_analyzer_plugin.py diff --git a/meson_options.txt b/meson_options.txt index 496fbb6f1..f187c821f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -64,7 +64,8 @@ option('plugin_python_pack', type: 'boolean') option('plugin_qemu', type: 'boolean') option('plugin_quick_highlight', type: 'boolean') option('plugin_retab', type: 'boolean') -option('plugin_rls', type: 'boolean') +option('plugin_rls', type: 'boolean', value: false) +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/plugins/meson.build b/src/plugins/meson.build index 5690b5ffa..bee20b6fd 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 000000000..3d7097d3c --- /dev/null +++ b/src/plugins/rust-analyzer/meson.build @@ -0,0 +1,13 @@ +if get_option('plugin_rust_analyzer') + +install_data('rust_analyzer_plugin.py', install_dir: plugindir) + +configure_file( + input: 'rust-analyzer.plugin', + output: 'rust-analyzer.plugin', + configuration: config_h, + install: true, + install_dir: plugindir, +) + +endif diff --git a/src/plugins/rust-analyzer/rust-analyzer.plugin b/src/plugins/rust-analyzer/rust-analyzer.plugin new file mode 100644 index 000000000..231d28819 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer.plugin @@ -0,0 +1,16 @@ +[Plugin] +Authors=Günther Wagner +Builtin=true +Copyright=Copyright © 2020 Günther Wagner +Description=Provides auto-completion, diagnostics, and other IDE features +Loader=python3 +Module=rust_analyzer_plugin +Name=Rust Analyzer Language Server Integration +X-Completion-Provider-Languages=rust +X-Diagnostic-Provider-Languages=rust +X-Formatter-Languages=rust +X-Highlighter-Languages=rust +X-Hover-Provider-Languages=rust +X-Rename-Provider-Languages=rust +X-Symbol-Resolver-Languages=rust +X-Builder-ABI=@PACKAGE_ABI@ diff --git a/src/plugins/rust-analyzer/rust_analyzer_plugin.py b/src/plugins/rust-analyzer/rust_analyzer_plugin.py new file mode 100644 index 000000000..93dff63e2 --- /dev/null +++ b/src/plugins/rust-analyzer/rust_analyzer_plugin.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python + +# rust_langserv_plugin.py +# +# Copyright 2016 Christian Hergert +# +# 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 . + +""" +This plugin provides integration with the Rust Language Server. +It builds off the generic language service components in libide +by bridging them to our supervised Rust Language Server. +""" + +import gi +import os + +from gi.repository import GLib +from gi.repository import Gio +from gi.repository import GObject +from gi.repository import Ide + +DEV_MODE = False + +class RlsService(Ide.Object): + _client = None + _has_started = False + _supervisor = None + _monitor = None + + @classmethod + def from_context(klass, context): + return context.ensure_child_typed(RlsService) + + @GObject.Property(type=Ide.LspClient) + def client(self): + return self._client + + @client.setter + def client(self, value): + self._client = value + self.notify('client') + + def do_parent_set(self, parent): + """ + After the context has been loaded, we want to watch the project + Cargo.toml for changes if we find one. That will allow us to + restart the process as necessary to pick up changes. + """ + if parent is None: + return + + context = self.get_context() + workdir = context.ref_workdir() + cargo_toml = workdir.get_child('Cargo.toml') + + if cargo_toml.query_exists(): + try: + self._monitor = cargo_toml.monitor(0, None) + self._monitor.set_rate_limit(5 * 1000) # 5 Seconds + self._monitor.connect('changed', self._monitor_changed_cb) + except Exception as ex: + Ide.debug('Failed to monitor Cargo.toml for changes:', repr(ex)) + + def _monitor_changed_cb(self, monitor, file, other_file, event_type): + """ + This method is called when Cargo.toml has changed. We need to + cancel any supervised process and force the language server to + restart. Otherwise, we risk it not picking up necessary changes. + """ + if self._supervisor is not None: + subprocess = self._supervisor.get_subprocess() + if subprocess is not None: + subprocess.force_exit() + + def do_stop(self): + """ + Stops the Rust Language Server upon request to shutdown the + RlsService. + """ + if self._monitor is not None: + monitor, self._monitor = self._monitor, None + if monitor is not None: + monitor.cancel() + + if self._supervisor is not None: + supervisor, self._supervisor = self._supervisor, None + supervisor.stop() + + def _ensure_started(self): + """ + Start the rust service which provides communication with the + Rust Analyzer Language Server. We supervise our own instance of the + language server and restart it as necessary using the + Ide.SubprocessSupervisor. + + Various extension points (diagnostics, symbol providers, etc) use + the RlsService to access the rust components they need. + """ + # To avoid starting the `rust-analyzer-linux` process unconditionally at startup, + # we lazily start it when the first provider tries to bind a client + # to its :client property. + if not self._has_started: + self._has_started = True + + # Setup a launcher to spawn the rust language server + launcher = self._create_launcher() + launcher.set_clear_env(False) + sysroot = self._discover_sysroot() + if sysroot: + launcher.setenv("SYS_ROOT", sysroot, True) + launcher.setenv("LD_LIBRARY_PATH", os.path.join(sysroot, "lib"), True) + if DEV_MODE: + launcher.setenv('RUST_LOG', 'debug', True) + + # Locate the directory of the project and run rls from there. + workdir = self.get_context().ref_workdir() + launcher.set_cwd(workdir.get_path()) + + # If rls was installed with Cargo, try to discover that + # to save the user having to update PATH. + path_to_rust_analyzer_bin = os.path.expanduser("~/.cargo/bin/rust-analyzer-linux") + if os.path.exists(path_to_rust_analyzer_bin): + old_path = os.getenv('PATH') + new_path = os.path.expanduser('~/.cargo/bin') + if old_path is not None: + new_path += os.path.pathsep + old_path + launcher.setenv('PATH', new_path, True) + else: + path_to_rls = "rust-analyzer-linux" + + # Setup our Argv. We want to communicate over STDIN/STDOUT, + # so it does not require any command line options. + launcher.push_argv(path_to_rls) + + # Spawn our peer process and monitor it for + # crashes. We may need to restart it occasionally. + self._supervisor = Ide.SubprocessSupervisor() + self._supervisor.connect('spawned', self._rls_spawned) + self._supervisor.set_launcher(launcher) + self._supervisor.start() + + def _rls_spawned(self, supervisor, subprocess): + """ + This callback is executed when the `rls` process is spawned. + We can use the stdin/stdout to create a channel for our + LspClient. + """ + stdin = subprocess.get_stdin_pipe() + stdout = subprocess.get_stdout_pipe() + io_stream = Gio.SimpleIOStream.new(stdout, stdin) + + if self._client: + self._client.stop() + self._client.destroy() + + self._client = Ide.LspClient.new(io_stream) + self.append(self._client) + self._client.add_language('rust') + self._client.start() + self.notify('client') + + def _create_launcher(self): + """ + Creates a launcher to be used by the rust service. This needs + to be run on the host because we do not currently bundle rust + inside our flatpak. + + In the future, we might be able to rely on the runtime for + the tooling. Maybe even the program if flatpak-builder has + prebuilt our dependencies. + """ + flags = Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE + if not DEV_MODE: + flags |= Gio.SubprocessFlags.STDERR_SILENCE + launcher = Ide.SubprocessLauncher() + launcher.set_flags(flags) + launcher.set_cwd(GLib.get_home_dir()) + launcher.set_run_on_host(True) + return launcher + + def _discover_sysroot(self): + """ + The Rust Language Server needs to know where the sysroot is of + the Rust installation we are using. This is simple enough to + get, by using `rust --print sysroot` as the rust-language-server + documentation suggests. + """ + for rustc in ['rustc', os.path.expanduser('~/.cargo/bin/rustc')]: + try: + launcher = self._create_launcher() + launcher.push_args([rustc, '--print', 'sysroot']) + subprocess = launcher.spawn() + _, stdout, _ = subprocess.communicate_utf8() + return stdout.strip() + except: + pass + + @classmethod + def bind_client(klass, provider): + """ + This helper tracks changes to our client as it might happen when + our `rls` process has crashed. + """ + context = provider.get_context() + self = RlsService.from_context(context) + self._ensure_started() + self.bind_property('client', provider, 'client', GObject.BindingFlags.SYNC_CREATE) + +class RlsDiagnosticProvider(Ide.LspDiagnosticProvider, Ide.DiagnosticProvider): + def do_load(self): + RlsService.bind_client(self) + +class RlsCompletionProvider(Ide.LspCompletionProvider, Ide.CompletionProvider): + def do_load(self, context): + RlsService.bind_client(self) + + def do_get_priority(self, context): + # This provider only activates when it is very likely that we + # want the results. So use high priority (negative is better). + return -1000 + +class RlsRenameProvider(Ide.LspRenameProvider, Ide.RenameProvider): + def do_load(self): + RlsService.bind_client(self) + +class RlsSymbolResolver(Ide.LspSymbolResolver, Ide.SymbolResolver): + def do_load(self): + RlsService.bind_client(self) + +class RlsHighlighter(Ide.LspHighlighter, Ide.Highlighter): + def do_load(self): + RlsService.bind_client(self) + +class RlsFormatter(Ide.LspFormatter, Ide.Formatter): + def do_load(self): + RlsService.bind_client(self) + +class RlsHoverProvider(Ide.LspHoverProvider, Ide.HoverProvider): + def do_prepare(self): + self.props.category = 'Rust' + self.props.priority = 200 + RlsService.bind_client(self) -- GitLab From 78125e020a350c886796f4c6d43333d3678a94be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Sun, 26 Apr 2020 11:52:39 +0200 Subject: [PATCH 02/10] LSP: add full text synchronization alternative for didChange signal --- meson_options.txt | 2 +- src/libide/lsp/ide-lsp-client.c | 97 ++++++++++++++----- .../rust-analyzer/rust_analyzer_plugin.py | 48 ++++----- 3 files changed, 97 insertions(+), 50 deletions(-) diff --git a/meson_options.txt b/meson_options.txt index f187c821f..0a103b57e 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -64,7 +64,7 @@ option('plugin_python_pack', type: 'boolean') option('plugin_qemu', type: 'boolean') option('plugin_quick_highlight', type: 'boolean') option('plugin_retab', type: 'boolean') -option('plugin_rls', type: 'boolean', value: false) +option('plugin_rls', type: 'boolean') option('plugin_rust_analyzer', type: 'boolean') option('plugin_rustup', type: 'boolean') option('plugin_shellcmd', type: 'boolean') diff --git a/src/libide/lsp/ide-lsp-client.c b/src/libide/lsp/ide-lsp-client.c index 927a8ea05..fb4e85605 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/plugins/rust-analyzer/rust_analyzer_plugin.py b/src/plugins/rust-analyzer/rust_analyzer_plugin.py index 93dff63e2..eeec191b1 100644 --- a/src/plugins/rust-analyzer/rust_analyzer_plugin.py +++ b/src/plugins/rust-analyzer/rust_analyzer_plugin.py @@ -33,7 +33,7 @@ from gi.repository import Ide DEV_MODE = False -class RlsService(Ide.Object): +class RustAnalyzerService(Ide.Object): _client = None _has_started = False _supervisor = None @@ -41,7 +41,7 @@ class RlsService(Ide.Object): @classmethod def from_context(klass, context): - return context.ensure_child_typed(RlsService) + return context.ensure_child_typed(RustAnalyzerService) @GObject.Property(type=Ide.LspClient) def client(self): @@ -86,8 +86,8 @@ class RlsService(Ide.Object): def do_stop(self): """ - Stops the Rust Language Server upon request to shutdown the - RlsService. + Stops the Rust Analyzer Language Server upon request to shutdown the + RustAnalyzerService. """ if self._monitor is not None: monitor, self._monitor = self._monitor, None @@ -106,9 +106,9 @@ class RlsService(Ide.Object): Ide.SubprocessSupervisor. Various extension points (diagnostics, symbol providers, etc) use - the RlsService to access the rust components they need. + the RustAnalyzerService to access the rust components they need. """ - # To avoid starting the `rust-analyzer-linux` process unconditionally at startup, + # To avoid starting the `rust-analyzer` process unconditionally at startup, # we lazily start it when the first provider tries to bind a client # to its :client property. if not self._has_started: @@ -130,7 +130,7 @@ class RlsService(Ide.Object): # If rls was installed with Cargo, try to discover that # to save the user having to update PATH. - path_to_rust_analyzer_bin = os.path.expanduser("~/.cargo/bin/rust-analyzer-linux") + path_to_rust_analyzer_bin = os.path.expanduser("~/.cargo/bin/rust-analyzer") if os.path.exists(path_to_rust_analyzer_bin): old_path = os.getenv('PATH') new_path = os.path.expanduser('~/.cargo/bin') @@ -138,11 +138,11 @@ class RlsService(Ide.Object): new_path += os.path.pathsep + old_path launcher.setenv('PATH', new_path, True) else: - path_to_rls = "rust-analyzer-linux" + path_to_rust_analyzer_bin = "rust-analyzer" # Setup our Argv. We want to communicate over STDIN/STDOUT, # so it does not require any command line options. - launcher.push_argv(path_to_rls) + launcher.push_argv(path_to_rust_analyzer_bin) # Spawn our peer process and monitor it for # crashes. We may need to restart it occasionally. @@ -214,41 +214,41 @@ class RlsService(Ide.Object): our `rls` process has crashed. """ context = provider.get_context() - self = RlsService.from_context(context) + self = RustAnalyzerService.from_context(context) self._ensure_started() self.bind_property('client', provider, 'client', GObject.BindingFlags.SYNC_CREATE) -class RlsDiagnosticProvider(Ide.LspDiagnosticProvider, Ide.DiagnosticProvider): +class RustAnalyzerDiagnosticProvider(Ide.LspDiagnosticProvider, Ide.DiagnosticProvider): def do_load(self): - RlsService.bind_client(self) + RustAnalyzerService.bind_client(self) -class RlsCompletionProvider(Ide.LspCompletionProvider, Ide.CompletionProvider): +class RustAnalyzerCompletionProvider(Ide.LspCompletionProvider, Ide.CompletionProvider): def do_load(self, context): - RlsService.bind_client(self) + RustAnalyzerService.bind_client(self) def do_get_priority(self, context): # This provider only activates when it is very likely that we # want the results. So use high priority (negative is better). return -1000 -class RlsRenameProvider(Ide.LspRenameProvider, Ide.RenameProvider): +class RustAnalyzerRenameProvider(Ide.LspRenameProvider, Ide.RenameProvider): def do_load(self): - RlsService.bind_client(self) + RustAnalyzerService.bind_client(self) -class RlsSymbolResolver(Ide.LspSymbolResolver, Ide.SymbolResolver): +class RustAnalyzerSymbolResolver(Ide.LspSymbolResolver, Ide.SymbolResolver): def do_load(self): - RlsService.bind_client(self) + RustAnalyzerService.bind_client(self) -class RlsHighlighter(Ide.LspHighlighter, Ide.Highlighter): +class RustAnalyzerHighlighter(Ide.LspHighlighter, Ide.Highlighter): def do_load(self): - RlsService.bind_client(self) + RustAnalyzerService.bind_client(self) -class RlsFormatter(Ide.LspFormatter, Ide.Formatter): +class RustAnalyzerFormatter(Ide.LspFormatter, Ide.Formatter): def do_load(self): - RlsService.bind_client(self) + RustAnalyzerService.bind_client(self) -class RlsHoverProvider(Ide.LspHoverProvider, Ide.HoverProvider): +class RustAnalyzerHoverProvider(Ide.LspHoverProvider, Ide.HoverProvider): def do_prepare(self): self.props.category = 'Rust' self.props.priority = 200 - RlsService.bind_client(self) + RustAnalyzerService.bind_client(self) -- GitLab From 001741fd79e4b6708c1d16e1c24b1f3bacebbf2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Sun, 3 May 2020 18:27:27 +0200 Subject: [PATCH 03/10] Rewritten Rust Analyzer plugin in C --- src/plugins/rust-analyzer/meson.build | 24 +- .../rust-analyzer-completion-provider.c | 67 ++++ .../rust-analyzer-completion-provider.h | 32 ++ .../rust-analyzer-diagnostic-provider.c | 59 ++++ .../rust-analyzer-diagnostic-provider.h | 31 ++ .../rust-analyzer/rust-analyzer-service.c | 295 ++++++++++++++++++ .../rust-analyzer/rust-analyzer-service.h | 27 ++ .../rust-analyzer-symbol-resolver.c | 59 ++++ .../rust-analyzer-symbol-resolver.h | 31 ++ .../rust-analyzer/rust-analyzer-transfer.c | 155 +++++++++ .../rust-analyzer/rust-analyzer-transfer.h | 33 ++ .../rust-analyzer-workbench-addin.c | 139 +++++++++ .../rust-analyzer-workbench-addin.h | 31 ++ src/plugins/rust-analyzer/rust-analyzer.c | 44 +++ .../rust-analyzer/rust-analyzer.gresource.xml | 6 + .../rust-analyzer/rust-analyzer.plugin | 15 +- .../rust-analyzer/rust_analyzer_plugin.py | 254 --------------- 17 files changed, 1032 insertions(+), 270 deletions(-) create mode 100644 src/plugins/rust-analyzer/rust-analyzer-completion-provider.c create mode 100644 src/plugins/rust-analyzer/rust-analyzer-completion-provider.h create mode 100644 src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.c create mode 100644 src/plugins/rust-analyzer/rust-analyzer-diagnostic-provider.h create mode 100644 src/plugins/rust-analyzer/rust-analyzer-service.c create mode 100644 src/plugins/rust-analyzer/rust-analyzer-service.h create mode 100644 src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.c create mode 100644 src/plugins/rust-analyzer/rust-analyzer-symbol-resolver.h create mode 100644 src/plugins/rust-analyzer/rust-analyzer-transfer.c create mode 100644 src/plugins/rust-analyzer/rust-analyzer-transfer.h create mode 100644 src/plugins/rust-analyzer/rust-analyzer-workbench-addin.c create mode 100644 src/plugins/rust-analyzer/rust-analyzer-workbench-addin.h create mode 100644 src/plugins/rust-analyzer/rust-analyzer.c create mode 100644 src/plugins/rust-analyzer/rust-analyzer.gresource.xml delete mode 100644 src/plugins/rust-analyzer/rust_analyzer_plugin.py diff --git a/src/plugins/rust-analyzer/meson.build b/src/plugins/rust-analyzer/meson.build index 3d7097d3c..7f7ce0f7e 100644 --- a/src/plugins/rust-analyzer/meson.build +++ b/src/plugins/rust-analyzer/meson.build @@ -1,13 +1,21 @@ if get_option('plugin_rust_analyzer') -install_data('rust_analyzer_plugin.py', install_dir: plugindir) - -configure_file( - input: 'rust-analyzer.plugin', - output: 'rust-analyzer.plugin', - configuration: config_h, - install: true, - install_dir: plugindir, +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-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 000000000..6b88d081c --- /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 000000000..f06faaff5 --- /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 000000000..dd5e16c78 --- /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 000000000..5da85ef38 --- /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-service.c b/src/plugins/rust-analyzer/rust-analyzer-service.c new file mode 100644 index 000000000..d9aa76715 --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-service.c @@ -0,0 +1,295 @@ +/* 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 + +struct _RustAnalyzerService +{ + IdeObject parent_instance; + IdeLspClient *client; + IdeSubprocessSupervisor *supervisor; + + 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 +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_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->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]); + } +} + +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); + 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 000000000..6c3ffae66 --- /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 000000000..980138b89 --- /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 000000000..12d8bd507 --- /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 000000000..46b7bc7cc --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer-transfer.c @@ -0,0 +1,155 @@ +/* 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; + GInputStream *stream = G_INPUT_STREAM (source_object); + DownloadData *data = user_data; + + gsize count = g_input_stream_read_finish (stream, result, NULL); + if (count == -1 || 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), NULL); + 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; + SoupRequest *request = SOUP_REQUEST (source_object); + GInputStream *stream = NULL; + + stream = soup_request_send_finish (request, result, NULL); + + DownloadData *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, NULL, NULL)); // handle error + 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_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", NULL); + + 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 000000000..7d45fccc2 --- /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 000000000..375f38aad --- /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 000000000..f5eb0befd --- /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 000000000..c9b38d35f --- /dev/null +++ b/src/plugins/rust-analyzer/rust-analyzer.c @@ -0,0 +1,44 @@ +/* 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-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); +} 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 000000000..79bbf331a --- /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 index 231d28819..2d56ac03d 100644 --- a/src/plugins/rust-analyzer/rust-analyzer.plugin +++ b/src/plugins/rust-analyzer/rust-analyzer.plugin @@ -3,14 +3,13 @@ Authors=Günther Wagner Builtin=true Copyright=Copyright © 2020 Günther Wagner Description=Provides auto-completion, diagnostics, and other IDE features -Loader=python3 -Module=rust_analyzer_plugin -Name=Rust Analyzer Language Server Integration +Module=rust-analyzer +Embedded=_rust_analyzer_register_types +Name=Rust Analyzer Language Server Integration2 X-Completion-Provider-Languages=rust X-Diagnostic-Provider-Languages=rust -X-Formatter-Languages=rust -X-Highlighter-Languages=rust -X-Hover-Provider-Languages=rust -X-Rename-Provider-Languages=rust X-Symbol-Resolver-Languages=rust -X-Builder-ABI=@PACKAGE_ABI@ +#X-Formatter-Languages=rust +#X-Highlighter-Languages=rust +#X-Hover-Provider-Languages=rust +#X-Rename-Provider-Languages=rust diff --git a/src/plugins/rust-analyzer/rust_analyzer_plugin.py b/src/plugins/rust-analyzer/rust_analyzer_plugin.py deleted file mode 100644 index eeec191b1..000000000 --- a/src/plugins/rust-analyzer/rust_analyzer_plugin.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/usr/bin/env python - -# rust_langserv_plugin.py -# -# Copyright 2016 Christian Hergert -# -# 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 . - -""" -This plugin provides integration with the Rust Language Server. -It builds off the generic language service components in libide -by bridging them to our supervised Rust Language Server. -""" - -import gi -import os - -from gi.repository import GLib -from gi.repository import Gio -from gi.repository import GObject -from gi.repository import Ide - -DEV_MODE = False - -class RustAnalyzerService(Ide.Object): - _client = None - _has_started = False - _supervisor = None - _monitor = None - - @classmethod - def from_context(klass, context): - return context.ensure_child_typed(RustAnalyzerService) - - @GObject.Property(type=Ide.LspClient) - def client(self): - return self._client - - @client.setter - def client(self, value): - self._client = value - self.notify('client') - - def do_parent_set(self, parent): - """ - After the context has been loaded, we want to watch the project - Cargo.toml for changes if we find one. That will allow us to - restart the process as necessary to pick up changes. - """ - if parent is None: - return - - context = self.get_context() - workdir = context.ref_workdir() - cargo_toml = workdir.get_child('Cargo.toml') - - if cargo_toml.query_exists(): - try: - self._monitor = cargo_toml.monitor(0, None) - self._monitor.set_rate_limit(5 * 1000) # 5 Seconds - self._monitor.connect('changed', self._monitor_changed_cb) - except Exception as ex: - Ide.debug('Failed to monitor Cargo.toml for changes:', repr(ex)) - - def _monitor_changed_cb(self, monitor, file, other_file, event_type): - """ - This method is called when Cargo.toml has changed. We need to - cancel any supervised process and force the language server to - restart. Otherwise, we risk it not picking up necessary changes. - """ - if self._supervisor is not None: - subprocess = self._supervisor.get_subprocess() - if subprocess is not None: - subprocess.force_exit() - - def do_stop(self): - """ - Stops the Rust Analyzer Language Server upon request to shutdown the - RustAnalyzerService. - """ - if self._monitor is not None: - monitor, self._monitor = self._monitor, None - if monitor is not None: - monitor.cancel() - - if self._supervisor is not None: - supervisor, self._supervisor = self._supervisor, None - supervisor.stop() - - def _ensure_started(self): - """ - Start the rust service which provides communication with the - Rust Analyzer Language Server. We supervise our own instance of the - language server and restart it as necessary using the - Ide.SubprocessSupervisor. - - Various extension points (diagnostics, symbol providers, etc) use - the RustAnalyzerService to access the rust components they need. - """ - # To avoid starting the `rust-analyzer` process unconditionally at startup, - # we lazily start it when the first provider tries to bind a client - # to its :client property. - if not self._has_started: - self._has_started = True - - # Setup a launcher to spawn the rust language server - launcher = self._create_launcher() - launcher.set_clear_env(False) - sysroot = self._discover_sysroot() - if sysroot: - launcher.setenv("SYS_ROOT", sysroot, True) - launcher.setenv("LD_LIBRARY_PATH", os.path.join(sysroot, "lib"), True) - if DEV_MODE: - launcher.setenv('RUST_LOG', 'debug', True) - - # Locate the directory of the project and run rls from there. - workdir = self.get_context().ref_workdir() - launcher.set_cwd(workdir.get_path()) - - # If rls was installed with Cargo, try to discover that - # to save the user having to update PATH. - path_to_rust_analyzer_bin = os.path.expanduser("~/.cargo/bin/rust-analyzer") - if os.path.exists(path_to_rust_analyzer_bin): - old_path = os.getenv('PATH') - new_path = os.path.expanduser('~/.cargo/bin') - if old_path is not None: - new_path += os.path.pathsep + old_path - launcher.setenv('PATH', new_path, True) - else: - path_to_rust_analyzer_bin = "rust-analyzer" - - # Setup our Argv. We want to communicate over STDIN/STDOUT, - # so it does not require any command line options. - launcher.push_argv(path_to_rust_analyzer_bin) - - # Spawn our peer process and monitor it for - # crashes. We may need to restart it occasionally. - self._supervisor = Ide.SubprocessSupervisor() - self._supervisor.connect('spawned', self._rls_spawned) - self._supervisor.set_launcher(launcher) - self._supervisor.start() - - def _rls_spawned(self, supervisor, subprocess): - """ - This callback is executed when the `rls` process is spawned. - We can use the stdin/stdout to create a channel for our - LspClient. - """ - stdin = subprocess.get_stdin_pipe() - stdout = subprocess.get_stdout_pipe() - io_stream = Gio.SimpleIOStream.new(stdout, stdin) - - if self._client: - self._client.stop() - self._client.destroy() - - self._client = Ide.LspClient.new(io_stream) - self.append(self._client) - self._client.add_language('rust') - self._client.start() - self.notify('client') - - def _create_launcher(self): - """ - Creates a launcher to be used by the rust service. This needs - to be run on the host because we do not currently bundle rust - inside our flatpak. - - In the future, we might be able to rely on the runtime for - the tooling. Maybe even the program if flatpak-builder has - prebuilt our dependencies. - """ - flags = Gio.SubprocessFlags.STDIN_PIPE | Gio.SubprocessFlags.STDOUT_PIPE - if not DEV_MODE: - flags |= Gio.SubprocessFlags.STDERR_SILENCE - launcher = Ide.SubprocessLauncher() - launcher.set_flags(flags) - launcher.set_cwd(GLib.get_home_dir()) - launcher.set_run_on_host(True) - return launcher - - def _discover_sysroot(self): - """ - The Rust Language Server needs to know where the sysroot is of - the Rust installation we are using. This is simple enough to - get, by using `rust --print sysroot` as the rust-language-server - documentation suggests. - """ - for rustc in ['rustc', os.path.expanduser('~/.cargo/bin/rustc')]: - try: - launcher = self._create_launcher() - launcher.push_args([rustc, '--print', 'sysroot']) - subprocess = launcher.spawn() - _, stdout, _ = subprocess.communicate_utf8() - return stdout.strip() - except: - pass - - @classmethod - def bind_client(klass, provider): - """ - This helper tracks changes to our client as it might happen when - our `rls` process has crashed. - """ - context = provider.get_context() - self = RustAnalyzerService.from_context(context) - self._ensure_started() - self.bind_property('client', provider, 'client', GObject.BindingFlags.SYNC_CREATE) - -class RustAnalyzerDiagnosticProvider(Ide.LspDiagnosticProvider, Ide.DiagnosticProvider): - def do_load(self): - RustAnalyzerService.bind_client(self) - -class RustAnalyzerCompletionProvider(Ide.LspCompletionProvider, Ide.CompletionProvider): - def do_load(self, context): - RustAnalyzerService.bind_client(self) - - def do_get_priority(self, context): - # This provider only activates when it is very likely that we - # want the results. So use high priority (negative is better). - return -1000 - -class RustAnalyzerRenameProvider(Ide.LspRenameProvider, Ide.RenameProvider): - def do_load(self): - RustAnalyzerService.bind_client(self) - -class RustAnalyzerSymbolResolver(Ide.LspSymbolResolver, Ide.SymbolResolver): - def do_load(self): - RustAnalyzerService.bind_client(self) - -class RustAnalyzerHighlighter(Ide.LspHighlighter, Ide.Highlighter): - def do_load(self): - RustAnalyzerService.bind_client(self) - -class RustAnalyzerFormatter(Ide.LspFormatter, Ide.Formatter): - def do_load(self): - RustAnalyzerService.bind_client(self) - -class RustAnalyzerHoverProvider(Ide.LspHoverProvider, Ide.HoverProvider): - def do_prepare(self): - self.props.category = 'Rust' - self.props.priority = 200 - RustAnalyzerService.bind_client(self) -- GitLab From 999769ca71dc0d6822b2c013d6a53b5b5285665e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Mon, 4 May 2020 10:44:28 +0200 Subject: [PATCH 04/10] Show progress information from LSP --- .../rust-analyzer/rust-analyzer-service.c | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/plugins/rust-analyzer/rust-analyzer-service.c b/src/plugins/rust-analyzer/rust-analyzer-service.c index d9aa76715..efe69bffd 100644 --- a/src/plugins/rust-analyzer/rust-analyzer-service.c +++ b/src/plugins/rust-analyzer/rust-analyzer-service.c @@ -26,6 +26,7 @@ #include #include #include +#include struct _RustAnalyzerService { @@ -163,6 +164,54 @@ rust_analyzer_service_set_client (RustAnalyzerService *self, } } +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, @@ -186,6 +235,7 @@ rust_analyzer_service_lsp_started (IdeSubprocessSupervisor *supervisor, } 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"); -- GitLab From 52e25a7f914208eca77ae98ce16b782d9794ae7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Mon, 4 May 2020 17:54:35 +0200 Subject: [PATCH 05/10] nitpick: declaration-after-statement --- src/plugins/rust-analyzer/rust-analyzer-transfer.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/rust-analyzer/rust-analyzer-transfer.c b/src/plugins/rust-analyzer/rust-analyzer-transfer.c index 46b7bc7cc..f5776e579 100644 --- a/src/plugins/rust-analyzer/rust-analyzer-transfer.c +++ b/src/plugins/rust-analyzer/rust-analyzer-transfer.c @@ -89,10 +89,11 @@ _download_lsp (GObject *source_object, g_autoptr(GFile) file = NULL; SoupRequest *request = SOUP_REQUEST (source_object); GInputStream *stream = NULL; + DownloadData *data; stream = soup_request_send_finish (request, result, NULL); - DownloadData *data = g_slice_new0 (DownloadData); + 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)); -- GitLab From 16057215302360a3dafcca583775d14b9902d174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Wed, 6 May 2020 22:05:09 +0200 Subject: [PATCH 06/10] proper error handling for transfer of analyzer --- .../rust-analyzer/rust-analyzer-transfer.c | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/plugins/rust-analyzer/rust-analyzer-transfer.c b/src/plugins/rust-analyzer/rust-analyzer-transfer.c index f5776e579..4ccae579e 100644 --- a/src/plugins/rust-analyzer/rust-analyzer-transfer.c +++ b/src/plugins/rust-analyzer/rust-analyzer-transfer.c @@ -55,11 +55,18 @@ _downloaded_chunk (GObject *source_object, 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, NULL); - if (count == -1 || count == 0) + 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); @@ -76,7 +83,12 @@ _downloaded_chunk (GObject *source_object, 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), NULL); + 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); } @@ -87,6 +99,7 @@ _download_lsp (GObject *source_object, { 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; @@ -97,7 +110,12 @@ _download_lsp (GObject *source_object, 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, NULL, NULL)); // handle error + 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); @@ -114,6 +132,7 @@ rust_analyzer_transfer_execute_async (IdeTransfer *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)); @@ -123,9 +142,11 @@ rust_analyzer_transfer_execute_async (IdeTransfer *transfer, session = soup_session_new (); - request = soup_session_request (session, "https://github.com/rust-analyzer/rust-analyzer/releases/download/nightly/rust-analyzer-linux", NULL); - - soup_request_send_async (request, NULL, _download_lsp, g_steal_pointer (&task)); + 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 -- GitLab From 999673bbab1e205ced494767e0c21e72f3794aaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Sat, 9 May 2020 08:00:15 +0000 Subject: [PATCH 07/10] Update rust-analyzer.plugin --- src/plugins/rust-analyzer/rust-analyzer.plugin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/rust-analyzer/rust-analyzer.plugin b/src/plugins/rust-analyzer/rust-analyzer.plugin index 2d56ac03d..9ec32a765 100644 --- a/src/plugins/rust-analyzer/rust-analyzer.plugin +++ b/src/plugins/rust-analyzer/rust-analyzer.plugin @@ -5,7 +5,7 @@ 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 Integration2 +Name=Rust Analyzer Language Server Integration X-Completion-Provider-Languages=rust X-Diagnostic-Provider-Languages=rust X-Symbol-Resolver-Languages=rust -- GitLab From 1b0c8b65f77fddf7598045b927eed394910cb156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Sat, 9 May 2020 10:37:22 +0200 Subject: [PATCH 08/10] Use editText for completion if available --- src/libide/lsp/ide-lsp-completion-item.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/libide/lsp/ide-lsp-completion-item.c b/src/libide/lsp/ide-lsp-completion-item.c index 1af194671..6d69acb01 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; -- GitLab From 3a8c577c2964407da1bff9ed5c24ad8e77ee889a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Sun, 10 May 2020 17:37:13 +0200 Subject: [PATCH 09/10] monitor Cargo.toml file and restart in case of a change --- doc/help/plugins/langserv.rst | 74 +++++++++++++++++++ .../rust-analyzer/rust-analyzer-service.c | 55 ++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/doc/help/plugins/langserv.rst b/doc/help/plugins/langserv.rst index ed99dbe5d..63692234f 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/src/plugins/rust-analyzer/rust-analyzer-service.c b/src/plugins/rust-analyzer/rust-analyzer-service.c index efe69bffd..96c286305 100644 --- a/src/plugins/rust-analyzer/rust-analyzer-service.c +++ b/src/plugins/rust-analyzer/rust-analyzer-service.c @@ -33,6 +33,7 @@ struct _RustAnalyzerService IdeObject parent_instance; IdeLspClient *client; IdeSubprocessSupervisor *supervisor; + GFileMonitor *cargo_monitor; ServiceState state; }; @@ -53,6 +54,23 @@ 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) { @@ -99,6 +117,42 @@ rust_analyzer_service_set_property (GObject *object, } } +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) { @@ -124,6 +178,7 @@ rust_analyzer_service_class_init (RustAnalyzerServiceClass *klass) 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] = -- GitLab From 324afd1e40532f99396399e5dc3c4f7786640b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCnther=20Wagner?= Date: Sun, 10 May 2020 19:42:11 +0200 Subject: [PATCH 10/10] added formatter and possibility to trigger a full format from vim --- src/libide/lsp/ide-lsp-formatter.c | 2 +- src/plugins/rust-analyzer/meson.build | 1 + .../rust-analyzer/rust-analyzer-formatter.c | 59 +++++++++++++++++++ .../rust-analyzer/rust-analyzer-formatter.h | 31 ++++++++++ src/plugins/rust-analyzer/rust-analyzer.c | 4 ++ .../rust-analyzer/rust-analyzer.plugin | 2 +- src/plugins/vim/keybindings/vim.css | 1 + 7 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 src/plugins/rust-analyzer/rust-analyzer-formatter.c create mode 100644 src/plugins/rust-analyzer/rust-analyzer-formatter.h diff --git a/src/libide/lsp/ide-lsp-formatter.c b/src/libide/lsp/ide-lsp-formatter.c index 0a5c4c244..2f006c2dd 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/rust-analyzer/meson.build b/src/plugins/rust-analyzer/meson.build index 7f7ce0f7e..492ed9844 100644 --- a/src/plugins/rust-analyzer/meson.build +++ b/src/plugins/rust-analyzer/meson.build @@ -6,6 +6,7 @@ plugins_sources += files([ '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', ]) 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 000000000..8ddee6981 --- /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 000000000..cd1640631 --- /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.c b/src/plugins/rust-analyzer/rust-analyzer.c index c9b38d35f..6fbbfc10b 100644 --- a/src/plugins/rust-analyzer/rust-analyzer.c +++ b/src/plugins/rust-analyzer/rust-analyzer.c @@ -24,6 +24,7 @@ #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 @@ -41,4 +42,7 @@ _rust_analyzer_register_types (PeasObjectModule *module) 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.plugin b/src/plugins/rust-analyzer/rust-analyzer.plugin index 9ec32a765..5a1ee9326 100644 --- a/src/plugins/rust-analyzer/rust-analyzer.plugin +++ b/src/plugins/rust-analyzer/rust-analyzer.plugin @@ -9,7 +9,7 @@ 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-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 134f6cf38..2914d913f 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 -- GitLab