Skip to content

Commit 36c8992

Browse files
Add code to download, extract, and setup Vale LS
1 parent c0ec86c commit 36c8992

File tree

1 file changed

+144
-84
lines changed

1 file changed

+144
-84
lines changed

src/lsp.ts

Lines changed: 144 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,76 @@
11
import { workspace, ExtensionContext } from "vscode";
22
import * as vscode from "vscode";
33

4+
import { writeFile } from "node:fs/promises";
5+
import { Readable } from "node:stream";
6+
import * as unzipper from "unzipper";
7+
import fs from "fs";
8+
import * as path from "path";
9+
410
import {
511
LanguageClient,
612
LanguageClientOptions,
713
ServerOptions,
8-
TransportKind,
914
} from "vscode-languageclient/node";
1015

1116
let client: LanguageClient;
17+
let downloadLs = true;
18+
19+
export function getArch(): String | null {
20+
if (process.arch == "x64") return "x86_64";
21+
if (process.arch == "arm64") return "aarch64";
22+
else {
23+
vscode.window.showErrorMessage("Unsupported architecture: " + process.arch);
24+
downloadLs = false;
25+
return null;
26+
}
27+
}
28+
29+
export function getPlatform(): String | null {
30+
if (process.platform == "darwin") return "apple-darwin";
31+
if (process.platform == "win32") return "pc-windows-gnu";
32+
if (process.platform == "linux") return "unknown-linux-gnu";
33+
else {
34+
vscode.window.showErrorMessage("Unsupported platform: " + process.platform);
35+
downloadLs = false;
36+
return null;
37+
}
38+
}
39+
40+
async function downloadLSP(context: ExtensionContext) {
41+
const TAG = "v0.3.8";
42+
const URL = `https://github.com/errata-ai/vale-ls/releases/download/${TAG}/vale-ls-${getArch()}-${getPlatform()}.zip`;
43+
const extStorage = context.extensionPath;
44+
const tmpZip = path.join(extStorage, "vale-ls.zip");
1245

13-
const TAG = "v0.3.8";
14-
const URL =
15-
"https://github.com/errata-ai/vale-ls/releases/download/{tag}/vale-ls-{arch}-{platform}.zip";
46+
vscode.window.showInformationMessage(
47+
"First launch: Downloading Vale Language Server"
48+
);
49+
50+
const response = await fetch(URL);
51+
if (response.body) {
52+
const stream = Readable.fromWeb(response.body);
53+
await writeFile(tmpZip, stream).then(async () => {
54+
// console.log("Downloaded to " + tmpZip);
55+
await unzipper.Open.file(tmpZip).then((directory) => {
56+
// console.log("Extracting to " + extStorage);
57+
directory
58+
.extract({ path: extStorage })
59+
.then(async () => {
60+
fs.chmodSync(path.join(extStorage, "vale-ls"), 766);
61+
})
62+
.finally(() => {
63+
fs.unlinkSync(tmpZip);
64+
vscode.window.showInformationMessage(
65+
"First launch: Vale Language Server downloaded"
66+
);
67+
});
68+
});
69+
});
70+
} else {
71+
throw new Error("Failed to fetch the response body.");
72+
}
73+
}
1674

1775
type valeConfigOptions =
1876
| "configPath"
@@ -24,53 +82,49 @@ interface valeArgs {
2482
value: string;
2583
}
2684

