Skip to content

Commit b665779

Browse files
committed
feat: xpath semantic tokens
1 parent 8cc6c0f commit b665779

12 files changed

+407
-119
lines changed

.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ out/
44
.pnpm-debug.log
55
*.ast
66
*.nrs
7-
dist/
7+
dist/*
88
*.vsix
99
Cargo.toml

.vscodeignore

+1
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ examples
1515
Cargo.*
1616
pnpm-lock.yaml
1717
*.vsix
18+
syntaxes/gen/

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ pathdiff = "0.2.1"
6565
rayon = "1.8.0"
6666
derive_more = "0.99.17"
6767
ignore = "0.4.22"
68+
fomat-macros = "0.3.2"
6869

6970
[target.'cfg(all(target_os = "linux", any(not(target_env = "gnu"), not(target_pointer_width = "64"))))'.dependencies]
7071
self_update = { version = "0.39.0", default-features = false, features = ["archive-tar", "archive-zip", "compression-flate2", "compression-zip-deflate", "rustls"] }

biome.json

+11-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,15 @@
33
"formatter": {
44
"lineWidth": 120,
55
"indentStyle": "tab"
6-
}
6+
},
7+
"overrides": [
8+
{
9+
"include": [".vscode/**"],
10+
"json": {
11+
"parser": {
12+
"allowComments": true
13+
}
14+
}
15+
}
16+
]
717
}

client/src/extension.ts

+55-115
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,27 @@
44
* ------------------------------------------------------------------------------------------ */
55

66
import { mkdir, rm } from "node:fs/promises";
7-
import { ObjectEncodingOptions, existsSync } from "node:fs";
8-
import { exec, spawn, ExecOptions } from "node:child_process";
9-
import { workspace, window, ExtensionContext, commands, WorkspaceFolder, extensions } from "vscode";
7+
import { existsSync } from "node:fs";
8+
import { exec, spawn } from "node:child_process";
9+
import * as vscode from "vscode";
1010
import { get } from "node:https";
11-
1211
import { LanguageClient, LanguageClientOptions, ServerOptions } from "vscode-languageclient/node";
12+
import { registerXmlFileAssociations, registerXPathSemanticTokensProvider } from "./xml";
13+
import { execAsync as $, guessRustTarget, makeStates } from "./utils";
1314

1415
let client: LanguageClient;
1516

16-
function guessRustTarget() {
17-
const platform = process.platform;
18-
const arch = process.arch;
19-
if (platform === "win32") {
20-
if (arch === "x64") return "x86_64-pc-windows-msvc";
21-
return "i686-pc-windows-msvc";
22-
}
23-
if (platform === "darwin") {
24-
if (arch === "x64") return "x86_64-apple-darwin";
25-
if (arch === "arm64") return "aarch64-apple-darwin";
26-
} else if (platform === "linux") {
27-
if (arch === "x64") return "x86_64-unknown-linux-gnu";
28-
return "i686-unknown-linux-gnu";
29-
}
30-
}
31-
32-
function execAsync(command: string, options?: ObjectEncodingOptions & ExecOptions) {
33-
return new Promise<{ stdout: string | Buffer; stderr: string | Buffer }>((resolve, reject) => {
34-
exec(command, options, (err, stdout, stderr) => {
35-
if (err) reject(err);
36-
else resolve({ stdout, stderr });
37-
});
38-
});
39-
}
40-
4117
const repo = "https://github.com/Desdaemon/odoo-lsp";
4218

