Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Rust analyzer support #868

Merged
merged 8 commits into from
Feb 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
51 changes: 50 additions & 1 deletion src/cli/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
9 changes: 7 additions & 2 deletions src/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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`);
Expand Down
4 changes: 2 additions & 2 deletions src/langs/blueprint/BlueprintDocument.js
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
4 changes: 2 additions & 2 deletions src/langs/javascript/JavaScriptDocument.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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() {
Expand Down
5 changes: 1 addition & 4 deletions src/langs/rust/Compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
48 changes: 21 additions & 27 deletions src/langs/rust/RustDocument.js
Original file line number Diff line number Diff line change
@@ -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;
}
37 changes: 37 additions & 0 deletions src/langs/rust/rust.js
Original file line number Diff line number Diff line change
@@ -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");
2 changes: 2 additions & 0 deletions src/sessions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -64,6 +65,7 @@ export function createSessionFromDemo(demo) {

const { file, settings } = session;
copyDirectory(demo_dir, file);
copyDirectory(rust_template_dir, file);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will copy Rust template files such as Cargo.toml and workbench.rs when creating a new session from a demo.

We already do this on compile but if we want diagnostics to work before pressing "Run" we need this here too.


settings.set_string("name", name);
settings.set_boolean("show-code", panels.includes("code"));
Expand Down
1 change: 1 addition & 0 deletions src/workbench
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading