Skip to content

Prepare VS Code extension for marketplace preview #884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
2 changes: 1 addition & 1 deletion Herebyfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,6 @@ export const installExtension = task({
console.log(pc.yellowBright("\nExtension installed. ") + "To enable this extension, set:\n");
console.log(pc.whiteBright(` "typescript.experimental.useTsgo": true\n`));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this going to be the final setting name?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

@DanielRosenwasser DanielRosenwasser May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mjbvz can we at least change how this looks on the UI so Tsgo is not awkwardly Pascal-cased?

image

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned here that extensions may not be able to change it: microsoft/vscode#73374

@rzhao271 Can let the TS team know if there's a way around the auto casing in the settings UI

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the Tsgo setting in the package.json file.

There's no workaround yet. What is the expected display name, "Use TS Go"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting users don’t need to see this setting if they use this
image

console.log("To configure the extension to use built/local instead of its bundled tsgo, set:\n");
console.log(pc.whiteBright(` "typescript-go.executablePath": "${path.join(__dirname, "built", "local", process.platform === "win32" ? "tsgo.exe" : "tsgo")}"\n`));
console.log(pc.whiteBright(` "typescript.native-preview.tsdk": "${path.join(__dirname, "built", "local")}"\n`));
},
});
58 changes: 39 additions & 19 deletions _extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,44 +29,64 @@
"contributes": {
"configuration": [
{
"title": "TypeScript Go",
"title": "TypeScript Native Preview",
"properties": {
"typescript-go.trace.server": {
"typescript.native-preview.trace.server": {
"type": "string",
"enum": [
"off",
"messages",
"verbose"
],
"default": "verbose",
"description": "Trace TypeScript Go server communication."
"description": "Trace TypeScript Go server communication.",
"tags": ["experimental"]
},
"typescript-go.pprofDir": {
"typescript.native-preview.pprofDir": {
"type": "string",
"description": "Directory to write pprof profiles to."
"description": "Directory to write pprof profiles to.",
"tags": ["experimental"]
},
"typescript-go.executablePath": {
"typescript.native-preview.tsdk": {
"type": "string",
"description": "Path to the tsgo binary. If not specified, the extension will look for it in the default location."
"description": "Path to the @typescript/native-preview package or tsgo binary directory. If not specified, the extension will look for it in the default location.",
"tags": ["experimental"]
}
}
}
],
"commands": [
{
"command": "typescript-go.restart",
"title": "TypeScript Go: Restart Language Server",
"enablement": "typescript-go.serverRunning"
"command": "typescript.native-preview.enable",
"title": "Enable (Experimental)",
"enablement": "!typescript.native-preview.serverRunning",
"category": "TypeScript Native Preview"
},
{
"command": "typescript.native-preview.disable",
"title": "Disable",
"enablement": "typescript.native-preview.serverRunning",
"category": "TypeScript Native Preview"
},
{
"command": "typescript.native-preview.restart",
"title": "Restart Language Server",
"enablement": "typescript.native-preview.serverRunning",
"category": "TypeScript Native Preview"
},
{
"command": "typescript.native-preview.output.focus",
"title": "Show Output",
"enablement": "typescript.native-preview.serverRunning",
"category": "TypeScript Native Preview"
},
{
"command": "typescript.native-preview.lsp-trace.focus",
"title": "Show LSP Trace",
"enablement": "typescript.native-preview.serverRunning",
"category": "TypeScript Native Preview"
}
],
"menus": {
"commandPalette": [
{
"command": "typescript-go.restart",
"when": "typescript-go.serverRunning"
}
]
}
]
},
"main": "./dist/extension.js",
"files": [
Expand Down
162 changes: 162 additions & 0 deletions _extension/src/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import * as vscode from "vscode";
import {
LanguageClient,
LanguageClientOptions,
NotebookDocumentFilter,
ServerOptions,
TextDocumentFilter,
TransportKind,
} from "vscode-languageclient/node";
import {
ExeInfo,
getExe,
jsTsLanguageModes,
} from "./util";
import { getLanguageForUri } from "./util";

export class Client {
private outputChannel: vscode.OutputChannel;
private traceOutputChannel: vscode.OutputChannel;
private clientOptions: LanguageClientOptions;
private client?: LanguageClient;
private exe: ExeInfo | undefined;
private onStartedCallbacks: Set<() => void> = new Set();

constructor(outputChannel: vscode.OutputChannel, traceOutputChannel: vscode.OutputChannel) {
this.outputChannel = outputChannel;
this.traceOutputChannel = traceOutputChannel;
this.clientOptions = {
documentSelector: [
...jsTsLanguageModes.map(language => ({ scheme: "file", language })),
...jsTsLanguageModes.map(language => ({ scheme: "untitled", language })),
],
outputChannel: this.outputChannel,
traceOutputChannel: this.traceOutputChannel,
diagnosticPullOptions: {
onChange: true,
onSave: true,
onTabs: true,
match(documentSelector, resource) {
// This function is called when diagnostics are requested but
// only the URI itself is known (e.g. open but not yet focused tabs),
// so will not be present in vscode.workspace.textDocuments.
// See if this file matches without consulting vscode.languages.match
// (which requires a TextDocument).

const language = getLanguageForUri(resource);

for (const selector of documentSelector) {
if (typeof selector === "string") {
if (selector === language) {
return true;
}
continue;
}
if (NotebookDocumentFilter.is(selector)) {
continue;
}
if (TextDocumentFilter.is(selector)) {
if (selector.language !== undefined && selector.language !== language) {
continue;
}

if (selector.scheme !== undefined && selector.scheme !== resource.scheme) {
continue;
}

if (selector.pattern !== undefined) {
// VS Code's glob matcher is not available via the API;
// see: https://github.com/microsoft/vscode/issues/237304
// But, we're only called on selectors passed above, so just ignore this for now.
throw new Error("Not implemented");
}

return true;
}
}

return false;
},
},
};
}

async initialize(context: vscode.ExtensionContext): Promise<void> {
const exe = await getExe(context);
this.start(context, exe);
}

async start(context: vscode.ExtensionContext, exe: { path: string; version: string; }): Promise<void> {
this.exe = exe;
this.outputChannel.appendLine(`Resolved to ${this.exe.path}`);

// Get pprofDir
const config = vscode.workspace.getConfiguration("typescript.native-preview");
const pprofDir = config.get<string>("pprofDir");
const pprofArgs = pprofDir ? ["--pprofDir", pprofDir] : [];

const serverOptions: ServerOptions = {
run: {
command: this.exe.path,
args: ["--lsp", ...pprofArgs],
transport: TransportKind.stdio,
},
debug: {
command: this.exe.path,
args: ["--lsp", ...pprofArgs],
transport: TransportKind.stdio,
},
};

this.client = new LanguageClient(
"typescript.native-preview",
"typescript.native-preview-lsp",
serverOptions,
this.clientOptions,
);

this.outputChannel.appendLine(`Starting language server...`);
await this.client.start();
vscode.commands.executeCommand("setContext", "typescript.native-preview.serverRunning", true);
this.onStartedCallbacks.forEach(callback => callback());
context.subscriptions.push(
new vscode.Disposable(() => {
if (this.client) {
this.client.stop();
}
vscode.commands.executeCommand("setContext", "typescript.native-preview.serverRunning", false);
}),
);
}

getCurrentExe(): { path: string; version: string; } | undefined {
return this.exe;
}

onStarted(callback: () => void): vscode.Disposable {
if (this.exe) {
callback();
return new vscode.Disposable(() => {});
}

this.onStartedCallbacks.add(callback);
return new vscode.Disposable(() => {
this.onStartedCallbacks.delete(callback);
});
}

async restart(context: vscode.ExtensionContext): Promise<void> {
if (!this.client) {
return Promise.reject(new Error("Language client is not initialized"));
}
const exe = await getExe(context);
if (exe.path !== this.exe?.path) {
this.outputChannel.appendLine(`Executable path changed from ${this.exe?.path} to ${exe.path}`);
this.outputChannel.appendLine(`Restarting language server with new executable...`);
return this.start(context, exe);
}

this.outputChannel.appendLine(`Restarting language server...`);
return this.client.restart();
}
}
84 changes: 84 additions & 0 deletions _extension/src/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as vscode from "vscode";
import { Client } from "./client";

export function registerCommands(context: vscode.ExtensionContext, client: Client, outputChannel: vscode.OutputChannel, traceOutputChannel: vscode.OutputChannel): void {
context.subscriptions.push(vscode.commands.registerCommand("typescript.native-preview.enable", () => {
// Fire and forget, because this will restart the extension host and cause an error if we await
updateUseTsgoSetting(true);
}));

context.subscriptions.push(vscode.commands.registerCommand("typescript.native-preview.disable", () => {
// Fire and forget, because this will restart the extension host and cause an error if we await
updateUseTsgoSetting(false);
}));

context.subscriptions.push(vscode.commands.registerCommand("typescript.native-preview.restart", () => {
return client.restart(context);
}));

context.subscriptions.push(vscode.commands.registerCommand("typescript.native-preview.output.focus", () => {
outputChannel.show();
}));

context.subscriptions.push(vscode.commands.registerCommand("typescript.native-preview.lsp-trace.focus", () => {
traceOutputChannel.show();
}));

context.subscriptions.push(vscode.commands.registerCommand("typescript.native-preview.selectVersion", async () => {
}));

context.subscriptions.push(vscode.commands.registerCommand("typescript.native-preview.showMenu", showCommands));
}

/**
* Updates the TypeScript Native Preview setting and reloads extension host.
*/
async function updateUseTsgoSetting(enable: boolean): Promise<void> {
const tsConfig = vscode.workspace.getConfiguration("typescript");
let target: vscode.ConfigurationTarget | undefined;
const useTsgo = tsConfig.inspect("experimental.useTsgo");
if (useTsgo) {
target = useTsgo.workspaceFolderValue !== undefined ? vscode.ConfigurationTarget.WorkspaceFolder :
useTsgo.workspaceValue !== undefined ? vscode.ConfigurationTarget.Workspace :
useTsgo.globalValue !== undefined ? vscode.ConfigurationTarget.Global : undefined;
}
// Update the setting and restart the extension host (needed to change the state of the built-in TS extension)
await tsConfig.update("experimental.useTsgo", enable, target);
await vscode.commands.executeCommand("workbench.action.restartExtensionHost");
}

/**
* Shows the quick pick menu for TypeScript Native Preview commands
*/
async function showCommands(): Promise<void> {
const commands: readonly { label: string; description: string; command: string; }[] = [
{
label: "$(refresh) Restart Server",
description: "Restart the TypeScript Native Preview language server",
command: "typescript.native-preview.restart",
},
{
label: "$(output) Show TS Server Log",
description: "Show the TypeScript Native Preview server log",
command: "typescript.native-preview.output.focus",
},
{
label: "$(debug-console) Show LSP Messages",
description: "Show the LSP communication trace",
command: "typescript.native-preview.lsp-trace.focus",
},
{
label: "$(stop-circle) Disable TypeScript Native Preview",
description: "Switch back to the built-in TypeScript extension",
command: "typescript.native-preview.disable",
},
];

const selected = await vscode.window.showQuickPick(commands, {
placeHolder: "TypeScript Native Preview Commands",
});

if (selected) {
await vscode.commands.executeCommand(selected.command);
}
}
Loading