Skip to content
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
161 changes: 161 additions & 0 deletions editors/vscode/client/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { ConfigurationChangeEvent, workspace, WorkspaceConfiguration } from 'vscode';
import { IDisposable } from './types';

export class ConfigService implements Config, IDisposable {
private static readonly _namespace = 'oxc';
private readonly _disposables: IDisposable[] = [];
private _inner: WorkspaceConfiguration;
private _runTrigger: 'onSave' | 'onType';
private _enable: boolean;
private _trace: 'off' | 'messages' | 'verbose';
private _configPath: string;
private _binPath: string | undefined;

public onConfigChange:
| ((this: ConfigService, config: ConfigurationChangeEvent) => void)
| undefined;

constructor() {
this._inner = workspace.getConfiguration(ConfigService._namespace);
this._runTrigger = this._inner.get<Trigger>('lint.run') || 'onType';
this._enable = this._inner.get<boolean>('enable') ?? true;
this._trace = this._inner.get<TraceLevel>('trace.server') || 'off';
this._configPath = this._inner.get<string>('configPath') || '.eslintrc';
this._binPath = this._inner.get<string>('path.server');
this.onConfigChange = undefined;

const disposeChangeListener = workspace.onDidChangeConfiguration(
this.onVscodeConfigChange.bind(this),
);
this._disposables.push(disposeChangeListener);
}

get rawConfig(): WorkspaceConfiguration {
return this._inner;
}

get runTrigger(): Trigger {
return this._runTrigger;
}

set runTrigger(value: Trigger) {
this._runTrigger = value;
workspace
.getConfiguration(ConfigService._namespace)
.update('lint.run', value);
}

get enable(): boolean {
return this._enable;
}

set enable(value: boolean) {
this._enable = value;
workspace
.getConfiguration(ConfigService._namespace)
.update('enable', value);
}

get trace(): TraceLevel {
return this._trace;
}

set trace(value: TraceLevel) {
this._trace = value;
workspace
.getConfiguration(ConfigService._namespace)
.update('trace.server', value);
}

get configPath(): string {
return this._configPath;
}

set configPath(value: string) {
this._configPath = value;
workspace
.getConfiguration(ConfigService._namespace)
.update('configPath', value);
}

get binPath(): string | undefined {
return this._binPath;
}

set binPath(value: string | undefined) {
this._binPath = value;
workspace
.getConfiguration(ConfigService._namespace)
.update('path.server', value);
}

private onVscodeConfigChange(event: ConfigurationChangeEvent): void {
if (event.affectsConfiguration(ConfigService._namespace)) {
this._runTrigger = this._inner.get<Trigger>('lint.run') || 'onType';
this._enable = this._inner.get<boolean>('enable') ?? true;
this._trace = this._inner.get<TraceLevel>('trace.server') || 'off';
this._configPath = this._inner.get<string>('configPath') || '.eslintrc';
this._binPath = this._inner.get<string>('path.server');
this.onConfigChange?.call(this, event);
}
}

dispose() {
for (const disposable of this._disposables) {
disposable.dispose();
}
}

public toJSON(): Config {
return {
runTrigger: this.runTrigger,
enable: this.enable,
trace: this.trace,
configPath: this.configPath,
binPath: this.binPath,
};
}
}

type Trigger = 'onSave' | 'onType';
type TraceLevel = 'off' | 'messages' | 'verbose';

/**
* See `"contributes.configuration"` in `package.json`
*/
interface Config {
/**
* When to run the linter and generate diagnostics
* `oxc.lint.run`
*
* @default 'onType'
*/
runTrigger: Trigger;
/**
* `oxc.enable`
*
* @default true
*/
enable: boolean;
/**
* Trace VSCode <-> Oxc Language Server communication
* `oxc.trace.server`
*
* @default 'off'
*/
trace: TraceLevel;
/**
* oxlint config path
*
* `oxc.configPath`
*
* @default ".eslintrc"
*/
configPath: string;
/**
* Path to LSP binary
* `oxc.path.server`
* @default undefined
*/
binPath: string | undefined;
}
42 changes: 10 additions & 32 deletions editors/vscode/client/extension.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { promises as fsPromises } from 'node:fs';

