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
4 changes: 2 additions & 2 deletions .dprint.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
"**/testdata",
"packages/vscode-typescript/l10n/**",
"tsc/internal/bundled/libs/**",
"tsc/internal/lsp/lsproto/_generate/*.json",
"tsc/internal/lsp/lsproto/_generate/metaModelSchema.mts",
"tools/scripts/lsp/*.json",
"tools/scripts/lsp/metaModelSchema.mts",
// Needs to be LF to have a working shebang.
"packages/typescript/bin/tsc",
"tsc/internal/bundled/source/**",
Expand Down
37 changes: 26 additions & 11 deletions Herebyfile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -613,32 +613,47 @@ export const generateExtensionTest = task({

async function runGenerateLSP() {
const { GeneratedFile } = await import("./tools/scripts/gen/generatedFile.mts");
const directory = path.join(__dirname, "tsc/internal/lsp/lsproto/_generate");
const directory = path.join(__dirname, "tools/scripts/lsp");
const modelFiles = ["metaModel.json", "metaModelSchema.mts"].map(file => new GeneratedFile(path.join(directory, file), [path.join(directory, "fetchModel.mts"), path.join(__dirname, "package-lock.json")]));
if (!modelFiles.every(file => file.isCurrent(!!options.force))) {
for (const file of modelFiles) file.invalidate();
const { default: fetchModel } = await import("./tsc/internal/lsp/lsproto/_generate/fetchModel.mts");
const { default: fetchModel } = await import("./tools/scripts/lsp/fetchModel.mts");
await fetchModel();
for (const file of modelFiles) file.markCurrent();
}
const output = new GeneratedFile(path.join(directory, "../lsp_generated.go"), [
const output = new GeneratedFile(path.join(__dirname, "tsc/internal/lsp/lsproto/lsp_generated.go"), [
__filename,
path.join(directory, "generate.mts"),
...modelFiles.map(file => file.fileName),
]);
if (output.isCurrent(!!options.force)) {
if (!output.isCurrent(!!options.force)) {
output.invalidate();
const { default: generate } = await import("./tools/scripts/lsp/generate.mts");
await generate();
output.markCurrent();
}
else {
console.log("LSP bindings are up to date.");
return;
}
output.invalidate();
const { default: generate } = await import("./tsc/internal/lsp/lsproto/_generate/generate.mts");
await generate();
output.markCurrent();
const typeScriptOutput = new GeneratedFile(path.join(__dirname, "packages/typescript/src/vscode/protocol.generated.ts"), [
__filename,
path.join(directory, "generate.mts"),
path.join(directory, "generateTypeScript.mts"),
path.join(directory, "typeScript.mts"),
path.join(__dirname, "packages/vscode-typescript/src/lspMiddleware.ts"),
...modelFiles.map(file => file.fileName),
]);
if (!typeScriptOutput.isCurrent(!!options.force)) {
typeScriptOutput.invalidate();
const { default: generate } = await import("./tools/scripts/lsp/generateTypeScript.mts");
await generate();
typeScriptOutput.markCurrent();
}
}

export const generateLSP = task({
name: "generate:lsp",
description: "Generates LSP bindings from the pinned protocol model. Pass --force to regenerate unchanged files.",
description: "Generates Go LSP bindings and extension API types from the pinned protocol model. Pass --force to regenerate unchanged files.",
run: runGenerateLSP,
});

Expand Down Expand Up @@ -1315,7 +1330,7 @@ export const checkVsceVersion = task({
const scriptTsconfigs = [
"./tools/scripts/gen/tsconfig.json",
"./tools/scripts/tsc/tsconfig.json",
"./tsc/internal/lsp/lsproto/_generate/tsconfig.json",
"./tools/scripts/lsp/tsconfig.json",
];

export const checkScripts = task({
Expand Down
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions packages/typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
"./schemas/tsconfig.schema.json": "./schemas/tsconfig.schema.json",
"./schemas/jsconfig.schema.json": "./schemas/jsconfig.schema.json",
".": "./lib/version.cjs",
"./unstable/vscode": {
"types": "./dist/vscode/extensionApi.d.ts"
},
"./unstable/sync": {
"@typescript/source": "./src/api/sync/api.ts",
"default": "./dist/api/sync/api.js"
Expand Down
69 changes: 69 additions & 0 deletions packages/typescript/src/vscode/extensionApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { LspMiddlewareRequests } from "./protocol.generated.ts";
export type * from "./protocol.generated.ts";

export interface Disposable {
dispose(): unknown;
}

export interface Event<T> {
(listener: (event: T) => unknown, thisArgs?: unknown, disposables?: Disposable[] | undefined): Disposable;
}

/** The URI properties used when launching a content mapper. */
export interface ContentMapperUri {
readonly scheme: string;
readonly fsPath: string;
}

export interface ContentMapperManifest {
readonly name: string;
readonly version?: string | undefined;
readonly exec: readonly string[];
readonly cwd?: ContentMapperUri | undefined;
readonly compilerOptions?: readonly string[] | undefined;
readonly dynamicConfig?: boolean | undefined;
}

export interface ContentMapperContribution {
readonly extensions: readonly string[];
readonly inferredProjectContribution?: {
readonly options?: Readonly<Record<string, unknown>> | undefined;
readonly manifest: ContentMapperManifest;
} | undefined;
}

export type LspMiddlewareMethod = keyof LspMiddlewareRequests;

export type DeepReadonly<T> = unknown extends T ? unknown : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]>; } : T;

export type LspMiddlewareResult<M extends LspMiddlewareMethod> = LspMiddlewareRequests[M]["result"];

export type LspMiddlewareContext<M extends LspMiddlewareMethod> = { readonly params: DeepReadonly<LspMiddlewareRequests[M]["params"]>; };

export type LspMiddlewareTransformer<M extends LspMiddlewareMethod> = (
result: LspMiddlewareResult<M>,
context: LspMiddlewareContext<M>,
) => LspMiddlewareResult<M> | PromiseLike<LspMiddlewareResult<M>>;

export interface ExtensionAPI {
onLanguageServerInitialized: Event<void>;
initializeAPIConnection(pipe?: string): Promise<string>;
registerContentMappers(contributorId: string, contributions: readonly ContentMapperContribution[]): Disposable;

/**
* Changes an LSP language-feature response before conversion to VS Code objects.
* Each callback gets its own copy of the request parameters.
*
* Callbacks run in registration order. Order between extensions may vary.
* If a callback fails, we log the error and use the original server response.
*
* Registrations survive server restarts. Disposing removes the callback from
* future responses but does not interrupt a response already being processed.
*
* Requests, notifications, lifecycle messages, and partial results are excluded.
*/
registerLspMiddleware<M extends LspMiddlewareMethod>(
method: M,
transformer: LspMiddlewareTransformer<NoInfer<M>>,
): Disposable;
}
Loading
Loading