27-
export function getArch(): String {
28-
if (process.arch == "x64") return "x86_64";
29-
if (process.arch == "arm64") return "aaarch64";
30-
else return "unsupported";
31-
}
32-
33-
export function getPlatform(): String {
34-
if (process.platform == "darwin") return "apple-darwin";
35-
if (process.platform == "win32") return "pc-windows-gnu";
36-
else return "unknown-linux-gnu";
37-
}
38-
39-
export function activate(context: ExtensionContext) {
40-
// Not possible when using `command`?
41-
// let debugOptions = { execArgv: ['--nolazy', '--inspect=6009'] };
42-
const valePath = context.extensionPath + "/tmp-bin/vale-ls";
43-
//let valeArgs: any = new Object();
44-
45-
// TODO: Factor in https://vale.sh/docs/integrations/guide/#vale-ls
46-
// Has the user defined a config file manually?
47-
const configuration = vscode.workspace.getConfiguration();
48-
// Make global constant for now as will reuse and build upon later
49-
let valeFilter: valeArgs = { value: "" };
50-
let filters: string[] = [];
51-
// let customConfigPath = configuration.get<string>("vale.valeCLI.config");
52-
// if (customConfigPath) {
53-
// console.log("Using config: " + customConfigPath);
54-
55-
// valeArgs = {configPath: customConfigPath};
56-
// }
57-
// console.log(valeArgs)
58-
console.log("Using binary: " + valePath);
59-
60-
// Handle old minAlertLevel to output as filter
61-
if (configuration.get("vale.valeCLI.minAlertLevel") !== "inherited") {
62-
let minAlertLevel = configuration.get("vale.valeCLI.minAlertLevel");
63-
64-
if (minAlertLevel === "suggestion") {
65-
filters.push(`.Level in ["suggestion", "warning", "error"]`);
66-
}
67-
if (minAlertLevel === "warning") {
68-
filters.push(`.Level in ["warning", "error"]`);
85+
export async function activate(context: ExtensionContext) {
86+
// TODO: Always needs reload on first activate
87+
const filePath = path.join(context.extensionPath, "vale-ls");
88+
console.log(filePath);
89+
try {
90+
await vscode.workspace.fs.stat(vscode.Uri.file(filePath));
91+
console.log("File exists");
92+
} catch {
93+
console.log("File doesn't exist");
94+
95+
await vscode.workspace.fs
96+
.createDirectory(context.globalStorageUri)
97+
.then(async () => {
98+
await downloadLSP(context);
99+
});
100+
} finally {
101+
const valePath = path.join(context.extensionPath, "vale-ls");
102+
// TODO: Must be a better way?
103+
var escapedPath = valePath.replace(/(\s)/, "\\ ");
104+
105+
// TODO: Factor in https://vale.sh/docs/integrations/guide/#vale-ls
106+
// Has the user defined a config file manually?
107+
const configuration = vscode.workspace.getConfiguration();
108+
// Make global constant for now as will reuse and build upon later
109+
let valeFilter: valeArgs = { value: "" };
110+
let filters: string[] = [];
111+
112+
// console.log("Using binary: " + escapedPath);
113+
114+
// Handle old minAlertLevel to output as filter
115+
if (configuration.get("vale.valeCLI.minAlertLevel") !== "inherited") {
116+
let minAlertLevel = configuration.get("vale.valeCLI.minAlertLevel");
117+
118+
if (minAlertLevel === "suggestion") {
119+
filters.push(`.Level in ["suggestion", "warning", "error"]`);
120+
}
121+
if (minAlertLevel === "warning") {
122+
filters.push(`.Level in ["warning", "error"]`);
123+
}
124+
if (minAlertLevel === "error") {
125+
filters.push(`.Level in ["error"]`);
126+
}
69127
}
70-
if (minAlertLevel === "error") {
71-
filters.push(`.Level in ["error"]`);
72-
}
73-
}
74128

75129
// Handle old enableSpellcheck to output as filter
76130
if (configuration.get("vale.enableSpellcheck") === false) {
@@ -82,40 +136,46 @@ export function activate(context: ExtensionContext) {
82136
valeFilter = filters.join(" and ") as unknown as valeArgs;
83137
}
84138

85-
let valeConfig: Record<valeConfigOptions, valeArgs> = {
86-
configPath: configuration.get("vale.valeCLI.configPath") as valeArgs,
87-
syncOnStartup: configuration.get("vale.valeCLI.syncOnStartup") as valeArgs,
88-
filter: valeFilter as unknown as valeArgs,
89-
installVale: configuration.get("vale.valeCLI.installVale") as valeArgs,
90-
};
91-
console.log(valeConfig);
92-
// TODO: So do I need the below?
93-
let tempArgs: never[] = [];
94-
let serverOptions: ServerOptions = {
95-
run: { command: valePath, args: tempArgs },
96-
debug: { command: valePath, args: tempArgs },
97-
};
98-
99-
// Options to control the language client
100-
let clientOptions: LanguageClientOptions = {
101-
// TODO: Refine
102-
initializationOptions: valeConfig,
103-
documentSelector: [{ scheme: "file", language: "*" }],
104-
synchronize: {
105-
fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
106-
},
107-
};
108-
109-
// Create the language client and start the client.
110-
client = new LanguageClient(
111-
"vale",
112-
"Vale VSCode",
113-
serverOptions,
114-
clientOptions
115-
);
116-
117-
// Start the client. This will also launch the server
118-
client.start();
139+
let valeConfig: Record<valeConfigOptions, valeArgs> = {
140+
configPath: configuration.get("vale.valeCLI.configPath") as valeArgs,
141+
syncOnStartup: configuration.get(
142+
"vale.valeCLI.syncOnStartup"
143+
) as valeArgs,
144+
filter: valeFilter as unknown as valeArgs,
145+
installVale: configuration.get("vale.valeCLI.installVale") as valeArgs,
146+
};
147+
// console.log(valeConfig);
148+
// TODO: So do I need the below?
149+
let tempArgs: never[] = [];
150+
let serverOptions: ServerOptions = {
151+
run: { command: escapedPath, args: tempArgs },
152+
debug: { command: escapedPath, args: tempArgs },
153+
};
154+
155+
// Options to control the language client
156+
let clientOptions: LanguageClientOptions = {
157+
// TODO: Refine
158+
initializationOptions: valeConfig,
159+
documentSelector: [{ scheme: "file", language: "*" }],
160+
synchronize: {
161+
fileEvents: workspace.createFileSystemWatcher("**/.clientrc"),
162+
},
163+
};
164+
165+
// Create the language client and start the client.
166+
client = new LanguageClient(
167+
"vale",
168+
"Vale VSCode",
169+
serverOptions,
170+
clientOptions
171+
);
172+
173+
// Start the client. This will also launch the server
174+
await client.start().catch((err) => {
175+
console.error(err);
176+
vscode.window.showErrorMessage("Failed to start Vale Language Server");
177+
});
178+
}
119179
}
120180

121181
export function deactivate(): Thenable<void> | undefined {

0 commit comments

Comments
 (0)