import {
commands,
ConfigurationTarget,
ExtensionContext,
StatusBarAlignment,
StatusBarItem,
ThemeColor,
window,
workspace,
} from 'vscode';
import { commands, ExtensionContext, StatusBarAlignment, StatusBarItem, ThemeColor, window, workspace } from 'vscode';

import { Executable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node';

import { join } from 'node:path';
import { ConfigService } from './config';

const languageClientId = 'oxc-vscode';
const languageClientName = 'oxc';
Expand All @@ -34,6 +26,7 @@ let client: LanguageClient;
let myStatusBarItem: StatusBarItem;

export async function activate(context: ExtensionContext) {
const config = new ConfigService();
const restartCommand = commands.registerCommand(
OxcCommands.RestartServer,
async () => {
Expand Down Expand Up @@ -73,13 +66,7 @@ export async function activate(context: ExtensionContext) {
const toggleEnable = commands.registerCommand(
OxcCommands.ToggleEnable,
() => {
let enabled = workspace
.getConfiguration('oxc_language_server')
.get('enable');
let nextState = !enabled;
workspace
.getConfiguration('oxc_language_server')
.update('enable', nextState, ConfigurationTarget.Global);
config.enable = !config.enable;
},
);

Expand All @@ -88,15 +75,14 @@ export async function activate(context: ExtensionContext) {
showOutputCommand,
showTraceOutputCommand,
toggleEnable,
config,
);

const outputChannel = window.createOutputChannel(outputChannelName);
const traceOutputChannel = window.createOutputChannel(traceOutputChannelName);

async function findBinary(): Promise<string> {
const cfg = workspace.getConfiguration('oxc');

let bin = cfg.get<string>('binPath', '');
let bin = config.binPath;
if (bin) {
try {
await fsPromises.access(bin);
Expand Down Expand Up @@ -150,9 +136,7 @@ export async function activate(context: ExtensionContext) {
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
// Options to control the language client
let clientConfig: any = JSON.parse(
JSON.stringify(workspace.getConfiguration('oxc_language_server')),
);
let clientConfig: any = JSON.parse(JSON.stringify(config.rawConfig));
let clientOptions: LanguageClientOptions = {
// Register the server for plain text documents
documentSelector: [
Expand Down Expand Up @@ -191,17 +175,11 @@ export async function activate(context: ExtensionContext) {
});
});

workspace.onDidChangeConfiguration((e) => {
let isAffected = e.affectsConfiguration('oxc_language_server');
if (!isAffected) {
return;
}
let settings: any = JSON.parse(
JSON.stringify(workspace.getConfiguration('oxc_language_server')),
);
config.onConfigChange = function onConfigChange() {
let settings: any = JSON.parse(JSON.stringify(this));
updateStatsBar(settings.enable);
client.sendNotification('workspace/didChangeConfiguration', { settings });
});
};

function updateStatsBar(enable: boolean) {
if (!myStatusBarItem) {
Expand Down
9 changes: 9 additions & 0 deletions editors/vscode/client/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* A type with a destructor for releasing resources when de-registered by an LSP client.
*
* There's a newer {@link Disposable} interface that works with `using`, but
* VSCode uses this in its APIs.
*/
export interface IDisposable {
dispose(): void | Promise<void>;
}
5 changes: 5 additions & 0 deletions editors/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@
"scope": "window",
"default": ".eslintrc",
"description": "Path to ESlint configuration."
},
"oxc.path.server": {
"type": "string",
"scope": "window",
"description": "Path to Oxc language server binary."
}
}
},
Expand Down