From 2514485ca494e4997935e7ca811a5134f60555db Mon Sep 17 00:00:00 2001 From: Sonny Piers Date: Tue, 13 Feb 2024 00:44:55 +0100 Subject: [PATCH] Add Rust analyzer support (#868) Fixes #520 Only supports syntax diagnostics for now. I made a follow up for types https://github.com/workbenchdev/Workbench/issues/881 --- .github/CODEOWNERS | 2 +- src/cli/main.js | 51 +++++++++++++++++++++- src/common.js | 9 +++- src/langs/blueprint/BlueprintDocument.js | 4 +- src/langs/javascript/JavaScriptDocument.js | 4 +- src/langs/rust/Compiler.js | 5 +-- src/langs/rust/RustDocument.js | 48 +++++++++----------- src/langs/rust/rust.js | 37 ++++++++++++++++ src/sessions.js | 2 + src/workbench | 1 + 10 files changed, 124 insertions(+), 39 deletions(-) create mode 100644 src/langs/rust/rust.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a36075cab..c97cf781a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,7 @@ /src/langs/vala/ @lw64 *.vala @lw64 -/src/langs/rust/ @lw64 +/src/langs/rust/ @Hofer-Julian *.rs @Hofer-Julian Cargo.toml @Hofer-Julian Cargo.lock @Hofer-Julian diff --git a/src/cli/main.js b/src/cli/main.js index a44bdd395..565ea02ab 100644 --- a/src/cli/main.js +++ b/src/cli/main.js @@ -78,7 +78,7 @@ const window = new Adw.ApplicationWindow(); function createLSPClients({ root_uri }) { return Object.fromEntries( - ["javascript", "blueprint", "css", "vala"].map((id) => { + ["javascript", "blueprint", "css", "vala", "rust"].map((id) => { const lang = languages.find((language) => language.id === id); const lspc = createLSPClient({ lang, @@ -397,6 +397,55 @@ async function ci({ filenames, current_dir }) { }); } + const file_rust = demo_dir.get_child("code.rs"); + if (file_rust.query_exists(null)) { + print(` ${file_rust.get_path()}`); + + const uri = file_rust.get_uri(); + const languageId = "rust"; + let version = 0; + + const [contents] = await file_rust.load_contents_async(null); + const text = new TextDecoder().decode(contents); + + await lsp_clients.rust._notify("textDocument/didOpen", { + textDocument: { + uri, + languageId, + version: version++, + text, + }, + }); + + // FIXME: rust analyzer doesn't publish diagnostics if there are none + // probably we should switch to pulling diagnostics but unknown if supported + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_pullDiagnostics + + // const diagnostics = await waitForDiagnostics({ + // uri, + // lspc: lsp_clients.rust, + // }); + // if (diagnostics.length > 0) { + // printerr(serializeDiagnostics({ diagnostics })); + // return false; + // } + // print(` ✅ lints`); + + const checks = await checkFile({ + lspc: lsp_clients.rust, + file: file_rust, + lang: getLanguage("rust"), + uri, + }); + if (!checks) return false; + + await lsp_clients.rust._notify("textDocument/didClose", { + textDocument: { + uri, + }, + }); + } + await Promise.all( Object.entries(lsp_clients).map(([, lspc]) => { return lspc.stop(); diff --git a/src/common.js b/src/common.js index 8d2d84877..2430b7d1b 100644 --- a/src/common.js +++ b/src/common.js @@ -94,6 +94,11 @@ export const languages = [ document: null, default_file: "code.rs", index: 2, + language_server: ["rust-analyzer"], + formatting_options: { + ...formatting_options, + tabSize: 4, + }, }, { id: "python", @@ -113,13 +118,13 @@ export function getLanguage(id) { ); } -export function createLSPClient({ lang, root_uri }) { +export function createLSPClient({ lang, root_uri, quiet = true }) { const language_id = lang.id; const lspc = new LSPClient(lang.language_server, { rootUri: root_uri, languageId: language_id, - // quiet: false, + quiet, }); lspc.connect("exit", () => { console.debug(`${language_id} language server exit`); diff --git a/src/langs/blueprint/BlueprintDocument.js b/src/langs/blueprint/BlueprintDocument.js index 3675ccc01..f33d96890 100644 --- a/src/langs/blueprint/BlueprintDocument.js +++ b/src/langs/blueprint/BlueprintDocument.js @@ -1,13 +1,13 @@ import Document from "../../Document.js"; import { applyTextEdits } from "../../lsp/sourceview.js"; -import { setup as setupBlueprint } from "./blueprint.js"; +import { setup } from "./blueprint.js"; export class BlueprintDocument extends Document { constructor(...args) { super(...args); - this.lspc = setupBlueprint({ document: this }); + this.lspc = setup({ document: this }); } async update() { return this.lspc.didChange(); diff --git a/src/langs/javascript/JavaScriptDocument.js b/src/langs/javascript/JavaScriptDocument.js index 16837cb77..15b621c69 100644 --- a/src/langs/javascript/JavaScriptDocument.js +++ b/src/langs/javascript/JavaScriptDocument.js @@ -1,4 +1,4 @@ -import { setup as setupJavaScript } from "./javascript.js"; +import { setup } from "./javascript.js"; import Document from "../../Document.js"; import { applyTextEdits } from "../../lsp/sourceview.js"; @@ -7,7 +7,7 @@ export class JavaScriptDocument extends Document { constructor(...args) { super(...args); - this.lspc = setupJavaScript({ document: this }); + this.lspc = setup({ document: this }); } async format() { diff --git a/src/langs/rust/Compiler.js b/src/langs/rust/Compiler.js index b31796a0b..4e7c129b1 100644 --- a/src/langs/rust/Compiler.js +++ b/src/langs/rust/Compiler.js @@ -2,10 +2,7 @@ import Gio from "gi://Gio"; import GLib from "gi://GLib"; import dbus_previewer from "../../Previewer/DBusPreviewer.js"; import { copyDirectory, decode, encode } from "../../util.js"; - -const rust_template_dir = Gio.File.new_for_path( - pkg.pkgdatadir, -).resolve_relative_path("langs/rust/template"); +import { rust_template_dir } from "./rust.js"; export default function Compiler({ session }) { const { file } = session; diff --git a/src/langs/rust/RustDocument.js b/src/langs/rust/RustDocument.js index 60e2266a9..75eed2148 100644 --- a/src/langs/rust/RustDocument.js +++ b/src/langs/rust/RustDocument.js @@ -1,36 +1,30 @@ -import Gio from "gi://Gio"; +import { setup } from "./rust.js"; import Document from "../../Document.js"; +import { applyTextEdits } from "../../lsp/sourceview.js"; export class RustDocument extends Document { - async format() { - const code = await formatRustCode(this.buffer.text); - this.code_view.replaceText(code, true); - } -} - -function formatRustCode(text) { - const rustfmtLauncher = Gio.SubprocessLauncher.new( - Gio.SubprocessFlags.STDIN_PIPE | - Gio.SubprocessFlags.STDOUT_PIPE | - Gio.SubprocessFlags.STDERR_PIPE, - ); + constructor(...args) { + super(...args); - const rustfmtProcess = rustfmtLauncher.spawnv([ - "rustfmt", - "--quiet", - "--emit", - "stdout", - "--edition", - "2021", - ]); + this.lspc = setup({ document: this }); + } - const [success, stdout, stderr] = rustfmtProcess.communicate_utf8(text, null); + async format() { + // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting + const text_edits = await this.lspc.request("textDocument/formatting", { + textDocument: { + uri: this.file.get_uri(), + }, + options: { + tabSize: 4, + insertSpaces: true, + trimTrailingWhitespace: true, + insertFinalNewline: true, + trimFinalNewlines: true, + }, + }); - if (!success || stderr !== "") { - console.error(`Error running rustfmt: ${stderr}`); - return text; + applyTextEdits(text_edits, this.buffer); } - - return stdout; } diff --git a/src/langs/rust/rust.js b/src/langs/rust/rust.js new file mode 100644 index 000000000..20746eca6 --- /dev/null +++ b/src/langs/rust/rust.js @@ -0,0 +1,37 @@ +import Gio from "gi://Gio"; + +import { createLSPClient } from "../../common.js"; +import { getLanguage } from "../../util.js"; + +export function setup({ document }) { + const { file, buffer, code_view } = document; + + const lspc = createLSPClient({ + lang: getLanguage("rust"), + root_uri: file.get_parent().get_uri(), + }); + lspc.buffer = buffer; + lspc.uri = file.get_uri(); + lspc.connect( + "notification::textDocument/publishDiagnostics", + (_self, params) => { + if (params.uri !== file.get_uri()) { + return; + } + code_view.handleDiagnostics(params.diagnostics); + }, + ); + + lspc.start().catch(console.error); + + buffer.connect("modified-changed", () => { + if (!buffer.get_modified()) return; + lspc.didChange().catch(console.error); + }); + + return lspc; +} + +export const rust_template_dir = Gio.File.new_for_path( + pkg.pkgdatadir, +).resolve_relative_path("langs/rust/template"); diff --git a/src/sessions.js b/src/sessions.js index edd83b7c3..e6576ca45 100644 --- a/src/sessions.js +++ b/src/sessions.js @@ -13,6 +13,7 @@ import { copyDirectory, } from "./util.js"; import { languages } from "./common.js"; +import { rust_template_dir } from "./langs/rust/rust.js"; export const sessions_dir = data_dir.get_child("sessions"); @@ -64,6 +65,7 @@ export function createSessionFromDemo(demo) { const { file, settings } = session; copyDirectory(demo_dir, file); + copyDirectory(rust_template_dir, file); settings.set_string("name", name); settings.set_boolean("show-code", panels.includes("code")); diff --git a/src/workbench b/src/workbench index f7020feb0..762f298d0 100755 --- a/src/workbench +++ b/src/workbench @@ -2,6 +2,7 @@ export WEBKIT_DISABLE_DMABUF_RENDERER=1 # export G_MESSAGES_DEBUG=@app_id@ +export GSK_RENDERER=gl # Required to allow pkgconfig to find pc files in /app/lib/pkgconfig export PKG_CONFIG_PATH=/app/lib/pkgconfig/:$PKG_CONFIG_PATH