Skip to content

Commit 2514485

Browse files
authored
Add Rust analyzer support (#868)
Fixes #520 Only supports syntax diagnostics for now. I made a follow up for types #881
1 parent deb048d commit 2514485

File tree

10 files changed

+124
-39
lines changed

10 files changed

+124
-39
lines changed

.github/CODEOWNERS

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/src/langs/vala/ @lw64
55
*.vala @lw64
66

7-
/src/langs/rust/ @lw64
7+
/src/langs/rust/ @Hofer-Julian
88
*.rs @Hofer-Julian
99
Cargo.toml @Hofer-Julian
1010
Cargo.lock @Hofer-Julian

src/cli/main.js

+50-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const window = new Adw.ApplicationWindow();
7878

7979
function createLSPClients({ root_uri }) {
8080
return Object.fromEntries(
81-
["javascript", "blueprint", "css", "vala"].map((id) => {
81+
["javascript", "blueprint", "css", "vala", "rust"].map((id) => {
8282
const lang = languages.find((language) => language.id === id);
8383
const lspc = createLSPClient({
8484
lang,
@@ -397,6 +397,55 @@ async function ci({ filenames, current_dir }) {
397397
});
398398
}
399399

400+
const file_rust = demo_dir.get_child("code.rs");
401+
if (file_rust.query_exists(null)) {
402+
print(` ${file_rust.get_path()}`);
403+
404+
const uri = file_rust.get_uri();
405+
const languageId = "rust";
406+
let version = 0;
407+
408+
const [contents] = await file_rust.load_contents_async(null);
409+
const text = new TextDecoder().decode(contents);
410+
411+
await lsp_clients.rust._notify("textDocument/didOpen", {
412+
textDocument: {
413+
uri,
414+
languageId,
415+
version: version++,
416+
text,
417+
},
418+
});
419+
420+
// FIXME: rust analyzer doesn't publish diagnostics if there are none
421+
// probably we should switch to pulling diagnostics but unknown if supported
422+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#textDocument_pullDiagnostics
423+
424+
// const diagnostics = await waitForDiagnostics({
425+
// uri,
426+
// lspc: lsp_clients.rust,
427+
// });
428+
// if (diagnostics.length > 0) {
429+
// printerr(serializeDiagnostics({ diagnostics }));
430+
// return false;
431+
// }
432+
// print(` ✅ lints`);
433+
434+
const checks = await checkFile({
435+
lspc: lsp_clients.rust,
436+
file: file_rust,
437+
lang: getLanguage("rust"),
438+
uri,
439+
});
440+
if (!checks) return false;
441+
442+
await lsp_clients.rust._notify("textDocument/didClose", {
443+
textDocument: {
444+
uri,
445+
},
446+
});
447+
}
448+
400449
await Promise.all(
401450
Object.entries(lsp_clients).map(([, lspc]) => {
402451
return lspc.stop();

src/common.js

+7-2
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ export const languages = [
9494
document: null,
9595
default_file: "code.rs",
9696
index: 2,
97+
language_server: ["rust-analyzer"],
98+
formatting_options: {
99+
...formatting_options,
100+
tabSize: 4,
101+
},
97102
},
98103
{
99104
id: "python",
@@ -113,13 +118,13 @@ export function getLanguage(id) {
113118
);
114119
}
115120

116-
export function createLSPClient({ lang, root_uri }) {
121+
export function createLSPClient({ lang, root_uri, quiet = true }) {
117122
const language_id = lang.id;
118123

119124
const lspc = new LSPClient(lang.language_server, {
120125
rootUri: root_uri,
121126
languageId: language_id,
122-
// quiet: false,
127+
quiet,
123128
});
124129
lspc.connect("exit", () => {
125130
console.debug(`${language_id} language server exit`);

src/langs/blueprint/BlueprintDocument.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import Document from "../../Document.js";
22
import { applyTextEdits } from "../../lsp/sourceview.js";
33

4-
import { setup as setupBlueprint } from "./blueprint.js";
4+
import { setup } from "./blueprint.js";
55

66
export class BlueprintDocument extends Document {
77
constructor(...args) {
88
super(...args);
99

10-
this.lspc = setupBlueprint({ document: this });
10+
this.lspc = setup({ document: this });
1111
}
1212
async update() {
1313
return this.lspc.didChange();

src/langs/javascript/JavaScriptDocument.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { setup as setupJavaScript } from "./javascript.js";
1+
import { setup } from "./javascript.js";
22

33
import Document from "../../Document.js";
44
import { applyTextEdits } from "../../lsp/sourceview.js";
@@ -7,7 +7,7 @@ export class JavaScriptDocument extends Document {
77
constructor(...args) {
88
super(...args);
99

10-
this.lspc = setupJavaScript({ document: this });
10+
this.lspc = setup({ document: this });
1111
}
1212

1313
async format() {

src/langs/rust/Compiler.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ import Gio from "gi://Gio";
22
import GLib from "gi://GLib";
33
import dbus_previewer from "../../Previewer/DBusPreviewer.js";
44
import { copyDirectory, decode, encode } from "../../util.js";
5-
6-
const rust_template_dir = Gio.File.new_for_path(
7-
pkg.pkgdatadir,
8-
).resolve_relative_path("langs/rust/template");
5+
import { rust_template_dir } from "./rust.js";
96

107
export default function Compiler({ session }) {
118
const { file } = session;

src/langs/rust/RustDocument.js

+21-27
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,30 @@
1-
import Gio from "gi://Gio";
1+
import { setup } from "./rust.js";
22

33
import Document from "../../Document.js";
4+
import { applyTextEdits } from "../../lsp/sourceview.js";
45

56
export class RustDocument extends Document {
6-
async format() {
7-
const code = await formatRustCode(this.buffer.text);
8-
this.code_view.replaceText(code, true);
9-
}
10-
}
11-
12-
function formatRustCode(text) {
13-
const rustfmtLauncher = Gio.SubprocessLauncher.new(
14-
Gio.SubprocessFlags.STDIN_PIPE |
15-
Gio.SubprocessFlags.STDOUT_PIPE |
16-
Gio.SubprocessFlags.STDERR_PIPE,
17-
);
7+
constructor(...args) {
8+
super(...args);
189

19-
const rustfmtProcess = rustfmtLauncher.spawnv([
20-
"rustfmt",
21-
"--quiet",
22-
"--emit",
23-
"stdout",
24-
"--edition",
25-
"2021",
26-
]);
10+
this.lspc = setup({ document: this });
11+
}
2712

28-
const [success, stdout, stderr] = rustfmtProcess.communicate_utf8(text, null);
13+
async format() {
14+
// https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_formatting
15+
const text_edits = await this.lspc.request("textDocument/formatting", {
16+
textDocument: {
17+
uri: this.file.get_uri(),
18+
},
19+
options: {
20+
tabSize: 4,
21+
insertSpaces: true,
22+
trimTrailingWhitespace: true,
23+
insertFinalNewline: true,
24+
trimFinalNewlines: true,
25+
},
26+
});
2927

30-
if (!success || stderr !== "") {
31-
console.error(`Error running rustfmt: ${stderr}`);
32-
return text;
28+
applyTextEdits(text_edits, this.buffer);
3329
}
34-
35-
return stdout;
3630
}

src/langs/rust/rust.js

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Gio from "gi://Gio";
2+
3+
import { createLSPClient } from "../../common.js";
4+
import { getLanguage } from "../../util.js";
5+
6+
export function setup({ document }) {
7+
const { file, buffer, code_view } = document;
8+
9+
const lspc = createLSPClient({
10+
lang: getLanguage("rust"),
11+
root_uri: file.get_parent().get_uri(),
12+
});
13+
lspc.buffer = buffer;
14+
lspc.uri = file.get_uri();
15+
lspc.connect(
16+
"notification::textDocument/publishDiagnostics",
17+
(_self, params) => {
18+
if (params.uri !== file.get_uri()) {
19+
return;
20+
}
21+
code_view.handleDiagnostics(params.diagnostics);
22+
},
23+
);
24+
25+
lspc.start().catch(console.error);
26+
27+
buffer.connect("modified-changed", () => {
28+
if (!buffer.get_modified()) return;
29+
lspc.didChange().catch(console.error);
30+
});
31+
32+
return lspc;
33+
}
34+
35+
export const rust_template_dir = Gio.File.new_for_path(
36+
pkg.pkgdatadir,
37+
).resolve_relative_path("langs/rust/template");

src/sessions.js

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
copyDirectory,
1414
} from "./util.js";
1515
import { languages } from "./common.js";
16+
import { rust_template_dir } from "./langs/rust/rust.js";
1617

1718
export const sessions_dir = data_dir.get_child("sessions");
1819

@@ -64,6 +65,7 @@ export function createSessionFromDemo(demo) {
6465

6566
const { file, settings } = session;
6667
copyDirectory(demo_dir, file);
68+
copyDirectory(rust_template_dir, file);
6769

6870
settings.set_string("name", name);
6971
settings.set_boolean("show-code", panels.includes("code"));

src/workbench

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
export WEBKIT_DISABLE_DMABUF_RENDERER=1
44
# export G_MESSAGES_DEBUG=@app_id@
5+
export GSK_RENDERER=gl
56

67
# Required to allow pkgconfig to find pc files in /app/lib/pkgconfig
78
export PKG_CONFIG_PATH=/app/lib/pkgconfig/:$PKG_CONFIG_PATH

0 commit comments

Comments
 (0)