43-
async function downloadLspBinary(context: ExtensionContext) {
19+
async function downloadLspBinary(context: vscode.ExtensionContext) {
4420
const isWindows = process.platform === "win32";
4521
const archiveExtension = isWindows ? ".zip" : ".tgz";
4622
const runtimeDir = context.globalStorageUri.fsPath;
4723
await mkdir(runtimeDir, { recursive: true });
48-
const preferNightly = !!workspace.getConfiguration("odoo-lsp.binary").get("preferNightly");
49-
const overrideVersion: string | undefined = workspace.getConfiguration("odoo-lsp.binary").get("overrideVersion");
24+
const preferNightly = !!vscode.workspace.getConfiguration("odoo-lsp.binary").get("preferNightly");
25+
const overrideVersion: string | undefined = vscode.workspace
26+
.getConfiguration("odoo-lsp.binary")
27+
.get("overrideVersion");
5028

5129
let release = overrideVersion;
5230
if (!preferNightly && !overrideVersion) {
@@ -70,7 +48,7 @@ async function downloadLspBinary(context: ExtensionContext) {
7048
const latest = releases.find((r) => r.name === "nightly");
7149
resolve(latest?.tag_name);
7250
} catch (err) {
73-
window.showErrorMessage(`Unable to fetch nightly release: ${err}`);
51+
vscode.window.showErrorMessage(`Unable to fetch nightly release: ${err}`);
7452
resolve(context.extension.packageJSON._release);
7553
}
7654
});
@@ -79,7 +57,7 @@ async function downloadLspBinary(context: ExtensionContext) {
7957
)) || release;
8058
}
8159
if (typeof release !== "string" || !release) {
82-
window.showErrorMessage(`Bug: invalid release "${release}"`);
60+
vscode.window.showErrorMessage(`Bug: invalid release "${release}"`);
8361
return;
8462
}
8563
const latest = `${runtimeDir}/${release}${archiveExtension}`;
@@ -101,7 +79,7 @@ async function downloadLspBinary(context: ExtensionContext) {
10179
if (await which("cargo-binstall")) {
10280
actions.push(Actions.Binstall);
10381
}
104-
const resp = await window.showInformationMessage(
82+
const resp = await vscode.window.showInformationMessage(
10583
`odoo-lsp: No prebuilt binaries available for your platform (platform=${process.platform}, arch=${process.arch}).
10684
Please file an issue at ${repo}, or install odoo-lsp from source.`,
10785
...actions,
@@ -111,7 +89,7 @@ async function downloadLspBinary(context: ExtensionContext) {
11189
await openLink(repo);
11290
break;
11391
case Actions.InstallSource: {
114-
const channel = window.createOutputChannel("cargo install odoo-lsp");
92+
const channel = vscode.window.createOutputChannel("cargo install odoo-lsp");
11593
channel.show();
11694
context.subscriptions.push(channel);
11795
const cargo = spawn(`cargo install --git ${repo}`, { shell: true });
@@ -127,7 +105,7 @@ async function downloadLspBinary(context: ExtensionContext) {
127105
break;
128106
}
129107
case Actions.Binstall: {
130-
const channel = window.createOutputChannel("cargo-binstall odoo-lsp");
108+
const channel = vscode.window.createOutputChannel("cargo-binstall odoo-lsp");
131109
channel.show();
132110
context.subscriptions.push(channel);
133111
const cargo = spawn("cargo binstall odoo-lsp", { shell: true });
@@ -156,38 +134,38 @@ async function downloadLspBinary(context: ExtensionContext) {
156134
const powershell = { shell: "powershell.exe" };
157135
const sh = { shell: "sh" };
158136
if (!existsSync(latest)) {
159-
window.setStatusBarMessage(`Downloading odoo-lsp@${release}...`, 5);
137+
vscode.window.setStatusBarMessage(`Downloading odoo-lsp@${release}...`, 5);
160138
try {
161139
if (isWindows) {
162-
await execAsync(`Invoke-WebRequest -Uri ${link} -OutFile ${latest}`, powershell);
163-
await execAsync(`Invoke-WebRequest -Uri ${shaLink} -OutFile ${shaOutput}`, powershell);
164-
const { stdout } = await execAsync(
140+
await $(`Invoke-WebRequest -Uri ${link} -OutFile ${latest}`, powershell);
141+
await $(`Invoke-WebRequest -Uri ${shaLink} -OutFile ${shaOutput}`, powershell);
142+
const { stdout } = await $(
165143
`(Get-FileHash ${latest} -Algorithm SHA256).Hash -eq (Get-Content ${shaOutput})`,
166144
powershell,
167145
);
168146
if (stdout.toString().trim() !== "True") throw new Error("Checksum verification failed");
169-
await execAsync(`Expand-Archive -Path ${latest} -DestinationPath ${runtimeDir}`, powershell);
147+
await $(`Expand-Archive -Path ${latest} -DestinationPath ${runtimeDir}`, powershell);
170148
} else {
171-
await execAsync(`wget -O ${latest} ${link}`, sh);
172-
await execAsync(`wget -O ${shaOutput} ${shaLink}`, sh);
173-
await execAsync(
149+
await $(`wget -O ${latest} ${link}`, sh);
150+
await $(`wget -O ${shaOutput} ${shaLink}`, sh);
151+
await $(
174152
`if [ "$(shasum -a 256 ${latest} | cut -d ' ' -f 1)" != "$(cat ${shaOutput})" ]; then exit 1; fi`,
175153
sh,
176154
);
177-
await execAsync(`tar -xzf ${latest} -C ${runtimeDir}`, sh);
155+
await $(`tar -xzf ${latest} -C ${runtimeDir}`, sh);
178156
}
179157
} catch (err) {
180158
// We only build nightly when there are changes, so there will be days without nightly builds.
181159
if (!(err instanceof Error) || !err.message.includes("404")) {
182-
window.showErrorMessage(`Failed to download odoo-lsp binary: ${err}`);
160+
vscode.window.showErrorMessage(`Failed to download odoo-lsp binary: ${err}`);
183161
}
184162
await rm(latest);
185163
}
186164
} else if (!existsSync(odooLspBin)) {
187165
if (isWindows) {
188-
await execAsync(`Expand-Archive -Path ${latest} -DestinationPath ${runtimeDir}`, powershell);
166+
await $(`Expand-Archive -Path ${latest} -DestinationPath ${runtimeDir}`, powershell);
189167
} else {
190-
await execAsync(`tar -xzf ${latest} -C ${runtimeDir}`, sh);
168+
await $(`tar -xzf ${latest} -C ${runtimeDir}`, sh);
191169
}
192170
}
193171

@@ -213,63 +191,30 @@ async function openLink(url: string) {
213191
} else {
214192
opener = "xdg-open";
215193
}
216-
return await execAsync(`${opener} ${url}`);
194+
return await $(`${opener} ${url}`);
217195
}
218196

219-
export async function activate(context: ExtensionContext) {
220-
const traceOutputChannel = window.createOutputChannel("Odoo LSP Extension");
197+
const makeExtensionState = (context: vscode.ExtensionContext) => makeStates(context, {
198+
noXmlReminders: Boolean,
199+
noXPathReminders: Boolean,
200+
});
221201

222-
try {
223-
// see https://github.com/redhat-developer/vscode-xml/blob/main/src/api/xmlExtensionApi.ts
224-
const xmlApi = await extensions.getExtension<XMLExtensionApi>("redhat.vscode-xml")?.activate();
225-
if (xmlApi) {
226-
xmlApi.addXMLFileAssociations([
227-
// HACK: systemId must be unique
228-
{
229-
systemId: `${context.extensionUri}/odoo.rng`,
230-
pattern: "views/*.xml",
231-
},
232-
{
233-
systemId: `${context.extensionUri}/./odoo.rng`,
234-
pattern: "data/*.xml",
235-
},
236-
{
237-
systemId: `${context.extensionUri}/././odoo.rng`,
238-
pattern: "security/*.xml",
239-
},
240-
{
241-
systemId: `${context.extensionUri}/./././odoo.rng`,
242-
pattern: "report/*.xml",
243-
},
244-
{
245-
systemId: `${context.extensionUri}/././././odoo.rng`,
246-
pattern: "wizard/*.xml",
247-
},
248-
]);
249-
} else {
250-
// Recommend that the user install the XML extension
251-
window
252-
.showInformationMessage(
253-
"Install the 'XML' extension for a better XML editing experience.",
254-
"Install",
255-
"Remind me later",
256-
)
257-
.then((choice) => {
258-
if (choice !== "Install") return;
259-
commands.executeCommand("workbench.extensions.search", "@id:redhat.vscode-xml");
260-
});
261-
}
262-
} catch (err) {
263-
traceOutputChannel.appendLine(`Failed to register XML file associations: ${err}`);
264-
}
202+
export type State = ReturnType<typeof makeExtensionState>;
203+
204+
export async function activate(context: vscode.ExtensionContext) {
205+
const traceOutputChannel = vscode.window.createOutputChannel("Odoo LSP Extension");
206+
const extensionState = makeExtensionState(context);
207+
208+
await registerXmlFileAssociations(context, traceOutputChannel, extensionState);
209+
await registerXPathSemanticTokensProvider(context, traceOutputChannel, extensionState);
265210

266211
let command = process.env.SERVER_PATH || "odoo-lsp";
267212
if (!(await which(command))) {
268213
command = (await downloadLspBinary(context)) || command;
269214
}
270215
traceOutputChannel.appendLine(`odoo-lsp executable: ${command}`);
271216
if (!(await which(command))) {
272-
window.showErrorMessage(`no odoo-lsp executable present: ${command}`);
217+
vscode.window.showErrorMessage(`no odoo-lsp executable present: ${command}`);
273218
return;
274219
}
275220
const serverOptions: ServerOptions = {
@@ -293,24 +238,24 @@ export async function activate(context: ExtensionContext) {
293238
{ language: "javascript", scheme: "file" },
294239
],
295240
synchronize: {
296-
fileEvents: workspace.createFileSystemWatcher("**/.odoo_lsp*"),
241+
fileEvents: vscode.workspace.createFileSystemWatcher("**/.odoo_lsp*"),
297242
},
298243
traceOutputChannel,
299244
};
300245

301246
context.subscriptions.push(
302-
commands.registerCommand("odoo-lsp.tsconfig", async () => {
303-
const activeWindow = window.activeTextEditor?.document.uri.fsPath;
304-
let folder: WorkspaceFolder | undefined;
247+
vscode.commands.registerCommand("odoo-lsp.tsconfig", async () => {
248+
const activeWindow = vscode.window.activeTextEditor?.document.uri.fsPath;
249+
let folder: vscode.WorkspaceFolder | undefined;
305250
if (activeWindow) {
306-
folder = workspace.workspaceFolders?.find((ws) => activeWindow.includes(ws.uri.fsPath));
251+
folder = vscode.workspace.workspaceFolders?.find((ws) => activeWindow.includes(ws.uri.fsPath));
307252
}
308253

309-
if (!folder) folder = await window.showWorkspaceFolderPick();
254+
if (!folder) folder = await vscode.window.showWorkspaceFolderPick();
310255
if (!folder) return;
311256

312257
const selection =
313-
(await window.showOpenDialog({
258+
(await vscode.window.showOpenDialog({
314259
canSelectFiles: false,
315260
canSelectFolders: true,
316261
canSelectMany: true,
@@ -319,31 +264,31 @@ export async function activate(context: ExtensionContext) {
319264
})) ?? [];
320265

321266
const paths = selection.map((sel) => `--addons-path ${sel.fsPath}`).join(" ");
322-
const { stdout } = await execAsync(`${command} tsconfig ${paths}`, {
267+
const { stdout } = await $(`${command} tsconfig ${paths}`, {
323268
cwd: folder.uri.fsPath,
324269
});
325270

326-
const doc = await workspace.openTextDocument({
271+
const doc = await vscode.workspace.openTextDocument({
327272
language: "json",
328273
content: stdout as string,
329274
});
330-
await window.showTextDocument(doc);
275+
await vscode.window.showTextDocument(doc);
331276
}),
332277
);
333278

334279
context.subscriptions.push(
335-
commands.registerCommand("odoo-lsp.statistics", async () => {
280+
vscode.commands.registerCommand("odoo-lsp.statistics", async () => {
336281
const response = await client.sendRequest("odoo-lsp/statistics");
337-
const doc = await workspace.openTextDocument({
282+
const doc = await vscode.workspace.openTextDocument({
338283
language: "json",
339284
content: JSON.stringify(response, undefined, 2),
340285
});
341-
await window.showTextDocument(doc);
286+
await vscode.window.showTextDocument(doc);
342287
}),
343288
);
344289

345290
context.subscriptions.push(
346-
commands.registerCommand("odoo-lsp.restart-lsp", async () => {
291+
vscode.commands.registerCommand("odoo-lsp.restart-lsp", async () => {
347292
await client.restart();
348293
traceOutputChannel.appendLine("Odoo LSP restarted");
349294
}),
@@ -360,8 +305,3 @@ export function deactivate(): Thenable<void> | undefined {
360305
}
361306
return client.stop();
362307
}
363-
364-
interface XMLExtensionApi {
365-
// addXMLCatalogs(_: string[]): void;
366-
addXMLFileAssociations(_: { systemId: string; pattern: string }[]): void;
367-
}

0 commit comments

Comments
 (0)