diff --git a/.vscode/launch.json b/.vscode/launch.json index 9dbed82ee94c2..74dfd6a3da637 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -602,6 +602,17 @@ "order": 4 } }, + { + "name": "Component Explorer", + "type": "chrome", + "request": "launch", + "url": "http://localhost:5199/___explorer", + "preLaunchTask": "Launch Monaco Editor Vite", + "presentation": { + "group": "monaco", + "order": 4 + } + }, { "name": "Monaco Editor - Self Contained Diff Editor", "type": "chrome", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index f601633b570d4..14e637aaf2d1d 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,10 +1,40 @@ { "version": "2.0.0", "tasks": [ + { + "type": "npm", + "script": "watch-client-transpiled", + "label": "Core - Transpile", + "isBackground": true, + "presentation": { + "reveal": "never", + "group": "buildWatchers", + "close": false + }, + "problemMatcher": { + "owner": "esbuild", + "applyTo": "closedDocuments", + "fileLocation": [ + "relative", + "${workspaceFolder}/src" + ], + "pattern": { + "regexp": "^(.+?):(\\d+):(\\d+): ERROR: (.+)$", + "file": 1, + "line": 2, + "column": 3, + "message": 4 + }, + "background": { + "beginsPattern": "Starting transpilation...", + "endsPattern": "Finished transpilation with" + } + } + }, { "type": "npm", "script": "watch-clientd", - "label": "Core - Build", + "label": "Core - Typecheck", "isBackground": true, "presentation": { "reveal": "never", @@ -60,7 +90,8 @@ { "label": "VS Code - Build", "dependsOn": [ - "Core - Build", + "Core - Transpile", + "Core - Typecheck", "Ext - Build" ], "group": { @@ -69,10 +100,22 @@ }, "problemMatcher": [] }, + { + "type": "npm", + "script": "kill-watch-client-transpiled", + "label": "Kill Core - Transpile", + "group": "build", + "presentation": { + "reveal": "never", + "group": "buildKillers", + "close": true + }, + "problemMatcher": "$tsc" + }, { "type": "npm", "script": "kill-watch-clientd", - "label": "Kill Core - Build", + "label": "Kill Core - Typecheck", "group": "build", "presentation": { "reveal": "never", @@ -96,7 +139,8 @@ { "label": "Kill VS Code - Build", "dependsOn": [ - "Kill Core - Build", + "Kill Core - Transpile", + "Kill Core - Typecheck", "Kill Ext - Build" ], "group": "build", diff --git a/build/buildConfig.ts b/build/buildConfig.ts new file mode 100644 index 0000000000000..a4299d2647fe2 --- /dev/null +++ b/build/buildConfig.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/** + * When `true`, self-hosting uses esbuild for fast transpilation (build/next) + * and gulp-tsb only for type-checking (`noEmit`). + * + * When `false`, gulp-tsb does both transpilation and type-checking (old behavior). + */ +export const useEsbuildTranspile = true; diff --git a/build/gulpfile.ts b/build/gulpfile.ts index a57218b844517..1c21618ca608f 100644 --- a/build/gulpfile.ts +++ b/build/gulpfile.ts @@ -13,6 +13,7 @@ import { compileExtensionMediaTask, compileExtensionsTask, watchExtensionsTask } import * as compilation from './lib/compilation.ts'; import * as task from './lib/task.ts'; import * as util from './lib/util.ts'; +import { useEsbuildTranspile } from './buildConfig.ts'; const require = createRequire(import.meta.url); @@ -32,7 +33,9 @@ gulp.task(transpileClientTask); const compileClientTask = task.define('compile-client', task.series(util.rimraf('out'), compilation.copyCodiconsTask, compilation.compileApiProposalNamesTask, compilation.compileTask('src', 'out', false))); gulp.task(compileClientTask); -const watchClientTask = task.define('watch-client', task.series(util.rimraf('out'), task.parallel(compilation.watchTask('out', false), compilation.watchApiProposalNamesTask, compilation.watchCodiconsTask))); +const watchClientTask = useEsbuildTranspile + ? task.define('watch-client', task.parallel(compilation.watchTask('out', false, 'src', { noEmit: true }), compilation.watchApiProposalNamesTask, compilation.watchCodiconsTask)) + : task.define('watch-client', task.series(util.rimraf('out'), task.parallel(compilation.watchTask('out', false), compilation.watchApiProposalNamesTask, compilation.watchCodiconsTask))); gulp.task(watchClientTask); // All diff --git a/build/gulpfile.vscode.ts b/build/gulpfile.vscode.ts index 614f2ee7d1c74..92ae01fd01cf0 100644 --- a/build/gulpfile.vscode.ts +++ b/build/gulpfile.vscode.ts @@ -30,9 +30,12 @@ import { createAsar } from './lib/asar.ts'; import minimist from 'minimist'; import { compileBuildWithoutManglingTask, compileBuildWithManglingTask } from './gulpfile.compile.ts'; import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask } from './gulpfile.extensions.ts'; +import { copyCodiconsTask } from './lib/compilation.ts'; +import { useEsbuildTranspile } from './buildConfig.ts'; import { promisify } from 'util'; import globCallback from 'glob'; import rceditCallback from 'rcedit'; +import * as cp from 'child_process'; const glob = promisify(globCallback); @@ -152,6 +155,81 @@ const bundleVSCodeTask = task.define('bundle-vscode', task.series( )); gulp.task(bundleVSCodeTask); +// esbuild-based bundle tasks (drop-in replacement for bundle-vscode / minify-vscode) +function runEsbuildTranspile(outDir: string, excludeTests: boolean): Promise { + return new Promise((resolve, reject) => { + const scriptPath = path.join(root, 'build/next/index.ts'); + const args = [scriptPath, 'transpile', '--out', outDir]; + if (excludeTests) { + args.push('--exclude-tests'); + } + + const proc = cp.spawn(process.execPath, args, { + cwd: root, + stdio: 'inherit' + }); + + proc.on('error', reject); + proc.on('close', code => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`esbuild transpile failed with exit code ${code} (outDir: ${outDir})`)); + } + }); + }); +} + +function runEsbuildBundle(outDir: string, minify: boolean, nls: boolean, target: 'desktop' | 'server' | 'server-web' = 'desktop', sourceMapBaseUrl?: string): Promise { + return new Promise((resolve, reject) => { + // const tsxPath = path.join(root, 'build/node_modules/tsx/dist/cli.mjs'); + const scriptPath = path.join(root, 'build/next/index.ts'); + const args = [scriptPath, 'bundle', '--out', outDir, '--target', target]; + if (minify) { + args.push('--minify'); + } + if (nls) { + args.push('--nls'); + } + if (sourceMapBaseUrl) { + args.push('--source-map-base-url', sourceMapBaseUrl); + } + + const proc = cp.spawn(process.execPath, args, { + cwd: root, + stdio: 'inherit' + }); + + proc.on('error', reject); + proc.on('close', code => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`esbuild bundle failed with exit code ${code} (outDir: ${outDir}, minify: ${minify}, nls: ${nls}, target: ${target})`)); + } + }); + }); +} + +function runTsGoTypeCheck(): Promise { + return new Promise((resolve, reject) => { + const proc = cp.spawn('tsgo', ['--project', 'src/tsconfig.json', '--noEmit', '--skipLibCheck'], { + cwd: root, + stdio: 'inherit', + shell: true + }); + + proc.on('error', reject); + proc.on('close', code => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`tsgo typecheck failed with exit code ${code}`)); + } + }); + }); +} + const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`; const minifyVSCodeTask = task.define('minify-vscode', task.series( bundleVSCodeTask, @@ -160,7 +238,7 @@ const minifyVSCodeTask = task.define('minify-vscode', task.series( )); gulp.task(minifyVSCodeTask); -const coreCI = task.define('core-ci', task.series( +const coreCIOld = task.define('core-ci-old', task.series( gulp.task('compile-build-with-mangling') as task.Task, task.parallel( gulp.task('minify-vscode') as task.Task, @@ -168,7 +246,27 @@ const coreCI = task.define('core-ci', task.series( gulp.task('minify-vscode-reh-web') as task.Task, ) )); -gulp.task(coreCI); +gulp.task(coreCIOld); + +const coreCIEsbuild = task.define('core-ci-esbuild', task.series( + copyCodiconsTask, + cleanExtensionsBuildTask, + compileNonNativeExtensionsBuildTask, + compileExtensionMediaBuildTask, + // Type-check with tsgo (no emit) + task.define('tsgo-typecheck', () => runTsGoTypeCheck()), + // Transpile individual files to out-build first (for unit tests) + task.define('esbuild-out-build', () => runEsbuildTranspile('out-build', false)), + // Then bundle for shipping (bundles also write NLS files to out-build) + task.parallel( + task.define('esbuild-vscode-min', () => runEsbuildBundle('out-vscode-min', true, true, 'desktop', `${sourceMappingURLBase}/core`)), + task.define('esbuild-vscode-reh-min', () => runEsbuildBundle('out-vscode-reh-min', true, true, 'server', `${sourceMappingURLBase}/core`)), + task.define('esbuild-vscode-reh-web-min', () => runEsbuildBundle('out-vscode-reh-web-min', true, true, 'server-web', `${sourceMappingURLBase}/core`)), + ) +)); +gulp.task(coreCIEsbuild); + +gulp.task(task.define('core-ci', useEsbuildTranspile ? coreCIEsbuild : coreCIOld)); const coreCIPR = task.define('core-ci-pr', task.series( gulp.task('compile-build-without-mangling') as task.Task, @@ -516,27 +614,43 @@ BUILD_TARGETS.forEach(buildTarget => { const sourceFolderName = `out-vscode${dashed(minified)}`; const destinationFolderName = `VSCode${dashed(platform)}${dashed(arch)}`; - const tasks = [ + const packageTasks: task.Task[] = [ compileNativeExtensionsBuildTask, util.rimraf(path.join(buildRoot, destinationFolderName)), packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) ]; if (platform === 'win32') { - tasks.push(patchWin32DependenciesTask(destinationFolderName)); + packageTasks.push(patchWin32DependenciesTask(destinationFolderName)); } - const vscodeTaskCI = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series(...tasks)); + const vscodeTaskCI = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series(...packageTasks)); gulp.task(vscodeTaskCI); - const vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( - minified ? compileBuildWithManglingTask : compileBuildWithoutManglingTask, - cleanExtensionsBuildTask, - compileNonNativeExtensionsBuildTask, - compileExtensionMediaBuildTask, - minified ? minifyVSCodeTask : bundleVSCodeTask, - vscodeTaskCI - )); + let vscodeTask: task.Task; + if (useEsbuildTranspile) { + const esbuildBundleTask = task.define( + `esbuild-bundle${dashed(platform)}${dashed(arch)}${dashed(minified)}`, + () => runEsbuildBundle(sourceFolderName, !!minified, true, 'desktop', minified ? `${sourceMappingURLBase}/core` : undefined) + ); + vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( + copyCodiconsTask, + cleanExtensionsBuildTask, + compileNonNativeExtensionsBuildTask, + compileExtensionMediaBuildTask, + esbuildBundleTask, + vscodeTaskCI + )); + } else { + vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( + minified ? compileBuildWithManglingTask : compileBuildWithoutManglingTask, + cleanExtensionsBuildTask, + compileNonNativeExtensionsBuildTask, + compileExtensionMediaBuildTask, + minified ? minifyVSCodeTask : bundleVSCodeTask, + vscodeTaskCI + )); + } gulp.task(vscodeTask); return vscodeTask; @@ -568,7 +682,7 @@ const innoSetupConfig: Record { + return new Promise((resolve, reject) => { + const scriptPath = path.join(REPO_ROOT, 'build/next/index.ts'); + const args = [scriptPath, 'bundle', '--out', outDir, '--target', 'web']; + if (minify) { + args.push('--minify'); + } + if (nls) { + args.push('--nls'); + } + + const proc = cp.spawn(process.execPath, args, { + cwd: REPO_ROOT, + stdio: 'inherit' + }); + + proc.on('error', reject); + proc.on('close', code => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`esbuild web bundle failed with exit code ${code} (outDir: ${outDir}, minify: ${minify}, nls: ${nls})`)); + } + }); + }); +} + export const vscodeWebResourceIncludes = [ // NLS @@ -110,7 +140,7 @@ export const createVSCodeWebFileContentMapper = (extensionsRoot: string, product }; }; -const bundleVSCodeWebTask = task.define('bundle-vscode-web', task.series( +const bundleVSCodeWebTask = task.define('bundle-vscode-web-OLD', task.series( util.rimraf('out-vscode-web'), optimize.bundleTask( { @@ -125,13 +155,17 @@ const bundleVSCodeWebTask = task.define('bundle-vscode-web', task.series( ) )); -const minifyVSCodeWebTask = task.define('minify-vscode-web', task.series( +const minifyVSCodeWebTask = task.define('minify-vscode-web-OLD', task.series( bundleVSCodeWebTask, util.rimraf('out-vscode-web-min'), optimize.minifyTask('out-vscode-web', `https://main.vscode-cdn.net/sourcemaps/${commit}/core`) )); gulp.task(minifyVSCodeWebTask); +// esbuild-based tasks (new) +const esbuildBundleVSCodeWebTask = task.define('esbuild-vscode-web', () => runEsbuildBundle('out-vscode-web', false, true)); +const esbuildBundleVSCodeWebMinTask = task.define('esbuild-vscode-web-min', () => runEsbuildBundle('out-vscode-web-min', true, true)); + function packageTask(sourceFolderName: string, destinationFolderName: string) { const destination = path.join(BUILD_ROOT, destinationFolderName); @@ -197,8 +231,9 @@ const dashed = (str: string) => (str ? `-${str}` : ``); const destinationFolderName = `vscode-web`; const vscodeWebTaskCI = task.define(`vscode-web${dashed(minified)}-ci`, task.series( + copyCodiconsTask, compileWebExtensionsBuildTask, - minified ? minifyVSCodeWebTask : bundleVSCodeWebTask, + minified ? esbuildBundleVSCodeWebMinTask : esbuildBundleVSCodeWebTask, util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), packageTask(sourceFolderName, destinationFolderName) )); diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index f440dc28dd0c2..2484e884a5512 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -49,14 +49,18 @@ interface ICompileTaskOptions { readonly emitError: boolean; readonly transpileOnly: boolean | { esbuild: boolean }; readonly preserveEnglish: boolean; + readonly noEmit?: boolean; } -export function createCompile(src: string, { build, emitError, transpileOnly, preserveEnglish }: ICompileTaskOptions) { +export function createCompile(src: string, { build, emitError, transpileOnly, preserveEnglish, noEmit }: ICompileTaskOptions) { const projectPath = path.join(import.meta.dirname, '../../', src, 'tsconfig.json'); const overrideOptions = { ...getTypeScriptCompilerOptions(src), inlineSources: Boolean(build) }; if (!build) { overrideOptions.inlineSourceMap = true; } + if (noEmit) { + overrideOptions.noEmit = true; + } const compilation = tsb.create(projectPath, overrideOptions, { verbose: false, @@ -163,10 +167,10 @@ export function compileTask(src: string, out: string, build: boolean, options: { return task; } -export function watchTask(out: string, build: boolean, srcPath: string = 'src'): task.StreamTask { +export function watchTask(out: string, build: boolean, srcPath: string = 'src', options?: { noEmit?: boolean }): task.StreamTask { const task = () => { - const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false }); + const compile = createCompile(srcPath, { build, emitError: false, transpileOnly: false, preserveEnglish: false, noEmit: options?.noEmit }); const src = gulp.src(`${srcPath}/**`, { base: srcPath }); const watchSrc = watch(`${srcPath}/**`, { base: srcPath, readDelay: 200 }); diff --git a/build/lib/nls-analysis.ts b/build/lib/nls-analysis.ts new file mode 100644 index 0000000000000..2b00da68c7a23 --- /dev/null +++ b/build/lib/nls-analysis.ts @@ -0,0 +1,317 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as ts from 'typescript'; + +// ============================================================================ +// Types +// ============================================================================ + +export interface ISpan { + start: ts.LineAndCharacter; + end: ts.LineAndCharacter; +} + +export interface ILocalizeCall { + keySpan: ISpan; + key: string; + valueSpan: ISpan; + value: string; +} + +// ============================================================================ +// AST Collection +// ============================================================================ + +export const CollectStepResult = Object.freeze({ + Yes: 'Yes', + YesAndRecurse: 'YesAndRecurse', + No: 'No', + NoAndRecurse: 'NoAndRecurse' +}); + +export type CollectStepResult = typeof CollectStepResult[keyof typeof CollectStepResult]; + +export function collect(node: ts.Node, fn: (node: ts.Node) => CollectStepResult): ts.Node[] { + const result: ts.Node[] = []; + + function loop(node: ts.Node) { + const stepResult = fn(node); + + if (stepResult === CollectStepResult.Yes || stepResult === CollectStepResult.YesAndRecurse) { + result.push(node); + } + + if (stepResult === CollectStepResult.YesAndRecurse || stepResult === CollectStepResult.NoAndRecurse) { + ts.forEachChild(node, loop); + } + } + + loop(node); + return result; +} + +export function isImportNode(node: ts.Node): boolean { + return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration; +} + +export function isCallExpressionWithinTextSpanCollectStep(textSpan: ts.TextSpan, node: ts.Node): CollectStepResult { + if (!ts.textSpanContainsTextSpan({ start: node.pos, length: node.end - node.pos }, textSpan)) { + return CollectStepResult.No; + } + + return node.kind === ts.SyntaxKind.CallExpression ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse; +} + +// ============================================================================ +// Language Service Host +// ============================================================================ + +export class SingleFileServiceHost implements ts.LanguageServiceHost { + private file: ts.IScriptSnapshot; + private lib: ts.IScriptSnapshot; + private options: ts.CompilerOptions; + private filename: string; + + constructor(options: ts.CompilerOptions, filename: string, contents: string) { + this.options = options; + this.filename = filename; + this.file = ts.ScriptSnapshot.fromString(contents); + this.lib = ts.ScriptSnapshot.fromString(''); + } + + getCompilationSettings = () => this.options; + getScriptFileNames = () => [this.filename]; + getScriptVersion = () => '1'; + getScriptSnapshot = (name: string) => name === this.filename ? this.file : this.lib; + getCurrentDirectory = () => ''; + getDefaultLibFileName = () => 'lib.d.ts'; + + readFile(path: string): string | undefined { + if (path === this.filename) { + return this.file.getText(0, this.file.getLength()); + } + return undefined; + } + + fileExists(path: string): boolean { + return path === this.filename; + } +} + +// ============================================================================ +// Analysis +// ============================================================================ + +/** + * Analyzes TypeScript source code to find localize() or localize2() calls. + */ +export function analyzeLocalizeCalls( + contents: string, + functionName: 'localize' | 'localize2' +): ILocalizeCall[] { + const filename = 'file.ts'; + const options: ts.CompilerOptions = { noResolve: true }; + const serviceHost = new SingleFileServiceHost(options, filename, contents); + const service = ts.createLanguageService(serviceHost); + const sourceFile = ts.createSourceFile(filename, contents, ts.ScriptTarget.ES5, true); + + // Find all imports + const imports = collect(sourceFile, n => isImportNode(n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse); + + // import nls = require('vs/nls'); + const importEqualsDeclarations = imports + .filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration) + .map(n => n as ts.ImportEqualsDeclaration) + .filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) + .filter(d => { + const text = (d.moduleReference as ts.ExternalModuleReference).expression.getText(); + return text.endsWith(`/nls'`) || text.endsWith(`/nls"`) || text.endsWith(`/nls.js'`) || text.endsWith(`/nls.js"`); + }); + + // import ... from 'vs/nls'; + const importDeclarations = imports + .filter(n => n.kind === ts.SyntaxKind.ImportDeclaration) + .map(n => n as ts.ImportDeclaration) + .filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) + .filter(d => { + const text = d.moduleSpecifier.getText(); + return text.endsWith(`/nls'`) || text.endsWith(`/nls"`) || text.endsWith(`/nls.js'`) || text.endsWith(`/nls.js"`); + }) + .filter(d => !!d.importClause && !!d.importClause.namedBindings); + + // `nls.localize(...)` calls via namespace import + const nlsLocalizeCallExpressions: ts.CallExpression[] = []; + + const namespaceImports = importDeclarations + .filter(d => d.importClause?.namedBindings?.kind === ts.SyntaxKind.NamespaceImport) + .map(d => (d.importClause!.namedBindings as ts.NamespaceImport).name); + + const importEqualsNames = importEqualsDeclarations.map(d => d.name); + + for (const name of [...namespaceImports, ...importEqualsNames]) { + const refs = service.getReferencesAtPosition(filename, name.pos + 1) ?? []; + for (const ref of refs) { + if (ref.isWriteAccess) { + continue; + } + const calls = collect(sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ref.textSpan, n)); + const lastCall = calls[calls.length - 1] as ts.CallExpression | undefined; + if (lastCall && + lastCall.expression.kind === ts.SyntaxKind.PropertyAccessExpression && + (lastCall.expression as ts.PropertyAccessExpression).name.getText() === functionName) { + nlsLocalizeCallExpressions.push(lastCall); + } + } + } + + // `localize` named imports + const namedImports = importDeclarations + .filter(d => d.importClause?.namedBindings?.kind === ts.SyntaxKind.NamedImports) + .flatMap(d => Array.from((d.importClause!.namedBindings! as ts.NamedImports).elements)); + + const localizeCallExpressions: ts.CallExpression[] = []; + + // Direct named import: import { localize } from 'vs/nls' + for (const namedImport of namedImports) { + const isTarget = namedImport.name.getText() === functionName || + (namedImport.propertyName && namedImport.propertyName.getText() === functionName); + + if (!isTarget) { + continue; + } + + const searchName = namedImport.propertyName ? namedImport.name : namedImport.name; + const refs = service.getReferencesAtPosition(filename, searchName.pos + 1) ?? []; + + for (const ref of refs) { + if (ref.isWriteAccess) { + continue; + } + const calls = collect(sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ref.textSpan, n)); + const lastCall = calls[calls.length - 1] as ts.CallExpression | undefined; + if (lastCall) { + localizeCallExpressions.push(lastCall); + } + } + } + + // Combine and deduplicate + const allCalls = [...nlsLocalizeCallExpressions, ...localizeCallExpressions]; + const seen = new Set(); + const uniqueCalls = allCalls.filter(call => { + const start = call.getStart(); + if (seen.has(start)) { + return false; + } + seen.add(start); + return true; + }); + + // Convert to ILocalizeCall + return uniqueCalls + .filter(e => e.arguments.length > 1) + .sort((a, b) => a.arguments[0].getStart() - b.arguments[0].getStart()) + .map(e => { + const args = e.arguments; + return { + keySpan: { + start: ts.getLineAndCharacterOfPosition(sourceFile, args[0].getStart()), + end: ts.getLineAndCharacterOfPosition(sourceFile, args[0].getEnd()) + }, + key: args[0].getText(), + valueSpan: { + start: ts.getLineAndCharacterOfPosition(sourceFile, args[1].getStart()), + end: ts.getLineAndCharacterOfPosition(sourceFile, args[1].getEnd()) + }, + value: args[1].getText() + }; + }); +} + +// ============================================================================ +// Text Model for patching +// ============================================================================ + +export class TextModel { + private lines: string[]; + private lineEndings: string[]; + + constructor(contents: string) { + const regex = /\r\n|\r|\n/g; + let index = 0; + let match: RegExpExecArray | null; + + this.lines = []; + this.lineEndings = []; + + while (match = regex.exec(contents)) { + this.lines.push(contents.substring(index, match.index)); + this.lineEndings.push(match[0]); + index = regex.lastIndex; + } + + if (contents.length > 0) { + this.lines.push(contents.substring(index, contents.length)); + this.lineEndings.push(''); + } + } + + get(index: number): string { + return this.lines[index]; + } + + set(index: number, line: string): void { + this.lines[index] = line; + } + + get lineCount(): number { + return this.lines.length; + } + + /** + * Applies patch(es) to the model. + * Multiple patches must be ordered. + * Does not support patches spanning multiple lines. + */ + apply(span: ISpan, content: string): void { + const startLineNumber = span.start.line; + const endLineNumber = span.end.line; + + const startLine = this.lines[startLineNumber] || ''; + const endLine = this.lines[endLineNumber] || ''; + + this.lines[startLineNumber] = [ + startLine.substring(0, span.start.character), + content, + endLine.substring(span.end.character) + ].join(''); + + for (let i = startLineNumber + 1; i <= endLineNumber; i++) { + this.lines[i] = ''; + } + } + + toString(): string { + let result = ''; + for (let i = 0; i < this.lines.length; i++) { + result += this.lines[i] + this.lineEndings[i]; + } + return result; + } +} + +// ============================================================================ +// Utilities +// ============================================================================ + +/** + * Parses a localize key or value expression. + * sourceExpression can be "foo", 'foo', `foo` or { key: 'foo', comment: [...] } + */ +export function parseLocalizeKeyOrValue(sourceExpression: string): string | { key: string; comment?: string[] } { + // eslint-disable-next-line no-eval + return eval(`(${sourceExpression})`); +} diff --git a/build/lib/nls.ts b/build/lib/nls.ts index 39cc07d9d01ee..1c054c97399c2 100644 --- a/build/lib/nls.ts +++ b/build/lib/nls.ts @@ -10,45 +10,10 @@ import File from 'vinyl'; import sm from 'source-map'; import path from 'path'; import sort from 'gulp-sort'; +import { type ISpan, analyzeLocalizeCalls, TextModel, parseLocalizeKeyOrValue } from './nls-analysis.ts'; type FileWithSourcemap = File & { sourceMap: sm.RawSourceMap }; -const CollectStepResult = Object.freeze({ - Yes: 'Yes', - YesAndRecurse: 'YesAndRecurse', - No: 'No', - NoAndRecurse: 'NoAndRecurse' -}); - -type CollectStepResult = typeof CollectStepResult[keyof typeof CollectStepResult]; - -function collect(ts: typeof import('typescript'), node: ts.Node, fn: (node: ts.Node) => CollectStepResult): ts.Node[] { - const result: ts.Node[] = []; - - function loop(node: ts.Node) { - const stepResult = fn(node); - - if (stepResult === CollectStepResult.Yes || stepResult === CollectStepResult.YesAndRecurse) { - result.push(node); - } - - if (stepResult === CollectStepResult.YesAndRecurse || stepResult === CollectStepResult.NoAndRecurse) { - ts.forEachChild(node, loop); - } - } - - loop(node); - return result; -} - -function clone(object: T): T { - const result: Record = {}; - for (const id in object) { - result[id] = object[id]; - } - return result as T; -} - /** * Returns a stream containing the patched JavaScript and source maps. */ @@ -117,10 +82,6 @@ globalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(_nls.allNLSMessages)};`), return eventStream.duplex(input, output); } -function isImportNode(ts: typeof import('typescript'), node: ts.Node): boolean { - return node.kind === ts.SyntaxKind.ImportDeclaration || node.kind === ts.SyntaxKind.ImportEqualsDeclaration; -} - const _nls = (() => { const moduleToNLSKeys: { [name: string /* module ID */]: ILocalizeKey[] /* keys */ } = {}; @@ -138,22 +99,6 @@ const _nls = (() => { nlsKeys?: ILocalizeKey[]; } - interface ISpan { - start: ts.LineAndCharacter; - end: ts.LineAndCharacter; - } - - interface ILocalizeCall { - keySpan: ISpan; - key: string; - valueSpan: ISpan; - value: string; - } - - interface ILocalizeAnalysisResult { - localizeCalls: ILocalizeCall[]; - } - interface IPatch { span: ISpan; content: string; @@ -176,212 +121,11 @@ const _nls = (() => { return { line: position.line - 1, character: position.column }; } - class SingleFileServiceHost implements ts.LanguageServiceHost { - - private file: ts.IScriptSnapshot; - private lib: ts.IScriptSnapshot; - private options: ts.CompilerOptions; - private filename: string; - - constructor(ts: typeof import('typescript'), options: ts.CompilerOptions, filename: string, contents: string) { - this.options = options; - this.filename = filename; - this.file = ts.ScriptSnapshot.fromString(contents); - this.lib = ts.ScriptSnapshot.fromString(''); - } - - getCompilationSettings = () => this.options; - getScriptFileNames = () => [this.filename]; - getScriptVersion = () => '1'; - getScriptSnapshot = (name: string) => name === this.filename ? this.file : this.lib; - getCurrentDirectory = () => ''; - getDefaultLibFileName = () => 'lib.d.ts'; - - readFile(path: string, _encoding?: string): string | undefined { - if (path === this.filename) { - return this.file.getText(0, this.file.getLength()); - } - return undefined; - } - fileExists(path: string): boolean { - return path === this.filename; - } - } - - function isCallExpressionWithinTextSpanCollectStep(ts: typeof import('typescript'), textSpan: ts.TextSpan, node: ts.Node): CollectStepResult { - if (!ts.textSpanContainsTextSpan({ start: node.pos, length: node.end - node.pos }, textSpan)) { - return CollectStepResult.No; - } - - return node.kind === ts.SyntaxKind.CallExpression ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse; - } - - function analyze( - ts: typeof import('typescript'), - contents: string, - functionName: 'localize' | 'localize2', - options: ts.CompilerOptions = {} - ): ILocalizeAnalysisResult { - const filename = 'file.ts'; - const serviceHost = new SingleFileServiceHost(ts, Object.assign(clone(options), { noResolve: true }), filename, contents); - const service = ts.createLanguageService(serviceHost); - const sourceFile = ts.createSourceFile(filename, contents, ts.ScriptTarget.ES5, true); - - // all imports - const imports = lazy(collect(ts, sourceFile, n => isImportNode(ts, n) ? CollectStepResult.YesAndRecurse : CollectStepResult.NoAndRecurse)); - - // import nls = require('vs/nls'); - const importEqualsDeclarations = imports - .filter(n => n.kind === ts.SyntaxKind.ImportEqualsDeclaration) - .map(n => n as ts.ImportEqualsDeclaration) - .filter(d => d.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) - .filter(d => (d.moduleReference as ts.ExternalModuleReference).expression.getText().endsWith(`/nls.js'`)); - - // import ... from 'vs/nls'; - const importDeclarations = imports - .filter(n => n.kind === ts.SyntaxKind.ImportDeclaration) - .map(n => n as ts.ImportDeclaration) - .filter(d => d.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral) - .filter(d => d.moduleSpecifier.getText().endsWith(`/nls.js'`)) - .filter(d => !!d.importClause && !!d.importClause.namedBindings); - - // `nls.localize(...)` calls - const nlsLocalizeCallExpressions = importDeclarations - .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamespaceImport)) - .map(d => (d.importClause!.namedBindings as ts.NamespaceImport).name) - .concat(importEqualsDeclarations.map(d => d.name)) - - // find read-only references to `nls` - .map(n => service.getReferencesAtPosition(filename, n.pos + 1) ?? []) - .flatten() - .filter(r => !r.isWriteAccess) - - // find the deepest call expressions AST nodes that contain those references - .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) - .map(a => lazy(a).last()) - .filter(n => !!n) - .map(n => n as ts.CallExpression) - - // only `localize` calls - .filter(n => n.expression.kind === ts.SyntaxKind.PropertyAccessExpression && (n.expression as ts.PropertyAccessExpression).name.getText() === functionName); - - // `localize` named imports - const allLocalizeImportDeclarations = importDeclarations - .filter(d => !!(d.importClause && d.importClause.namedBindings && d.importClause.namedBindings.kind === ts.SyntaxKind.NamedImports)) - .map(d => (d.importClause!.namedBindings! as ts.NamedImports).elements) - .flatten(); - - // `localize` read-only references - const localizeReferences = allLocalizeImportDeclarations - .filter(d => d.name.getText() === functionName) - .map(n => service.getReferencesAtPosition(filename, n.pos + 1) ?? []) - .flatten() - .filter(r => !r.isWriteAccess); - - // custom named `localize` read-only references - const namedLocalizeReferences = allLocalizeImportDeclarations - .filter(d => !!d.propertyName && d.propertyName.getText() === functionName) - .map(n => service.getReferencesAtPosition(filename, n.name.pos + 1) ?? []) - .flatten() - .filter(r => !r.isWriteAccess); - - // find the deepest call expressions AST nodes that contain those references - const localizeCallExpressions = localizeReferences - .concat(namedLocalizeReferences) - .map(r => collect(ts, sourceFile, n => isCallExpressionWithinTextSpanCollectStep(ts, r.textSpan, n))) - .map(a => lazy(a).last()) - .filter(n => !!n) - .map(n => n as ts.CallExpression); - - // collect everything - const localizeCalls = nlsLocalizeCallExpressions - .concat(localizeCallExpressions) - .map(e => e.arguments) - .filter(a => a.length > 1) - .sort((a, b) => a[0].getStart() - b[0].getStart()) - .map(a => ({ - keySpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[0].getEnd()) }, - key: a[0].getText(), - valueSpan: { start: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getStart()), end: ts.getLineAndCharacterOfPosition(sourceFile, a[1].getEnd()) }, - value: a[1].getText() - })); - - return { - localizeCalls: localizeCalls.toArray() - }; - } - - class TextModel { - - private lines: string[]; - private lineEndings: string[]; - - constructor(contents: string) { - const regex = /\r\n|\r|\n/g; - let index = 0; - let match: RegExpExecArray | null; - - this.lines = []; - this.lineEndings = []; - - while (match = regex.exec(contents)) { - this.lines.push(contents.substring(index, match.index)); - this.lineEndings.push(match[0]); - index = regex.lastIndex; - } - - if (contents.length > 0) { - this.lines.push(contents.substring(index, contents.length)); - this.lineEndings.push(''); - } - } - - public get(index: number): string { - return this.lines[index]; - } - - public set(index: number, line: string): void { - this.lines[index] = line; - } - - public get lineCount(): number { - return this.lines.length; - } - - /** - * Applies patch(es) to the model. - * Multiple patches must be ordered. - * Does not support patches spanning multiple lines. - */ - public apply(patch: IPatch): void { - const startLineNumber = patch.span.start.line; - const endLineNumber = patch.span.end.line; - - const startLine = this.lines[startLineNumber] || ''; - const endLine = this.lines[endLineNumber] || ''; - - this.lines[startLineNumber] = [ - startLine.substring(0, patch.span.start.character), - patch.content, - endLine.substring(patch.span.end.character) - ].join(''); - - for (let i = startLineNumber + 1; i <= endLineNumber; i++) { - this.lines[i] = ''; - } - } - - public toString(): string { - return lazy(this.lines).zip(this.lineEndings) - .flatten().toArray().join(''); - } - } - function patchJavascript(patches: IPatch[], contents: string): string { const model = new TextModel(contents); // patch the localize calls - lazy(patches).reverse().each(p => model.apply(p)); + lazy(patches).reverse().each(p => model.apply(p.span, p.content)); return model.toString(); } @@ -431,24 +175,16 @@ const _nls = (() => { return JSON.parse(smg.toString()); } - function parseLocalizeKeyOrValue(sourceExpression: string) { - // sourceValue can be "foo", 'foo', `foo` or { .... } - // in its evalulated form - // we want to return either the string or the object - // eslint-disable-next-line no-eval - return eval(`(${sourceExpression})`); - } - - function patch(ts: typeof import('typescript'), typescript: string, javascript: string, sourcemap: sm.RawSourceMap, options: { preserveEnglish: boolean }): INlsPatchResult { - const { localizeCalls } = analyze(ts, typescript, 'localize'); - const { localizeCalls: localize2Calls } = analyze(ts, typescript, 'localize2'); + function patch(typescript: string, javascript: string, sourcemap: sm.RawSourceMap, options: { preserveEnglish: boolean }): INlsPatchResult { + const localizeCalls = analyzeLocalizeCalls(typescript, 'localize'); + const localize2Calls = analyzeLocalizeCalls(typescript, 'localize2'); if (localizeCalls.length === 0 && localize2Calls.length === 0) { return { javascript, sourcemap }; } const nlsKeys = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.key)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.key))); - const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value)).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value))); + const nlsMessages = localizeCalls.map(lc => parseLocalizeKeyOrValue(lc.value) as string).concat(localize2Calls.map(lc => parseLocalizeKeyOrValue(lc.value) as string)); const smc = new sm.SourceMapConsumer(sourcemap); const positionFrom = mappedPositionFrom.bind(null, sourcemap.sources[0]); @@ -505,7 +241,6 @@ const _nls = (() => { .replace(/\\/g, '/'); const { javascript, sourcemap, nlsKeys, nlsMessages } = patch( - ts, typescript, javascriptFile.contents!.toString(), javascriptFile.sourceMap, diff --git a/build/next/index.ts b/build/next/index.ts new file mode 100644 index 0000000000000..8d8429012de12 --- /dev/null +++ b/build/next/index.ts @@ -0,0 +1,1134 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as esbuild from 'esbuild'; +import * as fs from 'fs'; +import * as path from 'path'; +import { promisify } from 'util'; +import glob from 'glob'; +import gulpWatch from '../lib/watch/index.ts'; +import { nlsPlugin, createNLSCollector, finalizeNLS, postProcessNLS } from './nls-plugin.ts'; +import { getVersion } from '../lib/getVersion.ts'; +import product from '../../product.json' with { type: 'json' }; +import packageJson from '../../package.json' with { type: 'json' }; +import { useEsbuildTranspile } from '../buildConfig.ts'; + +const globAsync = promisify(glob); + +// ============================================================================ +// Configuration +// ============================================================================ + +const REPO_ROOT = path.dirname(path.dirname(import.meta.dirname)); +const commit = getVersion(REPO_ROOT); +const quality = (product as { quality?: string }).quality; +const version = (quality && quality !== 'stable') ? `${packageJson.version}-${quality}` : packageJson.version; + +// CLI: transpile [--watch] | bundle [--minify] [--nls] [--out ] +const command = process.argv[2]; // 'transpile' or 'bundle' + +function getArgValue(name: string): string | undefined { + const index = process.argv.indexOf(name); + if (index !== -1 && index + 1 < process.argv.length) { + return process.argv[index + 1]; + } + return undefined; +} + +const options = { + watch: process.argv.includes('--watch'), + minify: process.argv.includes('--minify'), + nls: process.argv.includes('--nls'), + excludeTests: process.argv.includes('--exclude-tests'), + out: getArgValue('--out'), + target: getArgValue('--target') ?? 'desktop', // 'desktop' | 'server' | 'server-web' | 'web' + sourceMapBaseUrl: getArgValue('--source-map-base-url'), +}; + +// Build targets +type BuildTarget = 'desktop' | 'server' | 'server-web' | 'web'; + +const SRC_DIR = 'src'; +const OUT_DIR = 'out'; +const OUT_VSCODE_DIR = 'out-vscode'; + +// UTF-8 BOM - added to test files with 'utf8' in the path (matches gulp build behavior) +const UTF8_BOM = Buffer.from([0xef, 0xbb, 0xbf]); + +// ============================================================================ +// Entry Points (from build/buildfile.ts) +// ============================================================================ + +// Workers - shared between targets +const workerEntryPoints = [ + 'vs/editor/common/services/editorWebWorkerMain', + 'vs/workbench/api/worker/extensionHostWorkerMain', + 'vs/workbench/contrib/notebook/common/services/notebookWebWorkerMain', + 'vs/workbench/services/languageDetection/browser/languageDetectionWebWorkerMain', + 'vs/workbench/services/search/worker/localFileSearchMain', + 'vs/workbench/contrib/output/common/outputLinkComputerMain', + 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.workerMain', +]; + +// Desktop-only workers (use electron-browser) +const desktopWorkerEntryPoints = [ + 'vs/platform/profiling/electron-browser/profileAnalysisWorkerMain', +]; + +// Desktop workbench and code entry points +const desktopEntryPoints = [ + 'vs/workbench/workbench.desktop.main', + 'vs/workbench/contrib/debug/node/telemetryApp', + 'vs/platform/files/node/watcher/watcherMain', + 'vs/platform/terminal/node/ptyHostMain', + 'vs/workbench/api/node/extensionHostProcess', +]; + +const codeEntryPoints = [ + 'vs/code/node/cliProcessMain', + 'vs/code/electron-utility/sharedProcess/sharedProcessMain', + 'vs/code/electron-browser/workbench/workbench', +]; + +// Web entry points (used in server-web and vscode-web) +const webEntryPoints = [ + 'vs/workbench/workbench.web.main.internal', + 'vs/code/browser/workbench/workbench', +]; + +const keyboardMapEntryPoints = [ + 'vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.linux', + 'vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.darwin', + 'vs/workbench/services/keybinding/browser/keyboardLayouts/layout.contribution.win', +]; + +// Server entry points (reh) +const serverEntryPoints = [ + 'vs/workbench/api/node/extensionHostProcess', + 'vs/platform/files/node/watcher/watcherMain', + 'vs/platform/terminal/node/ptyHostMain', +]; + +// Bootstrap files per target +const bootstrapEntryPointsDesktop = [ + 'main', + 'cli', + 'bootstrap-fork', +]; + +const bootstrapEntryPointsServer = [ + 'server-main', + 'server-cli', + 'bootstrap-fork', +]; + +/** + * Get entry points for a build target. + */ +function getEntryPointsForTarget(target: BuildTarget): string[] { + switch (target) { + case 'desktop': + return [ + ...workerEntryPoints, + ...desktopWorkerEntryPoints, + ...desktopEntryPoints, + ...codeEntryPoints, + ]; + case 'server': + return [ + ...serverEntryPoints, + ]; + case 'server-web': + return [ + ...serverEntryPoints, + ...workerEntryPoints, + ...webEntryPoints, + ...keyboardMapEntryPoints, + ]; + case 'web': + return [ + ...workerEntryPoints, + 'vs/workbench/workbench.web.main.internal', // web workbench only (no browser shell) + ...keyboardMapEntryPoints, + ]; + default: + throw new Error(`Unknown target: ${target}`); + } +} + +/** + * Get bootstrap entry points for a build target. + */ +function getBootstrapEntryPointsForTarget(target: BuildTarget): string[] { + switch (target) { + case 'desktop': + return bootstrapEntryPointsDesktop; + case 'server': + case 'server-web': + return bootstrapEntryPointsServer; + case 'web': + return []; // Web has no bootstrap files (served by external server) + default: + throw new Error(`Unknown target: ${target}`); + } +} + +/** + * Get entry points that should bundle CSS (workbench mains). + */ +function getCssBundleEntryPointsForTarget(target: BuildTarget): Set { + switch (target) { + case 'desktop': + return new Set([ + 'vs/workbench/workbench.desktop.main', + 'vs/code/electron-browser/workbench/workbench', + ]); + case 'server': + return new Set(); // Server has no UI + case 'server-web': + return new Set([ + 'vs/workbench/workbench.web.main.internal', + 'vs/code/browser/workbench/workbench', + ]); + case 'web': + return new Set([ + 'vs/workbench/workbench.web.main.internal', + ]); + default: + throw new Error(`Unknown target: ${target}`); + } +} + +// ============================================================================ +// Resource Patterns (files to copy, not transpile/bundle) +// ============================================================================ + +// Common resources needed by all targets +const commonResourcePatterns = [ + // Tree-sitter queries + 'vs/editor/common/languages/highlights/*.scm', + 'vs/editor/common/languages/injections/*.scm', +]; + +// Resources only needed for dev/transpile builds (these get bundled into the main +// JS/CSS bundles for production, so separate copies are redundant) +const devOnlyResourcePatterns = [ + // Fonts (esbuild file loader copies to media/codicon.ttf for production) + 'vs/base/browser/ui/codicons/codicon/codicon.ttf', + + // Vendor JavaScript libraries (bundled into workbench main JS for production) + 'vs/base/common/marked/marked.js', + 'vs/base/common/semver/semver.js', + 'vs/base/browser/dompurify/dompurify.js', +]; + +// Resources for desktop target +const desktopResourcePatterns = [ + ...commonResourcePatterns, + + // HTML + 'vs/code/electron-browser/workbench/workbench.html', + 'vs/code/electron-browser/workbench/workbench-dev.html', + 'vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', + 'vs/workbench/contrib/webview/browser/pre/*.html', + + // Webview pre scripts + 'vs/workbench/contrib/webview/browser/pre/*.js', + + // Shell scripts + 'vs/base/node/*.sh', + 'vs/workbench/contrib/terminal/common/scripts/*.sh', + 'vs/workbench/contrib/terminal/common/scripts/*.ps1', + 'vs/workbench/contrib/terminal/common/scripts/*.psm1', + 'vs/workbench/contrib/terminal/common/scripts/*.fish', + 'vs/workbench/contrib/terminal/common/scripts/*.zsh', + 'vs/workbench/contrib/externalTerminal/**/*.scpt', + + // Media - audio + 'vs/platform/accessibilitySignal/browser/media/*.mp3', + + // Media - images + 'vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.svg', + 'vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.png', + 'vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}', + 'vs/workbench/services/extensionManagement/common/media/*.svg', + 'vs/workbench/services/extensionManagement/common/media/*.png', + 'vs/workbench/browser/parts/editor/media/*.png', + 'vs/workbench/contrib/debug/browser/media/*.png', +]; + +// Resources for server target (minimal - no UI) +const serverResourcePatterns = [ + // Shell scripts for process monitoring + 'vs/base/node/cpuUsage.sh', + 'vs/base/node/ps.sh', + + // External Terminal + 'vs/workbench/contrib/externalTerminal/**/*.scpt', + + // Terminal shell integration + 'vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1', + 'vs/workbench/contrib/terminal/common/scripts/CodeTabExpansion.psm1', + 'vs/workbench/contrib/terminal/common/scripts/GitTabExpansion.psm1', + 'vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh', + 'vs/workbench/contrib/terminal/common/scripts/shellIntegration-env.zsh', + 'vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh', + 'vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh', + 'vs/workbench/contrib/terminal/common/scripts/shellIntegration-login.zsh', + 'vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish', +]; + +// Resources for server-web target (server + web UI) +const serverWebResourcePatterns = [ + ...serverResourcePatterns, + ...commonResourcePatterns, + + // Web HTML + 'vs/code/browser/workbench/workbench.html', + 'vs/code/browser/workbench/workbench-dev.html', + 'vs/code/browser/workbench/callback.html', + 'vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', + 'vs/workbench/contrib/webview/browser/pre/*.html', + + // Webview pre scripts + 'vs/workbench/contrib/webview/browser/pre/*.js', + + // Media - audio + 'vs/platform/accessibilitySignal/browser/media/*.mp3', + + // Media - images + 'vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.svg', + 'vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.png', + 'vs/workbench/contrib/extensions/browser/media/*.svg', + 'vs/workbench/contrib/extensions/browser/media/*.png', + 'vs/workbench/services/extensionManagement/common/media/*.svg', + 'vs/workbench/services/extensionManagement/common/media/*.png', +]; + +// Resources for standalone web target (browser-only, no server) +const webResourcePatterns = [ + ...commonResourcePatterns, + + // Web HTML + 'vs/code/browser/workbench/workbench.html', + 'vs/code/browser/workbench/workbench-dev.html', + 'vs/code/browser/workbench/callback.html', + 'vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html', + 'vs/workbench/contrib/webview/browser/pre/*.html', + + // Webview pre scripts + 'vs/workbench/contrib/webview/browser/pre/*.js', + + // Media - audio + 'vs/platform/accessibilitySignal/browser/media/*.mp3', + + // Media - images + 'vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.svg', + 'vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.png', + 'vs/workbench/contrib/extensions/browser/media/*.svg', + 'vs/workbench/contrib/extensions/browser/media/*.png', + 'vs/workbench/services/extensionManagement/common/media/*.svg', + 'vs/workbench/services/extensionManagement/common/media/*.png', +]; + +/** + * Get resource patterns for a build target. + */ +function getResourcePatternsForTarget(target: BuildTarget): string[] { + switch (target) { + case 'desktop': + return desktopResourcePatterns; + case 'server': + return serverResourcePatterns; + case 'server-web': + return serverWebResourcePatterns; + case 'web': + return webResourcePatterns; + default: + throw new Error(`Unknown target: ${target}`); + } +} + +// Test fixtures (only copied for development builds, not production) +const testFixturePatterns = [ + '**/test/**/*.json', + '**/test/**/*.txt', + '**/test/**/*.snap', + '**/test/**/*.tst', + '**/test/**/*.html', + '**/test/**/*.js', + '**/test/**/*.jxs', + '**/test/**/*.tsx', + '**/test/**/*.css', + '**/test/**/*.png', + '**/test/**/*.md', + '**/test/**/*.zip', + '**/test/**/*.pdf', + '**/test/**/*.qwoff', + '**/test/**/*.wuff', + '**/test/**/*.less', + // Files without extensions (executables, etc.) + '**/test/**/fixtures/executable/*', +]; + +// ============================================================================ +// Utilities +// ============================================================================ + +async function cleanDir(dir: string): Promise { + const fullPath = path.join(REPO_ROOT, dir); + console.log(`[clean] ${dir}`); + await fs.promises.rm(fullPath, { recursive: true, force: true }); + await fs.promises.mkdir(fullPath, { recursive: true }); +} + +/** + * Scan for built-in extensions in the given directory. + * Returns an array of extension entries for the builtinExtensionsScannerService. + */ +function scanBuiltinExtensions(extensionsRoot: string): Array<{ extensionPath: string; packageJSON: unknown }> { + const result: Array<{ extensionPath: string; packageJSON: unknown }> = []; + const extensionsPath = path.join(REPO_ROOT, extensionsRoot); + + if (!fs.existsSync(extensionsPath)) { + return result; + } + + for (const entry of fs.readdirSync(extensionsPath, { withFileTypes: true })) { + if (!entry.isDirectory()) { + continue; + } + const packageJsonPath = path.join(extensionsPath, entry.name, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + try { + const packageJSON = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + result.push({ + extensionPath: entry.name, + packageJSON + }); + } catch (e) { + // Skip invalid extensions + } + } + } + + return result; +} + +/** + * Get the date from the out directory date file, or return current date. + */ +function readISODate(outDir: string): string { + try { + return fs.readFileSync(path.join(REPO_ROOT, outDir, 'date'), 'utf8'); + } catch { + return new Date().toISOString(); + } +} + +/** + * Only used to make encoding tests happy. The source files don't have a BOM but the + * tests expect one... so we add it here. + */ +function needsBomAdded(filePath: string): boolean { + return /([\/\\])test\1.*utf8/.test(filePath); +} + +async function copyFile(srcPath: string, destPath: string): Promise { + await fs.promises.mkdir(path.dirname(destPath), { recursive: true }); + + if (needsBomAdded(srcPath)) { + const content = await fs.promises.readFile(srcPath); + if (content[0] !== 0xef || content[1] !== 0xbb || content[2] !== 0xbf) { + await fs.promises.writeFile(destPath, Buffer.concat([UTF8_BOM, content])); + return; + } + } + await fs.promises.copyFile(srcPath, destPath); +} + +/** + * Standalone TypeScript files that need to be compiled separately (not bundled). + * These run in special contexts (e.g., Electron preload) where bundling isn't appropriate. + * Only needed for desktop target. + */ +const desktopStandaloneFiles = [ + 'vs/base/parts/sandbox/electron-browser/preload.ts', + 'vs/base/parts/sandbox/electron-browser/preload-aux.ts', + 'vs/platform/browserView/electron-browser/preload-browserView.ts', +]; + +async function compileStandaloneFiles(outDir: string, doMinify: boolean, target: BuildTarget): Promise { + // Only desktop needs preload scripts + if (target !== 'desktop') { + return; + } + + console.log(`[standalone] Compiling ${desktopStandaloneFiles.length} standalone files...`); + + const banner = `/*!-------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/`; + + await Promise.all(desktopStandaloneFiles.map(async (file) => { + const entryPath = path.join(REPO_ROOT, SRC_DIR, file); + const outPath = path.join(REPO_ROOT, outDir, file.replace(/\.ts$/, '.js')); + + await esbuild.build({ + entryPoints: [entryPath], + outfile: outPath, + bundle: false, // Don't bundle - these are standalone scripts + format: 'cjs', // CommonJS for Electron preload + platform: 'node', + target: ['es2024'], + sourcemap: 'external', + sourcesContent: false, + minify: doMinify, + banner: { js: banner }, + logLevel: 'warning', + }); + })); + + console.log(`[standalone] Done`); +} + +async function copyCssFiles(outDir: string, excludeTests = false): Promise { + // Copy all CSS files from src to output (they're imported by JS) + const cssFiles = await globAsync('**/*.css', { + cwd: path.join(REPO_ROOT, SRC_DIR), + ignore: excludeTests ? ['**/test/**'] : [], + }); + + for (const file of cssFiles) { + const srcPath = path.join(REPO_ROOT, SRC_DIR, file); + const destPath = path.join(REPO_ROOT, outDir, file); + + await copyFile(srcPath, destPath); + } + + return cssFiles.length; +} + +async function copyResources(outDir: string, target: BuildTarget, excludeDevFiles = false, excludeTests = false): Promise { + console.log(`[resources] Copying to ${outDir} for target '${target}'...`); + let copied = 0; + + const ignorePatterns: string[] = []; + if (excludeTests) { + ignorePatterns.push('**/test/**'); + } + if (excludeDevFiles) { + ignorePatterns.push('**/*-dev.html'); + } + + const resourcePatterns = getResourcePatternsForTarget(target); + for (const pattern of resourcePatterns) { + const files = await globAsync(pattern, { + cwd: path.join(REPO_ROOT, SRC_DIR), + ignore: ignorePatterns, + }); + + for (const file of files) { + const srcPath = path.join(REPO_ROOT, SRC_DIR, file); + const destPath = path.join(REPO_ROOT, outDir, file); + + await copyFile(srcPath, destPath); + copied++; + } + } + + // Copy test fixtures (only for development builds) + if (!excludeTests) { + for (const pattern of testFixturePatterns) { + const files = await globAsync(pattern, { + cwd: path.join(REPO_ROOT, SRC_DIR), + }); + + for (const file of files) { + const srcPath = path.join(REPO_ROOT, SRC_DIR, file); + const destPath = path.join(REPO_ROOT, outDir, file); + + await copyFile(srcPath, destPath); + copied++; + } + } + } + + // Copy dev-only resources (vendor JS, codicon font) - only for development/transpile + // builds. In production bundles these are inlined by esbuild. + if (!excludeDevFiles) { + for (const pattern of devOnlyResourcePatterns) { + const files = await globAsync(pattern, { + cwd: path.join(REPO_ROOT, SRC_DIR), + ignore: ignorePatterns, + }); + for (const file of files) { + await copyFile(path.join(REPO_ROOT, SRC_DIR, file), path.join(REPO_ROOT, outDir, file)); + copied++; + } + } + } + + // Copy CSS files (only for development/transpile builds, not production bundles + // where CSS is already bundled into combined files like workbench.desktop.main.css) + if (!excludeDevFiles) { + const cssCount = await copyCssFiles(outDir, excludeTests); + copied += cssCount; + console.log(`[resources] Copied ${copied} files (${cssCount} CSS)`); + } else { + console.log(`[resources] Copied ${copied} files (CSS skipped - bundled)`); + } +} + +// ============================================================================ +// Plugins +// ============================================================================ + +function inlineMinimistPlugin(): esbuild.Plugin { + return { + name: 'inline-minimist', + setup(build) { + build.onResolve({ filter: /^minimist$/ }, () => ({ + path: path.join(REPO_ROOT, 'node_modules/minimist/index.js'), + external: false, + })); + }, + }; +} + +function cssExternalPlugin(): esbuild.Plugin { + // Mark CSS imports as external so they stay as import statements + // The CSS files are copied separately and loaded by the browser at runtime + return { + name: 'css-external', + setup(build) { + build.onResolve({ filter: /\.css$/ }, (args) => ({ + path: args.path, + external: true, + })); + }, + }; +} + +/** + * esbuild plugin that transforms source files to inject build-time configuration. + * This runs during onLoad so the transformation happens before esbuild processes the content, + * ensuring placeholders like `/*BUILD->INSERT_PRODUCT_CONFIGURATION* /` are replaced + * before esbuild strips them as non-legal comments. + */ +function fileContentMapperPlugin(outDir: string, target: BuildTarget): esbuild.Plugin { + // Cache the replacement strings (computed once) + let productConfigReplacement: string | undefined; + let builtinExtensionsReplacement: string | undefined; + + return { + name: 'file-content-mapper', + setup(build) { + build.onLoad({ filter: /\.ts$/ }, async (args) => { + // Skip .d.ts files + if (args.path.endsWith('.d.ts')) { + return undefined; + } + + let contents = await fs.promises.readFile(args.path, 'utf-8'); + let modified = false; + + // Inject product configuration + if (contents.includes('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/')) { + if (productConfigReplacement === undefined) { + // For server-web, remove webEndpointUrlTemplate + const productForTarget = target === 'server-web' + ? { ...product, webEndpointUrlTemplate: undefined } + : product; + const productConfiguration = JSON.stringify({ + ...productForTarget, + version, + commit, + date: readISODate(outDir) + }); + // Remove the outer braces since the placeholder is inside an object literal + productConfigReplacement = productConfiguration.substring(1, productConfiguration.length - 1); + } + contents = contents.replace('/*BUILD->INSERT_PRODUCT_CONFIGURATION*/', () => productConfigReplacement!); + modified = true; + } + + // Inject built-in extensions list + if (contents.includes('/*BUILD->INSERT_BUILTIN_EXTENSIONS*/')) { + if (builtinExtensionsReplacement === undefined) { + // Web target uses .build/web/extensions (from compileWebExtensionsBuildTask) + // Other targets use .build/extensions + const extensionsRoot = target === 'web' ? '.build/web/extensions' : '.build/extensions'; + const builtinExtensions = JSON.stringify(scanBuiltinExtensions(extensionsRoot)); + // Remove the outer brackets since the placeholder is inside an array literal + builtinExtensionsReplacement = builtinExtensions.substring(1, builtinExtensions.length - 1); + } + contents = contents.replace('/*BUILD->INSERT_BUILTIN_EXTENSIONS*/', () => builtinExtensionsReplacement!); + modified = true; + } + + if (modified) { + return { contents, loader: 'ts' }; + } + + // No modifications, let esbuild handle normally + return undefined; + }); + }, + }; +} + +// ============================================================================ +// Transpile (Goal 1: TS → JS using esbuild.transform for maximum speed) +// ============================================================================ + +// Shared transform options for single-file transpilation +const transformOptions: esbuild.TransformOptions = { + loader: 'ts', + format: 'esm', + target: 'es2024', + sourcemap: 'inline', + sourcesContent: false, + tsconfigRaw: JSON.stringify({ + compilerOptions: { + experimentalDecorators: true, + useDefineForClassFields: false + } + }), +}; + +async function transpileFile(srcPath: string, destPath: string, relativePath: string): Promise { + const source = await fs.promises.readFile(srcPath, 'utf-8'); + const result = await esbuild.transform(source, { + ...transformOptions, + sourcefile: relativePath, + }); + + await fs.promises.mkdir(path.dirname(destPath), { recursive: true }); + await fs.promises.writeFile(destPath, result.code); +} + +async function transpile(outDir: string, excludeTests: boolean): Promise { + // Find all .ts files + const ignorePatterns = ['**/*.d.ts']; + if (excludeTests) { + ignorePatterns.push('**/test/**'); + } + + const files = await globAsync('**/*.ts', { + cwd: path.join(REPO_ROOT, SRC_DIR), + ignore: ignorePatterns, + }); + + console.log(`[transpile] Found ${files.length} files`); + + // Transpile all files in parallel using esbuild.transform (fastest approach) + await Promise.all(files.map(file => { + const srcPath = path.join(REPO_ROOT, SRC_DIR, file); + const destPath = path.join(REPO_ROOT, outDir, file.replace(/\.ts$/, '.js')); + return transpileFile(srcPath, destPath, file); + })); +} + +// ============================================================================ +// Bundle (Goal 2: JS → bundled JS) +// ============================================================================ + +async function bundle(outDir: string, doMinify: boolean, doNls: boolean, target: BuildTarget, sourceMapBaseUrl?: string): Promise { + await cleanDir(outDir); + + // Write build date file (used by packaging to embed in product.json) + const outDirPath = path.join(REPO_ROOT, outDir); + await fs.promises.mkdir(outDirPath, { recursive: true }); + await fs.promises.writeFile(path.join(outDirPath, 'date'), new Date().toISOString(), 'utf8'); + + console.log(`[bundle] ${SRC_DIR} → ${outDir} (target: ${target})${doMinify ? ' (minify)' : ''}${doNls ? ' (nls)' : ''}`); + const t1 = Date.now(); + + // Read TSLib for banner + const tslibPath = path.join(REPO_ROOT, 'node_modules/tslib/tslib.es6.js'); + const tslib = await fs.promises.readFile(tslibPath, 'utf-8'); + const banner = { + js: `/*!-------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ +${tslib}`, + css: `/*!-------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/`, + }; + + // Shared TypeScript options for bundling directly from source + const tsconfigRaw = JSON.stringify({ + compilerOptions: { + experimentalDecorators: true, + useDefineForClassFields: false + } + }); + + // Create shared NLS collector (only used if doNls is true) + const nlsCollector = createNLSCollector(); + const preserveEnglish = false; // Production mode: replace messages with null + + // Get entry points based on target + const allEntryPoints = getEntryPointsForTarget(target); + const bootstrapEntryPoints = getBootstrapEntryPointsForTarget(target); + const bundleCssEntryPoints = getCssBundleEntryPointsForTarget(target); + + // Collect all build results (with write: false) + const buildResults: { outPath: string; result: esbuild.BuildResult }[] = []; + + // Create the file content mapper plugin (injects product config, builtin extensions) + const contentMapperPlugin = fileContentMapperPlugin(outDir, target); + + // Bundle each entry point directly from TypeScript source + await Promise.all(allEntryPoints.map(async (entryPoint) => { + const entryPath = path.join(REPO_ROOT, SRC_DIR, `${entryPoint}.ts`); + const outPath = path.join(REPO_ROOT, outDir, `${entryPoint}.js`); + + // Use CSS external plugin for entry points that don't need bundled CSS + const plugins: esbuild.Plugin[] = bundleCssEntryPoints.has(entryPoint) ? [] : [cssExternalPlugin()]; + // Add content mapper plugin to inject product config and builtin extensions + plugins.push(contentMapperPlugin); + if (doNls) { + plugins.unshift(nlsPlugin({ + baseDir: path.join(REPO_ROOT, SRC_DIR), + collector: nlsCollector, + })); + } + + // For entry points that bundle CSS, we need to use outdir instead of outfile + // because esbuild can't produce multiple output files (JS + CSS) with outfile + const needsCssBundling = bundleCssEntryPoints.has(entryPoint); + + const buildOptions: esbuild.BuildOptions = { + entryPoints: needsCssBundling + ? [{ in: entryPath, out: entryPoint }] + : [entryPath], + ...(needsCssBundling + ? { outdir: path.join(REPO_ROOT, outDir) } + : { outfile: outPath }), + bundle: true, + format: 'esm', + platform: 'neutral', + target: ['es2024'], + packages: 'external', + sourcemap: 'external', + sourcesContent: true, + minify: doMinify, + treeShaking: true, + banner, + loader: { + '.ttf': 'file', + '.svg': 'file', + '.png': 'file', + '.sh': 'file', + }, + assetNames: 'media/[name]', + plugins, + write: false, // Don't write yet, we need to post-process + logLevel: 'warning', + logOverride: { + 'unsupported-require-call': 'silent', + }, + tsconfigRaw, + }; + + const result = await esbuild.build(buildOptions); + + buildResults.push({ outPath, result }); + })); + + // Bundle bootstrap files (with minimist inlined) directly from TypeScript source + for (const entry of bootstrapEntryPoints) { + const entryPath = path.join(REPO_ROOT, SRC_DIR, `${entry}.ts`); + if (!fs.existsSync(entryPath)) { + console.log(`[bundle] Skipping ${entry} (not found)`); + continue; + } + + const outPath = path.join(REPO_ROOT, outDir, `${entry}.js`); + + const bootstrapPlugins: esbuild.Plugin[] = [inlineMinimistPlugin(), contentMapperPlugin]; + if (doNls) { + bootstrapPlugins.unshift(nlsPlugin({ + baseDir: path.join(REPO_ROOT, SRC_DIR), + collector: nlsCollector, + })); + } + + const result = await esbuild.build({ + entryPoints: [entryPath], + outfile: outPath, + bundle: true, + format: 'esm', + platform: 'node', + target: ['es2024'], + packages: 'external', + sourcemap: 'external', + sourcesContent: true, + minify: doMinify, + treeShaking: true, + banner, + plugins: bootstrapPlugins, + write: false, // Don't write yet, we need to post-process + logLevel: 'warning', + logOverride: { + 'unsupported-require-call': 'silent', + }, + tsconfigRaw, + }); + + buildResults.push({ outPath, result }); + } + + // Finalize NLS: sort entries, assign indices, write metadata files + let indexMap = new Map(); + if (doNls) { + // Also write NLS files to out-build for backwards compatibility with test runner + const nlsResult = await finalizeNLS( + nlsCollector, + path.join(REPO_ROOT, outDir), + [path.join(REPO_ROOT, 'out-build')] + ); + indexMap = nlsResult.indexMap; + } + + // Post-process and write all output files + let bundled = 0; + for (const { result } of buildResults) { + if (!result.outputFiles) { + continue; + } + + for (const file of result.outputFiles) { + await fs.promises.mkdir(path.dirname(file.path), { recursive: true }); + + if (file.path.endsWith('.js') || file.path.endsWith('.css')) { + let content = file.text; + + // Apply NLS post-processing if enabled (JS only) + if (file.path.endsWith('.js') && doNls && indexMap.size > 0) { + content = postProcessNLS(content, indexMap, preserveEnglish); + } + + // Rewrite sourceMappingURL to CDN URL if configured + if (sourceMapBaseUrl) { + const relativePath = path.relative(path.join(REPO_ROOT, outDir), file.path); + content = content.replace( + /\/\/# sourceMappingURL=.+$/m, + `//# sourceMappingURL=${sourceMapBaseUrl}/${relativePath}.map` + ); + content = content.replace( + /\/\*# sourceMappingURL=.+\*\/$/m, + `/*# sourceMappingURL=${sourceMapBaseUrl}/${relativePath}.map*/` + ); + } + + await fs.promises.writeFile(file.path, content); + } else { + // Write other files (source maps, assets) as-is + await fs.promises.writeFile(file.path, file.contents); + } + } + bundled++; + } + + // Copy resources (exclude dev files and tests for production) + await copyResources(outDir, target, true, true); + + // Compile standalone TypeScript files (like Electron preload scripts) that cannot be bundled + await compileStandaloneFiles(outDir, doMinify, target); + + console.log(`[bundle] Done in ${Date.now() - t1}ms (${bundled} bundles)`); +} + +// ============================================================================ +// Watch Mode +// ============================================================================ + +async function watch(): Promise { + if (!useEsbuildTranspile) { + console.log('Starting transpilation...'); + console.log('Finished transpilation with 0 errors after 0 ms'); + console.log('[watch] esbuild transpile disabled (useEsbuildTranspile=false). Keeping process alive as no-op.'); + await new Promise(() => { }); // keep alive + return; + } + + console.log('Starting transpilation...'); + + const outDir = OUT_DIR; + + // Initial setup + await cleanDir(outDir); + console.log(`[transpile] ${SRC_DIR} → ${outDir}`); + + // Initial full build + const t1 = Date.now(); + try { + await transpile(outDir, false); + await copyResources(outDir, 'desktop', false, false); + console.log(`Finished transpilation with 0 errors after ${Date.now() - t1} ms`); + } catch (err) { + console.error('[watch] Initial build failed:', err); + console.log(`Finished transpilation with 1 errors after ${Date.now() - t1} ms`); + // Continue watching anyway + } + + let pendingTsFiles: Set = new Set(); + let pendingCopyFiles: Set = new Set(); + + const processChanges = async () => { + console.log('Starting transpilation...'); + const t1 = Date.now(); + const tsFiles = [...pendingTsFiles]; + const filesToCopy = [...pendingCopyFiles]; + pendingTsFiles = new Set(); + pendingCopyFiles = new Set(); + + try { + // Transform changed TypeScript files in parallel + if (tsFiles.length > 0) { + console.log(`[watch] Transpiling ${tsFiles.length} file(s)...`); + await Promise.all(tsFiles.map(srcPath => { + const relativePath = path.relative(path.join(REPO_ROOT, SRC_DIR), srcPath); + const destPath = path.join(REPO_ROOT, outDir, relativePath.replace(/\.ts$/, '.js')); + return transpileFile(srcPath, destPath, relativePath); + })); + } + + // Copy changed resource files in parallel + if (filesToCopy.length > 0) { + await Promise.all(filesToCopy.map(async (srcPath) => { + const relativePath = path.relative(path.join(REPO_ROOT, SRC_DIR), srcPath); + const destPath = path.join(REPO_ROOT, outDir, relativePath); + await fs.promises.mkdir(path.dirname(destPath), { recursive: true }); + await fs.promises.copyFile(srcPath, destPath); + console.log(`[watch] Copied ${relativePath}`); + })); + } + + if (tsFiles.length > 0 || filesToCopy.length > 0) { + console.log(`Finished transpilation with 0 errors after ${Date.now() - t1} ms`); + } + } catch (err) { + console.error('[watch] Rebuild failed:', err); + console.log(`Finished transpilation with 1 errors after ${Date.now() - t1} ms`); + // Continue watching + } + }; + + // Extensions to watch and copy (non-TypeScript resources) + const copyExtensions = ['.css', '.html', '.js', '.json', '.ttf', '.svg', '.png', '.mp3', '.scm', '.sh', '.ps1', '.psm1', '.fish', '.zsh', '.scpt']; + + // Watch src directory using existing gulp-watch based watcher + let debounceTimer: ReturnType | undefined; + const srcDir = path.join(REPO_ROOT, SRC_DIR); + const watchStream = gulpWatch('src/**', { base: srcDir, readDelay: 200 }); + + watchStream.on('data', (file: { path: string }) => { + if (file.path.endsWith('.ts') && !file.path.endsWith('.d.ts')) { + pendingTsFiles.add(file.path); + } else if (copyExtensions.some(ext => file.path.endsWith(ext))) { + pendingCopyFiles.add(file.path); + } + + if (pendingTsFiles.size > 0 || pendingCopyFiles.size > 0) { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(processChanges, 200); + } + }); + + console.log('[watch] Watching src/**/*.{ts,css,...} (Ctrl+C to stop)'); + + // Keep process alive + process.on('SIGINT', () => { + console.log('\n[watch] Stopping...'); + watchStream.end(); + process.exit(0); + }); +} + +// ============================================================================ +// Main +// ============================================================================ + +function printUsage(): void { + console.log(`Usage: npx tsx build/next/index.ts [options] + +Commands: + transpile Transpile TypeScript to JavaScript (single-file, fast) + bundle Bundle entry points into optimized bundles + +Options for 'transpile': + --watch Watch for changes and rebuild incrementally + --out Output directory (default: out) + --exclude-tests Exclude test files from transpilation + +Options for 'bundle': + --minify Minify the output bundles + --nls Process NLS (localization) strings + --out Output directory (default: out-vscode) + --target Build target: desktop (default), server, server-web, web + --source-map-base-url Rewrite sourceMappingURL to CDN URL + +Examples: + npx tsx build/next/index.ts transpile + npx tsx build/next/index.ts transpile --watch + npx tsx build/next/index.ts transpile --out out-build + npx tsx build/next/index.ts transpile --out out-build --exclude-tests + npx tsx build/next/index.ts bundle + npx tsx build/next/index.ts bundle --minify --nls + npx tsx build/next/index.ts bundle --nls --out out-vscode-min + npx tsx build/next/index.ts bundle --minify --nls --target server --out out-vscode-reh-min + npx tsx build/next/index.ts bundle --minify --nls --target server-web --out out-vscode-reh-web-min +`); +} + +async function main(): Promise { + const t1 = Date.now(); + + try { + switch (command) { + case 'transpile': + if (options.watch) { + await watch(); + } else { + const outDir = options.out ?? OUT_DIR; + await cleanDir(outDir); + + // Write build date file (used by packaging to embed in product.json) + const outDirPath = path.join(REPO_ROOT, outDir); + await fs.promises.mkdir(outDirPath, { recursive: true }); + await fs.promises.writeFile(path.join(outDirPath, 'date'), new Date().toISOString(), 'utf8'); + + console.log(`[transpile] ${SRC_DIR} → ${outDir}${options.excludeTests ? ' (excluding tests)' : ''}`); + const t1 = Date.now(); + await transpile(outDir, options.excludeTests); + await copyResources(outDir, 'desktop', false, options.excludeTests); + console.log(`[transpile] Done in ${Date.now() - t1}ms`); + } + break; + + case 'bundle': + await bundle(options.out ?? OUT_VSCODE_DIR, options.minify, options.nls, options.target as BuildTarget, options.sourceMapBaseUrl); + break; + + default: + printUsage(); + process.exit(command ? 1 : 0); + } + + if (!options.watch) { + console.log(`\n✓ Total: ${Date.now() - t1}ms`); + } + } catch (err) { + console.error('Build failed:', err); + process.exit(1); + } +} + +main(); diff --git a/build/next/nls-plugin.ts b/build/next/nls-plugin.ts new file mode 100644 index 0000000000000..56cb84fa33a06 --- /dev/null +++ b/build/next/nls-plugin.ts @@ -0,0 +1,319 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as esbuild from 'esbuild'; +import * as path from 'path'; +import * as fs from 'fs'; +import { + TextModel, + analyzeLocalizeCalls, + parseLocalizeKeyOrValue +} from '../lib/nls-analysis.ts'; + +// ============================================================================ +// Types +// ============================================================================ + +interface NLSEntry { + moduleId: string; + key: string | { key: string; comment: string[] }; + message: string; + placeholder: string; +} + +export interface NLSPluginOptions { + /** + * Base path for computing module IDs (e.g., 'src') + */ + baseDir: string; + + /** + * Shared collector for NLS entries across multiple builds. + * Create with createNLSCollector() and pass to multiple plugin instances. + */ + collector: NLSCollector; +} + +/** + * Collector for NLS entries across multiple esbuild builds. + */ +export interface NLSCollector { + entries: Map; + add(entry: NLSEntry): void; +} + +/** + * Creates a shared NLS collector that can be passed to multiple plugin instances. + */ +export function createNLSCollector(): NLSCollector { + const entries = new Map(); + return { + entries, + add(entry: NLSEntry) { + entries.set(entry.placeholder, entry); + } + }; +} + +/** + * Finalizes NLS collection and writes output files. + * Call this after all esbuild builds have completed. + */ +export async function finalizeNLS( + collector: NLSCollector, + outDir: string, + alsoWriteTo?: string[] +): Promise<{ indexMap: Map; messageCount: number }> { + if (collector.entries.size === 0) { + return { indexMap: new Map(), messageCount: 0 }; + } + + // Sort entries by moduleId, then by key for stable indices + const sortedEntries = [...collector.entries.values()].sort((a, b) => { + const aKey = typeof a.key === 'string' ? a.key : a.key.key; + const bKey = typeof b.key === 'string' ? b.key : b.key.key; + const moduleCompare = a.moduleId.localeCompare(b.moduleId); + if (moduleCompare !== 0) { + return moduleCompare; + } + return aKey.localeCompare(bKey); + }); + + // Create index map + const indexMap = new Map(); + sortedEntries.forEach((entry, idx) => { + indexMap.set(entry.placeholder, idx); + }); + + // Build NLS metadata + const allMessages: string[] = []; + const moduleToKeys: Map = new Map(); + const moduleToMessages: Map = new Map(); + + for (const entry of sortedEntries) { + allMessages.push(entry.message); + + if (!moduleToKeys.has(entry.moduleId)) { + moduleToKeys.set(entry.moduleId, []); + moduleToMessages.set(entry.moduleId, []); + } + moduleToKeys.get(entry.moduleId)!.push(entry.key); + moduleToMessages.get(entry.moduleId)!.push(entry.message); + } + + // nls.keys.json: [["moduleId", ["key1", "key2"]], ...] + const nlsKeysJson: [string, string[]][] = []; + for (const [moduleId, keys] of moduleToKeys) { + nlsKeysJson.push([moduleId, keys.map(k => typeof k === 'string' ? k : k.key)]); + } + + // nls.metadata.json: { keys: {...}, messages: {...} } + const nlsMetadataJson = { + keys: Object.fromEntries(moduleToKeys), + messages: Object.fromEntries(moduleToMessages) + }; + + // Write NLS files + const allOutDirs = [outDir, ...(alsoWriteTo ?? [])]; + for (const dir of allOutDirs) { + await fs.promises.mkdir(dir, { recursive: true }); + } + + await Promise.all(allOutDirs.flatMap(dir => [ + fs.promises.writeFile( + path.join(dir, 'nls.messages.json'), + JSON.stringify(allMessages) + ), + fs.promises.writeFile( + path.join(dir, 'nls.keys.json'), + JSON.stringify(nlsKeysJson) + ), + fs.promises.writeFile( + path.join(dir, 'nls.metadata.json'), + JSON.stringify(nlsMetadataJson, null, '\t') + ), + fs.promises.writeFile( + path.join(dir, 'nls.messages.js'), + `/*---------------------------------------------------------\n * Copyright (C) Microsoft Corporation. All rights reserved.\n *--------------------------------------------------------*/\nglobalThis._VSCODE_NLS_MESSAGES=${JSON.stringify(allMessages)};` + ), + ])); + + console.log(`[nls] Extracted ${allMessages.length} messages from ${moduleToKeys.size} modules`); + + return { indexMap, messageCount: allMessages.length }; +} + +/** + * Post-processes a JavaScript file to replace NLS placeholders with indices. + */ +export function postProcessNLS( + content: string, + indexMap: Map, + preserveEnglish: boolean +): string { + return replaceInOutput(content, indexMap, preserveEnglish); +} + +// ============================================================================ +// Transformation +// ============================================================================ + +function transformToPlaceholders( + source: string, + moduleId: string +): { code: string; entries: NLSEntry[] } { + const localizeCalls = analyzeLocalizeCalls(source, 'localize'); + const localize2Calls = analyzeLocalizeCalls(source, 'localize2'); + + // Tag calls with their type so we can handle them differently later + const taggedLocalize = localizeCalls.map(call => ({ call, isLocalize2: false })); + const taggedLocalize2 = localize2Calls.map(call => ({ call, isLocalize2: true })); + const allCalls = [...taggedLocalize, ...taggedLocalize2].sort( + (a, b) => a.call.keySpan.start.line - b.call.keySpan.start.line || + a.call.keySpan.start.character - b.call.keySpan.start.character + ); + + if (allCalls.length === 0) { + return { code: source, entries: [] }; + } + + const entries: NLSEntry[] = []; + const model = new TextModel(source); + + // Process in reverse order to preserve positions + for (const { call, isLocalize2 } of allCalls.reverse()) { + const keyParsed = parseLocalizeKeyOrValue(call.key) as string | { key: string; comment: string[] }; + const messageParsed = parseLocalizeKeyOrValue(call.value); + const keyString = typeof keyParsed === 'string' ? keyParsed : keyParsed.key; + + // Use different placeholder prefix for localize vs localize2 + // localize: message will be replaced with null + // localize2: message will be preserved (only key replaced) + const prefix = isLocalize2 ? 'NLS2' : 'NLS'; + const placeholder = `%%${prefix}:${moduleId}#${keyString}%%`; + + entries.push({ + moduleId, + key: keyParsed, + message: String(messageParsed), + placeholder + }); + + // Replace the key with the placeholder string + model.apply(call.keySpan, `"${placeholder}"`); + } + + // Reverse entries to match source order + entries.reverse(); + + return { code: model.toString(), entries }; +} + +function replaceInOutput( + content: string, + indexMap: Map, + preserveEnglish: boolean +): string { + // Replace all placeholders in a single pass using regex + // Two types of placeholders: + // - %%NLS:moduleId#key%% for localize() - message replaced with null + // - %%NLS2:moduleId#key%% for localize2() - message preserved + // Note: esbuild may use single or double quotes, so we handle both + + if (preserveEnglish) { + // Just replace the placeholder with the index (both NLS and NLS2) + return content.replace(/["']%%NLS2?:([^%]+)%%["']/g, (match, inner) => { + // Try NLS first, then NLS2 + let placeholder = `%%NLS:${inner}%%`; + let index = indexMap.get(placeholder); + if (index === undefined) { + placeholder = `%%NLS2:${inner}%%`; + index = indexMap.get(placeholder); + } + if (index !== undefined) { + return String(index); + } + // Placeholder not found in map, leave as-is (shouldn't happen) + return match; + }); + } else { + // For NLS (localize): replace placeholder with index AND replace message with null + // For NLS2 (localize2): replace placeholder with index, keep message + // Note: Use (?:[^"\\]|\\.)* to properly handle escaped quotes like \" or \\ + // Note: esbuild may use single or double quotes, so we handle both + + // First handle NLS (localize) - replace both key and message + content = content.replace( + /["']%%NLS:([^%]+)%%["'](\s*,\s*)(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`)/g, + (match, inner, comma) => { + const placeholder = `%%NLS:${inner}%%`; + const index = indexMap.get(placeholder); + if (index !== undefined) { + return `${index}${comma}null`; + } + return match; + } + ); + + // Then handle NLS2 (localize2) - replace only key, keep message + content = content.replace( + /["']%%NLS2:([^%]+)%%["']/g, + (match, inner) => { + const placeholder = `%%NLS2:${inner}%%`; + const index = indexMap.get(placeholder); + if (index !== undefined) { + return String(index); + } + return match; + } + ); + + return content; + } +} + +// ============================================================================ +// Plugin +// ============================================================================ + +export function nlsPlugin(options: NLSPluginOptions): esbuild.Plugin { + const { collector } = options; + + return { + name: 'nls', + setup(build) { + // Transform TypeScript files to replace localize() calls with placeholders + build.onLoad({ filter: /\.ts$/ }, async (args) => { + // Skip .d.ts files + if (args.path.endsWith('.d.ts')) { + return undefined; + } + + const source = await fs.promises.readFile(args.path, 'utf-8'); + + // Compute module ID (e.g., "vs/editor/editor" from "src/vs/editor/editor.ts") + const relativePath = path.relative(options.baseDir, args.path); + const moduleId = relativePath + .replace(/\\/g, '/') + .replace(/\.ts$/, ''); + + // Transform localize() calls to placeholders + const { code, entries: fileEntries } = transformToPlaceholders(source, moduleId); + + // Collect entries + for (const entry of fileEntries) { + collector.add(entry); + } + + if (fileEntries.length > 0) { + return { contents: code, loader: 'ts' }; + } + + // No NLS calls, return undefined to let esbuild handle normally + return undefined; + }); + } + }; +} diff --git a/build/next/working.md b/build/next/working.md new file mode 100644 index 0000000000000..bbf23a99806b1 --- /dev/null +++ b/build/next/working.md @@ -0,0 +1,235 @@ +# Working Notes: New esbuild-based Build System + +> These notes are for AI agents to help with context in new or summarized sessions. + +## Important: Validating Changes + +**The `VS Code - Build` task is NOT needed to validate changes in the `build/` folder!** + +Build scripts in `build/` are TypeScript files that run directly with `tsx` (e.g., `npx tsx build/next/index.ts`). They are not compiled by the main VS Code build. + +To test changes: +```bash +# Test transpile +npx tsx build/next/index.ts transpile --out out-test + +# Test bundle (server-web target to test the auth fix) +npx tsx build/next/index.ts bundle --nls --target server-web --out out-vscode-reh-web-test + +# Verify product config was injected +grep -l "serverLicense" out-vscode-reh-web-test/vs/code/browser/workbench/workbench.js +``` + +--- + +## Architecture Overview + +### Files + +- **[index.ts](index.ts)** - Main build orchestrator + - `transpile` command: Fast TS → JS using `esbuild.transform()` + - `bundle` command: TS → bundled JS using `esbuild.build()` +- **[nls-plugin.ts](nls-plugin.ts)** - NLS (localization) esbuild plugin + +### Integration with Old Build + +In [gulpfile.vscode.ts](../gulpfile.vscode.ts#L228-L242), the `core-ci` task uses these new scripts: +- `runEsbuildTranspile()` → transpile command +- `runEsbuildBundle()` → bundle command + +Old gulp-based bundling renamed to `core-ci-OLD`. + +--- + +## Key Learnings + +### 1. Comment Stripping by esbuild + +**Problem:** esbuild strips comments like `/*BUILD->INSERT_PRODUCT_CONFIGURATION*/` during bundling. + +**Solution:** Use an `onLoad` plugin to transform source files BEFORE esbuild processes them. See `fileContentMapperPlugin()` in index.ts. + +**Why post-processing doesn't work:** By the time we post-process the bundled output, the comment placeholder has already been stripped. + +### 2. Authorization Error: "Unauthorized client refused" + +**Root cause:** Missing product configuration in browser bundle. + +**Flow:** +1. Browser loads with empty product config (placeholder was stripped) +2. `productService.serverLicense` is empty/undefined +3. Browser's `SignService.vsda()` can't decrypt vsda WASM (needs serverLicense as key) +4. Browser's `sign()` returns original challenge instead of signed value +5. Server validates signature → fails +6. Server is in built mode (no `VSCODE_DEV`) → rejects connection + +**Fix:** The `fileContentMapperPlugin` now runs during `onLoad`, replacing placeholders before esbuild strips them. + +### 3. Build-Time Placeholders + +Two placeholders that need injection: + +| Placeholder | Location | Purpose | +|-------------|----------|---------| +| `/*BUILD->INSERT_PRODUCT_CONFIGURATION*/` | `src/vs/platform/product/common/product.ts` | Product config (commit, version, serverLicense, etc.) | +| `/*BUILD->INSERT_BUILTIN_EXTENSIONS*/` | `src/vs/workbench/services/extensionManagement/browser/builtinExtensionsScannerService.ts` | List of built-in extensions | + +### 4. Server-web Target Specifics + +- Removes `webEndpointUrlTemplate` from product config (see `tweakProductForServerWeb` in old build) +- Uses `.build/extensions` for builtin extensions (not `.build/web/extensions`) + +### 5. Entry Point Parity with Old Build + +**Problem:** The desktop target had `keyboardMapEntryPoints` as separate esbuild entry points, producing `layout.contribution.darwin.js`, `layout.contribution.linux.js`, and `layout.contribution.win.js` as standalone files in the output. + +**Root cause:** In the old build (`gulpfile.vscode.ts`), `vscodeEntryPoints` does NOT include `buildfile.keyboardMaps`. These files are only separate entry points for server-web (`gulpfile.reh.ts`) and web (`gulpfile.vscode.web.ts`). For desktop, they're imported as dependencies of `workbench.desktop.main` and get bundled into it. + +**Fix:** Removed `...keyboardMapEntryPoints` from the `desktop` case in `getEntryPointsForTarget()`. Keep for `server-web` and `web`. + +**Lesson:** Always verify new build entry points against the old build's per-target definitions in `buildfile.ts` and the respective gulpfiles. + +### 6. NLS Output File Parity + +**Problem:** `finalizeNLS()` was generating `nls.messages.js` (with `globalThis._VSCODE_NLS_MESSAGES=...`) in addition to the standard `.json` files. The old build only produces `nls.messages.json`, `nls.keys.json`, and `nls.metadata.json`. + +**Fix:** Removed `nls.messages.js` generation from `finalizeNLS()` in `nls-plugin.ts`. + +**Lesson:** Don't add new output file formats that create parity differences with the old build. The old build is the reference. + +--- + +## Testing the Fix + +```bash +# Build server-web with new system +npx tsx build/next/index.ts bundle --nls --target server-web --out out-vscode-reh-web-min + +# Package it (uses gulp task) +npm run gulp vscode-reh-web-darwin-arm64-min + +# Run server +./vscode-server-darwin-arm64-web/bin/code-server-oss --connection-token dev-token + +# Open browser - should connect without "Unauthorized client refused" +``` + +--- + +## Open Items / Future Work + +1. **`BUILD_INSERT_PACKAGE_CONFIGURATION`** - Server bootstrap files ([bootstrap-meta.ts](../../src/bootstrap-meta.ts)) have this marker for package.json injection. Currently handled by [inlineMeta.ts](../lib/inlineMeta.ts) in the old build's packaging step. + +2. **Mangling** - The new build doesn't do TypeScript-based mangling yet. Old `core-ci` with mangling is now `core-ci-OLD`. + +3. **Entry point duplication** - Entry points are duplicated between [buildfile.ts](../buildfile.ts) and [index.ts](index.ts). Consider consolidating. + +--- + +## Build Comparison: OLD (gulp-tsb) vs NEW (esbuild) — Desktop Build + +### Summary + +| Metric | OLD | NEW | Delta | +|--------|-----|-----|-------| +| Total files in `out/` | 3993 | 4301 | +309 extra, 1 missing | +| Total size of `out/` | 25.8 MB | 64.6 MB | +38.8 MB (2.5×) | +| `workbench.desktop.main.js` | 13.0 MB | 15.5 MB | +2.5 MB | + +### 1 Missing File (in OLD, not in NEW) + +| File | Why Missing | Fix | +|------|-------------|-----| +| `out/vs/platform/browserView/electron-browser/preload-browserView.js` | Not listed in `desktopStandaloneFiles` in index.ts. Only `preload.ts` and `preload-aux.ts` are compiled as standalone files. | **Add** `'vs/platform/browserView/electron-browser/preload-browserView.ts'` to the `desktopStandaloneFiles` array in `index.ts`. | + +### 309 Extra Files (in NEW, not in OLD) — Breakdown + +| Category | Count | Explanation | +|----------|-------|-------------| +| **CSS files** | 291 | `copyCssFiles()` copies ALL `.css` from `src/` to the output. The old bundler inlines CSS into the main `.css` bundle (e.g., `workbench.desktop.main.css`) and never ships individual CSS files. These individual files ARE needed at runtime because the new ESM system uses `import './foo.css'` resolved by an import map. | +| **Vendor JS files** | 3 | `dompurify.js`, `marked.js`, `semver.js` — listed in `commonResourcePatterns`. The old bundler inlines these into the main bundle. The new system keeps them as separate files because they're plain JS (not TS). They're needed. | +| **Web workbench bundle** | 1 | `vs/code/browser/workbench/workbench.js` (15.4 MB). This is the web workbench entry point bundle. It should NOT be in a desktop build — the old build explicitly excludes `out-build/vs/code/browser/**`. The `desktopResourcePatterns` in index.ts includes `vs/code/browser/workbench/*.html` and `callback.html` which is correct, but the actual bundle gets written by the esbuild desktop bundle step because the desktop entry points include web entry points. | +| **Web workbench internal** | 1 | `vs/workbench/workbench.web.main.internal.js` (15.4 MB). Similar: shouldn't ship in a desktop build. It's output by the esbuild bundler. | +| **Keyboard layout contributions** | 3 | `layout.contribution.{darwin,linux,win}.js` — the old bundler inlines these into the main bundle. These are new separate files from the esbuild bundler. | +| **NLS files** | 2 | `nls.messages.js` (new) and `nls.metadata.json` (new). The old build has `nls.messages.json` and `nls.keys.json` but not a `.js` version or metadata. The `.js` version is produced by the NLS plugin. | +| **HTML files** | 2 | `vs/code/browser/workbench/workbench.html` and `callback.html` — correctly listed in `desktopResourcePatterns` (these are needed for desktop's built-in web server). | +| **SVG loading spinners** | 3 | `loading-dark.svg`, `loading-hc.svg`, `loading.svg` in `vs/workbench/contrib/extensions/browser/media/`. The old build only copies `theme-icon.png` and `language-icon.svg` from that folder; the new build's `desktopResourcePatterns` uses `*.svg` which is broader. | +| **codicon.ttf (duplicate)** | 1 | At `vs/base/browser/ui/codicons/codicon/codicon.ttf`. The old build copies this to `out/media/codicon.ttf` only. The new build has BOTH: the copy in `out/media/` (from esbuild's `file` loader) AND the original path (from `commonResourcePatterns`). Duplicate. | +| **PSReadLine.psm1** | 1 | `vs/workbench/contrib/terminal/common/scripts/psreadline/PSReadLine.psm1` — the old build uses `*.psm1` in `terminal/common/scripts/` (non-recursive?). The new build uses `**/*.psm1` (recursive), picking up this subdirectory file. Check if it's needed. | +| **date file** | 1 | `out/date` — build timestamp, produced by the new build's `bundle()` function. The old build doesn't write this; it reads `package.json.date` instead. | + +### Size Increase Breakdown by Area + +| Area | OLD | NEW | Delta | Why | +|------|-----|-----|-------|-----| +| `vs/code` | 1.5 MB | 17.4 MB | +15.9 MB | Web workbench bundle (15.4 MB) shouldn't be in desktop build | +| `vs/workbench` | 18.9 MB | 38.7 MB | +19.8 MB | `workbench.web.main.internal.js` (15.4 MB) + unmangled desktop bundle (+2.5 MB) + individual CSS files (~1 MB) | +| `vs/base` | 0 MB | 0.4 MB | +0.4 MB | Individual CSS files + vendor JS | +| `vs/editor` | 0.3 MB | 0.5 MB | +0.1 MB | Individual CSS files | +| `vs/platform` | 1.7 MB | 1.9 MB | +0.2 MB | Individual CSS files | + +### JS Files with >2× Size Change + +| File | OLD | NEW | Ratio | Reason | +|------|-----|-----|-------|--------| +| `vs/workbench/contrib/webview/browser/pre/service-worker.js` | 7 KB | 15 KB | 2.2× | Not minified / includes more inlined code | +| `vs/code/electron-browser/workbench/workbench.js` | 10 KB | 28 KB | 2.75× | OLD is minified to 6 lines; NEW is 380 lines (not compressed, includes tslib banner) | + +### Action Items + +1. **[CRITICAL] Missing `preload-browserView.ts`** — Add to `desktopStandaloneFiles` in index.ts. Without it, BrowserView (used for Simple Browser) may fail. +2. **[SIZE] Web bundles in desktop build** — `workbench.web.main.internal.js` and `vs/code/browser/workbench/workbench.js` together add ~31 MB. These are written by the esbuild bundler and not filtered out. Consider: either don't bundle web entry points for the desktop target, or ensure the packaging step excludes them (currently `packageTask` takes `out-vscode-min/**` without filtering). +3. **[SIZE] No mangling** — The desktop main bundle is 2.5 MB larger due to no property mangling. Known open item. +4. **[MINOR] Duplicate codicon.ttf** — Exists at both `out/media/codicon.ttf` (from esbuild `file` loader) and `out/vs/base/browser/ui/codicons/codicon/codicon.ttf` (from `commonResourcePatterns`). Consider removing from `commonResourcePatterns` if it's already handled by the loader. +5. **[MINOR] Extra SVGs** — `desktopResourcePatterns` uses `*.svg` for extensions media but old build only ships `language-icon.svg`. The loading spinners may be unused in the desktop build. +6. **[MINOR] Extra PSReadLine.psm1** from recursive glob — verify if needed. + +--- + +## Source Maps + +### Fixes Applied + +1. **`sourcesContent: true`** — Production bundles now embed original TypeScript source content in `.map` files, matching the old build's `includeContent: true` behavior. Without this, crash reports from CDN-hosted source maps can't show original source. + +2. **`--source-map-base-url` option** — The `bundle` command accepts an optional `--source-map-base-url ` flag. When set, post-processing rewrites `sourceMappingURL` comments in `.js` and `.css` output files to point to the CDN (e.g., `https://main.vscode-cdn.net/sourcemaps//core/vs/...`). This matches the old build's `sourceMappingURL` function in `minifyTask()`. Wired up in `gulpfile.vscode.ts` for `core-ci-esbuild` and `vscode-esbuild-min` tasks. + +### NLS Source Map Accuracy (Decision: Accept Imprecision) + +**Problem:** `postProcessNLS()` replaces `"%%NLS:moduleId#key%%"` placeholders (~40 chars) with short index values like `null` (4 chars) in the final JS output. This shifts column positions without updating the `.map` files. + +**Options considered:** + +| Option | Description | Effort | Accuracy | +|--------|-------------|--------|----------| +| A. Fixed-width placeholders | Pad placeholders to match replacement length | Hard — indices unknown until all modules are collected across parallel bundles | Perfect | +| B. Post-process source map | Parse `.map`, track replacement offsets per line, adjust VLQ mappings | Medium | Perfect | +| C. Two-pass build | Assign NLS indices during plugin phase | Not feasible with parallel bundling | N/A | +| **D. Accept imprecision** | NLS replacements only affect column positions; line-level debugging works | Zero | Line-level | + +**Decision: Option D — accept imprecision.** Rationale: + +- NLS replacements only shift **columns**, never lines — line-level stack traces and breakpoints remain correct. +- Production crash reporting (the primary consumer of CDN source maps) uses line numbers; column-level accuracy is rarely needed. +- The old gulp build had the same fundamental issue in its `nls.nls()` step and used `SourceMapConsumer`/`SourceMapGenerator` to fix it — but that approach was fragile and slow. +- If column-level precision becomes important later (e.g., for minified+NLS bundles), Option B can be implemented: after NLS replacement, re-parse the source map, walk replacement sites, and adjust column offsets. This is a localized change in the post-processing loop. + +--- + +## Self-hosting Setup + +The default `VS Code - Build` task now runs three parallel watchers: + +| Task | What it does | Script | +|------|-------------|--------| +| **Core - Transpile** | esbuild single-file TS→JS (fast, no type checking) | `watch-client-transpiled` → `npx tsx build/next/index.ts transpile --watch` | +| **Core - Typecheck** | gulp-tsb `noEmit` watch (type errors only, no output) | `watch-clientd` → `gulp watch-client` (with `noEmit: true`) | +| **Ext - Build** | Extension compilation (unchanged) | `watch-extensionsd` | + +### Key Changes + +- **`compilation.ts`**: `ICompileTaskOptions` gained `noEmit?: boolean`. When set, `overrideOptions.noEmit = true` is passed to tsb. `watchTask()` accepts an optional 4th parameter `{ noEmit?: boolean }`. +- **`gulpfile.ts`**: `watchClientTask` no longer runs `rimraf('out')` (the transpiler owns that). Passes `{ noEmit: true }` to `watchTask`. +- **`index.ts`**: Watch mode emits `Starting transpilation...` / `Finished transpilation with N errors after X ms` for VS Code problem matcher. +- **`tasks.json`**: Old "Core - Build" split into "Core - Transpile" + "Core - Typecheck" with separate problem matchers (owners: `esbuild` vs `typescript`). +- **`package.json`**: Added `watch-client-transpile`, `watch-client-transpiled`, `kill-watch-client-transpiled` scripts. diff --git a/build/vite/fixtures/aiStats.fixture.ts b/build/vite/fixtures/aiStats.fixture.ts new file mode 100644 index 0000000000000..5ebe414b52b62 --- /dev/null +++ b/build/vite/fixtures/aiStats.fixture.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { observableValue } from '../../../src/vs/base/common/observable'; +import { createAiStatsHover, IAiStatsHoverData } from '../../../src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar'; +import { ISessionData } from '../../../src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsChart'; +import { Random } from '../../../src/vs/editor/test/common/core/random'; +import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils'; + +export default defineThemedFixtureGroup({ + AiStatsHover: defineComponentFixture({ + render: (context) => renderAiStatsHover({ ...context, data: createSampleDataWithSessions() }), + }), + + AiStatsHoverNoData: defineComponentFixture({ + render: (context) => renderAiStatsHover({ ...context, data: createEmptyData() }), + }), +}); + +function createSampleDataWithSessions(): IAiStatsHoverData { + const random = Random.create(42); + + // Use a fixed base time for determinism (Jan 1, 2025, 12:00:00 UTC) + const baseTime = 1735732800000; + const dayMs = 24 * 60 * 60 * 1000; + const sessionLengthMs = 5 * 60 * 1000; + + // Generate fake session data for the last 7 days + const fakeSessions: ISessionData[] = []; + for (let day = 6; day >= 0; day--) { + const dayStart = baseTime - day * dayMs; + const sessionsPerDay = random.nextIntRange(3, 9); + for (let s = 0; s < sessionsPerDay; s++) { + const sessionTime = dayStart + s * sessionLengthMs * 2; + fakeSessions.push({ + startTime: sessionTime, + typedCharacters: random.nextIntRange(100, 600), + aiCharacters: random.nextIntRange(200, 1000), + acceptedInlineSuggestions: random.nextIntRange(1, 16), + chatEditCount: random.nextIntRange(0, 5), + }); + } + } + + const totalAi = fakeSessions.reduce((sum, s) => sum + s.aiCharacters, 0); + const totalTyped = fakeSessions.reduce((sum, s) => sum + s.typedCharacters, 0); + const aiRate = totalAi / (totalAi + totalTyped); + + // "Today" for the fixture is the baseTime day + const startOfToday = baseTime - (baseTime % dayMs); + const todaySessions = fakeSessions.filter(s => s.startTime >= startOfToday); + const acceptedToday = todaySessions.reduce((sum, s) => sum + (s.acceptedInlineSuggestions ?? 0), 0); + + return { + aiRate: observableValue('aiRate', aiRate), + acceptedInlineSuggestionsToday: observableValue('acceptedToday', acceptedToday), + sessions: observableValue('sessions', fakeSessions), + }; +} + +function createEmptyData(): IAiStatsHoverData { + return { + aiRate: observableValue('aiRate', 0), + acceptedInlineSuggestionsToday: observableValue('acceptedToday', 0), + sessions: observableValue('sessions', []), + }; +} + +interface RenderAiStatsOptions extends ComponentFixtureContext { + data: IAiStatsHoverData; +} + +function renderAiStatsHover({ container, disposableStore, data }: RenderAiStatsOptions): HTMLElement { + container.style.width = '320px'; + container.style.padding = '8px'; + container.style.backgroundColor = 'var(--vscode-editorHoverWidget-background)'; + container.style.border = '1px solid var(--vscode-editorHoverWidget-border)'; + container.style.borderRadius = '4px'; + container.style.color = 'var(--vscode-editorHoverWidget-foreground)'; + + const hover = createAiStatsHover({ + data, + onOpenSettings: () => console.log('Open settings clicked'), + }); + + const elem = hover.keepUpdated(disposableStore).element; + container.appendChild(elem); + + return container; +} diff --git a/build/vite/fixtures/baseUI.fixture.ts b/build/vite/fixtures/baseUI.fixture.ts new file mode 100644 index 0000000000000..4bcf0be523519 --- /dev/null +++ b/build/vite/fixtures/baseUI.fixture.ts @@ -0,0 +1,531 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { $ } from '../../../src/vs/base/browser/dom'; +import { Codicon } from '../../../src/vs/base/common/codicons'; +import { ThemeIcon } from '../../../src/vs/base/common/themables'; +import { Action, Separator } from '../../../src/vs/base/common/actions'; + +// UI Components +import { Button, ButtonBar, ButtonWithDescription, unthemedButtonStyles } from '../../../src/vs/base/browser/ui/button/button'; +import { Toggle, Checkbox, unthemedToggleStyles } from '../../../src/vs/base/browser/ui/toggle/toggle'; +import { InputBox, MessageType, unthemedInboxStyles } from '../../../src/vs/base/browser/ui/inputbox/inputBox'; +import { CountBadge } from '../../../src/vs/base/browser/ui/countBadge/countBadge'; +import { ActionBar } from '../../../src/vs/base/browser/ui/actionbar/actionbar'; +import { ProgressBar } from '../../../src/vs/base/browser/ui/progressbar/progressbar'; +import { HighlightedLabel } from '../../../src/vs/base/browser/ui/highlightedlabel/highlightedLabel'; + +import { ComponentFixtureContext, defineComponentFixture, defineThemedFixtureGroup } from './fixtureUtils'; + + +// ============================================================================ +// Styles (themed versions for fixture display) +// ============================================================================ + +const themedButtonStyles = { + ...unthemedButtonStyles, + buttonBackground: 'var(--vscode-button-background)', + buttonHoverBackground: 'var(--vscode-button-hoverBackground)', + buttonForeground: 'var(--vscode-button-foreground)', + buttonSecondaryBackground: 'var(--vscode-button-secondaryBackground)', + buttonSecondaryHoverBackground: 'var(--vscode-button-secondaryHoverBackground)', + buttonSecondaryForeground: 'var(--vscode-button-secondaryForeground)', + buttonBorder: 'var(--vscode-button-border)', +}; + +const themedToggleStyles = { + ...unthemedToggleStyles, + inputActiveOptionBorder: 'var(--vscode-inputOption-activeBorder)', + inputActiveOptionForeground: 'var(--vscode-inputOption-activeForeground)', + inputActiveOptionBackground: 'var(--vscode-inputOption-activeBackground)', +}; + +const themedCheckboxStyles = { + checkboxBackground: 'var(--vscode-checkbox-background)', + checkboxBorder: 'var(--vscode-checkbox-border)', + checkboxForeground: 'var(--vscode-checkbox-foreground)', + checkboxDisabledBackground: undefined, + checkboxDisabledForeground: undefined, +}; + +const themedInputBoxStyles = { + ...unthemedInboxStyles, + inputBackground: 'var(--vscode-input-background)', + inputForeground: 'var(--vscode-input-foreground)', + inputBorder: 'var(--vscode-input-border)', + inputValidationInfoBackground: 'var(--vscode-inputValidation-infoBackground)', + inputValidationInfoBorder: 'var(--vscode-inputValidation-infoBorder)', + inputValidationWarningBackground: 'var(--vscode-inputValidation-warningBackground)', + inputValidationWarningBorder: 'var(--vscode-inputValidation-warningBorder)', + inputValidationErrorBackground: 'var(--vscode-inputValidation-errorBackground)', + inputValidationErrorBorder: 'var(--vscode-inputValidation-errorBorder)', +}; + +const themedBadgeStyles = { + badgeBackground: 'var(--vscode-badge-background)', + badgeForeground: 'var(--vscode-badge-foreground)', + badgeBorder: undefined, +}; + +const themedProgressBarOptions = { + progressBarBackground: 'var(--vscode-progressBar-background)', +}; + + +// ============================================================================ +// Buttons +// ============================================================================ + +function renderButtons({ container, disposableStore }: ComponentFixtureContext): HTMLElement { + container.style.padding = '16px'; + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.gap = '12px'; + + // Section: Primary Buttons + const primarySection = $('div'); + primarySection.style.display = 'flex'; + primarySection.style.gap = '8px'; + primarySection.style.alignItems = 'center'; + container.appendChild(primarySection); + + const primaryButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'Primary button' })); + primaryButton.label = 'Primary Button'; + + const primaryIconButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'With Icon', supportIcons: true })); + primaryIconButton.label = '$(add) Add Item'; + + const smallButton = disposableStore.add(new Button(primarySection, { ...themedButtonStyles, title: 'Small button', small: true })); + smallButton.label = 'Small'; + + // Section: Secondary Buttons + const secondarySection = $('div'); + secondarySection.style.display = 'flex'; + secondarySection.style.gap = '8px'; + secondarySection.style.alignItems = 'center'; + container.appendChild(secondarySection); + + const secondaryButton = disposableStore.add(new Button(secondarySection, { ...themedButtonStyles, secondary: true, title: 'Secondary button' })); + secondaryButton.label = 'Secondary Button'; + + const secondaryIconButton = disposableStore.add(new Button(secondarySection, { ...themedButtonStyles, secondary: true, title: 'Cancel', supportIcons: true })); + secondaryIconButton.label = '$(close) Cancel'; + + // Section: Disabled Buttons + const disabledSection = $('div'); + disabledSection.style.display = 'flex'; + disabledSection.style.gap = '8px'; + disabledSection.style.alignItems = 'center'; + container.appendChild(disabledSection); + + const disabledButton = disposableStore.add(new Button(disabledSection, { ...themedButtonStyles, title: 'Disabled', disabled: true })); + disabledButton.label = 'Disabled'; + disabledButton.enabled = false; + + const disabledSecondary = disposableStore.add(new Button(disabledSection, { ...themedButtonStyles, secondary: true, title: 'Disabled Secondary', disabled: true })); + disabledSecondary.label = 'Disabled Secondary'; + disabledSecondary.enabled = false; + + return container; +} + +function renderButtonBar({ container, disposableStore }: ComponentFixtureContext): HTMLElement { + container.style.padding = '16px'; + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.gap = '16px'; + + // Button Bar + const barContainer = $('div'); + container.appendChild(barContainer); + + const buttonBar = new ButtonBar(barContainer); + disposableStore.add(buttonBar); + + const okButton = buttonBar.addButton({ ...themedButtonStyles, title: 'OK' }); + okButton.label = 'OK'; + + const cancelButton = buttonBar.addButton({ ...themedButtonStyles, secondary: true, title: 'Cancel' }); + cancelButton.label = 'Cancel'; + + // Button with Description + const descContainer = $('div'); + descContainer.style.width = '300px'; + container.appendChild(descContainer); + + const buttonWithDesc = disposableStore.add(new ButtonWithDescription(descContainer, { ...themedButtonStyles, title: 'Install Extension', supportIcons: true })); + buttonWithDesc.label = '$(extensions) Install Extension'; + buttonWithDesc.description = 'This will install the extension and enable it globally'; + + return container; +} + + +// ============================================================================ +// Toggles and Checkboxes +// ============================================================================ + +function renderToggles({ container, disposableStore }: ComponentFixtureContext): HTMLElement { + container.style.padding = '16px'; + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.gap = '12px'; + + // Toggles + const toggleSection = $('div'); + toggleSection.style.display = 'flex'; + toggleSection.style.gap = '16px'; + toggleSection.style.alignItems = 'center'; + container.appendChild(toggleSection); + + const toggle1 = disposableStore.add(new Toggle({ + ...themedToggleStyles, + title: 'Case Sensitive', + isChecked: false, + icon: Codicon.caseSensitive, + })); + toggleSection.appendChild(toggle1.domNode); + + const toggle2 = disposableStore.add(new Toggle({ + ...themedToggleStyles, + title: 'Whole Word', + isChecked: true, + icon: Codicon.wholeWord, + })); + toggleSection.appendChild(toggle2.domNode); + + const toggle3 = disposableStore.add(new Toggle({ + ...themedToggleStyles, + title: 'Use Regular Expression', + isChecked: false, + icon: Codicon.regex, + })); + toggleSection.appendChild(toggle3.domNode); + + // Checkboxes + const checkboxSection = $('div'); + checkboxSection.style.display = 'flex'; + checkboxSection.style.flexDirection = 'column'; + checkboxSection.style.gap = '8px'; + container.appendChild(checkboxSection); + + const createCheckboxRow = (label: string, checked: boolean) => { + const row = $('div'); + row.style.display = 'flex'; + row.style.alignItems = 'center'; + row.style.gap = '8px'; + + const checkbox = disposableStore.add(new Checkbox(label, checked, themedCheckboxStyles)); + row.appendChild(checkbox.domNode); + + const labelEl = $('span'); + labelEl.textContent = label; + labelEl.style.color = 'var(--vscode-foreground)'; + row.appendChild(labelEl); + + return row; + }; + + checkboxSection.appendChild(createCheckboxRow('Enable auto-save', true)); + checkboxSection.appendChild(createCheckboxRow('Show line numbers', true)); + checkboxSection.appendChild(createCheckboxRow('Word wrap', false)); + + return container; +} + + +// ============================================================================ +// Input Boxes +// ============================================================================ + +function renderInputBoxes({ container, disposableStore }: ComponentFixtureContext): HTMLElement { + container.style.padding = '16px'; + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.gap = '16px'; + container.style.width = '350px'; + + // Normal input + const normalInput = disposableStore.add(new InputBox(container, undefined, { + placeholder: 'Enter search query...', + inputBoxStyles: themedInputBoxStyles, + })); + + // Input with value + const filledInput = disposableStore.add(new InputBox(container, undefined, { + placeholder: 'File path', + inputBoxStyles: themedInputBoxStyles, + })); + filledInput.value = '/src/vs/editor/browser'; + + // Input with info validation + const infoInput = disposableStore.add(new InputBox(container, undefined, { + placeholder: 'Username', + inputBoxStyles: themedInputBoxStyles, + validationOptions: { + validation: (value) => value.length < 3 ? { content: 'Username must be at least 3 characters', type: MessageType.INFO } : null + } + })); + infoInput.value = 'ab'; + infoInput.validate(); + + // Input with warning validation + const warningInput = disposableStore.add(new InputBox(container, undefined, { + placeholder: 'Password', + inputBoxStyles: themedInputBoxStyles, + validationOptions: { + validation: (value) => value.length < 8 ? { content: 'Password should be at least 8 characters for security', type: MessageType.WARNING } : null + } + })); + warningInput.value = 'pass'; + warningInput.validate(); + + // Input with error validation + const errorInput = disposableStore.add(new InputBox(container, undefined, { + placeholder: 'Email address', + inputBoxStyles: themedInputBoxStyles, + validationOptions: { + validation: (value) => !value.includes('@') ? { content: 'Please enter a valid email address', type: MessageType.ERROR } : null + } + })); + errorInput.value = 'invalid-email'; + errorInput.validate(); + + return container; +} + + +// ============================================================================ +// Count Badges +// ============================================================================ + +function renderCountBadges({ container }: ComponentFixtureContext): HTMLElement { + container.style.padding = '16px'; + container.style.display = 'flex'; + container.style.gap = '12px'; + container.style.alignItems = 'center'; + + // Various badge counts + const counts = [1, 5, 12, 99, 999]; + + for (const count of counts) { + const badgeContainer = $('div'); + badgeContainer.style.display = 'flex'; + badgeContainer.style.alignItems = 'center'; + badgeContainer.style.gap = '8px'; + + const label = $('span'); + label.textContent = 'Issues'; + label.style.color = 'var(--vscode-foreground)'; + badgeContainer.appendChild(label); + + new CountBadge(badgeContainer, { count }, themedBadgeStyles); + container.appendChild(badgeContainer); + } + + return container; +} + + +// ============================================================================ +// Action Bar +// ============================================================================ + +function renderActionBar({ container, disposableStore }: ComponentFixtureContext): HTMLElement { + container.style.padding = '16px'; + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.gap = '16px'; + + // Horizontal action bar + const horizontalLabel = $('div'); + horizontalLabel.textContent = 'Horizontal Actions:'; + horizontalLabel.style.color = 'var(--vscode-foreground)'; + horizontalLabel.style.marginBottom = '4px'; + container.appendChild(horizontalLabel); + + const horizontalContainer = $('div'); + container.appendChild(horizontalContainer); + + const horizontalBar = disposableStore.add(new ActionBar(horizontalContainer, { + ariaLabel: 'Editor Actions', + })); + + horizontalBar.push([ + new Action('editor.action.save', 'Save', ThemeIcon.asClassName(Codicon.save), true, async () => console.log('Save')), + new Action('editor.action.undo', 'Undo', ThemeIcon.asClassName(Codicon.discard), true, async () => console.log('Undo')), + new Action('editor.action.redo', 'Redo', ThemeIcon.asClassName(Codicon.redo), true, async () => console.log('Redo')), + new Separator(), + new Action('editor.action.find', 'Find', ThemeIcon.asClassName(Codicon.search), true, async () => console.log('Find')), + new Action('editor.action.replace', 'Replace', ThemeIcon.asClassName(Codicon.replaceAll), true, async () => console.log('Replace')), + ]); + + // Action bar with disabled items + const mixedLabel = $('div'); + mixedLabel.textContent = 'Mixed States:'; + mixedLabel.style.color = 'var(--vscode-foreground)'; + mixedLabel.style.marginBottom = '4px'; + container.appendChild(mixedLabel); + + const mixedContainer = $('div'); + container.appendChild(mixedContainer); + + const mixedBar = disposableStore.add(new ActionBar(mixedContainer, { + ariaLabel: 'Mixed Actions', + })); + + mixedBar.push([ + new Action('action.enabled', 'Enabled', ThemeIcon.asClassName(Codicon.play), true, async () => { }), + new Action('action.disabled', 'Disabled', ThemeIcon.asClassName(Codicon.debugPause), false, async () => { }), + new Action('action.enabled2', 'Enabled', ThemeIcon.asClassName(Codicon.debugStop), true, async () => { }), + ]); + + return container; +} + + +// ============================================================================ +// Progress Bar +// ============================================================================ + +function renderProgressBars({ container, disposableStore }: ComponentFixtureContext): HTMLElement { + container.style.padding = '16px'; + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.gap = '24px'; + container.style.width = '400px'; + + const createSection = (label: string) => { + const section = $('div'); + const labelEl = $('div'); + labelEl.textContent = label; + labelEl.style.color = 'var(--vscode-foreground)'; + labelEl.style.marginBottom = '8px'; + labelEl.style.fontSize = '12px'; + section.appendChild(labelEl); + + // Progress bar container with proper constraints + const barContainer = $('div'); + barContainer.style.position = 'relative'; + barContainer.style.width = '100%'; + barContainer.style.height = '4px'; + barContainer.style.overflow = 'hidden'; + section.appendChild(barContainer); + + container.appendChild(section); + return barContainer; + }; + + // Infinite progress + const infiniteSection = createSection('Infinite Progress (loading...)'); + const infiniteBar = disposableStore.add(new ProgressBar(infiniteSection, themedProgressBarOptions)); + infiniteBar.infinite(); + + // Discrete progress - 30% + const progress30Section = createSection('Discrete Progress - 30%'); + const progress30Bar = disposableStore.add(new ProgressBar(progress30Section, themedProgressBarOptions)); + progress30Bar.total(100); + progress30Bar.worked(30); + + // Discrete progress - 60% + const progress60Section = createSection('Discrete Progress - 60%'); + const progress60Bar = disposableStore.add(new ProgressBar(progress60Section, themedProgressBarOptions)); + progress60Bar.total(100); + progress60Bar.worked(60); + + // Discrete progress - 90% + const progress90Section = createSection('Discrete Progress - 90%'); + const progress90Bar = disposableStore.add(new ProgressBar(progress90Section, themedProgressBarOptions)); + progress90Bar.total(100); + progress90Bar.worked(90); + + // Completed progress + const doneSection = createSection('Completed (100%)'); + const doneBar = disposableStore.add(new ProgressBar(doneSection, themedProgressBarOptions)); + doneBar.total(100); + doneBar.worked(100); + + return container; +} + + +// ============================================================================ +// Highlighted Label +// ============================================================================ + +function renderHighlightedLabels({ container }: ComponentFixtureContext): HTMLElement { + container.style.padding = '16px'; + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.gap = '8px'; + container.style.color = 'var(--vscode-foreground)'; + + const createHighlightedLabel = (text: string, highlights: { start: number; end: number }[]) => { + const row = $('div'); + row.style.display = 'flex'; + row.style.alignItems = 'center'; + row.style.gap = '8px'; + + const labelContainer = $('div'); + const label = new HighlightedLabel(labelContainer); + label.set(text, highlights); + row.appendChild(labelContainer); + + const queryLabel = $('span'); + queryLabel.style.color = 'var(--vscode-descriptionForeground)'; + queryLabel.style.fontSize = '12px'; + queryLabel.textContent = `(matches highlighted)`; + row.appendChild(queryLabel); + + return row; + }; + + // File search examples + container.appendChild(createHighlightedLabel('codeEditorWidget.ts', [{ start: 0, end: 4 }])); // "code" + container.appendChild(createHighlightedLabel('inlineCompletionsController.ts', [{ start: 6, end: 10 }])); // "Comp" + container.appendChild(createHighlightedLabel('diffEditorViewModel.ts', [{ start: 0, end: 4 }, { start: 10, end: 14 }])); // "diff" and "View" + container.appendChild(createHighlightedLabel('workbenchTestServices.ts', [{ start: 9, end: 13 }])); // "Test" + + return container; +} + + +// ============================================================================ +// Export Fixtures +// ============================================================================ + +export default defineThemedFixtureGroup({ + Buttons: defineComponentFixture({ + render: renderButtons, + }), + + ButtonBar: defineComponentFixture({ + render: renderButtonBar, + }), + + Toggles: defineComponentFixture({ + render: renderToggles, + }), + + InputBoxes: defineComponentFixture({ + render: renderInputBoxes, + }), + + CountBadges: defineComponentFixture({ + render: renderCountBadges, + }), + + ActionBar: defineComponentFixture({ + render: renderActionBar, + }), + + ProgressBars: defineComponentFixture({ + render: renderProgressBars, + }), + + HighlightedLabels: defineComponentFixture({ + render: renderHighlightedLabels, + }), +}); diff --git a/build/vite/fixtures/editor/codeEditor.fixture.ts b/build/vite/fixtures/editor/codeEditor.fixture.ts new file mode 100644 index 0000000000000..bdaaf35bf0fcf --- /dev/null +++ b/build/vite/fixtures/editor/codeEditor.fixture.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from '../../../../src/vs/base/common/uri'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../src/vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { ComponentFixtureContext, createEditorServices, createTextModel, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils'; + +const SAMPLE_CODE = `// Welcome to VS Code +function greet(name: string): string { + return \`Hello, \${name}!\`; +} + +class Counter { + private _count = 0; + + increment(): void { + this._count++; + } + + get count(): number { + return this._count; + } +} + +const counter = new Counter(); +counter.increment(); +console.log(greet('World')); +console.log(\`Count: \${counter.count}\`); +`; + +function renderCodeEditor({ container, disposableStore, theme }: ComponentFixtureContext): HTMLElement { + container.style.width = '600px'; + container.style.height = '400px'; + container.style.border = '1px solid var(--vscode-editorWidget-border)'; + + const instantiationService = createEditorServices(disposableStore, { colorTheme: theme }); + + const model = disposableStore.add(createTextModel( + instantiationService, + SAMPLE_CODE, + URI.parse('inmemory://sample.ts'), + 'typescript' + )); + + const editorOptions: ICodeEditorWidgetOptions = { + contributions: [] + }; + + const editor = disposableStore.add(instantiationService.createInstance( + CodeEditorWidget, + container, + { + automaticLayout: true, + minimap: { enabled: true }, + lineNumbers: 'on', + scrollBeyondLastLine: false, + fontSize: 14, + fontFamily: 'Consolas, "Courier New", monospace', + renderWhitespace: 'selection', + bracketPairColorization: { enabled: true }, + }, + editorOptions + )); + + editor.setModel(model); + + return container; +} + +export default defineThemedFixtureGroup({ + CodeEditor: defineComponentFixture({ + render: (context) => renderCodeEditor(context), + }), +}); diff --git a/build/vite/fixtures/editor/inlineCompletions.fixture.ts b/build/vite/fixtures/editor/inlineCompletions.fixture.ts new file mode 100644 index 0000000000000..06abdece6b686 --- /dev/null +++ b/build/vite/fixtures/editor/inlineCompletions.fixture.ts @@ -0,0 +1,164 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { constObservable } from '../../../../src/vs/base/common/observable'; +import { URI } from '../../../../src/vs/base/common/uri'; +import { Range } from '../../../../src/vs/editor/common/core/range'; +import { IEditorOptions } from '../../../../src/vs/editor/common/config/editorOptions'; +import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../src/vs/editor/browser/widget/codeEditor/codeEditorWidget'; +import { EditorExtensionsRegistry } from '../../../../src/vs/editor/browser/editorExtensions'; +import { InlineCompletionsController } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/controller/inlineCompletionsController'; +import { InlineCompletionsSource, InlineCompletionsState } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsSource'; +import { InlineEditItem } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/inlineSuggestionItem'; +import { TextModelValueReference } from '../../../../src/vs/editor/contrib/inlineCompletions/browser/model/textModelValueReference'; +import { ComponentFixtureContext, createEditorServices, createTextModel, defineComponentFixture, defineThemedFixtureGroup } from '../fixtureUtils'; + +// Import to register the inline completions contribution +import '../../../../src/vs/editor/contrib/inlineCompletions/browser/inlineCompletions.contribution'; + + +// ============================================================================ +// Inline Edit Fixture +// ============================================================================ + +interface InlineEditOptions extends ComponentFixtureContext { + code: string; + cursorLine: number; + range: { startLineNumber: number; startColumn: number; endLineNumber: number; endColumn: number }; + newText: string; + width?: string; + height?: string; + editorOptions?: IEditorOptions; +} + +function renderInlineEdit(options: InlineEditOptions): HTMLElement { + const { container, disposableStore, theme } = options; + container.style.width = options.width ?? '500px'; + container.style.height = options.height ?? '170px'; + container.style.border = '1px solid var(--vscode-editorWidget-border)'; + + const instantiationService = createEditorServices(disposableStore, { colorTheme: theme }); + + const textModel = disposableStore.add(createTextModel( + instantiationService, + options.code, + URI.parse('inmemory://inline-edit.ts'), + 'typescript' + )); + + // Mock the InlineCompletionsSource to provide our test completion + instantiationService.stubInstance(InlineCompletionsSource, { + cancelUpdate: () => { }, + clear: () => { }, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + clearOperationOnTextModelChange: constObservable(undefined) as any, + clearSuggestWidgetInlineCompletions: () => { }, + dispose: () => { }, + fetch: async () => true, + inlineCompletions: constObservable(new InlineCompletionsState([ + InlineEditItem.createForTest( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + TextModelValueReference.snapshot(textModel as any), + new Range( + options.range.startLineNumber, + options.range.startColumn, + options.range.endLineNumber, + options.range.endColumn + ), + options.newText + ) + ], undefined)), + loading: constObservable(false), + seedInlineCompletionsWithSuggestWidget: () => { }, + seedWithCompletion: () => { }, + suggestWidgetInlineCompletions: constObservable(InlineCompletionsState.createEmpty()), + }); + + const editorWidgetOptions: ICodeEditorWidgetOptions = { + contributions: EditorExtensionsRegistry.getEditorContributions() + }; + + const editor = disposableStore.add(instantiationService.createInstance( + CodeEditorWidget, + container, + { + automaticLayout: true, + minimap: { enabled: false }, + lineNumbers: 'on', + scrollBeyondLastLine: false, + fontSize: 14, + cursorBlinking: 'solid', + ...options.editorOptions, + }, + editorWidgetOptions + )); + + editor.setModel(textModel); + editor.setPosition({ lineNumber: options.cursorLine, column: 1 }); + editor.focus(); + + // Trigger inline completions + const controller = InlineCompletionsController.get(editor); + controller?.model?.get(); + + return container; +} + + +// ============================================================================ +// Fixtures +// ============================================================================ + +export default defineThemedFixtureGroup({ + // Side-by-side view: Multi-line replacement + SideBySideView: defineComponentFixture({ + render: (context) => renderInlineEdit({ + ...context, + code: `function greet(name) { + console.log("Hello, " + name); +}`, + cursorLine: 2, + range: { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 100 }, + newText: ' console.log(`Hello, ${name}!`);', + }), + }), + + // Word replacement view: Single word change + WordReplacementView: defineComponentFixture({ + render: (context) => renderInlineEdit({ + ...context, + code: `class BufferData { + append(data: number[]) { + this.data.push(data); + } +}`, + cursorLine: 2, + range: { startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 8 }, + newText: 'push', + height: '200px', + }), + }), + + // Insertion view: Insert new content + InsertionView: defineComponentFixture({ + render: (context) => renderInlineEdit({ + ...context, + code: `class BufferData { + append(data: number[]) {} // appends data +}`, + cursorLine: 2, + range: { startLineNumber: 2, startColumn: 26, endLineNumber: 2, endColumn: 26 }, + newText: ` + console.log(data); + `, + height: '200px', + editorOptions: { + inlineSuggest: { + edits: { allowCodeShifting: 'always' } + } + } + }), + }), +}); diff --git a/build/vite/fixtures/fixtureUtils.ts b/build/vite/fixtures/fixtureUtils.ts new file mode 100644 index 0000000000000..fd90169dae792 --- /dev/null +++ b/build/vite/fixtures/fixtureUtils.ts @@ -0,0 +1,512 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { defineFixture, defineFixtureGroup, defineFixtureVariants } from '@vscode/component-explorer'; +import { DisposableStore, toDisposable } from '../../../src/vs/base/common/lifecycle'; +import { URI } from '../../../src/vs/base/common/uri'; +import '../style.css'; + +// Theme +import { COLOR_THEME_DARK_INITIAL_COLORS, COLOR_THEME_LIGHT_INITIAL_COLORS } from '../../../src/vs/workbench/services/themes/common/workbenchThemeService'; +import { ColorThemeData } from '../../../src/vs/workbench/services/themes/common/colorThemeData'; +import { ColorScheme } from '../../../src/vs/platform/theme/common/theme'; +import { generateColorThemeCSS } from '../../../src/vs/workbench/services/themes/browser/colorThemeCss'; +import { Registry } from '../../../src/vs/platform/registry/common/platform'; +import { Extensions as ThemingExtensions, IThemingRegistry } from '../../../src/vs/platform/theme/common/themeService'; +import { IEnvironmentService } from '../../../src/vs/platform/environment/common/environment'; +import { getIconsStyleSheet } from '../../../src/vs/platform/theme/browser/iconsStyleSheet'; + +// Instantiation +import { ServiceCollection } from '../../../src/vs/platform/instantiation/common/serviceCollection'; +import { SyncDescriptor } from '../../../src/vs/platform/instantiation/common/descriptors'; +import { ServiceIdentifier } from '../../../src/vs/platform/instantiation/common/instantiation'; +import { TestInstantiationService } from '../../../src/vs/platform/instantiation/test/common/instantiationServiceMock'; + +// Test service implementations +import { TestAccessibilityService } from '../../../src/vs/platform/accessibility/test/common/testAccessibilityService'; +import { MockKeybindingService, MockContextKeyService } from '../../../src/vs/platform/keybinding/test/common/mockKeybindingService'; +import { TestClipboardService } from '../../../src/vs/platform/clipboard/test/common/testClipboardService'; +import { TestEditorWorkerService } from '../../../src/vs/editor/test/common/services/testEditorWorkerService'; +import { NullOpenerService } from '../../../src/vs/platform/opener/test/common/nullOpenerService'; +import { TestNotificationService } from '../../../src/vs/platform/notification/test/common/testNotificationService'; +import { TestDialogService } from '../../../src/vs/platform/dialogs/test/common/testDialogService'; +import { TestConfigurationService } from '../../../src/vs/platform/configuration/test/common/testConfigurationService'; +import { TestTextResourcePropertiesService } from '../../../src/vs/editor/test/common/services/testTextResourcePropertiesService'; +import { TestThemeService } from '../../../src/vs/platform/theme/test/common/testThemeService'; +import { TestLanguageConfigurationService } from '../../../src/vs/editor/test/common/modes/testLanguageConfigurationService'; +import { TestCodeEditorService, TestCommandService } from '../../../src/vs/editor/test/browser/editorTestServices'; +import { TestTreeSitterLibraryService } from '../../../src/vs/editor/test/common/services/testTreeSitterLibraryService'; +import { TestMenuService } from '../../../src/vs/workbench/test/browser/workbenchTestServices'; + +// Service interfaces +import { IAccessibilityService } from '../../../src/vs/platform/accessibility/common/accessibility'; +import { IKeybindingService } from '../../../src/vs/platform/keybinding/common/keybinding'; +import { IClipboardService } from '../../../src/vs/platform/clipboard/common/clipboardService'; +import { IEditorWorkerService } from '../../../src/vs/editor/common/services/editorWorker'; +import { IOpenerService } from '../../../src/vs/platform/opener/common/opener'; +import { INotificationService } from '../../../src/vs/platform/notification/common/notification'; +import { IDialogService } from '../../../src/vs/platform/dialogs/common/dialogs'; +import { IUndoRedoService } from '../../../src/vs/platform/undoRedo/common/undoRedo'; +import { UndoRedoService } from '../../../src/vs/platform/undoRedo/common/undoRedoService'; +import { ILanguageService } from '../../../src/vs/editor/common/languages/language'; +import { LanguageService } from '../../../src/vs/editor/common/services/languageService'; +import { ILanguageConfigurationService } from '../../../src/vs/editor/common/languages/languageConfigurationRegistry'; +import { IConfigurationService } from '../../../src/vs/platform/configuration/common/configuration'; +import { ITextResourcePropertiesService } from '../../../src/vs/editor/common/services/textResourceConfiguration'; +import { IColorTheme, IThemeService } from '../../../src/vs/platform/theme/common/themeService'; +import { ILogService, NullLogService, ILoggerService, NullLoggerService } from '../../../src/vs/platform/log/common/log'; +import { IModelService } from '../../../src/vs/editor/common/services/model'; +import { ModelService } from '../../../src/vs/editor/common/services/modelService'; +import { ICodeEditorService } from '../../../src/vs/editor/browser/services/codeEditorService'; +import { IContextKeyService } from '../../../src/vs/platform/contextkey/common/contextkey'; +import { ICommandService } from '../../../src/vs/platform/commands/common/commands'; +import { ITelemetryService } from '../../../src/vs/platform/telemetry/common/telemetry'; +import { NullTelemetryServiceShape } from '../../../src/vs/platform/telemetry/common/telemetryUtils'; +import { ILanguageFeatureDebounceService, LanguageFeatureDebounceService } from '../../../src/vs/editor/common/services/languageFeatureDebounce'; +import { ILanguageFeaturesService } from '../../../src/vs/editor/common/services/languageFeatures'; +import { LanguageFeaturesService } from '../../../src/vs/editor/common/services/languageFeaturesService'; +import { ITreeSitterLibraryService } from '../../../src/vs/editor/common/services/treeSitter/treeSitterLibraryService'; +import { IInlineCompletionsService, InlineCompletionsService } from '../../../src/vs/editor/browser/services/inlineCompletionsService'; +import { ICodeLensCache } from '../../../src/vs/editor/contrib/codelens/browser/codeLensCache'; +import { IHoverService } from '../../../src/vs/platform/hover/browser/hover'; +import { IDataChannelService, NullDataChannelService } from '../../../src/vs/platform/dataChannel/common/dataChannel'; +import { IContextMenuService, IContextViewService } from '../../../src/vs/platform/contextview/browser/contextView'; +import { ILabelService } from '../../../src/vs/platform/label/common/label'; +import { IMenuService } from '../../../src/vs/platform/actions/common/actions'; +import { IActionViewItemService, NullActionViewItemService } from '../../../src/vs/platform/actions/browser/actionViewItemService'; +import { IDefaultAccountService } from '../../../src/vs/platform/defaultAccount/common/defaultAccount'; +import { IStorageService, IStorageValueChangeEvent, IWillSaveStateEvent, StorageScope, StorageTarget, IStorageTargetChangeEvent, IStorageEntry, WillSaveStateReason, IWorkspaceStorageValueChangeEvent, IProfileStorageValueChangeEvent, IApplicationStorageValueChangeEvent } from '../../../src/vs/platform/storage/common/storage'; +import { Emitter, Event } from '../../../src/vs/base/common/event'; +import { mock } from '../../../src/vs/base/test/common/mock'; +import { IAnyWorkspaceIdentifier } from '../../../src/vs/platform/workspace/common/workspace'; +import { IUserDataProfile } from '../../../src/vs/platform/userDataProfile/common/userDataProfile'; +import { IUserInteractionService, MockUserInteractionService } from '../../../src/vs/platform/userInteraction/browser/userInteractionService'; + +// Editor +import { ITextModel } from '../../../src/vs/editor/common/model'; + + + +// Import color registrations to ensure colors are available +import '../../../src/vs/platform/theme/common/colors/baseColors'; +import '../../../src/vs/platform/theme/common/colors/editorColors'; +import '../../../src/vs/platform/theme/common/colors/listColors'; +import '../../../src/vs/platform/theme/common/colors/miscColors'; +import '../../../src/vs/workbench/common/theme'; + +/** + * A storage service that never stores anything and always returns the default/fallback value. + * This is useful for fixtures where we want consistent behavior without persisted state. + */ +class NullStorageService implements IStorageService { + + declare readonly _serviceBrand: undefined; + + private readonly _onDidChangeValue = new Emitter(); + onDidChangeValue(scope: StorageScope.WORKSPACE, key: string | undefined, disposable: DisposableStore): Event; + onDidChangeValue(scope: StorageScope.PROFILE, key: string | undefined, disposable: DisposableStore): Event; + onDidChangeValue(scope: StorageScope.APPLICATION, key: string | undefined, disposable: DisposableStore): Event; + onDidChangeValue(scope: StorageScope, key: string | undefined, disposable: DisposableStore): Event { + return Event.filter(this._onDidChangeValue.event, e => e.scope === scope && (key === undefined || e.key === key), disposable); + } + + private readonly _onDidChangeTarget = new Emitter(); + readonly onDidChangeTarget: Event = this._onDidChangeTarget.event; + + private readonly _onWillSaveState = new Emitter(); + readonly onWillSaveState: Event = this._onWillSaveState.event; + + get(key: string, scope: StorageScope, fallbackValue: string): string; + get(key: string, scope: StorageScope, fallbackValue?: string): string | undefined; + get(_key: string, _scope: StorageScope, fallbackValue?: string): string | undefined { + return fallbackValue; + } + + getBoolean(key: string, scope: StorageScope, fallbackValue: boolean): boolean; + getBoolean(key: string, scope: StorageScope, fallbackValue?: boolean): boolean | undefined; + getBoolean(_key: string, _scope: StorageScope, fallbackValue?: boolean): boolean | undefined { + return fallbackValue; + } + + getNumber(key: string, scope: StorageScope, fallbackValue: number): number; + getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined; + getNumber(_key: string, _scope: StorageScope, fallbackValue?: number): number | undefined { + return fallbackValue; + } + + getObject(key: string, scope: StorageScope, fallbackValue: T): T; + getObject(key: string, scope: StorageScope, fallbackValue?: T): T | undefined; + getObject(_key: string, _scope: StorageScope, fallbackValue?: T): T | undefined { + return fallbackValue; + } + + store(_key: string, _value: string | boolean | number | undefined | null, _scope: StorageScope, _target: StorageTarget): void { + // no-op + } + + storeAll(_entries: IStorageEntry[], _external: boolean): void { + // no-op + } + + remove(_key: string, _scope: StorageScope): void { + // no-op + } + + isNew(_scope: StorageScope): boolean { + return true; + } + + flush(_reason?: WillSaveStateReason): Promise { + return Promise.resolve(); + } + + optimize(_scope: StorageScope): Promise { + return Promise.resolve(); + } + + log(): void { + // no-op + } + + keys(_scope: StorageScope, _target: StorageTarget): string[] { + return []; + } + + switch(): Promise { + return Promise.resolve(); + } + + hasScope(_scope: IAnyWorkspaceIdentifier | IUserDataProfile): boolean { + return false; + } +} + + +// ============================================================================ +// Themes +// ============================================================================ + +const themingRegistry = Registry.as(ThemingExtensions.ThemingContribution); +const mockEnvironmentService: IEnvironmentService = Object.create(null); + +export const darkTheme = ColorThemeData.createUnloadedThemeForThemeType( + ColorScheme.DARK, + COLOR_THEME_DARK_INITIAL_COLORS +); + +export const lightTheme = ColorThemeData.createUnloadedThemeForThemeType( + ColorScheme.LIGHT, + COLOR_THEME_LIGHT_INITIAL_COLORS +); + +let globalStyleSheet: CSSStyleSheet | undefined; +let iconsStyleSheetCache: CSSStyleSheet | undefined; +let darkThemeStyleSheet: CSSStyleSheet | undefined; +let lightThemeStyleSheet: CSSStyleSheet | undefined; + +function getGlobalStyleSheet(): CSSStyleSheet { + if (!globalStyleSheet) { + globalStyleSheet = new CSSStyleSheet(); + const globalRules: string[] = []; + for (const sheet of Array.from(document.styleSheets)) { + try { + for (const rule of Array.from(sheet.cssRules)) { + globalRules.push(rule.cssText); + } + } catch { + // Cross-origin stylesheets can't be read + } + } + globalStyleSheet.replaceSync(globalRules.join('\n')); + } + return globalStyleSheet; +} + +function getIconsStyleSheetCached(): CSSStyleSheet { + if (!iconsStyleSheetCache) { + iconsStyleSheetCache = new CSSStyleSheet(); + const iconsSheet = getIconsStyleSheet(undefined); + iconsStyleSheetCache.replaceSync(iconsSheet.getCSS() as string); + iconsSheet.dispose(); + } + return iconsStyleSheetCache; +} + +function getThemeStyleSheet(theme: ColorThemeData): CSSStyleSheet { + const isDark = theme.type === ColorScheme.DARK; + if (isDark && darkThemeStyleSheet) { + return darkThemeStyleSheet; + } + if (!isDark && lightThemeStyleSheet) { + return lightThemeStyleSheet; + } + + const sheet = new CSSStyleSheet(); + const css = generateColorThemeCSS( + theme, + ':host', + themingRegistry.getThemingParticipants(), + mockEnvironmentService + ); + sheet.replaceSync(css.code); + + if (isDark) { + darkThemeStyleSheet = sheet; + } else { + lightThemeStyleSheet = sheet; + } + return sheet; +} + +/** + * Applies theme styling to a shadow DOM container. + * Adds theme class names and adopts shared stylesheets. + */ +export function setupTheme(container: HTMLElement, theme: ColorThemeData): void { + container.classList.add(...theme.classNames); + + const shadowRoot = container.getRootNode() as ShadowRoot; + if (shadowRoot.adoptedStyleSheets !== undefined) { + shadowRoot.adoptedStyleSheets = [ + getGlobalStyleSheet(), + getIconsStyleSheetCached(), + getThemeStyleSheet(theme), + ]; + } +} + + +// ============================================================================ +// Services +// ============================================================================ + +export interface ServiceRegistration { + define(id: ServiceIdentifier, ctor: new (...args: never[]) => T): void; + defineInstance(id: ServiceIdentifier, instance: T): void; +} + +export interface CreateServicesOptions { + /** + * The color theme to use for the theme service. + */ + colorTheme?: IColorTheme; + /** + * Additional services to register after the base editor services. + */ + additionalServices?: (registration: ServiceRegistration) => void; +} + +/** + * Creates a TestInstantiationService with all services needed for CodeEditorWidget. + * Additional services can be registered via the options callback. + */ +export function createEditorServices(disposables: DisposableStore, options?: CreateServicesOptions): TestInstantiationService { + const services = new ServiceCollection(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const serviceIdentifiers: ServiceIdentifier[] = []; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const define = (id: ServiceIdentifier, ctor: new (...args: any[]) => T) => { + if (!services.has(id)) { + services.set(id, new SyncDescriptor(ctor)); + } + serviceIdentifiers.push(id); + }; + + const defineInstance = (id: ServiceIdentifier, instance: T) => { + if (!services.has(id)) { + services.set(id, instance); + } + serviceIdentifiers.push(id); + }; + + // Base editor services + define(IAccessibilityService, TestAccessibilityService); + define(IKeybindingService, MockKeybindingService); + define(IClipboardService, TestClipboardService); + define(IEditorWorkerService, TestEditorWorkerService); + defineInstance(IOpenerService, NullOpenerService); + define(INotificationService, TestNotificationService); + define(IDialogService, TestDialogService); + define(IUndoRedoService, UndoRedoService); + define(ILanguageService, LanguageService); + define(ILanguageConfigurationService, TestLanguageConfigurationService); + define(IConfigurationService, TestConfigurationService); + define(ITextResourcePropertiesService, TestTextResourcePropertiesService); + defineInstance(IStorageService, new NullStorageService()); + if (options?.colorTheme) { + defineInstance(IThemeService, new TestThemeService(options.colorTheme)); + } else { + define(IThemeService, TestThemeService); + } + define(ILogService, NullLogService); + define(IModelService, ModelService); + define(ICodeEditorService, TestCodeEditorService); + define(IContextKeyService, MockContextKeyService); + define(ICommandService, TestCommandService); + define(ITelemetryService, NullTelemetryServiceShape); + define(ILoggerService, NullLoggerService); + define(IDataChannelService, NullDataChannelService); + define(IEnvironmentService, class extends mock() { + declare readonly _serviceBrand: undefined; + override isBuilt: boolean = true; + override isExtensionDevelopment: boolean = false; + }); + define(ILanguageFeatureDebounceService, LanguageFeatureDebounceService); + define(ILanguageFeaturesService, LanguageFeaturesService); + define(ITreeSitterLibraryService, TestTreeSitterLibraryService); + define(IInlineCompletionsService, InlineCompletionsService); + defineInstance(ICodeLensCache, { + _serviceBrand: undefined, + put: () => { }, + get: () => undefined, + delete: () => { }, + } as ICodeLensCache); + defineInstance(IHoverService, { + _serviceBrand: undefined, + showDelayedHover: () => undefined, + setupDelayedHover: () => ({ dispose: () => { } }), + setupDelayedHoverAtMouse: () => ({ dispose: () => { } }), + showInstantHover: () => undefined, + hideHover: () => { }, + showAndFocusLastHover: () => { }, + setupManagedHover: () => ({ dispose: () => { }, show: () => { }, hide: () => { }, update: () => { } }), + showManagedHover: () => { }, + } as IHoverService); + defineInstance(IDefaultAccountService, { + _serviceBrand: undefined, + onDidChangeDefaultAccount: new Emitter().event, + onDidChangePolicyData: new Emitter().event, + policyData: null, + getDefaultAccount: async () => null, + getDefaultAccountAuthenticationProvider: () => ({ id: 'test', name: 'Test', scopes: [], enterprise: false }), + setDefaultAccountProvider: () => { }, + refresh: async () => null, + signIn: async () => null, + } as IDefaultAccountService); + + // User interaction service with focus simulation enabled (all elements appear focused in fixtures) + defineInstance(IUserInteractionService, new MockUserInteractionService(true, false)); + + // Allow additional services to be registered + options?.additionalServices?.({ define, defineInstance }); + + const instantiationService = disposables.add(new TestInstantiationService(services, true)); + + disposables.add(toDisposable(() => { + for (const id of serviceIdentifiers) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const instanceOrDescriptor = services.get(id) as any; + if (typeof instanceOrDescriptor?.dispose === 'function') { + instanceOrDescriptor.dispose(); + } + } + })); + + return instantiationService; +} + +/** + * Registers additional services needed by workbench components (merge editor, etc.). + * Use with createEditorServices additionalServices option. + */ +export function registerWorkbenchServices(registration: ServiceRegistration): void { + registration.defineInstance(IContextMenuService, { + showContextMenu: () => { }, + onDidShowContextMenu: () => ({ dispose: () => { } }), + onDidHideContextMenu: () => ({ dispose: () => { } }), + } as unknown as IContextMenuService); + + registration.defineInstance(IContextViewService, { + showContextView: () => ({ dispose: () => { } }), + hideContextView: () => { }, + getContextViewElement: () => null, + layout: () => { }, + } as unknown as IContextViewService); + + registration.defineInstance(ILabelService, { + getUriLabel: (uri: URI) => uri.path, + getUriBasenameLabel: (uri: URI) => uri.path.split('/').pop() ?? '', + getWorkspaceLabel: () => '', + getHostLabel: () => '', + getSeparator: () => '/', + registerFormatter: () => ({ dispose: () => { } }), + onDidChangeFormatters: () => ({ dispose: () => { } }), + registerCachedFormatter: () => ({ dispose: () => { } }), + } as unknown as ILabelService); + + registration.define(IMenuService, TestMenuService); + registration.define(IActionViewItemService, NullActionViewItemService); +} + + +// ============================================================================ +// Text Models +// ============================================================================ + +/** + * Creates a text model using the ModelService. + */ +export function createTextModel( + instantiationService: TestInstantiationService, + text: string, + uri: URI, + languageId?: string +): ITextModel { + const modelService = instantiationService.get(IModelService); + const languageService = instantiationService.get(ILanguageService); + const languageSelection = languageId ? languageService.createById(languageId) : null; + return modelService.createModel(text, languageSelection, uri); +} + + +// ============================================================================ +// Fixture Adapters +// ============================================================================ + +export interface ComponentFixtureContext { + container: HTMLElement; + disposableStore: DisposableStore; + theme: ColorThemeData; +} + +export interface ComponentFixtureOptions { + render: (context: ComponentFixtureContext) => HTMLElement | Promise; +} + +type ThemedFixtures = ReturnType; + +/** + * Creates Dark and Light fixture variants from a single render function. + * The render function receives a context with container and disposableStore. + */ +export function defineComponentFixture(options: ComponentFixtureOptions): ThemedFixtures { + const createFixture = (theme: typeof darkTheme | typeof lightTheme) => defineFixture({ + isolation: 'shadow-dom', + displayMode: { type: 'component' }, + properties: [], + background: theme === darkTheme ? 'dark' : 'light', + render: async (container: HTMLElement) => { + const disposableStore = new DisposableStore(); + setupTheme(container, theme); + return options.render({ container, disposableStore, theme }); + }, + }); + + return defineFixtureVariants({ + Dark: createFixture(darkTheme), + Light: createFixture(lightTheme), + }); +} + +type ThemedFixtureGroupInput = Record; + +/** + * Creates a nested fixture group from themed fixtures. + * E.g., { MergeEditor: { Dark: ..., Light: ... } } becomes a nested group: MergeEditor > Dark/Light + */ +export function defineThemedFixtureGroup(group: ThemedFixtureGroupInput): ReturnType { + return defineFixtureGroup(group); +} diff --git a/build/vite/package-lock.json b/build/vite/package-lock.json index 97fb9dc76aa64..4db5338149d1f 100644 --- a/build/vite/package-lock.json +++ b/build/vite/package-lock.json @@ -8,130 +8,87 @@ "name": "@vscode/sample-source", "version": "0.0.0", "devDependencies": { - "@vscode/rollup-plugin-esm-url": "^1.0.1-0", - "vite": "^7.1.11" + "@vscode/component-explorer": "next", + "@vscode/component-explorer-vite-plugin": "next", + "@vscode/rollup-plugin-esm-url": "^1.0.1-1", + "vite": "npm:rolldown-vite@latest" } }, - "../lib": { - "name": "monaco-editor-core", - "version": "0.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "postcss-copy": "^7.1.0", - "postcss-copy-assets": "^0.3.1", - "rollup": "^4.35.0", - "rollup-plugin-esbuild": "^6.2.1", - "rollup-plugin-lib-style": "^2.3.2", - "rollup-plugin-postcss": "^4.0.2" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", - "cpu": [ - "ppc64" - ], + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", - "cpu": [ - "arm" - ], + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", - "cpu": [ - "arm64" - ], + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", - "cpu": [ - "x64" - ], + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", + "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", "dev": true, "license": "MIT", "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", - "cpu": [ - "arm64" - ], + "node_modules/@oxc-project/runtime": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-project/runtime/-/runtime-0.101.0.tgz", + "integrity": "sha512-t3qpfVZIqSiLQ5Kqt/MC4Ge/WCOGrrcagAdzTcDaggupjiGxUx4nJF2v6wUCXWSzWHn5Ns7XLv13fCJEwCOERQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", - "cpu": [ - "x64" - ], + "node_modules/@oxc-project/types": { + "version": "0.101.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.101.0.tgz", + "integrity": "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-beta.53.tgz", + "integrity": "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ==", "cpu": [ "arm64" ], @@ -139,50 +96,16 @@ "license": "MIT", "optional": true, "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "android" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.53.tgz", + "integrity": "sha512-yIsKqMz0CtRnVa6x3Pa+mzTihr4Ty+Z6HfPbZ7RVbk1Uxnco4+CUn7Qbm/5SBol1JD/7nvY8rphAgyAi7Lj6Vg==", "cpu": [ "arm64" ], @@ -190,69 +113,52 @@ "license": "MIT", "optional": true, "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.53.tgz", + "integrity": "sha512-GTXe+mxsCGUnJOFMhfGWmefP7Q9TpYUseHvhAhr21nCTgdS8jPsvirb0tJwM3lN0/u/cg7bpFNa16fQrjKrCjQ==", "cpu": [ - "loong64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.53.tgz", + "integrity": "sha512-9Tmp7bBvKqyDkMcL4e089pH3RsjD3SUungjmqWtyhNOxoQMh0fSmINTyYV8KXtE+JkxYMPWvnEt+/mfpVCkk8w==", "cpu": [ - "mips64el" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "freebsd" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.53.tgz", + "integrity": "sha512-a1y5fiB0iovuzdbjUxa7+Zcvgv+mTmlGGC4XydVIsyl48eoxgaYkA3l9079hyTyhECsPq+mbr0gVQsFU11OJAQ==", "cpu": [ - "ppc64" + "arm" ], "dev": true, "license": "MIT", @@ -261,15 +167,15 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.53.tgz", + "integrity": "sha512-bpIGX+ov9PhJYV+wHNXl9rzq4F0QvILiURn0y0oepbQx+7stmQsKA0DhPGwmhfvF856wq+gbM8L92SAa/CBcLg==", "cpu": [ - "riscv64" + "arm64" ], "dev": true, "license": "MIT", @@ -278,15 +184,15 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.53.tgz", + "integrity": "sha512-bGe5EBB8FVjHBR1mOLOPEFg1Lp3//7geqWkU5NIhxe+yH0W8FVrQ6WRYOap4SUTKdklD/dC4qPLREkMMQ855FA==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "license": "MIT", @@ -295,13 +201,13 @@ "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.53.tgz", + "integrity": "sha512-qL+63WKVQs1CMvFedlPt0U9PiEKJOAL/bsHMKUDS6Vp2Q+YAv/QLPu8rcvkfIMvQ0FPU2WL0aX4eWwF6e/GAnA==", "cpu": [ "x64" ], @@ -312,64 +218,13 @@ "linux" ], "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.53.tgz", + "integrity": "sha512-VGl9JIGjoJh3H8Mb+7xnVqODajBmrdOOb9lxWXdcmxyI+zjB2sux69br0hZJDTyLJfvBoYm439zPACYbCjGRmw==", "cpu": [ "x64" ], @@ -377,16 +232,16 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" + "linux" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-beta.53.tgz", + "integrity": "sha512-B4iIserJXuSnNzA5xBLFUIjTfhNy7d9sq4FUMQY3GhQWGVhS2RWWzzDnkSU6MUt7/aHUrep0CdQfXUJI9D3W7A==", "cpu": [ "arm64" ], @@ -397,30 +252,30 @@ "openharmony" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.53.tgz", + "integrity": "sha512-BUjAEgpABEJXilGq/BPh7jeU3WAJ5o15c1ZEgHaDWSz3LB881LQZnbNJHmUiM4d1JQWMYYyR1Y490IBHi2FPJg==", "cpu": [ - "x64" + "wasm32" ], "dev": true, "license": "MIT", "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.0" + }, "engines": { - "node": ">=18" + "node": ">=14.0.0" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.53.tgz", + "integrity": "sha512-s27uU7tpCWSjHBnxyVXHt3rMrQdJq5MHNv3BzsewCIroIw3DJFjMH1dzCPPMUFxnh1r52Nf9IJ/eWp6LDoyGcw==", "cpu": [ "arm64" ], @@ -431,15 +286,15 @@ "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.53.tgz", + "integrity": "sha512-cjWL/USPJ1g0en2htb4ssMjIycc36RvdQAx1WlXnS6DpULswiUTVXPDesTifSKYSyvx24E0YqQkEm0K/M2Z/AA==", "cpu": [ - "ia32" + "x64" ], "dev": true, "license": "MIT", @@ -448,30 +303,20 @@ "win32" ], "engines": { - "node": ">=18" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", - "cpu": [ - "x64" - ], + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } + "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.49.0.tgz", - "integrity": "sha512-rlKIeL854Ed0e09QGYFlmDNbka6I3EQFw7iZuugQjMb11KMpJCLPFL4ZPbMfaEhLADEL1yx0oujGkBQ7+qW3eA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", "cpu": [ "arm" ], @@ -480,12 +325,13 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.49.0.tgz", - "integrity": "sha512-cqPpZdKUSQYRtLLr6R4X3sD4jCBO1zUmeo3qrWBCqYIeH8Q3KRL4F3V7XJ2Rm8/RJOQBZuqzQGWPjjvFUcYa/w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", "cpu": [ "arm64" ], @@ -494,12 +340,13 @@ "optional": true, "os": [ "android" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.49.0.tgz", - "integrity": "sha512-99kMMSMQT7got6iYX3yyIiJfFndpojBmkHfTc1rIje8VbjhmqBXE+nb7ZZP3A5skLyujvT0eIUCUsxAe6NjWbw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", "cpu": [ "arm64" ], @@ -508,12 +355,13 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.49.0.tgz", - "integrity": "sha512-y8cXoD3wdWUDpjOLMKLx6l+NFz3NlkWKcBCBfttUn+VGSfgsQ5o/yDUGtzE9HvsodkP0+16N0P4Ty1VuhtRUGg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", "cpu": [ "x64" ], @@ -522,12 +370,13 @@ "optional": true, "os": [ "darwin" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.49.0.tgz", - "integrity": "sha512-3mY5Pr7qv4GS4ZvWoSP8zha8YoiqrU+e0ViPvB549jvliBbdNLrg2ywPGkgLC3cmvN8ya3za+Q2xVyT6z+vZqA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", "cpu": [ "arm64" ], @@ -536,12 +385,13 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.49.0.tgz", - "integrity": "sha512-C9KzzOAQU5gU4kG8DTk+tjdKjpWhVWd5uVkinCwwFub2m7cDYLOdtXoMrExfeBmeRy9kBQMkiyJ+HULyF1yj9w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", "cpu": [ "x64" ], @@ -550,12 +400,13 @@ "optional": true, "os": [ "freebsd" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.49.0.tgz", - "integrity": "sha512-OVSQgEZDVLnTbMq5NBs6xkmz3AADByCWI4RdKSFNlDsYXdFtlxS59J+w+LippJe8KcmeSSM3ba+GlsM9+WwC1w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", "cpu": [ "arm" ], @@ -564,12 +415,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.49.0.tgz", - "integrity": "sha512-ZnfSFA7fDUHNa4P3VwAcfaBLakCbYaxCk0jUnS3dTou9P95kwoOLAMlT3WmEJDBCSrOEFFV0Y1HXiwfLYJuLlA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", "cpu": [ "arm" ], @@ -578,12 +430,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.49.0.tgz", - "integrity": "sha512-Z81u+gfrobVK2iV7GqZCBfEB1y6+I61AH466lNK+xy1jfqFLiQ9Qv716WUM5fxFrYxwC7ziVdZRU9qvGHkYIJg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", "cpu": [ "arm64" ], @@ -592,12 +445,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.49.0.tgz", - "integrity": "sha512-zoAwS0KCXSnTp9NH/h9aamBAIve0DXeYpll85shf9NJ0URjSTzzS+Z9evmolN+ICfD3v8skKUPyk2PO0uGdFqg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", "cpu": [ "arm64" ], @@ -606,12 +460,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.49.0.tgz", - "integrity": "sha512-2QyUyQQ1ZtwZGiq0nvODL+vLJBtciItC3/5cYN8ncDQcv5avrt2MbKt1XU/vFAJlLta5KujqyHdYtdag4YEjYQ==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", "cpu": [ "loong64" ], @@ -620,12 +475,43 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.49.0.tgz", - "integrity": "sha512-k9aEmOWt+mrMuD3skjVJSSxHckJp+SiFzFG+v8JLXbc/xi9hv2icSkR3U7uQzqy+/QbbYY7iNB9eDTwrELo14g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", "cpu": [ "ppc64" ], @@ -634,12 +520,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.49.0.tgz", - "integrity": "sha512-rDKRFFIWJ/zJn6uk2IdYLc09Z7zkE5IFIOWqpuU0o6ZpHcdniAyWkwSUWE/Z25N/wNDmFHHMzin84qW7Wzkjsw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", "cpu": [ "riscv64" ], @@ -648,12 +535,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.49.0.tgz", - "integrity": "sha512-FkkhIY/hYFVnOzz1WeV3S9Bd1h0hda/gRqvZCMpHWDHdiIHn6pqsY3b5eSbvGccWHMQ1uUzgZTKS4oGpykf8Tw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", "cpu": [ "riscv64" ], @@ -662,12 +550,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.49.0.tgz", - "integrity": "sha512-gRf5c+A7QiOG3UwLyOOtyJMD31JJhMjBvpfhAitPAoqZFcOeK3Kc1Veg1z/trmt+2P6F/biT02fU19GGTS529A==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", "cpu": [ "s390x" ], @@ -676,12 +565,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.49.0.tgz", - "integrity": "sha512-BR7+blScdLW1h/2hB/2oXM+dhTmpW3rQt1DeSiCP9mc2NMMkqVgjIN3DDsNpKmezffGC9R8XKVOLmBkRUcK/sA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", "cpu": [ "x64" ], @@ -690,12 +580,13 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.49.0.tgz", - "integrity": "sha512-hDMOAe+6nX3V5ei1I7Au3wcr9h3ktKzDvF2ne5ovX8RZiAHEtX1A5SNNk4zt1Qt77CmnbqT+upb/umzoPMWiPg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", "cpu": [ "x64" ], @@ -704,12 +595,43 @@ "optional": true, "os": [ "linux" - ] + ], + "peer": true + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.49.0.tgz", - "integrity": "sha512-wkNRzfiIGaElC9kXUT+HLx17z7D0jl+9tGYRKwd8r7cUqTL7GYAvgUY++U2hK6Ar7z5Z6IRRoWC8kQxpmM7TDA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", "cpu": [ "arm64" ], @@ -718,12 +640,13 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.49.0.tgz", - "integrity": "sha512-gq5aW/SyNpjp71AAzroH37DtINDcX1Qw2iv9Chyz49ZgdOP3NV8QCyKZUrGsYX9Yyggj5soFiRCgsL3HwD8TdA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", "cpu": [ "ia32" ], @@ -732,12 +655,28 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.49.0.tgz", - "integrity": "sha512-gEtqFbzmZLFk2xKh7g0Rlo8xzho8KrEFEkzvHbfUGkrgXOpZ4XagQ6n+wIZFNh1nTb8UD16J4nFSFKXYgnbdBg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", "cpu": [ "x64" ], @@ -746,65 +685,69 @@ "optional": true, "os": [ "win32" - ] + ], + "peer": true + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/@vscode/component-explorer": { + "version": "0.1.1-2", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer/-/component-explorer-0.1.1-2.tgz", + "integrity": "sha512-2VMoXLnDBk+hKrhw+iGUsEjnCd1YiiZqe+1LdQIKdk16zqYRtJ5iO6yDxZ4cKy3Wphd+qLDUWmZSULNtKioMrQ==", + "dev": true, + "peerDependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@vscode/component-explorer-vite-plugin": { + "version": "0.1.1-2", + "resolved": "https://registry.npmjs.org/@vscode/component-explorer-vite-plugin/-/component-explorer-vite-plugin-0.1.1-2.tgz", + "integrity": "sha512-iYSp8shDZEJJrjMWGneWyjFbFyED5Og74c9h5XBmVPZBDN4INfOTmPlC+HYTv/CL5+NFxpl91CdtacCmqz2EXw==", + "dev": true, + "dependencies": { + "tinyglobby": "^0.2.0" + }, + "peerDependencies": { + "@vscode/component-explorer": "*", + "vite": "*" + } }, "node_modules/@vscode/rollup-plugin-esm-url": { - "version": "1.0.1-0", - "resolved": "https://registry.npmjs.org/@vscode/rollup-plugin-esm-url/-/rollup-plugin-esm-url-1.0.1-0.tgz", - "integrity": "sha512-5k9c2ZK6xTTa2MGa0uD+f/a1cZV8SCQm9ZhorQDyBRvobIzOTg57LjOl3di9Z6zawPh7nDgbWp6g+GnSSpdCrg==", + "version": "1.0.1-1", + "resolved": "https://registry.npmjs.org/@vscode/rollup-plugin-esm-url/-/rollup-plugin-esm-url-1.0.1-1.tgz", + "integrity": "sha512-vNmIR3ZyiwACUi8qnXhKNukoXaFkOM9skiqVOVHNKJTBb7kJS+evtyadrBc/fMm1y303WQWBNA90E7fCCsE2Sw==", "dev": true, "license": "MIT", "peerDependencies": { "rollup": "^3.0.0 || ^4.0.0" } }, - "node_modules/esbuild": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "license": "Apache-2.0", "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.9", - "@esbuild/android-arm": "0.25.9", - "@esbuild/android-arm64": "0.25.9", - "@esbuild/android-x64": "0.25.9", - "@esbuild/darwin-arm64": "0.25.9", - "@esbuild/darwin-x64": "0.25.9", - "@esbuild/freebsd-arm64": "0.25.9", - "@esbuild/freebsd-x64": "0.25.9", - "@esbuild/linux-arm": "0.25.9", - "@esbuild/linux-arm64": "0.25.9", - "@esbuild/linux-ia32": "0.25.9", - "@esbuild/linux-loong64": "0.25.9", - "@esbuild/linux-mips64el": "0.25.9", - "@esbuild/linux-ppc64": "0.25.9", - "@esbuild/linux-riscv64": "0.25.9", - "@esbuild/linux-s390x": "0.25.9", - "@esbuild/linux-x64": "0.25.9", - "@esbuild/netbsd-arm64": "0.25.9", - "@esbuild/netbsd-x64": "0.25.9", - "@esbuild/openbsd-arm64": "0.25.9", - "@esbuild/openbsd-x64": "0.25.9", - "@esbuild/openharmony-arm64": "0.25.9", - "@esbuild/sunos-x64": "0.25.9", - "@esbuild/win32-arm64": "0.25.9", - "@esbuild/win32-ia32": "0.25.9", - "@esbuild/win32-x64": "0.25.9" + "node": ">=8" } }, "node_modules/fdir": { @@ -840,6 +783,289 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lightningcss": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", + "integrity": "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.31.1", + "lightningcss-darwin-arm64": "1.31.1", + "lightningcss-darwin-x64": "1.31.1", + "lightningcss-freebsd-x64": "1.31.1", + "lightningcss-linux-arm-gnueabihf": "1.31.1", + "lightningcss-linux-arm64-gnu": "1.31.1", + "lightningcss-linux-arm64-musl": "1.31.1", + "lightningcss-linux-x64-gnu": "1.31.1", + "lightningcss-linux-x64-musl": "1.31.1", + "lightningcss-win32-arm64-msvc": "1.31.1", + "lightningcss-win32-x64-msvc": "1.31.1" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.31.1.tgz", + "integrity": "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.31.1.tgz", + "integrity": "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.31.1.tgz", + "integrity": "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.31.1.tgz", + "integrity": "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.31.1.tgz", + "integrity": "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.31.1.tgz", + "integrity": "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.31.1.tgz", + "integrity": "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.31.1.tgz", + "integrity": "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.31.1.tgz", + "integrity": "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.31.1.tgz", + "integrity": "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.31.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.31.1.tgz", + "integrity": "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -908,12 +1134,74 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-beta.53.tgz", + "integrity": "sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.101.0", + "@rolldown/pluginutils": "1.0.0-beta.53" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-beta.53", + "@rolldown/binding-darwin-arm64": "1.0.0-beta.53", + "@rolldown/binding-darwin-x64": "1.0.0-beta.53", + "@rolldown/binding-freebsd-x64": "1.0.0-beta.53", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.53", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.53", + "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.53", + "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.53", + "@rolldown/binding-linux-x64-musl": "1.0.0-beta.53", + "@rolldown/binding-openharmony-arm64": "1.0.0-beta.53", + "@rolldown/binding-wasm32-wasi": "1.0.0-beta.53", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.53", + "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.53" + } + }, "node_modules/rollup": { - "version": "4.49.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.49.0.tgz", - "integrity": "sha512-3IVq0cGJ6H7fKXXEdVt+RcYvRCt8beYY9K1760wGQwSAHZcS9eot1zDG5axUbcp/kWRi5zKIIDX8MoKv/TzvZA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -925,29 +1213,45 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.49.0", - "@rollup/rollup-android-arm64": "4.49.0", - "@rollup/rollup-darwin-arm64": "4.49.0", - "@rollup/rollup-darwin-x64": "4.49.0", - "@rollup/rollup-freebsd-arm64": "4.49.0", - "@rollup/rollup-freebsd-x64": "4.49.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.49.0", - "@rollup/rollup-linux-arm-musleabihf": "4.49.0", - "@rollup/rollup-linux-arm64-gnu": "4.49.0", - "@rollup/rollup-linux-arm64-musl": "4.49.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.49.0", - "@rollup/rollup-linux-ppc64-gnu": "4.49.0", - "@rollup/rollup-linux-riscv64-gnu": "4.49.0", - "@rollup/rollup-linux-riscv64-musl": "4.49.0", - "@rollup/rollup-linux-s390x-gnu": "4.49.0", - "@rollup/rollup-linux-x64-gnu": "4.49.0", - "@rollup/rollup-linux-x64-musl": "4.49.0", - "@rollup/rollup-win32-arm64-msvc": "4.49.0", - "@rollup/rollup-win32-ia32-msvc": "4.49.0", - "@rollup/rollup-win32-x64-msvc": "4.49.0", + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" } }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -975,18 +1279,28 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "name": "rolldown-vite", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/rolldown-vite/-/rolldown-vite-7.3.1.tgz", + "integrity": "sha512-LYzdNAjRHhF2yA4JUQm/QyARyi216N2rpJ0lJZb8E9FU2y5v6Vk+xq/U4XBOxMefpWixT5H3TslmAHm1rqIq2w==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", + "@oxc-project/runtime": "0.101.0", "fdir": "^6.5.0", + "lightningcss": "^1.30.2", "picomatch": "^4.0.3", "postcss": "^8.5.6", - "rollup": "^4.43.0", + "rolldown": "1.0.0-beta.53", "tinyglobby": "^0.2.15" }, "bin": { @@ -1003,9 +1317,9 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", + "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", - "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", @@ -1018,13 +1332,13 @@ "@types/node": { "optional": true }, - "jiti": { + "esbuild": { "optional": true }, - "less": { + "jiti": { "optional": true }, - "lightningcss": { + "less": { "optional": true }, "sass": { diff --git a/build/vite/package.json b/build/vite/package.json index f65f359145ed1..9800e54af40f1 100644 --- a/build/vite/package.json +++ b/build/vite/package.json @@ -9,7 +9,14 @@ "preview": "vite preview" }, "devDependencies": { - "@vscode/rollup-plugin-esm-url": "^1.0.1-0", - "vite": "^7.1.11" + "@vscode/rollup-plugin-esm-url": "^1.0.1-1", + "vite": "npm:rolldown-vite@latest", + "@vscode/component-explorer": "next", + "@vscode/component-explorer-vite-plugin": "next" + }, + "overrides": { + "@vscode/component-explorer-vite-plugin": { + "vite": "$vite" + } } } diff --git a/build/vite/setup-dev.ts b/build/vite/setup-dev.ts index 8f1f997599c18..b0ef8682c9a4e 100644 --- a/build/vite/setup-dev.ts +++ b/build/vite/setup-dev.ts @@ -10,6 +10,7 @@ import { getSingletonServiceDescriptors, InstantiationType, registerSingleton } import { IWebWorkerService } from '../../src/vs/platform/webWorker/browser/webWorkerService.ts'; // eslint-disable-next-line local/code-no-standalone-editor import { StandaloneWebWorkerService } from '../../src/vs/editor/standalone/browser/services/standaloneWebWorkerService.ts'; +import './style.css'; enableHotReload(); diff --git a/build/vite/style.css b/build/vite/style.css index b9573061e51fa..f1ee3ca812f62 100644 --- a/build/vite/style.css +++ b/build/vite/style.css @@ -7,3 +7,9 @@ height: 400px; border: 1px solid black; } + +@font-face { + font-family: "codicon"; + font-display: block; + src: url("~@vscode/codicons/dist/codicon.ttf") format("truetype"); +} diff --git a/build/vite/vite.config.ts b/build/vite/vite.config.ts index 1ddb4f53adfd5..6cdde88076a49 100644 --- a/build/vite/vite.config.ts +++ b/build/vite/vite.config.ts @@ -5,9 +5,10 @@ import { createLogger, defineConfig, Plugin } from 'vite'; import path, { join } from 'path'; -import { rollupEsmUrlPlugin } from '@vscode/rollup-plugin-esm-url'; +import { componentExplorer } from '@vscode/component-explorer-vite-plugin'; import { statSync } from 'fs'; import { pathToFileURL } from 'url'; +import { rollupEsmUrlPlugin } from '@vscode/rollup-plugin-esm-url'; function injectBuiltinExtensionsPlugin(): Plugin { let builtinExtensionsCache: unknown[] | null = null; @@ -166,9 +167,18 @@ export default defineConfig({ plugins: [ rollupEsmUrlPlugin({}), injectBuiltinExtensionsPlugin(), - createHotClassSupport() + createHotClassSupport(), + componentExplorer({ + logLevel: 'verbose', + include: 'build/vite/**/*.fixture.ts', + }), ], customLogger: logger, + resolve: { + alias: { + '~@vscode/codicons': '/node_modules/@vscode/codicons', + } + }, esbuild: { tsconfigRaw: { compilerOptions: { diff --git a/package-lock.json b/package-lock.json index 7c6806f2b4d98..47f013803d747 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "^2.5.6", "@types/semver": "^7.5.8", - "@vscode/codicons": "^0.0.45-5", + "@vscode/codicons": "^0.0.45-6", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", @@ -2947,9 +2947,9 @@ ] }, "node_modules/@vscode/codicons": { - "version": "0.0.45-5", - "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45-5.tgz", - "integrity": "sha512-tvRtMrVAe+CnlePF1Z3uhRfu0mLhAVgrSiY9zuEaGyXyBlN1/06bnpXno8XYb4O3ggOFx2phximqje4dhoLnkQ==", + "version": "0.0.45-6", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45-6.tgz", + "integrity": "sha512-HjJmIxw6anUPk/yiQTyF60ERjARNfc/A11kKoiO7jg2bzNeaCexunu4oUo/W8lHGr/dvHxYcruM1V3ZoGxyFNQ==", "license": "CC-BY-4.0" }, "node_modules/@vscode/deviceid": { diff --git a/package.json b/package.json index e79cfc226569a..545b1a0812d46 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "postinstall": "node build/npm/postinstall.ts", "compile": "npm run gulp compile", "compile-check-ts-native": "tsgo --project ./src/tsconfig.json --noEmit --skipLibCheck", - "watch": "npm-run-all2 -lp watch-client watch-extensions", + "watch": "npm-run-all2 -lp watch-client-transpile watch-client watch-extensions", "watchd": "deemon npm run watch", "watch-webd": "deemon npm run watch-web", "kill-watchd": "deemon --kill npm run watch", @@ -30,6 +30,9 @@ "watch-client": "npm run gulp watch-client", "watch-clientd": "deemon npm run watch-client", "kill-watch-clientd": "deemon --kill npm run watch-client", + "watch-client-transpile": "npx tsx build/next/index.ts transpile --watch", + "watch-client-transpiled": "deemon npm run watch-client-transpile", + "kill-watch-client-transpiled": "deemon --kill npm run watch-client-transpile", "watch-extensions": "npm run gulp watch-extensions watch-extension-media", "watch-extensionsd": "deemon npm run watch-extensions", "kill-watch-extensionsd": "deemon --kill npm run watch-extensions", @@ -77,7 +80,7 @@ "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "^2.5.6", "@types/semver": "^7.5.8", - "@vscode/codicons": "^0.0.45-5", + "@vscode/codicons": "^0.0.45-6", "@vscode/deviceid": "^0.1.1", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/native-watchdog": "^1.4.6", diff --git a/remote/web/package-lock.json b/remote/web/package-lock.json index e83d23193296b..48c97fba4c5fd 100644 --- a/remote/web/package-lock.json +++ b/remote/web/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@vscode/codicons": "^0.0.45-5", + "@vscode/codicons": "^0.0.45-6", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", @@ -73,9 +73,9 @@ "integrity": "sha512-n1VPsljTSkthsAFYdiWfC+DKzK2WwcRp83Y1YAqdX552BstvsDjft9YXppjUzp11BPsapDoO1LDgrDB0XVsfNQ==" }, "node_modules/@vscode/codicons": { - "version": "0.0.45-5", - "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45-5.tgz", - "integrity": "sha512-tvRtMrVAe+CnlePF1Z3uhRfu0mLhAVgrSiY9zuEaGyXyBlN1/06bnpXno8XYb4O3ggOFx2phximqje4dhoLnkQ==", + "version": "0.0.45-6", + "resolved": "https://registry.npmjs.org/@vscode/codicons/-/codicons-0.0.45-6.tgz", + "integrity": "sha512-HjJmIxw6anUPk/yiQTyF60ERjARNfc/A11kKoiO7jg2bzNeaCexunu4oUo/W8lHGr/dvHxYcruM1V3ZoGxyFNQ==", "license": "CC-BY-4.0" }, "node_modules/@vscode/iconv-lite-umd": { diff --git a/remote/web/package.json b/remote/web/package.json index f738d5554fa26..4f591ed99068f 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -5,7 +5,7 @@ "dependencies": { "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", - "@vscode/codicons": "^0.0.45-5", + "@vscode/codicons": "^0.0.45-6", "@vscode/iconv-lite-umd": "0.7.1", "@vscode/tree-sitter-wasm": "^0.3.0", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.css b/src/vs/base/browser/ui/codicons/codicon/codicon.css index 02154e77b6895..d7f257db9341e 100644 --- a/src/vs/base/browser/ui/codicons/codicon/codicon.css +++ b/src/vs/base/browser/ui/codicons/codicon/codicon.css @@ -6,7 +6,7 @@ @font-face { font-family: "codicon"; font-display: block; - src: url("./codicon.ttf?5d4d76ab2ce5108968ad644d591a16a6") format("truetype"); + src: url("./codicon.ttf") format("truetype"); } .codicon[class*='codicon-'] { diff --git a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf index a3098e1715c52..ff46f210b8ab9 100644 Binary files a/src/vs/base/browser/ui/codicons/codicon/codicon.ttf and b/src/vs/base/browser/ui/codicons/codicon/codicon.ttf differ diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index f39515c3348a3..5195e0b635374 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -1182,6 +1182,13 @@ export interface ITextModel { */ getCustomLineHeightsDecorations(ownerId?: number): IModelDecoration[]; + /** + * Gets all the decorations that contain custom line heights. + * @param range The range to search in + * @param ownerId If set, it will ignore decorations belonging to other owners. + */ + getCustomLineHeightsDecorationsInRange(range: Range, ownerId?: number): IModelDecoration[]; + /** * @internal */ diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index ebd3eccec5109..bb3a86c98575b 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -1563,6 +1563,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati rawContentChanges.push( new ModelRawLineChanged( editLineNumber, + currentEditLineNumber, this.getLineContent(currentEditLineNumber), decorationsInCurrentLine )); @@ -1593,7 +1594,8 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati rawContentChanges.push( new ModelRawLinesInserted( spliceLineNumber + 1, - startLineNumber + insertingLinesCnt, + fromLineNumber, + cnt, newLines, injectedTexts ) @@ -1653,7 +1655,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati if (affectedInjectedTextLines && affectedInjectedTextLines.size > 0) { const affectedLines = Array.from(affectedInjectedTextLines); - const lineChangeEvents = affectedLines.map(lineNumber => new ModelRawLineChanged(lineNumber, this.getLineContent(lineNumber), this._getInjectedTextInLine(lineNumber))); + const lineChangeEvents = affectedLines.map(lineNumber => new ModelRawLineChanged(lineNumber, lineNumber, this.getLineContent(lineNumber), this._getInjectedTextInLine(lineNumber))); this._onDidChangeContentOrInjectedText(new ModelInjectedTextChangedEvent(lineChangeEvents)); } this._fireOnDidChangeLineHeight(affectedLineHeights); @@ -1865,6 +1867,12 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati return decs; } + public getCustomLineHeightsDecorationsInRange(range: Range, ownerId: number = 0): model.IModelDecoration[] { + const decs = this._decorationsTree.getCustomLineHeightsInInterval(this, this.getOffsetAt(range.getStartPosition()), this.getOffsetAt(range.getEndPosition()), ownerId); + pushMany(decs, this._fontTokenDecorationsProvider.getDecorationsInRange(range, ownerId)); + return decs; + } + private _getInjectedTextInLine(lineNumber: number): LineInjectedText[] { const startOffset = this._buffer.getOffsetAt(lineNumber, 1); const endOffset = startOffset + this._buffer.getLineLength(lineNumber); @@ -2249,6 +2257,12 @@ class DecorationsTrees { return this._ensureNodesHaveRanges(host, result).filter((i) => typeof i.options.lineHeight === 'number'); } + public getCustomLineHeightsInInterval(host: IDecorationsTreesHost, start: number, end: number, filterOwnerId: number): model.IModelDecoration[] { + const versionId = host.getVersionId(); + const result = this._intervalSearch(start, end, filterOwnerId, false, false, versionId, false); + return this._ensureNodesHaveRanges(host, result).filter((i) => typeof i.options.lineHeight === 'number'); + } + public getAll(host: IDecorationsTreesHost, filterOwnerId: number, filterOutValidation: boolean, filterFontDecorations: boolean, overviewRulerOnly: boolean, onlyMarginDecorations: boolean): model.IModelDecoration[] { const versionId = host.getVersionId(); const result = this._search(filterOwnerId, filterOutValidation, filterFontDecorations, overviewRulerOnly, versionId, onlyMarginDecorations); diff --git a/src/vs/editor/common/textModelEvents.ts b/src/vs/editor/common/textModelEvents.ts index b25c00aae8a70..4fa24afd2c1b0 100644 --- a/src/vs/editor/common/textModelEvents.ts +++ b/src/vs/editor/common/textModelEvents.ts @@ -308,9 +308,13 @@ export class LineInjectedText { export class ModelRawLineChanged { public readonly changeType = RawContentChangedType.LineChanged; /** - * The line that has changed. + * The line number that has changed (before the change was applied). */ public readonly lineNumber: number; + /** + * The new line number the old one is mapped to (after the change was applied). + */ + public readonly lineNumberPostEdit: number; /** * The new value of the line. */ @@ -320,8 +324,9 @@ export class ModelRawLineChanged { */ public readonly injectedText: LineInjectedText[] | null; - constructor(lineNumber: number, detail: string, injectedText: LineInjectedText[] | null) { + constructor(lineNumber: number, lineNumberPostEdit: number, detail: string, injectedText: LineInjectedText[] | null) { this.lineNumber = lineNumber; + this.lineNumberPostEdit = lineNumberPostEdit; this.detail = detail; this.injectedText = injectedText; } @@ -409,10 +414,26 @@ export class ModelRawLinesInserted { * Before what line did the insertion begin */ public readonly fromLineNumber: number; + /** + * The actual start line number in the updated buffer where the newly inserted content can be found. + */ + public readonly fromLineNumberPostEdit: number; + /** + * The count of inserted lines. + */ + public readonly count: number; /** * `toLineNumber` - `fromLineNumber` + 1 denotes the number of lines that were inserted */ - public readonly toLineNumber: number; + public get toLineNumber(): number { + return this.fromLineNumber + this.count - 1; + } + /** + * The actual end line number of the insertion in the updated buffer. + */ + public get toLineNumberPostEdit(): number { + return this.fromLineNumberPostEdit + this.count - 1; + } /** * The text that was inserted */ @@ -422,10 +443,11 @@ export class ModelRawLinesInserted { */ public readonly injectedTexts: (LineInjectedText[] | null)[]; - constructor(fromLineNumber: number, toLineNumber: number, detail: string[], injectedTexts: (LineInjectedText[] | null)[]) { + constructor(fromLineNumber: number, fromLineNumberPostEdit: number, count: number, detail: string[], injectedTexts: (LineInjectedText[] | null)[]) { this.injectedTexts = injectedTexts; this.fromLineNumber = fromLineNumber; - this.toLineNumber = toLineNumber; + this.fromLineNumberPostEdit = fromLineNumberPostEdit; + this.count = count; this.detail = detail; } } diff --git a/src/vs/editor/common/viewLayout/lineHeights.ts b/src/vs/editor/common/viewLayout/lineHeights.ts index 47e402e075b95..bceebf4aad990 100644 --- a/src/vs/editor/common/viewLayout/lineHeights.ts +++ b/src/vs/editor/common/viewLayout/lineHeights.ts @@ -5,6 +5,10 @@ import { binarySearch2 } from '../../../base/common/arrays.js'; import { intersection } from '../../../base/common/collections.js'; +import { IEditorConfiguration } from '../config/editorConfiguration.js'; +import { EditorOption } from '../config/editorOptions.js'; +import { ICoordinatesConverter } from '../coordinatesConverter.js'; +import { IModelDecoration } from '../model.js'; export class CustomLine { @@ -62,7 +66,7 @@ export class LineHeightsManager { private _defaultLineHeight: number; private _hasPending: boolean = false; - constructor(defaultLineHeight: number, customLineHeightData: ICustomLineHeightData[]) { + constructor(defaultLineHeight: number, customLineHeightData: CustomLineHeightData[]) { this._defaultLineHeight = defaultLineHeight; if (customLineHeightData.length > 0) { for (const data of customLineHeightData) { @@ -227,7 +231,7 @@ export class LineHeightsManager { } } - public onLinesInserted(fromLineNumber: number, toLineNumber: number): void { + public onLinesInserted(fromLineNumber: number, toLineNumber: number, lineHeightsAdded: CustomLineHeightData[]): void { const insertCount = toLineNumber - fromLineNumber + 1; const candidateStartIndexOfInsertion = this._binarySearchOverOrderedCustomLinesArray(fromLineNumber); let startIndexOfInsertion: number; @@ -243,7 +247,22 @@ export class LineHeightsManager { } else { startIndexOfInsertion = -(candidateStartIndexOfInsertion + 1); } - const toReAdd: ICustomLineHeightData[] = []; + const maxLineHeightPerLine = new Map(); + for (const lineHeightAdded of lineHeightsAdded) { + for (let lineNumber = lineHeightAdded.startLineNumber; lineNumber <= lineHeightAdded.endLineNumber; lineNumber++) { + if (lineNumber >= fromLineNumber && lineNumber <= toLineNumber) { + const currentMax = maxLineHeightPerLine.get(lineNumber) ?? this._defaultLineHeight; + maxLineHeightPerLine.set(lineNumber, Math.max(currentMax, lineHeightAdded.lineHeight)); + } + } + this.insertOrChangeCustomLineHeight( + lineHeightAdded.decorationId, + lineHeightAdded.startLineNumber, + lineHeightAdded.endLineNumber, + lineHeightAdded.lineHeight + ); + } + const toReAdd: CustomLineHeightData[] = []; const decorationsImmediatelyAfter = new Set(); for (let i = startIndexOfInsertion; i < this._orderedCustomLines.length; i++) { if (this._orderedCustomLines[i].lineNumber === fromLineNumber) { @@ -257,9 +276,12 @@ export class LineHeightsManager { } } const decorationsWithGaps = intersection(decorationsImmediatelyBefore, decorationsImmediatelyAfter); + const specialHeightToAdd = Array.from(maxLineHeightPerLine.values()).reduce((acc, height) => acc + height, 0); + const defaultHeightToAdd = (insertCount - maxLineHeightPerLine.size) * this._defaultLineHeight; + const prefixSumToAdd = specialHeightToAdd + defaultHeightToAdd; for (let i = startIndexOfInsertion; i < this._orderedCustomLines.length; i++) { this._orderedCustomLines[i].lineNumber += insertCount; - this._orderedCustomLines[i].prefixSum += this._defaultLineHeight * insertCount; + this._orderedCustomLines[i].prefixSum += prefixSumToAdd; } if (decorationsWithGaps.size > 0) { @@ -281,8 +303,8 @@ export class LineHeightsManager { for (const dec of toReAdd) { this.insertOrChangeCustomLineHeight(dec.decorationId, dec.startLineNumber, dec.endLineNumber, dec.lineHeight); } - this.commit(); } + this.commit(); } public commit(): void { @@ -363,11 +385,27 @@ export class LineHeightsManager { } } -export interface ICustomLineHeightData { - readonly decorationId: string; - readonly startLineNumber: number; - readonly endLineNumber: number; - readonly lineHeight: number; +export class CustomLineHeightData { + + constructor( + readonly decorationId: string, + readonly startLineNumber: number, + readonly endLineNumber: number, + readonly lineHeight: number + ) { } + + public static fromDecorations(decorations: IModelDecoration[], coordinatesConverter: ICoordinatesConverter, configuration: IEditorConfiguration): CustomLineHeightData[] { + const defaultLineHeight = configuration.options.get(EditorOption.lineHeight); + return decorations.map((d) => { + const viewRange = coordinatesConverter.convertModelRangeToViewRange(d.range); + return new CustomLineHeightData( + d.id, + viewRange.startLineNumber, + viewRange.endLineNumber, + d.options.lineHeight ? d.options.lineHeight * defaultLineHeight : 0 + ); + }); + } } class ArrayMap { diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index b773e5dd4cb1f..376b0f2c7c9a8 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -5,7 +5,7 @@ import { IEditorWhitespace, IPartialViewLinesViewportData, ILineHeightChangeAccessor, IViewWhitespaceViewportData, IWhitespaceChangeAccessor } from '../viewModel.js'; import * as strings from '../../../base/common/strings.js'; -import { ICustomLineHeightData, LineHeightsManager } from './lineHeights.js'; +import { CustomLineHeightData, LineHeightsManager } from './lineHeights.js'; interface IPendingChange { id: string; newAfterLineNumber: number; newHeight: number } interface IPendingRemove { id: string } @@ -95,7 +95,7 @@ export class LinesLayout { private _paddingBottom: number; private _lineHeightsManager: LineHeightsManager; - constructor(lineCount: number, defaultLineHeight: number, paddingTop: number, paddingBottom: number, customLineHeightData: ICustomLineHeightData[]) { + constructor(lineCount: number, defaultLineHeight: number, paddingTop: number, paddingBottom: number, customLineHeightData: CustomLineHeightData[]) { this._instanceId = strings.singleLetterHash(++LinesLayout.INSTANCE_COUNT); this._pendingChanges = new PendingChanges(); this._lastWhitespaceId = 0; @@ -155,7 +155,7 @@ export class LinesLayout { * * @param lineCount New number of lines. */ - public onFlushed(lineCount: number, customLineHeightData: ICustomLineHeightData[]): void { + public onFlushed(lineCount: number, customLineHeightData: CustomLineHeightData[]): void { this._lineCount = lineCount; this._lineHeightsManager = new LineHeightsManager(this._lineHeightsManager.defaultLineHeight, customLineHeightData); } @@ -353,8 +353,9 @@ export class LinesLayout { * * @param fromLineNumber The line number at which the insertion started, inclusive * @param toLineNumber The line number at which the insertion ended, inclusive. + * @param lineHeightsAdded The custom line height data for the inserted lines. */ - public onLinesInserted(fromLineNumber: number, toLineNumber: number): void { + public onLinesInserted(fromLineNumber: number, toLineNumber: number, lineHeightsAdded: CustomLineHeightData[]): void { fromLineNumber = fromLineNumber | 0; toLineNumber = toLineNumber | 0; @@ -366,7 +367,7 @@ export class LinesLayout { this._arr[i].afterLineNumber += (toLineNumber - fromLineNumber + 1); } } - this._lineHeightsManager.onLinesInserted(fromLineNumber, toLineNumber); + this._lineHeightsManager.onLinesInserted(fromLineNumber, toLineNumber, lineHeightsAdded); } /** diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index 10130ea8dcb08..048dc241eae7d 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -12,7 +12,7 @@ import { IEditorConfiguration } from '../config/editorConfiguration.js'; import { LinesLayout } from './linesLayout.js'; import { IEditorWhitespace, IPartialViewLinesViewportData, ILineHeightChangeAccessor, IViewLayout, IViewWhitespaceViewportData, IWhitespaceChangeAccessor, Viewport } from '../viewModel.js'; import { ContentSizeChangedEvent } from '../viewModelEventDispatcher.js'; -import { ICustomLineHeightData } from './lineHeights.js'; +import { CustomLineHeightData } from './lineHeights.js'; const SMOOTH_SCROLLING_TIME = 125; @@ -164,7 +164,7 @@ export class ViewLayout extends Disposable implements IViewLayout { public readonly onDidScroll: Event; public readonly onDidContentSizeChange: Event; - constructor(configuration: IEditorConfiguration, lineCount: number, customLineHeightData: ICustomLineHeightData[], scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { + constructor(configuration: IEditorConfiguration, lineCount: number, customLineHeightData: CustomLineHeightData[], scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) { super(); this._configuration = configuration; @@ -237,14 +237,14 @@ export class ViewLayout extends Disposable implements IViewLayout { this._configureSmoothScrollDuration(); } } - public onFlushed(lineCount: number, customLineHeightData: ICustomLineHeightData[]): void { + public onFlushed(lineCount: number, customLineHeightData: CustomLineHeightData[]): void { this._linesLayout.onFlushed(lineCount, customLineHeightData); } public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void { this._linesLayout.onLinesDeleted(fromLineNumber, toLineNumber); } - public onLinesInserted(fromLineNumber: number, toLineNumber: number): void { - this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber); + public onLinesInserted(fromLineNumber: number, toLineNumber: number, lineHeightsAdded: CustomLineHeightData[]): void { + this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber, lineHeightsAdded); } // ---- end view event handlers diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index 954072212bce0..f3f33283bfc11 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -41,7 +41,7 @@ import { FocusChangedEvent, HiddenAreasChangedEvent, ModelContentChangedEvent, M import { IViewModelLines, ViewModelLinesFromModelAsIs, ViewModelLinesFromProjectedModel } from './viewModelLines.js'; import { IThemeService } from '../../../platform/theme/common/themeService.js'; import { GlyphMarginLanesModel } from './glyphLanesModel.js'; -import { ICustomLineHeightData } from '../viewLayout/lineHeights.js'; +import { CustomLineHeightData } from '../viewLayout/lineHeights.js'; import { TextModelEditSource } from '../textModelEditSource.js'; import { InlineDecoration } from './inlineDecorations.js'; import { ICoordinatesConverter } from '../coordinatesConverter.js'; @@ -196,23 +196,23 @@ export class ViewModel extends Disposable implements IViewModel { this._eventDispatcher.removeViewEventHandler(eventHandler); } - private _getCustomLineHeights(): ICustomLineHeightData[] { + private _getCustomLineHeights(): CustomLineHeightData[] { const allowVariableLineHeights = this._configuration.options.get(EditorOption.allowVariableLineHeights); if (!allowVariableLineHeights) { return []; } - const defaultLineHeight = this._configuration.options.get(EditorOption.lineHeight); const decorations = this.model.getCustomLineHeightsDecorations(this._editorId); - return decorations.map((d) => { - const lineNumber = d.range.startLineNumber; - const viewRange = this.coordinatesConverter.convertModelRangeToViewRange(new Range(lineNumber, 1, lineNumber, this.model.getLineMaxColumn(lineNumber))); - return { - decorationId: d.id, - startLineNumber: viewRange.startLineNumber, - endLineNumber: viewRange.endLineNumber, - lineHeight: d.options.lineHeight ? d.options.lineHeight * defaultLineHeight : 0 - }; - }); + return CustomLineHeightData.fromDecorations(decorations, this.coordinatesConverter, this._configuration); + } + + private _getCustomLineHeightsForLines(fromLineNumber: number, toLineNumber: number): CustomLineHeightData[] { + const allowVariableLineHeights = this._configuration.options.get(EditorOption.allowVariableLineHeights); + if (!allowVariableLineHeights) { + return []; + } + const modelRange = new Range(fromLineNumber, 1, toLineNumber, this.model.getLineMaxColumn(toLineNumber)); + const decorations = this.model.getCustomLineHeightsDecorationsInRange(modelRange, this._editorId); + return CustomLineHeightData.fromDecorations(decorations, this.coordinatesConverter, this._configuration); } private _updateConfigurationViewLineCountNow(): void { @@ -379,7 +379,7 @@ export class ViewModel extends Disposable implements IViewModel { const linesInsertedEvent = this._lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks); if (linesInsertedEvent !== null) { eventsCollector.emitViewEvent(linesInsertedEvent); - this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); + this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber, this._getCustomLineHeightsForLines(change.fromLineNumberPostEdit, change.toLineNumberPostEdit)); } hadOtherModelChange = true; break; @@ -394,7 +394,7 @@ export class ViewModel extends Disposable implements IViewModel { } if (linesInsertedEvent) { eventsCollector.emitViewEvent(linesInsertedEvent); - this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); + this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber, this._getCustomLineHeightsForLines(change.lineNumberPostEdit, change.lineNumberPostEdit)); } if (linesDeletedEvent) { eventsCollector.emitViewEvent(linesDeletedEvent); diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 9113fbdff2d84..72140c2636000 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -130,7 +130,7 @@ suite('Editor Model - Model', () => { }); assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ - new ModelRawLineChanged(1, 'foo My First Line', null) + new ModelRawLineChanged(1, 1, 'foo My First Line', null) ], 2, false, @@ -144,8 +144,8 @@ suite('Editor Model - Model', () => { }); assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ - new ModelRawLineChanged(1, 'My new line', null), - new ModelRawLinesInserted(2, 2, ['No longer First Line'], [null]), + new ModelRawLineChanged(1, 1, 'My new line', null), + new ModelRawLinesInserted(2, 2, 1, ['No longer First Line'], [null]), ], 2, false, @@ -216,7 +216,7 @@ suite('Editor Model - Model', () => { }); assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ - new ModelRawLineChanged(1, 'y First Line', null), + new ModelRawLineChanged(1, 1, 'y First Line', null), ], 2, false, @@ -230,7 +230,7 @@ suite('Editor Model - Model', () => { }); assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ - new ModelRawLineChanged(1, '', null), + new ModelRawLineChanged(1, 1, '', null), ], 2, false, @@ -244,7 +244,7 @@ suite('Editor Model - Model', () => { }); assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ - new ModelRawLineChanged(1, 'My Second Line', null), + new ModelRawLineChanged(1, 1, 'My Second Line', null), new ModelRawLinesDeleted(2, 2), ], 2, @@ -259,7 +259,7 @@ suite('Editor Model - Model', () => { }); assert.deepStrictEqual(e, new ModelRawContentChangedEvent( [ - new ModelRawLineChanged(1, 'My Third Line', null), + new ModelRawLineChanged(1, 1, 'My Third Line', null), new ModelRawLinesDeleted(2, 3), ], 2, diff --git a/src/vs/editor/test/common/viewLayout/lineHeights.test.ts b/src/vs/editor/test/common/viewLayout/lineHeights.test.ts index 191536462752c..c94726913baa3 100644 --- a/src/vs/editor/test/common/viewLayout/lineHeights.test.ts +++ b/src/vs/editor/test/common/viewLayout/lineHeights.test.ts @@ -195,7 +195,7 @@ suite('Editor ViewLayout - LineHeightsManager', () => { manager.insertOrChangeCustomLineHeight('dec1', 5, 7, 20); manager.commit(); - manager.onLinesInserted(3, 4); // Insert 2 lines at line 3 + manager.onLinesInserted(3, 4, []); // Insert 2 lines at line 3 assert.strictEqual(manager.heightForLineNumber(5), 10); assert.strictEqual(manager.heightForLineNumber(6), 10); @@ -209,7 +209,7 @@ suite('Editor ViewLayout - LineHeightsManager', () => { manager.insertOrChangeCustomLineHeight('dec1', 5, 7, 20); manager.commit(); - manager.onLinesInserted(6, 7); // Insert 2 lines at line 6 + manager.onLinesInserted(6, 7, []); // Insert 2 lines at line 6 assert.strictEqual(manager.heightForLineNumber(5), 20); assert.strictEqual(manager.heightForLineNumber(6), 20); diff --git a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts index 7bf20a78d8457..ceb624ac2740e 100644 --- a/src/vs/editor/test/common/viewLayout/linesLayout.test.ts +++ b/src/vs/editor/test/common/viewLayout/linesLayout.test.ts @@ -208,7 +208,7 @@ suite('Editor ViewLayout - LinesLayout', () => { // Insert two lines at the beginning // 10 lines // whitespace: - a(6,10) - linesLayout.onLinesInserted(1, 2); + linesLayout.onLinesInserted(1, 2, []); assert.strictEqual(linesLayout.getLinesTotalHeight(), 20); assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(1), 0); assert.strictEqual(linesLayout.getVerticalOffsetForLineNumber(2), 1); @@ -909,7 +909,7 @@ suite('Editor ViewLayout - LinesLayout', () => { assert.strictEqual(linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(5), 50); // Insert a line before line 1 - linesLayout.onLinesInserted(1, 1); + linesLayout.onLinesInserted(1, 1, []); // whitespaces: d(3, 30), c(4, 20) assert.strictEqual(linesLayout.getWhitespacesCount(), 2); assert.strictEqual(linesLayout.getAfterLineNumberForWhitespaceIndex(0), 3); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index be805ae83dfbc..80497d1a193b3 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2322,6 +2322,12 @@ declare namespace monaco.editor { * @param ownerId If set, it will ignore decorations belonging to other owners. */ getCustomLineHeightsDecorations(ownerId?: number): IModelDecoration[]; + /** + * Gets all the decorations that contain custom line heights. + * @param range The range to search in + * @param ownerId If set, it will ignore decorations belonging to other owners. + */ + getCustomLineHeightsDecorationsInRange(range: Range, ownerId?: number): IModelDecoration[]; /** * Normalize a string containing whitespace according to indentation rules (converts to spaces or to tabs). */ diff --git a/src/vs/workbench/api/common/extHostChatContext.ts b/src/vs/workbench/api/common/extHostChatContext.ts index 1e1a75fa152d1..f0c6509f8a5a8 100644 --- a/src/vs/workbench/api/common/extHostChatContext.ts +++ b/src/vs/workbench/api/common/extHostChatContext.ts @@ -50,7 +50,7 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext throw new Error('Workspace context provider not found'); } const provider = entry.provider as vscode.ChatWorkspaceContextProvider; - const result = (await provider.provideChatContext(token)) ?? []; + const result = (await provider.provideWorkspaceChatContext(token)) ?? (await provider.provideChatContext?.(token)) ?? []; return this._convertItems(handle, result); } @@ -63,7 +63,7 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext throw new Error('Explicit context provider not found'); } const provider = entry.provider as vscode.ChatExplicitContextProvider; - const result = (await provider.provideChatContext(token)) ?? []; + const result = (await provider.provideExplicitChatContext(token)) ?? (await provider.provideChatContext?.(token)) ?? []; return this._convertItems(handle, result); } @@ -77,7 +77,7 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext if (!extItem) { throw new Error('Chat context item not found'); } - return this._doResolve(provider.resolveChatContext.bind(provider), context, extItem, token); + return this._doResolve((provider.resolveExplicitChatContext ?? provider.resolveChatContext).bind(provider), context, extItem, token); } // Resource context provider methods @@ -89,7 +89,7 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext } const provider = entry.provider as vscode.ChatResourceContextProvider; - const result = await provider.provideChatContext({ resource: URI.revive(options.resource) }, token); + const result = (await provider.provideResourceChatContext({ resource: URI.revive(options.resource) }, token)) ?? (await provider.provideChatContext?.({ resource: URI.revive(options.resource) }, token)); if (!result) { return undefined; } @@ -109,7 +109,7 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext command: result.command ? { id: result.command.command } : undefined }; if (options.withValue && !item.value) { - const resolved = await provider.resolveChatContext(result, token); + const resolved = await (provider.resolveResourceChatContext ?? provider.resolveChatContext).bind(provider)(result, token); item.value = resolved?.value; item.tooltip = resolved?.tooltip ? MarkdownString.from(resolved.tooltip) : item.tooltip; } @@ -127,7 +127,7 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext if (!extItem) { throw new Error('Chat context item not found'); } - return this._doResolve(provider.resolveChatContext.bind(provider), context, extItem, token); + return this._doResolve((provider.resolveResourceChatContext ?? provider.resolveChatContext).bind(provider), context, extItem, token); } // Command execution @@ -209,7 +209,7 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext if (provider.provideWorkspaceChatContext) { const workspaceProvider: vscode.ChatWorkspaceContextProvider = { onDidChangeWorkspaceChatContext: provider.onDidChangeWorkspaceChatContext, - provideChatContext: (token) => provider.provideWorkspaceChatContext!(token) + provideWorkspaceChatContext: (token) => provider.provideWorkspaceChatContext!(token) }; disposables.push(this.registerChatWorkspaceContextProvider(id, workspaceProvider)); } @@ -217,8 +217,8 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext // Register explicit context provider if the provider supports it if (provider.provideChatContextExplicit) { const explicitProvider: vscode.ChatExplicitContextProvider = { - provideChatContext: (token) => provider.provideChatContextExplicit!(token), - resolveChatContext: provider.resolveChatContext + provideExplicitChatContext: (token) => provider.provideChatContextExplicit!(token), + resolveExplicitChatContext: provider.resolveChatContext ? (context, token) => provider.resolveChatContext!(context, token) : (context) => context }; @@ -228,8 +228,8 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext // Register resource context provider if the provider supports it and has a selector if (provider.provideChatContextForResource && selector) { const resourceProvider: vscode.ChatResourceContextProvider = { - provideChatContext: (options, token) => provider.provideChatContextForResource!(options, token), - resolveChatContext: provider.resolveChatContext + provideResourceChatContext: (options, token) => provider.provideChatContextForResource!(options, token), + resolveResourceChatContext: provider.resolveChatContext ? (context, token) => provider.resolveChatContext!(context, token) : (context) => context }; @@ -315,7 +315,7 @@ export class ExtHostChatContext extends Disposable implements ExtHostChatContext return; } const provideWorkspaceContext = async () => { - const workspaceContexts = await provider.provideChatContext(CancellationToken.None); + const workspaceContexts = await provider.provideWorkspaceChatContext(CancellationToken.None); const resolvedContexts = this._convertItems(handle, workspaceContexts ?? []); return this._proxy.$updateWorkspaceContextItems(handle, resolvedContexts); }; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 6978ffacbf0bd..60ffabb3a631e 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -77,7 +77,7 @@ export class ActivitybarPart extends Part { viewContainersWorkspaceStateKey: ActivitybarPart.viewContainersWorkspaceStateKey, orientation: ActionsOrientation.VERTICAL, icon: true, - iconSize: 24, + iconSize: 16, activityHoverOptions: { position: () => this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT, }, diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index ef940211d69e7..1e408be694c2a 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -180,7 +180,7 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut private _showPrompt(notificationService: INotificationService, storageService: IStorageService, openerService: IOpenerService, configurationService: IConfigurationService, taskNames: string[], locations: Map): Promise { return new Promise(resolve => { notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic', - "This workspace has tasks ({0}) defined ({1}) that run automatically when you open this workspace. Do you want to allow automatic tasks to run in all trusted workspaces?", + "This workspace has tasks ({0}) defined ({1}) that can launch processes automatically when you open this workspace. Do you want to allow automatic tasks to run in all trusted workspaces?", taskNames.join(', '), Array.from(locations.keys()).join(', ') ), diff --git a/src/vscode-dts/vscode.proposed.chatContextProvider.d.ts b/src/vscode-dts/vscode.proposed.chatContextProvider.d.ts index d9e6583c093eb..e7cd493ae50ad 100644 --- a/src/vscode-dts/vscode.proposed.chatContextProvider.d.ts +++ b/src/vscode-dts/vscode.proposed.chatContextProvider.d.ts @@ -24,7 +24,9 @@ declare module 'vscode' { export function registerChatWorkspaceContextProvider(id: string, provider: ChatWorkspaceContextProvider): Disposable; /** - * Register a chat explicit context provider. Explicit context items are shown as options when the user explicitly attaches context. + * Register a chat explicit context provider. Explicit context items are shown as options when the user explicitly attaches context use the "Attache Context" action in the chat input box. + * + * Explicit context providers should also be statically contributed in package.json using the `chatContext` contribution point. * * To ensure your extension is activated when chat context is requested, make sure to include the `onChatContextProvider:` activation event in your `package.json`. * @@ -109,7 +111,12 @@ declare module 'vscode' { * * @param token A cancellation token. */ - provideChatContext(token: CancellationToken): ProviderResult; + provideWorkspaceChatContext(token: CancellationToken): ProviderResult; + + /** + * @deprecated + */ + provideChatContext?(token: CancellationToken): ProviderResult; } export interface ChatExplicitContextProvider { @@ -121,7 +128,12 @@ declare module 'vscode' { * * @param token A cancellation token. */ - provideChatContext(token: CancellationToken): ProviderResult; + provideExplicitChatContext(token: CancellationToken): ProviderResult; + + /** + * @deprecated + */ + provideChatContext?(token: CancellationToken): ProviderResult; /** * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. @@ -129,7 +141,12 @@ declare module 'vscode' { * @param context The context item to resolve. * @param token A cancellation token. */ - resolveChatContext(context: T, token: CancellationToken): ProviderResult; + resolveExplicitChatContext(context: T, token: CancellationToken): ProviderResult; + + /** + * @deprecated + */ + resolveChatContext?(context: T, token: CancellationToken): ProviderResult; } export interface ChatResourceContextProvider { @@ -144,7 +161,12 @@ declare module 'vscode' { * @param options Options include the resource for which to provide context. * @param token A cancellation token. */ - provideChatContext(options: { resource: Uri }, token: CancellationToken): ProviderResult; + provideResourceChatContext(options: { resource: Uri }, token: CancellationToken): ProviderResult; + + /** + * @deprecated + */ + provideChatContext?(options: { resource: Uri }, token: CancellationToken): ProviderResult; /** * If a chat context item is provided without a `value`, this method is called to resolve the `value` for the item. @@ -152,7 +174,12 @@ declare module 'vscode' { * @param context The context item to resolve. * @param token A cancellation token. */ - resolveChatContext(context: T, token: CancellationToken): ProviderResult; + resolveResourceChatContext(context: T, token: CancellationToken): ProviderResult; + + /** + * @deprecated + */ + resolveChatContext?(context: T, token: CancellationToken): ProviderResult; } /** @@ -168,19 +195,19 @@ declare module 'vscode' { /** * Provide a list of chat context items to be included as workspace context for all chat requests. - * @deprecated Use {@link ChatWorkspaceContextProvider.provideChatContext} instead. + * @deprecated Use {@link ChatWorkspaceContextProvider.provideWorkspaceChatContext} instead. */ provideWorkspaceChatContext?(token: CancellationToken): ProviderResult; /** * Provide a list of chat context items that a user can choose from. - * @deprecated Use {@link ChatExplicitContextProvider.provideChatContext} instead. + * @deprecated Use {@link ChatExplicitContextProvider.provideExplicitChatContext} instead. */ provideChatContextExplicit?(token: CancellationToken): ProviderResult; /** * Given a particular resource, provide a chat context item for it. - * @deprecated Use {@link ChatResourceContextProvider.provideChatContext} instead. + * @deprecated Use {@link ChatResourceContextProvider.provideResourceChatContext} instead. */ provideChatContextForResource?(options: { resource: Uri }, token: CancellationToken): ProviderResult;