Skip to content

Commit 259a47b

Browse files
authored
refactor(vscode): move commands and findBinary to separate files (#8605)
pure refactor. wanted to add tests but needs to mocks :/
1 parent 997859c commit 259a47b

5 files changed

Lines changed: 178 additions & 164 deletions

File tree

editors/vscode/client/ConfigService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export class ConfigService implements IDisposable {
66
private static readonly _namespace = 'oxc';
77
private readonly _disposables: IDisposable[] = [];
88

9-
public config: Config;
9+
public readonly config: Config;
1010

1111
public onConfigChange:
1212
| ((this: ConfigService, config: ConfigurationChangeEvent) => void)

editors/vscode/client/commands.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { CodeAction, Command, commands, Disposable, window, workspace } from 'vscode';
2+
3+
import { CodeActionRequest, CodeActionTriggerKind, LanguageClient, Position, Range } from 'vscode-languageclient/node';
4+
import { Config } from './Config';
5+
6+
const commandPrefix = 'oxc';
7+
8+
export const enum OxcCommands {
9+
RestartServer = `${commandPrefix}.restartServer`,
10+
ApplyAllFixesFile = `${commandPrefix}.applyAllFixesFile`,
11+
ShowOutputChannel = `${commandPrefix}.showOutputChannel`,
12+
ToggleEnable = `${commandPrefix}.toggleEnable`,
13+
}
14+
15+
export const restartServerCommand = (client: LanguageClient): Disposable => {
16+
return commands.registerCommand(
17+
OxcCommands.RestartServer,
18+
async () => {
19+
if (!client) {
20+
window.showErrorMessage('oxc client not found');
21+
return;
22+
}
23+
24+
try {
25+
if (client.isRunning()) {
26+
await client.restart();
27+
28+
window.showInformationMessage('oxc server restarted.');
29+
} else {
30+
await client.start();
31+
}
32+
} catch (err) {
33+
client.error('Restarting client failed', err, 'force');
34+
}
35+
},
36+
);
37+
};
38+
39+
export const showOutputChannelCommand = (client: LanguageClient): Disposable => {
40+
return commands.registerCommand(
41+
OxcCommands.ShowOutputChannel,
42+
() => {
43+
client.outputChannel.show();
44+
},
45+
);
46+
};
47+
48+
export const toggleEnabledCommand = (config: Config): Disposable => {
49+
return commands.registerCommand(
50+
OxcCommands.ToggleEnable,
51+
() => {
52+
config.updateEnable(!config.enable);
53+
},
54+
);
55+
};
56+
57+
export const applyAllFixesFileCommand = (client: LanguageClient): Disposable => {
58+
return commands.registerCommand(
59+
OxcCommands.ApplyAllFixesFile,
60+
async () => {
61+
if (!client) {
62+
window.showErrorMessage('oxc client not found');
63+
return;
64+
}
65+
const textEditor = window.activeTextEditor;
66+
if (!textEditor) {
67+
window.showErrorMessage('active text editor not found');
68+
return;
69+
}
70+
71+
const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1);
72+
const codeActionResult = await client.sendRequest(CodeActionRequest.type, {
73+
textDocument: {
74+
uri: textEditor.document.uri.toString(),
75+
},
76+
range: Range.create(Position.create(0, 0), lastLine.range.end),
77+
context: {
78+
diagnostics: [],
79+
only: [],
80+
triggerKind: CodeActionTriggerKind.Invoked,
81+
},
82+
});
83+
const commandsOrCodeActions = await client.protocol2CodeConverter.asCodeActionResult(codeActionResult || []);
84+
85+
await Promise.all(
86+
commandsOrCodeActions
87+
.map(async (codeActionOrCommand) => {
88+
// Commands are always applied. Regardless of whether it's a Command or CodeAction#command.
89+
if (isCommand(codeActionOrCommand)) {
90+
await commands.executeCommand(codeActionOrCommand.command, codeActionOrCommand.arguments);
91+
} else {
92+
// Only preferred edits are applied
93+
// LSP states edits must be run first, then commands
94+
if (codeActionOrCommand.edit && codeActionOrCommand.isPreferred) {
95+
await workspace.applyEdit(codeActionOrCommand.edit);
96+
}
97+
if (codeActionOrCommand.command) {
98+
await commands.executeCommand(
99+
codeActionOrCommand.command.command,
100+
codeActionOrCommand.command.arguments,
101+
);
102+
}
103+
}
104+
}),
105+
);
106+
107+
function isCommand(codeActionOrCommand: CodeAction | Command): codeActionOrCommand is Command {
108+
return typeof codeActionOrCommand.command === 'string';
109+
}
110+
},
111+
);
112+
};

editors/vscode/client/extension.ts

Lines changed: 20 additions & 162 deletions
Original file line numberDiff line numberDiff line change
@@ -1,188 +1,45 @@
1-
import { promises as fsPromises } from 'node:fs';
1+
import { ExtensionContext, StatusBarAlignment, StatusBarItem, ThemeColor, window, workspace } from 'vscode';
22

33
import {
4-
CodeAction,
5-
Command,
6-
commands,
7-
ExtensionContext,
8-
StatusBarAlignment,
9-
StatusBarItem,
10-
ThemeColor,
11-
window,
12-
workspace,
13-
} from 'vscode';
14-
15-
import {
16-
CodeActionRequest,
17-
CodeActionTriggerKind,
4+
Executable,
5+
LanguageClient,
6+
LanguageClientOptions,
187
MessageType,
19-
Position,
20-
Range,
8+
ServerOptions,
219
ShowMessageNotification,
22-
} from 'vscode-languageclient';
23-
24-
import { Executable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node';
10+
} from 'vscode-languageclient/node';
2511

26-
import { join } from 'node:path';
12+
import {
13+
applyAllFixesFileCommand,
14+
OxcCommands,
15+
restartServerCommand,
16+
showOutputChannelCommand,
17+
toggleEnabledCommand,
18+
} from './commands';
2719
import { ConfigService } from './ConfigService';
20+
import findBinary from './findBinary';
2821

2922
const languageClientName = 'oxc';
3023
const outputChannelName = 'Oxc';
31-
const commandPrefix = 'oxc';
32-
33-
const enum OxcCommands {
34-
RestartServer = `${commandPrefix}.restartServer`,
35-
ApplyAllFixesFile = `${commandPrefix}.applyAllFixesFile`,
36-
ShowOutputChannel = `${commandPrefix}.showOutputChannel`,
37-
ToggleEnable = `${commandPrefix}.toggleEnable`,
38-
}
3924

4025
let client: LanguageClient;
4126

4227
let myStatusBarItem: StatusBarItem;
4328

4429
export async function activate(context: ExtensionContext) {
4530
const configService = new ConfigService();
46-
const restartCommand = commands.registerCommand(
47-
OxcCommands.RestartServer,
48-
async () => {
49-
if (!client) {
50-
window.showErrorMessage('oxc client not found');
51-
return;
52-
}
53-
54-
try {
55-
if (client.isRunning()) {
56-
await client.restart();
57-
58-
window.showInformationMessage('oxc server restarted.');
59-
} else {
60-
await client.start();
61-
}
62-
} catch (err) {
63-
client.error('Restarting client failed', err, 'force');
64-
}
65-
},
66-
);
67-
68-
const showOutputCommand = commands.registerCommand(
69-
OxcCommands.ShowOutputChannel,
70-
() => {
71-
client?.outputChannel?.show();
72-
},
73-
);
74-
75-
const toggleEnable = commands.registerCommand(
76-
OxcCommands.ToggleEnable,
77-
() => {
78-
configService.config.updateEnable(!configService.config.enable);
79-
},
80-
);
81-
82-
const applyAllFixesFile = commands.registerCommand(
83-
OxcCommands.ApplyAllFixesFile,
84-
async () => {
85-
if (!client) {
86-
window.showErrorMessage('oxc client not found');
87-
return;
88-
}
89-
const textEditor = window.activeTextEditor;
90-
if (!textEditor) {
91-
window.showErrorMessage('active text editor not found');
92-
return;
93-
}
94-
95-
const lastLine = textEditor.document.lineAt(textEditor.document.lineCount - 1);
96-
const codeActionResult = await client.sendRequest(CodeActionRequest.type, {
97-
textDocument: {
98-
uri: textEditor.document.uri.toString(),
99-
},
100-
range: Range.create(Position.create(0, 0), lastLine.range.end),
101-
context: {
102-
diagnostics: [],
103-
only: [],
104-
triggerKind: CodeActionTriggerKind.Invoked,
105-
},
106-
});
107-
const commandsOrCodeActions = await client.protocol2CodeConverter.asCodeActionResult(codeActionResult || []);
108-
109-
await Promise.all(
110-
commandsOrCodeActions
111-
.map(async (codeActionOrCommand) => {
112-
// Commands are always applied. Regardless of whether it's a Command or CodeAction#command.
113-
if (isCommand(codeActionOrCommand)) {
114-
await commands.executeCommand(codeActionOrCommand.command, codeActionOrCommand.arguments);
115-
} else {
116-
// Only preferred edits are applied
117-
// LSP states edits must be run first, then commands
118-
if (codeActionOrCommand.edit && codeActionOrCommand.isPreferred) {
119-
await workspace.applyEdit(codeActionOrCommand.edit);
120-
}
121-
if (codeActionOrCommand.command) {
122-
await commands.executeCommand(
123-
codeActionOrCommand.command.command,
124-
codeActionOrCommand.command.arguments,
125-
);
126-
}
127-
}
128-
}),
129-
);
130-
131-
function isCommand(codeActionOrCommand: CodeAction | Command): codeActionOrCommand is Command {
132-
return typeof codeActionOrCommand.command === 'string';
133-
}
134-
},
135-
);
13631

13732
context.subscriptions.push(
138-
applyAllFixesFile,
139-
restartCommand,
140-
showOutputCommand,
141-
toggleEnable,
33+
applyAllFixesFileCommand(client),
34+
restartServerCommand(client),
35+
showOutputChannelCommand(client),
36+
toggleEnabledCommand(configService.config),
14237
configService,
14338
);
14439

14540
const outputChannel = window.createOutputChannel(outputChannelName, { log: true });
14641

147-
async function findBinary(): Promise<string> {
148-
let bin = configService.config.binPath;
149-
if (bin) {
150-
try {
151-
await fsPromises.access(bin);
152-
return bin;
153-
} catch {}
154-
}
155-
156-
const workspaceFolders = workspace.workspaceFolders;
157-
const isWindows = process.platform === 'win32';
158-
159-
if (workspaceFolders?.length && !isWindows) {
160-
try {
161-
return await Promise.any(
162-
workspaceFolders.map(async (folder) => {
163-
const binPath = join(
164-
folder.uri.fsPath,
165-
'node_modules',
166-
'.bin',
167-
'oxc_language_server',
168-
);
169-
170-
await fsPromises.access(binPath);
171-
return binPath;
172-
}),
173-
);
174-
} catch {}
175-
}
176-
177-
const ext = isWindows ? '.exe' : '';
178-
// NOTE: The `./target/release` path is aligned with the path defined in .github/workflows/release_vscode.yml
179-
return (
180-
process.env.SERVER_PATH_DEV ??
181-
join(context.extensionPath, `./target/release/oxc_language_server${ext}`)
182-
);
183-
}
184-
185-
const command = await findBinary();
42+
const command = await findBinary(context, configService.config);
18643
const run: Executable = {
18744
command: command!,
18845
options: {
@@ -232,6 +89,7 @@ export async function activate(context: ExtensionContext) {
23289
serverOptions,
23390
clientOptions,
23491
);
92+
23593
client.onNotification(ShowMessageNotification.type, (params) => {
23694
switch (params.type) {
23795
case MessageType.Debug:
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { promises as fsPromises } from 'node:fs';
2+
import { join } from 'node:path';
3+
4+
import { ExtensionContext, workspace } from 'vscode';
5+
6+
import { Config } from './Config';
7+
8+
export default async function findBinary(context: ExtensionContext, config: Config): Promise<string> {
9+
let bin = config.binPath;
10+
if (bin) {
11+
try {
12+
await fsPromises.access(bin);
13+
return bin;
14+
} catch {}
15+
}
16+
17+
const workspaceFolders = workspace.workspaceFolders;
18+
const isWindows = process.platform === 'win32';
19+
20+
if (workspaceFolders?.length && !isWindows) {
21+
try {
22+
return await Promise.any(
23+
workspaceFolders.map(async (folder) => {
24+
const binPath = join(
25+
folder.uri.fsPath,
26+
'node_modules',
27+
'.bin',
28+
'oxc_language_server',
29+
);
30+
31+
await fsPromises.access(binPath);
32+
return binPath;
33+
}),
34+
);
35+
} catch {}
36+
}
37+
38+
const ext = isWindows ? '.exe' : '';
39+
// NOTE: The `./target/release` path is aligned with the path defined in .github/workflows/release_vscode.yml
40+
return (
41+
process.env.SERVER_PATH_DEV ??
42+
join(context.extensionPath, `./target/release/oxc_language_server${ext}`)
43+
);
44+
}

editors/vscode/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@
140140
"server:build:debug": "cargo build -p oxc_language_server",
141141
"server:build:release": "cross-env CARGO_TARGET_DIR=./target cargo build -p oxc_language_server --release",
142142
"lint": "npx oxlint --config=oxlint.json --tsconfig=tsconfig.json",
143-
"test": "esbuild client/config.spec.ts --bundle --outfile=out/config.spec.js --external:vscode --format=cjs --platform=node --target=node16 --minify --sourcemap && vscode-test",
143+
"test": "esbuild \"client/*.spec.ts\" --bundle --outdir=out --external:vscode --format=cjs --platform=node --target=node16 --minify --sourcemap && vscode-test",
144144
"type-check": "tsc --noEmit"
145145
},
146146
"devDependencies": {

0 commit comments

Comments
 (0)