diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml new file mode 100644 index 00000000000..0d94b83350a --- /dev/null +++ b/.github/workflows/basic.yml @@ -0,0 +1,184 @@ +name: Basic checks + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + main: + if: github.ref != 'refs/heads/main' + name: Compilation, Unit and Integration Tests + runs-on: ubuntu-latest + timeout-minutes: 40 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + # TODO: rename azure-pipelines/linux/xvfb.init to github-actions + - name: Setup Build Environment + run: | + sudo apt-get update + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + sudo cp build/azure-pipelines/linux/xvfb.init /etc/init.d/xvfb + sudo chmod +x /etc/init.d/xvfb + sudo update-rc.d xvfb defaults + sudo service xvfb start + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Compute node modules cache key + id: nodeModulesCacheKey + run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" + - name: Cache node modules + id: cacheNodeModules + uses: actions/cache@v3 + with: + path: "**/node_modules" + key: ${{ runner.os }}-cacheNodeModules21-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules21- + - name: Get yarn cache directory path + id: yarnCacheDirPath + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache yarn directory + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + uses: actions/cache@v3 + with: + path: ${{ steps.yarnCacheDirPath.outputs.dir }} + key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-yarnCacheDir- + - name: Execute yarn + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + run: yarn --frozen-lockfile --network-timeout 180000 + + - name: Compile and Download + run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions + + - name: Compile Integration Tests + run: yarn --cwd test/integration/browser compile + + - name: Run Unit Tests + id: electron-unit-tests + run: DISPLAY=:10 ./scripts/test.sh + + - name: Run Integration Tests (Electron) + id: electron-integration-tests + run: DISPLAY=:10 ./scripts/test-integration.sh + + hygiene: + if: github.ref != 'refs/heads/main' + name: Hygiene and Layering + runs-on: ubuntu-latest + timeout-minutes: 40 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Compute node modules cache key + id: nodeModulesCacheKey + run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" + - name: Cache node modules + id: cacheNodeModules + uses: actions/cache@v3 + with: + path: "**/node_modules" + key: ${{ runner.os }}-cacheNodeModules21-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules21- + - name: Get yarn cache directory path + id: yarnCacheDirPath + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache yarn directory + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + uses: actions/cache@v3 + with: + path: ${{ steps.yarnCacheDirPath.outputs.dir }} + key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-yarnCacheDir- + - name: Execute yarn + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + run: yarn --frozen-lockfile --network-timeout 180000 + + - name: Download Playwright + run: yarn playwright-install + + - name: Run Hygiene Checks + run: yarn gulp hygiene + + - name: Run Valid Layers Checks + run: yarn valid-layers-check + + - name: Compile /build/ + run: yarn --cwd build compile + + - name: Check clean git state + run: ./.github/workflows/check-clean-git-state.sh + + - name: Run eslint + run: yarn eslint + + - name: Run vscode-dts Compile Checks + run: yarn vscode-dts-compile-check + + - name: Run Trusted Types Checks + run: yarn tsec-compile-check + + warm-cache: + name: Warm up node modules cache + if: github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + timeout-minutes: 40 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Compute node modules cache key + id: nodeModulesCacheKey + run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)" + - name: Cache node modules + id: cacheNodeModules + uses: actions/cache@v3 + with: + path: "**/node_modules" + key: ${{ runner.os }}-cacheNodeModules21-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-cacheNodeModules21- + - name: Get yarn cache directory path + id: yarnCacheDirPath + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: Cache yarn directory + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + uses: actions/cache@v3 + with: + path: ${{ steps.yarnCacheDirPath.outputs.dir }} + key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} + restore-keys: ${{ runner.os }}-yarnCacheDir- + - name: Execute yarn + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + env: + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + run: yarn --frozen-lockfile --network-timeout 180000 diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 7b3a5043154..94b22026a07 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -65,7 +65,6 @@ const vscodeResources = [ 'out-build/vs/base/browser/ui/codicons/codicon/**', 'out-build/vs/base/parts/sandbox/electron-browser/preload.js', 'out-build/vs/platform/environment/node/userDataPath.js', - 'out-build/vs/platform/extensions/node/extensionHostStarterWorkerMain.js', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', diff --git a/build/hygiene.js b/build/hygiene.js index a5d2b080172..1668e508ec9 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -54,7 +54,7 @@ function hygiene(some, linting = true) { const m = /([^\t\n\r\x20-\x7E⊃⊇✔︎✓🎯⚠️🛑🔴🚗🚙🚕🎉✨❗⇧⌥⌘×÷¦⋯…↑↓→→←↔⟷·•●◆▼⟪⟫┌└├⏎↩√φ]+)/g.exec(line); if (m) { console.error( - file.relative + `(${i + 1},${m.index + 1}): Unexpected unicode character: "${m[0]}". To suppress, use // allow-any-unicode-next-line` + file.relative + `(${i + 1},${m.index + 1}): Unexpected unicode character: "${m[0]}" (charCode: ${m[0].charCodeAt(0)}). To suppress, use // allow-any-unicode-next-line` ); errorCount++; } diff --git a/extensions/css-language-features/client/src/browser/cssClientMain.ts b/extensions/css-language-features/client/src/browser/cssClientMain.ts index 0cfde9025f3..8fa2d81bd03 100644 --- a/extensions/css-language-features/client/src/browser/cssClientMain.ts +++ b/extensions/css-language-features/client/src/browser/cssClientMain.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionContext, Uri } from 'vscode'; -import { LanguageClientOptions } from 'vscode-languageclient'; +import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor } from '../cssClient'; import { LanguageClient } from 'vscode-languageclient/browser'; @@ -15,8 +15,10 @@ declare const TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string }; }; +let client: BaseLanguageClient | undefined; + // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/cssServerMain.js'); try { const worker = new Worker(serverMain.toString()); @@ -24,9 +26,17 @@ export function activate(context: ExtensionContext) { return new LanguageClient(id, name, clientOptions, worker); }; - startClient(context, newLanguageClient, { TextDecoder }); + client = await startClient(context, newLanguageClient, { TextDecoder }); } catch (e) { console.log(e); } } + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } +} + diff --git a/extensions/css-language-features/client/src/cssClient.ts b/extensions/css-language-features/client/src/cssClient.ts index 282c347bdd8..6f6238465d8 100644 --- a/extensions/css-language-features/client/src/cssClient.ts +++ b/extensions/css-language-features/client/src/cssClient.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { commands, CompletionItem, CompletionItemKind, ExtensionContext, languages, Position, Range, SnippetString, TextEdit, window, TextDocument, CompletionContext, CancellationToken, ProviderResult, CompletionList, FormattingOptions, workspace } from 'vscode'; -import { Disposable, LanguageClientOptions, ProvideCompletionItemsSignature, NotificationType, CommonLanguageClient, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient'; +import { Disposable, LanguageClientOptions, ProvideCompletionItemsSignature, NotificationType, BaseLanguageClient, DocumentRangeFormattingParams, DocumentRangeFormattingRequest } from 'vscode-languageclient'; import * as nls from 'vscode-nls'; import { getCustomDataSource } from './customData'; import { RequestService, serveFileSystemRequests } from './requests'; @@ -15,7 +15,7 @@ namespace CustomDataChangedNotification { const localize = nls.loadMessageBundle(); -export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient; +export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string } }; @@ -39,7 +39,7 @@ interface CSSFormatSettings { const cssFormatSettingKeys: (keyof CSSFormatSettings)[] = ['newlineBetweenSelectors', 'newlineBetweenRules', 'spaceAroundSelectorSeparator', 'braceStyle', 'preserveNewLines', 'maxPreserveNewLines']; -export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { const customDataSource = getCustomDataSource(context.subscriptions); @@ -100,31 +100,25 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua // Create the language client and start the client. let client = newLanguageClient('css', localize('cssserver.name', 'CSS Language Server'), clientOptions); client.registerProposedFeatures(); - client.onReady().then(() => { + await client.start(); + + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); + customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - customDataSource.onDidChange(() => { - client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - }); - - // manually register / deregister format provider based on the `css/less/scss.format.enable` setting avoiding issues with late registration. See #71652. - for (const registration of formatterRegistrations) { - updateFormatterRegistration(registration); - context.subscriptions.push({ dispose: () => registration.provider?.dispose() }); - context.subscriptions.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(registration.settingId) && updateFormatterRegistration(registration))); - } - - serveFileSystemRequests(client, runtime); }); - let disposable = client.start(); - // Push the disposable to the context's subscriptions so that the - // client can be deactivated on extension deactivation - context.subscriptions.push(disposable); + // manually register / deregister format provider based on the `css/less/scss.format.enable` setting avoiding issues with late registration. See #71652. + for (const registration of formatterRegistrations) { + updateFormatterRegistration(registration); + context.subscriptions.push({ dispose: () => registration.provider?.dispose() }); + context.subscriptions.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(registration.settingId) && updateFormatterRegistration(registration))); + } - client.onReady().then(() => { - context.subscriptions.push(initCompletionProvider()); - }); + serveFileSystemRequests(client, runtime); + + + context.subscriptions.push(initCompletionProvider()); function initCompletionProvider(): Disposable { const regionCompletionRegExpr = /^(\s*)(\/(\*\s*(#\w*)?)?)?$/; @@ -204,11 +198,10 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua } } } - console.log(JSON.stringify(params.options)); return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( client.protocol2CodeConverter.asTextEdits, (error) => { - client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []); + client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []); return Promise.resolve([]); } ); @@ -216,4 +209,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua }); } } + + return client; } diff --git a/extensions/css-language-features/client/src/node/cssClientMain.ts b/extensions/css-language-features/client/src/node/cssClientMain.ts index b88838d8912..dfbe121f822 100644 --- a/extensions/css-language-features/client/src/node/cssClientMain.ts +++ b/extensions/css-language-features/client/src/node/cssClientMain.ts @@ -6,11 +6,14 @@ import { getNodeFSRequestService } from './nodeFs'; import { ExtensionContext, extensions } from 'vscode'; import { startClient, LanguageClientConstructor } from '../cssClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; import { TextDecoder } from 'util'; + +let client: BaseLanguageClient | undefined; + // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { const clientMain = extensions.getExtension('vscode.css-language-features')?.packageJSON?.main || ''; const serverMain = `./server/${clientMain.indexOf('/dist/') !== -1 ? 'dist' : 'out'}/node/cssServerMain`; @@ -30,5 +33,12 @@ export function activate(context: ExtensionContext) { return new LanguageClient(id, name, serverOptions, clientOptions); }; - startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder }); + client = await startClient(context, newLanguageClient, { fs: getNodeFSRequestService(), TextDecoder }); +} + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } } diff --git a/extensions/css-language-features/client/src/requests.ts b/extensions/css-language-features/client/src/requests.ts index 6aea3ab8fad..f19918e57ef 100644 --- a/extensions/css-language-features/client/src/requests.ts +++ b/extensions/css-language-features/client/src/requests.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Uri, workspace } from 'vscode'; -import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; +import { RequestType, BaseLanguageClient } from 'vscode-languageclient'; import { Runtime } from './cssClient'; export namespace FsContentRequest { @@ -18,7 +18,7 @@ export namespace FsReadDirRequest { export const type: RequestType = new RequestType('fs/readDir'); } -export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime) { +export function serveFileSystemRequests(client: BaseLanguageClient, runtime: Runtime) { client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string }) => { const uri = Uri.parse(param.uri); if (uri.scheme === 'file' && runtime.fs) { diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index e9ea587d4db..c4955243c8f 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -336,7 +336,10 @@ "type": "string", "scope": "resource", "default": "collapse", - "enum": ["collapse", "expand"], + "enum": [ + "collapse", + "expand" + ], "markdownDescription": "%css.format.braceStyle.desc%" }, "css.format.preserveNewLines": { @@ -638,7 +641,10 @@ "type": "string", "scope": "resource", "default": "collapse", - "enum": ["collapse", "expand"], + "enum": [ + "collapse", + "expand" + ], "markdownDescription": "%scss.format.braceStyle.desc%" }, "scss.format.preserveNewLines": { @@ -941,7 +947,10 @@ "type": "string", "scope": "resource", "default": "collapse", - "enum": ["collapse", "expand"], + "enum": [ + "collapse", + "expand" + ], "markdownDescription": "%less.format.braceStyle.desc%" }, "less.format.preserveNewLines": { @@ -985,7 +994,7 @@ ] }, "dependencies": { - "vscode-languageclient": "^7.0.0", + "vscode-languageclient": "^8.0.1", "vscode-nls": "^5.0.0", "vscode-uri": "^3.0.3" }, diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 18c39540516..d2f0443e65b 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -10,8 +10,8 @@ "main": "./out/node/cssServerMain", "browser": "./dist/browser/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^5.4.2", - "vscode-languageserver": "^7.0.0", + "vscode-css-languageservice": "^6.0.1", + "vscode-languageserver": "^8.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/css-language-features/server/src/cssServer.ts b/extensions/css-language-features/server/src/cssServer.ts index 40e05b13ab1..3eed3962879 100644 --- a/extensions/css-language-features/server/src/cssServer.ts +++ b/extensions/css-language-features/server/src/cssServer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { - Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable, TextDocumentIdentifier, Range, FormattingOptions, TextEdit + Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType, Disposable, TextDocumentIdentifier, Range, FormattingOptions, TextEdit, Diagnostic } from 'vscode-languageserver'; import { URI } from 'vscode-uri'; import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position, CSSFormatConfiguration } from 'vscode-css-languageservice'; @@ -64,6 +64,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { + + const initializationOptions = params.initializationOptions as any || {}; + workspaceFolders = (params).workspaceFolders; if (!Array.isArray(workspaceFolders)) { workspaceFolders = []; @@ -72,7 +75,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } } - requestService = getRequestService(params.initializationOptions?.handledSchemas || ['file'], connection, runtime); + requestService = getRequestService(initializationOptions?.handledSchemas || ['file'], connection, runtime); function getClientCapability(name: string, def: T) { const keys = name.split('.'); @@ -88,7 +91,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const snippetSupport = !!getClientCapability('textDocument.completion.completionItem.snippetSupport', false); scopedSettingsSupport = !!getClientCapability('workspace.configuration', false); foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); - formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; languageServices.css = getCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); languageServices.scss = getSCSSLanguageService({ fileSystemProvider: requestService, clientCapabilities: params.capabilities }); @@ -110,8 +113,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) colorProvider: {}, foldingRangeProvider: true, selectionRangeProvider: true, - documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true, - documentFormattingProvider: params.initializationOptions?.provideFormatter === true, + documentRangeFormattingProvider: initializationOptions?.provideFormatter === true, + documentFormattingProvider: initializationOptions?.provideFormatter === true, }; return { capabilities }; }); @@ -135,7 +138,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) let promise = documentSettings[textDocument.uri]; if (!promise) { const configRequestParam = { items: [{ scopeUri: textDocument.uri, section: textDocument.languageId }] }; - promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => s[0]); + promise = connection.sendRequest(ConfigurationRequest.type, configRequestParam).then(s => s[0] as LanguageSettings | undefined); documentSettings[textDocument.uri] = promise; } return promise; @@ -145,12 +148,12 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration(change => { - updateConfiguration(change.settings); + updateConfiguration(change.settings as any); }); - function updateConfiguration(settings: Settings) { + function updateConfiguration(settings: any) { for (const languageId in languageServices) { - languageServices[languageId].configure((settings as any)[languageId]); + languageServices[languageId].configure(settings[languageId]); } // reset all document settings documentSettings = {}; @@ -193,7 +196,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) const settingsPromise = getDocumentSettings(textDocument); Promise.all([settingsPromise, dataProvidersReady]).then(async ([settings]) => { const stylesheet = stylesheets.get(textDocument); - const diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings); + const diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings) as Diagnostic[]; // Send the computed diagnostics to VSCode. connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); }, e => { diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index b7a44bc52c4..8749dcdebad 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -12,50 +12,50 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-css-languageservice@^5.4.2: - version "5.4.2" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.4.2.tgz#69ea74c000bd653dfc8e458a1720d28b9ffa5cfb" - integrity sha512-DT7+7vfdT2HDNjDoXWtYJ0lVDdeDEdbMNdK4PKqUl2MS8g7PWt7J5G9B6k9lYox8nOfhCEjLnoNC3UKHHCR1lg== +vscode-css-languageservice@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.0.1.tgz#ccf94944e094dcc5833d1b4ac276994b698e9283" + integrity sha512-81n/eeYuJwQdvpoy6IK1258PtPbO720fl13FcJ5YQECPyHMFkmld1qKHwPJkyLbLPfboqJPM53ys4xW8v+iBVw== dependencies: vscode-languageserver-textdocument "^1.0.4" - vscode-languageserver-types "^3.16.0" - vscode-nls "^5.0.0" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" vscode-languageserver-textdocument@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== -vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1, vscode-languageserver-types@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-languageserver@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" - integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== +vscode-languageserver@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.1.tgz#56bd7a01f5c88af075a77f1d220edcb30fc4bdc7" + integrity sha512-sn7SjBwWm3OlmLtgg7jbM0wBULppyL60rj8K5HF0ny/MzN+GzPBX1kCvYdybhl7UW63V5V5tRVnyB8iwC73lSQ== dependencies: - vscode-languageserver-protocol "3.16.0" + vscode-languageserver-protocol "3.17.1" -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/css-language-features/yarn.lock b/extensions/css-language-features/yarn.lock index 60ba7c2dcc3..76af92973f2 100644 --- a/extensions/css-language-features/yarn.lock +++ b/extensions/css-language-features/yarn.lock @@ -39,39 +39,39 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageclient@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" - integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg== +vscode-languageclient@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" + integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== dependencies: minimatch "^3.0.4" - semver "^7.3.4" - vscode-languageserver-protocol "3.16.0" + semver "^7.3.5" + vscode-languageserver-protocol "3.17.1" -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" -vscode-languageserver-types@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== vscode-nls@^5.0.0: version "5.0.0" diff --git a/extensions/html-language-features/client/src/browser/htmlClientMain.ts b/extensions/html-language-features/client/src/browser/htmlClientMain.ts index b69cca75854..ab23520fe79 100644 --- a/extensions/html-language-features/client/src/browser/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/browser/htmlClientMain.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable, ExtensionContext, Uri } from 'vscode'; -import { LanguageClientOptions } from 'vscode-languageclient'; +import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor } from '../htmlClient'; import { LanguageClient } from 'vscode-languageclient/browser'; @@ -15,8 +15,10 @@ declare const TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string }; }; +let client: BaseLanguageClient | undefined; + // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/htmlServerMain.js'); try { const worker = new Worker(serverMain.toString()); @@ -31,9 +33,17 @@ export function activate(context: ExtensionContext) { } }; - startClient(context, newLanguageClient, { TextDecoder, timer }); + client = await startClient(context, newLanguageClient, { TextDecoder, timer }); } catch (e) { console.log(e); } } + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } +} + diff --git a/extensions/html-language-features/client/src/htmlClient.ts b/extensions/html-language-features/client/src/htmlClient.ts index 7baceece6fb..6c44ceb72a8 100644 --- a/extensions/html-language-features/client/src/htmlClient.ts +++ b/extensions/html-language-features/client/src/htmlClient.ts @@ -13,7 +13,7 @@ import { } from 'vscode'; import { LanguageClientOptions, RequestType, DocumentRangeFormattingParams, - DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, Position as LspPosition, NotificationType, CommonLanguageClient + DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, Position as LspPosition, NotificationType, BaseLanguageClient } from 'vscode-languageclient'; import { FileSystemProvider, serveFileSystemRequests } from './requests'; import { getCustomDataSource } from './customData'; @@ -72,7 +72,7 @@ export interface TelemetryReporter { }): void; } -export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient; +export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; export interface Runtime { TextDecoder: { new(encoding?: string): { decode(buffer: ArrayBuffer): string } }; @@ -83,18 +83,17 @@ export interface Runtime { }; } -export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { - let toDispose = context.subscriptions; + const toDispose = context.subscriptions; - - let documentSelector = ['html', 'handlebars']; - let embeddedLanguages = { css: true, javascript: true }; + const documentSelector = ['html', 'handlebars']; + const embeddedLanguages = { css: true, javascript: true }; let rangeFormatting: Disposable | undefined = undefined; // Options to control the language client - let clientOptions: LanguageClientOptions = { + const clientOptions: LanguageClientOptions = { documentSelector, synchronize: { configurationSection: ['html', 'css', 'javascript'], // the settings to synchronize @@ -135,67 +134,65 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua let client = newLanguageClient('html', localize('htmlserver.name', 'HTML Language Server'), clientOptions); client.registerProposedFeatures(); - let disposable = client.start(); - toDispose.push(disposable); - client.onReady().then(() => { + await client.start(); - toDispose.push(serveFileSystemRequests(client, runtime)); + toDispose.push(serveFileSystemRequests(client, runtime)); - const customDataSource = getCustomDataSource(runtime, context.subscriptions); + const customDataSource = getCustomDataSource(runtime, context.subscriptions); + client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); + customDataSource.onDidChange(() => { client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - customDataSource.onDidChange(() => { - client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris); - }); - client.onRequest(CustomDataContent.type, customDataSource.getContent); - - - const insertRequestor = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position): Promise => { - let param: AutoInsertParams = { - kind, - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - position: client.code2ProtocolConverter.asPosition(position) - }; - return client.sendRequest(AutoInsertRequest.type, param); - }; - let disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime); - toDispose.push(disposable); - - disposable = client.onTelemetry(e => { - runtime.telemetry?.sendTelemetryEvent(e.key, e.data); - }); - toDispose.push(disposable); - - // manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652. - updateFormatterRegistration(); - toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); - toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(SettingIds.formatEnable) && updateFormatterRegistration())); - - client.sendRequest(SemanticTokenLegendRequest.type).then(legend => { - if (legend) { - const provider: DocumentSemanticTokensProvider & DocumentRangeSemanticTokensProvider = { - provideDocumentSemanticTokens(doc) { - const params: SemanticTokenParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), - }; - return client.sendRequest(SemanticTokenRequest.type, params).then(data => { - return data && new SemanticTokens(new Uint32Array(data)); - }); - }, - provideDocumentRangeSemanticTokens(doc, range) { - const params: SemanticTokenParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), - ranges: [client.code2ProtocolConverter.asRange(range)] - }; - return client.sendRequest(SemanticTokenRequest.type, params).then(data => { - return data && new SemanticTokens(new Uint32Array(data)); - }); - } - }; - toDispose.push(languages.registerDocumentSemanticTokensProvider(documentSelector, provider, new SemanticTokensLegend(legend.types, legend.modifiers))); - } - }); }); + client.onRequest(CustomDataContent.type, customDataSource.getContent); + + + const insertRequestor = (kind: 'autoQuote' | 'autoClose', document: TextDocument, position: Position): Promise => { + const param: AutoInsertParams = { + kind, + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), + position: client.code2ProtocolConverter.asPosition(position) + }; + return client.sendRequest(AutoInsertRequest.type, param); + }; + const disposable = activateAutoInsertion(insertRequestor, { html: true, handlebars: true }, runtime); + toDispose.push(disposable); + + const disposable2 = client.onTelemetry(e => { + runtime.telemetry?.sendTelemetryEvent(e.key, e.data); + }); + toDispose.push(disposable2); + + // manually register / deregister format provider based on the `html.format.enable` setting avoiding issues with late registration. See #71652. + updateFormatterRegistration(); + toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); + toDispose.push(workspace.onDidChangeConfiguration(e => e.affectsConfiguration(SettingIds.formatEnable) && updateFormatterRegistration())); + + client.sendRequest(SemanticTokenLegendRequest.type).then(legend => { + if (legend) { + const provider: DocumentSemanticTokensProvider & DocumentRangeSemanticTokensProvider = { + provideDocumentSemanticTokens(doc) { + const params: SemanticTokenParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), + }; + return client.sendRequest(SemanticTokenRequest.type, params).then(data => { + return data && new SemanticTokens(new Uint32Array(data)); + }); + }, + provideDocumentRangeSemanticTokens(doc, range) { + const params: SemanticTokenParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(doc), + ranges: [client.code2ProtocolConverter.asRange(range)] + }; + return client.sendRequest(SemanticTokenRequest.type, params).then(data => { + return data && new SemanticTokens(new Uint32Array(data)); + }); + } + }; + toDispose.push(languages.registerDocumentSemanticTokensProvider(documentSelector, provider, new SemanticTokensLegend(legend.types, legend.modifiers))); + } + }); + function updateFormatterRegistration() { const formatEnabled = workspace.getConfiguration().get(SettingIds.formatEnable); @@ -219,7 +216,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( client.protocol2CodeConverter.asTextEdits, (error) => { - client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []); + client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []); return Promise.resolve([]); } ); @@ -300,4 +297,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua } } + return client; + } diff --git a/extensions/html-language-features/client/src/node/htmlClientMain.ts b/extensions/html-language-features/client/src/node/htmlClientMain.ts index 4c7d24e397c..f460d0c1524 100644 --- a/extensions/html-language-features/client/src/node/htmlClientMain.ts +++ b/extensions/html-language-features/client/src/node/htmlClientMain.ts @@ -6,16 +6,17 @@ import { getNodeFileFS } from './nodeFs'; import { Disposable, ExtensionContext } from 'vscode'; import { startClient, LanguageClientConstructor } from '../htmlClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; import { TextDecoder } from 'util'; import * as fs from 'fs'; import TelemetryReporter from '@vscode/extension-telemetry'; let telemetry: TelemetryReporter | undefined; +let client: BaseLanguageClient | undefined; // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { let clientPackageJSON = getPackageInfo(context); telemetry = new TelemetryReporter(clientPackageJSON.name, clientPackageJSON.version, clientPackageJSON.aiKey); @@ -44,7 +45,14 @@ export function activate(context: ExtensionContext) { } }; - startClient(context, newLanguageClient, { fileFs: getNodeFileFS(), TextDecoder, telemetry, timer }); + client = await startClient(context, newLanguageClient, { fileFs: getNodeFileFS(), TextDecoder, telemetry, timer }); +} + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } } interface IPackageInfo { diff --git a/extensions/html-language-features/client/src/requests.ts b/extensions/html-language-features/client/src/requests.ts index ba124e28cd7..8106f044228 100644 --- a/extensions/html-language-features/client/src/requests.ts +++ b/extensions/html-language-features/client/src/requests.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Uri, workspace, Disposable } from 'vscode'; -import { RequestType, CommonLanguageClient } from 'vscode-languageclient'; +import { RequestType, BaseLanguageClient } from 'vscode-languageclient'; import { Runtime } from './htmlClient'; export namespace FsStatRequest { @@ -15,7 +15,7 @@ export namespace FsReadDirRequest { export const type: RequestType = new RequestType('fs/readDir'); } -export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime): Disposable { +export function serveFileSystemRequests(client: BaseLanguageClient, runtime: Runtime): Disposable { const disposables = []; disposables.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => { const uri = Uri.parse(uriString); diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index e7244d251c0..2f2dc4d1067 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -261,9 +261,9 @@ ] }, "dependencies": { - "@vscode/extension-telemetry": "0.4.10", - "vscode-languageclient": "^7.0.0", - "vscode-nls": "^5.0.0", + "@vscode/extension-telemetry": "0.5.1", + "vscode-languageclient": "^8.0.1", + "vscode-nls": "^5.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/html-language-features/server/package.json b/extensions/html-language-features/server/package.json index 423a395ba96..affcf98e566 100644 --- a/extensions/html-language-features/server/package.json +++ b/extensions/html-language-features/server/package.json @@ -9,11 +9,11 @@ }, "main": "./out/node/htmlServerMain", "dependencies": { - "vscode-css-languageservice": "^5.4.2", - "vscode-html-languageservice": "^4.2.5", - "vscode-languageserver": "^7.0.0", - "vscode-languageserver-textdocument": "^1.0.3", - "vscode-nls": "^5.0.0", + "vscode-css-languageservice": "^6.0.1", + "vscode-html-languageservice": "^5.0.0", + "vscode-languageserver": "^8.0.1", + "vscode-languageserver-textdocument": "^1.0.4", + "vscode-nls": "^5.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/html-language-features/server/src/htmlServer.ts b/extensions/html-language-features/server/src/htmlServer.ts index b3df5fc8e51..319399a038c 100644 --- a/extensions/html-language-features/server/src/htmlServer.ts +++ b/extensions/html-language-features/server/src/htmlServer.ts @@ -11,7 +11,7 @@ import { } from 'vscode-languageserver'; import { getLanguageModes, LanguageModes, Settings, TextDocument, Position, Diagnostic, WorkspaceFolder, ColorInformation, - Range, DocumentLink, SymbolInformation, TextDocumentIdentifier + Range, DocumentLink, SymbolInformation, TextDocumentIdentifier, isCompletionItemData } from './modes/languageModes'; import { format } from './modes/formatting'; @@ -129,7 +129,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // After the server has started the client sends an initialize request. The server receives // in the passed params the rootPath of the workspace plus the client capabilities connection.onInitialize((params: InitializeParams): InitializeResult => { - const initializationOptions = params.initializationOptions; + const initializationOptions = params.initializationOptions as any || {}; workspaceFolders = (params).workspaceFolders; if (!Array.isArray(workspaceFolders)) { @@ -179,14 +179,14 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) scopedSettingsSupport = getClientCapability('workspace.configuration', false); workspaceFoldersSupport = getClientCapability('workspace.workspaceFolders', false); foldingRangeLimit = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); - formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + formatterMaxNumberOfEdits = initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] } : undefined, hoverProvider: true, documentHighlightProvider: true, - documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true, - documentFormattingProvider: params.initializationOptions?.provideFormatter === true, + documentRangeFormattingProvider: initializationOptions?.provideFormatter === true, + documentFormattingProvider: initializationOptions?.provideFormatter === true, documentLinkProvider: { resolveProvider: false }, documentSymbolProvider: true, definitionProvider: true, @@ -226,7 +226,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // The settings have changed. Is send on server activation as well. connection.onDidChangeConfiguration((change) => { - globalSettings = change.settings; + globalSettings = change.settings as Settings; documentSettings = {}; // reset all document settings documents.all().forEach(triggerValidation); @@ -331,7 +331,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) connection.onCompletionResolve((item, token) => { return runSafe(runtime, async () => { const data = item.data; - if (data && data.languageId && data.uri) { + if (isCompletionItemData(data)) { const mode = languageModes.getMode(data.languageId); const document = documents.get(data.uri); if (mode && mode.doResolve && document) { diff --git a/extensions/html-language-features/server/src/modes/cssMode.ts b/extensions/html-language-features/server/src/modes/cssMode.ts index 9757d99cd88..6bc02acb510 100644 --- a/extensions/html-language-features/server/src/modes/cssMode.ts +++ b/extensions/html-language-features/server/src/modes/cssMode.ts @@ -5,7 +5,7 @@ import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache'; import { Stylesheet, LanguageService as CSSLanguageService } from 'vscode-css-languageservice'; -import { LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext } from './languageModes'; +import { LanguageMode, Workspace, Color, TextDocument, Position, Range, CompletionList, DocumentContext, Diagnostic } from './languageModes'; import { HTMLDocumentRegions, CSS_STYLE_RULE } from './embeddedSupport'; export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegions: LanguageModelCache, workspace: Workspace): LanguageMode { @@ -18,7 +18,7 @@ export function getCSSMode(cssLanguageService: CSSLanguageService, documentRegio }, async doValidation(document: TextDocument, settings = workspace.settings) { let embedded = embeddedCSSDocuments.get(document); - return cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded), settings && settings.css); + return (cssLanguageService.doValidation(embedded, cssStylesheets.get(embedded), settings && settings.css) as Diagnostic[]); }, async doComplete(document: TextDocument, position: Position, documentContext: DocumentContext, _settings = workspace.settings) { let embedded = embeddedCSSDocuments.get(document); diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index bbcba5e0f86..7f9e3f1d85a 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -8,7 +8,7 @@ import { SymbolInformation, SymbolKind, CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions, FoldingRange, FoldingRangeKind, SelectionRange, - LanguageMode, Settings, SemanticTokenData, Workspace, DocumentContext + LanguageMode, Settings, SemanticTokenData, Workspace, DocumentContext, CompletionItemData, isCompletionItemData } from './languageModes'; import { getWordAtText, isWhitespaceOnly, repeat } from '../utils/strings'; import { HTMLDocumentRegions } from './embeddedSupport'; @@ -122,6 +122,11 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { + const data: CompletionItemData = { // data used for resolving item details (see 'doResolve') + languageId, + uri: document.uri, + offset: offset + }; return { uri: document.uri, position: position, @@ -129,23 +134,21 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { - const jsDocument = jsDocuments.get(document); - const jsLanguageService = await host.getLanguageService(jsDocument); - let details = jsLanguageService.getCompletionEntryDetails(jsDocument.uri, item.data.offset, item.label, undefined, undefined, undefined, undefined); - if (details) { - item.detail = ts.displayPartsToString(details.displayParts); - item.documentation = ts.displayPartsToString(details.documentation); - delete item.data; + if (isCompletionItemData(item.data)) { + const jsDocument = jsDocuments.get(document); + const jsLanguageService = await host.getLanguageService(jsDocument); + let details = jsLanguageService.getCompletionEntryDetails(jsDocument.uri, item.data.offset, item.label, undefined, undefined, undefined, undefined); + if (details) { + item.detail = ts.displayPartsToString(details.displayParts); + item.documentation = ts.displayPartsToString(details.documentation); + delete item.data; + } } return item; }, diff --git a/extensions/html-language-features/server/src/modes/languageModes.ts b/extensions/html-language-features/server/src/modes/languageModes.ts index 3a8b9b797cc..59cc742b2b8 100644 --- a/extensions/html-language-features/server/src/modes/languageModes.ts +++ b/extensions/html-language-features/server/src/modes/languageModes.ts @@ -54,6 +54,16 @@ export interface SemanticTokenData { modifierSet: number; } +export type CompletionItemData = { + languageId: string; + uri: string; + offset: number; +}; + +export function isCompletionItemData(value: any): value is CompletionItemData { + return value && typeof value.languageId === 'string' && typeof value.uri === 'string' && typeof value.offset === 'number'; +} + export interface LanguageMode { getId(): string; getSelectionRange?: (document: TextDocument, position: Position) => Promise; diff --git a/extensions/html-language-features/server/yarn.lock b/extensions/html-language-features/server/yarn.lock index a5bfebda600..f086cbf6898 100644 --- a/extensions/html-language-features/server/yarn.lock +++ b/extensions/html-language-features/server/yarn.lock @@ -12,65 +12,60 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-css-languageservice@^5.4.2: - version "5.4.2" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.4.2.tgz#69ea74c000bd653dfc8e458a1720d28b9ffa5cfb" - integrity sha512-DT7+7vfdT2HDNjDoXWtYJ0lVDdeDEdbMNdK4PKqUl2MS8g7PWt7J5G9B6k9lYox8nOfhCEjLnoNC3UKHHCR1lg== +vscode-css-languageservice@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-6.0.1.tgz#ccf94944e094dcc5833d1b4ac276994b698e9283" + integrity sha512-81n/eeYuJwQdvpoy6IK1258PtPbO720fl13FcJ5YQECPyHMFkmld1qKHwPJkyLbLPfboqJPM53ys4xW8v+iBVw== dependencies: vscode-languageserver-textdocument "^1.0.4" - vscode-languageserver-types "^3.16.0" - vscode-nls "^5.0.0" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-html-languageservice@^4.2.5: - version "4.2.5" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-4.2.5.tgz#c0cc8ff3d824d16388bbac187e1828749eccf006" - integrity sha512-dbr10KHabB9EaK8lI0XZW7SqOsTfrNyT3Nuj0GoPi4LjGKUmMiLtsqzfedIzRTzqY+w0FiLdh0/kQrnQ0tLxrw== +vscode-html-languageservice@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-5.0.0.tgz#c68613f836d7fcff125183d78e6f1f0ff326fa55" + integrity sha512-KJG13z54aLszskp3ETf8b1EKDypr2Sf5RUsfR6OXmKqEl2ZUfyIxsWz4gbJWjPzoJZx/bGH0ZXVwxJ1rg8OKRQ== dependencies: vscode-languageserver-textdocument "^1.0.4" - vscode-languageserver-types "^3.16.0" - vscode-nls "^5.0.0" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" - -vscode-languageserver-textdocument@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz#879f2649bfa5a6e07bc8b392c23ede2dfbf43eff" - integrity sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A== + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" vscode-languageserver-textdocument@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== -vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1, vscode-languageserver-types@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-languageserver@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" - integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== +vscode-languageserver@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.1.tgz#56bd7a01f5c88af075a77f1d220edcb30fc4bdc7" + integrity sha512-sn7SjBwWm3OlmLtgg7jbM0wBULppyL60rj8K5HF0ny/MzN+GzPBX1kCvYdybhl7UW63V5V5tRVnyB8iwC73lSQ== dependencies: - vscode-languageserver-protocol "3.16.0" + vscode-languageserver-protocol "3.17.1" -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index d77421b701c..5157b15d877 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -"@vscode/extension-telemetry@0.4.10": - version "0.4.10" - resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910" - integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w== +"@vscode/extension-telemetry@0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.5.1.tgz#20150976629663b3d33799a4ad25944a1535f7db" + integrity sha512-cvFq8drxdLRF8KN72WcV4lTEa9GqDiRwy9EbnYuoSCD9Jdk8zHFF49MmACC1qs4R9Ko/C1uMOmeLJmVi8EA0rQ== balanced-match@^1.0.0: version "1.0.0" @@ -44,44 +44,44 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageclient@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" - integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg== +vscode-languageclient@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" + integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== dependencies: minimatch "^3.0.4" - semver "^7.3.4" - vscode-languageserver-protocol "3.16.0" + semver "^7.3.5" + vscode-languageserver-protocol "3.17.1" -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" -vscode-languageserver-types@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/json-language-features/client/src/browser/jsonClientMain.ts b/extensions/json-language-features/client/src/browser/jsonClientMain.ts index 9eb390b545c..e1fae6ffeb4 100644 --- a/extensions/json-language-features/client/src/browser/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/browser/jsonClientMain.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ExtensionContext, Uri } from 'vscode'; -import { LanguageClientOptions } from 'vscode-languageclient'; +import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient'; import { startClient, LanguageClientConstructor, SchemaRequestService } from '../jsonClient'; import { LanguageClient } from 'vscode-languageclient/browser'; @@ -14,8 +14,10 @@ declare const Worker: { declare function fetch(uri: string, options: any): any; +let client: BaseLanguageClient | undefined; + // this method is called when vs code is activated -export function activate(context: ExtensionContext) { +export async function activate(context: ExtensionContext) { const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/jsonServerMain.js'); try { const worker = new Worker(serverMain.toString()); @@ -32,9 +34,16 @@ export function activate(context: ExtensionContext) { } }; - startClient(context, newLanguageClient, { schemaRequests }); + client = await startClient(context, newLanguageClient, { schemaRequests }); } catch (e) { console.log(e); } } + +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } +} diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 5246257adc8..60225af8cef 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -16,9 +16,10 @@ import { import { LanguageClientOptions, RequestType, NotificationType, DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams, - DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, CommonLanguageClient + DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, BaseLanguageClient } from 'vscode-languageclient'; + import { hash } from './utils/hash'; import { createLanguageStatusItem } from './languageStatus'; @@ -56,7 +57,7 @@ namespace ResultLimitReachedNotification { export const type: NotificationType = new NotificationType('json/resultLimitReached'); } -interface Settings { +type Settings = { json?: { schemas?: JSONSchemaSettings[]; format?: { enable?: boolean }; @@ -67,13 +68,13 @@ interface Settings { proxy?: string; proxyStrictSSL?: boolean; }; -} +}; -export interface JSONSchemaSettings { +export type JSONSchemaSettings = { fileMatch?: string[]; url?: string; schema?: any; -} +}; namespace SettingIds { export const enableFormatter = 'json.format.enable'; @@ -94,7 +95,7 @@ export interface TelemetryReporter { }): void; } -export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient; +export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient; export interface Runtime { schemaRequests: SchemaRequestService; @@ -108,7 +109,7 @@ export interface SchemaRequestService { export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server'); -export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { +export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise { const toDispose = context.subscriptions; @@ -218,178 +219,177 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua const client = newLanguageClient('json', languageServerDescription, clientOptions); client.registerProposedFeatures(); - const disposable = client.start(); - toDispose.push(disposable); - client.onReady().then(() => { - isClientReady = true; + const schemaDocuments: { [uri: string]: boolean } = {}; - const schemaDocuments: { [uri: string]: boolean } = {}; - - // handle content request - client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { - const uri = Uri.parse(uriPath); - if (uri.scheme === 'untitled') { - return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString()))); - } - if (uri.scheme !== 'http' && uri.scheme !== 'https') { - return workspace.openTextDocument(uri).then(doc => { - schemaDocuments[uri.toString()] = true; - return doc.getText(); - }, error => { - return Promise.reject(new ResponseError(2, error.toString())); - }); - } else if (schemaDownloadEnabled) { - if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { - /* __GDPR__ - "json.schema" : { - "owner": "aeschli", - "comment": "Measure the use of the Azure resource manager schemas", - "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The azure schema URL that was requested." } - } - */ - runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); - } - return runtime.schemaRequests.getContent(uriPath).catch(e => { - return Promise.reject(new ResponseError(4, e.toString())); - }); - } else { - return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload))); - } - }); - - const handleContentChange = (uriString: string) => { - if (schemaDocuments[uriString]) { - client.sendNotification(SchemaContentChangeNotification.type, uriString); - return true; - } - return false; - }; - const handleActiveEditorChange = (activeEditor?: TextEditor) => { - if (!activeEditor) { - return; - } - - const activeDocUri = activeEditor.document.uri.toString(); - - if (activeDocUri && fileSchemaErrors.has(activeDocUri)) { - schemaResolutionErrorStatusBarItem.show(); - } else { - schemaResolutionErrorStatusBarItem.hide(); - } - }; - - toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString()))); - toDispose.push(workspace.onDidCloseTextDocument(d => { - const uriString = d.uri.toString(); - if (handleContentChange(uriString)) { - delete schemaDocuments[uriString]; - } - fileSchemaErrors.delete(uriString); - })); - toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange)); - - const handleRetryResolveSchemaCommand = () => { - if (window.activeTextEditor) { - schemaResolutionErrorStatusBarItem.text = '$(watch)'; - const activeDocUri = window.activeTextEditor.document.uri.toString(); - client.sendRequest(ForceValidateRequest.type, activeDocUri).then((diagnostics) => { - const schemaErrorIndex = diagnostics.findIndex(isSchemaResolveError); - if (schemaErrorIndex !== -1) { - // Show schema resolution errors in status bar only; ref: #51032 - const schemaResolveDiagnostic = diagnostics[schemaErrorIndex]; - fileSchemaErrors.set(activeDocUri, schemaResolveDiagnostic.message); - } else { - schemaResolutionErrorStatusBarItem.hide(); - } - schemaResolutionErrorStatusBarItem.text = '$(alert)'; - }); - } - }; - - toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand)); - - client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); - - toDispose.push(extensions.onDidChange(_ => { - client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); - })); - - // manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652. - updateFormatterRegistration(); - toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); - - updateSchemaDownloadSetting(); - - toDispose.push(workspace.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(SettingIds.enableFormatter)) { - updateFormatterRegistration(); - } else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) { - updateSchemaDownloadSetting(); - } - })); - - client.onNotification(ResultLimitReachedNotification.type, async message => { - const shouldPrompt = context.globalState.get(StorageIds.maxItemsExceededInformation) !== false; - if (shouldPrompt) { - const ok = localize('ok', "OK"); - const openSettings = localize('goToSetting', 'Open Settings'); - const neverAgain = localize('yes never again', "Don't Show Again"); - const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain); - if (pick === neverAgain) { - await context.globalState.update(StorageIds.maxItemsExceededInformation, false); - } else if (pick === openSettings) { - await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed); - } - } - }); - - toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri))); - - function updateFormatterRegistration() { - const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter); - if (!formatEnabled && rangeFormatting) { - rangeFormatting.dispose(); - rangeFormatting = undefined; - } else if (formatEnabled && !rangeFormatting) { - rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, { - provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult { - const filesConfig = workspace.getConfiguration('files', document); - const fileFormattingOptions = { - trimTrailingWhitespace: filesConfig.get('trimTrailingWhitespace'), - trimFinalNewlines: filesConfig.get('trimFinalNewlines'), - insertFinalNewline: filesConfig.get('insertFinalNewline'), - }; - const params: DocumentRangeFormattingParams = { - textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), - range: client.code2ProtocolConverter.asRange(range), - options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions) - }; - - return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( - client.protocol2CodeConverter.asTextEdits, - (error) => { - client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []); - return Promise.resolve([]); - } - ); - } - }); - } + // handle content request + client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { + const uri = Uri.parse(uriPath); + if (uri.scheme === 'untitled') { + return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString()))); } - - function updateSchemaDownloadSetting() { - schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false; - if (schemaDownloadEnabled) { - schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.'); - schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema'; - handleRetryResolveSchemaCommand(); - } else { - schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.'); - schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' }; + if (uri.scheme !== 'http' && uri.scheme !== 'https') { + return workspace.openTextDocument(uri).then(doc => { + schemaDocuments[uri.toString()] = true; + return doc.getText(); + }, error => { + return Promise.reject(new ResponseError(2, error.toString())); + }); + } else if (schemaDownloadEnabled) { + if (runtime.telemetry && uri.authority === 'schema.management.azure.com') { + /* __GDPR__ + "json.schema" : { + "owner": "aeschli", + "comment": "Measure the use of the Azure resource manager schemas", + "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The azure schema URL that was requested." } + } + */ + runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath }); } + return runtime.schemaRequests.getContent(uriPath).catch(e => { + return Promise.reject(new ResponseError(4, e.toString())); + }); + } else { + return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload))); } - }); + + await client.start(); + + isClientReady = true; + + const handleContentChange = (uriString: string) => { + if (schemaDocuments[uriString]) { + client.sendNotification(SchemaContentChangeNotification.type, uriString); + return true; + } + return false; + }; + const handleActiveEditorChange = (activeEditor?: TextEditor) => { + if (!activeEditor) { + return; + } + + const activeDocUri = activeEditor.document.uri.toString(); + + if (activeDocUri && fileSchemaErrors.has(activeDocUri)) { + schemaResolutionErrorStatusBarItem.show(); + } else { + schemaResolutionErrorStatusBarItem.hide(); + } + }; + + toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString()))); + toDispose.push(workspace.onDidCloseTextDocument(d => { + const uriString = d.uri.toString(); + if (handleContentChange(uriString)) { + delete schemaDocuments[uriString]; + } + fileSchemaErrors.delete(uriString); + })); + toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange)); + + const handleRetryResolveSchemaCommand = () => { + if (window.activeTextEditor) { + schemaResolutionErrorStatusBarItem.text = '$(watch)'; + const activeDocUri = window.activeTextEditor.document.uri.toString(); + client.sendRequest(ForceValidateRequest.type, activeDocUri).then((diagnostics) => { + const schemaErrorIndex = diagnostics.findIndex(isSchemaResolveError); + if (schemaErrorIndex !== -1) { + // Show schema resolution errors in status bar only; ref: #51032 + const schemaResolveDiagnostic = diagnostics[schemaErrorIndex]; + fileSchemaErrors.set(activeDocUri, schemaResolveDiagnostic.message); + } else { + schemaResolutionErrorStatusBarItem.hide(); + } + schemaResolutionErrorStatusBarItem.text = '$(alert)'; + }); + } + }; + + toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand)); + + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); + + toDispose.push(extensions.onDidChange(_ => { + client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context)); + })); + + // manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652. + updateFormatterRegistration(); + toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() }); + + updateSchemaDownloadSetting(); + + toDispose.push(workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(SettingIds.enableFormatter)) { + updateFormatterRegistration(); + } else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) { + updateSchemaDownloadSetting(); + } + })); + + client.onNotification(ResultLimitReachedNotification.type, async message => { + const shouldPrompt = context.globalState.get(StorageIds.maxItemsExceededInformation) !== false; + if (shouldPrompt) { + const ok = localize('ok', "OK"); + const openSettings = localize('goToSetting', 'Open Settings'); + const neverAgain = localize('yes never again', "Don't Show Again"); + const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain); + if (pick === neverAgain) { + await context.globalState.update(StorageIds.maxItemsExceededInformation, false); + } else if (pick === openSettings) { + await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed); + } + } + }); + + toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri))); + + function updateFormatterRegistration() { + const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter); + if (!formatEnabled && rangeFormatting) { + rangeFormatting.dispose(); + rangeFormatting = undefined; + } else if (formatEnabled && !rangeFormatting) { + rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, { + provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult { + const filesConfig = workspace.getConfiguration('files', document); + const fileFormattingOptions = { + trimTrailingWhitespace: filesConfig.get('trimTrailingWhitespace'), + trimFinalNewlines: filesConfig.get('trimFinalNewlines'), + insertFinalNewline: filesConfig.get('insertFinalNewline'), + }; + const params: DocumentRangeFormattingParams = { + textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), + range: client.code2ProtocolConverter.asRange(range), + options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions) + }; + + return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then( + client.protocol2CodeConverter.asTextEdits, + (error) => { + client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []); + return Promise.resolve([]); + } + ); + } + }); + } + } + + function updateSchemaDownloadSetting() { + schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false; + if (schemaDownloadEnabled) { + schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.'); + schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema'; + handleRetryResolveSchemaCommand(); + } else { + schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.'); + schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' }; + } + } + + return client; } function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] { diff --git a/extensions/json-language-features/client/src/node/jsonClientMain.ts b/extensions/json-language-features/client/src/node/jsonClientMain.ts index 9809074a5a9..752abe59a9f 100644 --- a/extensions/json-language-features/client/src/node/jsonClientMain.ts +++ b/extensions/json-language-features/client/src/node/jsonClientMain.ts @@ -5,7 +5,7 @@ import { ExtensionContext, OutputChannel, window, workspace } from 'vscode'; import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription } from '../jsonClient'; -import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node'; +import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node'; import { promises as fs } from 'fs'; import * as path from 'path'; @@ -15,6 +15,7 @@ import TelemetryReporter from '@vscode/extension-telemetry'; import { JSONSchemaCache } from './schemaCache'; let telemetry: TelemetryReporter | undefined; +let client: BaseLanguageClient | undefined; // this method is called when vs code is activated export async function activate(context: ExtensionContext) { @@ -45,11 +46,15 @@ export async function activate(context: ExtensionContext) { const schemaRequests = await getSchemaRequestService(context, log); - startClient(context, newLanguageClient, { schemaRequests, telemetry }); + client = await startClient(context, newLanguageClient, { schemaRequests, telemetry }); } -export function deactivate(): Promise { - return telemetry ? telemetry.dispose() : Promise.resolve(null); +export async function deactivate(): Promise { + if (client) { + await client.stop(); + client = undefined; + } + telemetry?.dispose(); } interface IPackageInfo { diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 4941fced265..1fc6753efbb 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -149,8 +149,8 @@ "dependencies": { "@vscode/extension-telemetry": "0.5.1", "request-light": "^0.5.8", - "vscode-languageclient": "^7.0.0", - "vscode-nls": "^5.0.0" + "vscode-languageclient": "^8.0.1", + "vscode-nls": "^5.0.1" }, "devDependencies": { "@types/node": "16.x" diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index cd04f24c3ab..a60bc177dc1 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -14,8 +14,8 @@ "dependencies": { "jsonc-parser": "^3.0.0", "request-light": "^0.5.8", - "vscode-json-languageservice": "^4.2.1", - "vscode-languageserver": "^7.0.0", + "vscode-json-languageservice": "^5.0.0", + "vscode-languageserver": "^8.0.1", "vscode-uri": "^3.0.3" }, "devDependencies": { diff --git a/extensions/json-language-features/server/src/jsonServer.ts b/extensions/json-language-features/server/src/jsonServer.ts index e105859371f..b818bcefbf2 100644 --- a/extensions/json-language-features/server/src/jsonServer.ts +++ b/extensions/json-language-features/server/src/jsonServer.ts @@ -6,11 +6,11 @@ import { Connection, TextDocuments, InitializeParams, InitializeResult, NotificationType, RequestType, - DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind, TextEdit, DocumentFormattingRequest, TextDocumentIdentifier, FormattingOptions + DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind, TextEdit, DocumentFormattingRequest, TextDocumentIdentifier, FormattingOptions, Diagnostic } from 'vscode-languageserver'; import { formatError, runSafe, runSafeAsync } from './utils/runner'; -import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Diagnostic, Range, Position } from 'vscode-json-languageservice'; +import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Range, Position } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; import { Utils, URI } from 'vscode-uri'; @@ -117,7 +117,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { - const handledProtocols = params.initializationOptions?.handledSchemaProtocols; + const initializationOptions = params.initializationOptions as any || {}; + + const handledProtocols = initializationOptions?.handledSchemaProtocols; languageService = getLanguageService({ schemaRequestService: getSchemaRequestService(handledProtocols), @@ -138,11 +140,13 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) return c; } + + clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false); - dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions?.provideFormatter !== 'boolean'); + dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof initializationOptions.provideFormatter !== 'boolean'); foldingRangeLimitDefault = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE); hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false); - formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; + formatterMaxNumberOfEdits = initializationOptions.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE; const capabilities: ServerCapabilities = { textDocumentSync: TextDocumentSyncKind.Incremental, completionProvider: clientSnippetSupport ? { @@ -151,8 +155,8 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) } : undefined, hoverProvider: true, documentSymbolProvider: true, - documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true, - documentFormattingProvider: params.initializationOptions?.provideFormatter === true, + documentRangeFormattingProvider: initializationOptions.provideFormatter === true, + documentFormattingProvider: initializationOptions.provideFormatter === true, colorProvider: {}, foldingRangeProvider: true, selectionRangeProvider: true, @@ -403,7 +407,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment) runtime.timer.setImmediate(() => { const currDocument = documents.get(textDocument.uri); if (currDocument && currDocument.version === version) { - respond(diagnostics); // Send the computed diagnostics to VSCode. + respond(diagnostics as Diagnostic[]); // Send the computed diagnostics to VSCode. } }); }, error => { diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 5cd1cf5f480..2e74a77e9d3 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -22,51 +22,51 @@ request-light@^0.5.8: resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== -vscode-json-languageservice@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.1.tgz#94b6f471ece193bf4a1ef37f6ab5cce86d50a8b4" - integrity sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA== +vscode-json-languageservice@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.0.0.tgz#465d76cfe5dfeed4c3d5a2123b50e3f115bb7f78" + integrity sha512-1/+1TJBRFrfCNizmrW0fbIvguKzzO+4ehlqWCCnF7ioSACUGHrYop4ANb+eRnFaCP6fi3+i+llJC5Y5yAvmL6w== dependencies: jsonc-parser "^3.0.0" - vscode-languageserver-textdocument "^1.0.3" - vscode-languageserver-types "^3.16.0" - vscode-nls "^5.0.0" + vscode-languageserver-textdocument "^1.0.4" + vscode-languageserver-types "^3.17.1" + vscode-nls "^5.0.1" vscode-uri "^3.0.3" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" -vscode-languageserver-textdocument@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz#879f2649bfa5a6e07bc8b392c23ede2dfbf43eff" - integrity sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A== +vscode-languageserver-textdocument@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157" + integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ== -vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1, vscode-languageserver-types@^3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-languageserver@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0" - integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw== +vscode-languageserver@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.1.tgz#56bd7a01f5c88af075a77f1d220edcb30fc4bdc7" + integrity sha512-sn7SjBwWm3OlmLtgg7jbM0wBULppyL60rj8K5HF0ny/MzN+GzPBX1kCvYdybhl7UW63V5V5tRVnyB8iwC73lSQ== dependencies: - vscode-languageserver-protocol "3.16.0" + vscode-languageserver-protocol "3.17.1" -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== vscode-uri@^3.0.3: version "3.0.3" diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 7e56398f062..3b6ad62624a 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -49,44 +49,44 @@ request-light@^0.5.8: resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27" integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg== -semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== +semver@^7.3.5: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" -vscode-jsonrpc@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e" - integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg== +vscode-jsonrpc@8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" + integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== -vscode-languageclient@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2" - integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg== +vscode-languageclient@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" + integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== dependencies: minimatch "^3.0.4" - semver "^7.3.4" - vscode-languageserver-protocol "3.16.0" + semver "^7.3.5" + vscode-languageserver-protocol "3.17.1" -vscode-languageserver-protocol@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821" - integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A== +vscode-languageserver-protocol@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" + integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== dependencies: - vscode-jsonrpc "6.0.0" - vscode-languageserver-types "3.16.0" + vscode-jsonrpc "8.0.1" + vscode-languageserver-types "3.17.1" -vscode-languageserver-types@3.16.0: - version "3.16.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247" - integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA== +vscode-languageserver-types@3.17.1: + version "3.17.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" + integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== -vscode-nls@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840" - integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA== +vscode-nls@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2" + integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A== yallist@^4.0.0: version "4.0.0" diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index b1d4d1bc529..4ef07a0dd65 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -453,10 +453,10 @@ "error" ] }, - "markdown.experimental.validate.fileLinks.skipPaths": { + "markdown.experimental.validate.ignoreLinks": { "type": "array", "scope": "resource", - "markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.skipPaths.description%", + "markdownDescription": "%configuration.markdown.experimental.validate.ignoreLinks.description%", "items": { "type": "string" } diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index b2958199b91..2c8977cf2e0 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -28,11 +28,11 @@ "configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.", "configuration.markdown.links.openLocation.beside": "Open links beside the active editor.", "configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links", - "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbenck.experimental.editor.dropIntoEditor.enabled#`.", + "configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbench.experimental.editor.dropIntoEditor.enabled#`.", "configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.", "configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.headerLinks.enabled.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.", "configuration.markdown.experimental.validate.fileLinks.enabled.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.", - "configuration.markdown.experimental.validate.fileLinks.skipPaths.description": "Configure glob patterns for links to treat as valid, even if they don't exist in the workspace. For example `/about` would make the link `[about](/about)` valid, while the glob `/assets/**/*.svg` would let you link to any `.svg` asset under the `assets` directory", + "configuration.markdown.experimental.validate.ignoreLinks.description": "Configure links that should not be validated. For example `/about` would not validate the link `[about](/about)`, while the glob `/assets/**/*.svg` would let you skip validation for any link to `.svg` files under the `assets` directory.", "workspaceTrust": "Required for loading styles configured in the workspace." } diff --git a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts index eb4c436afd5..89e6b29f240 100644 --- a/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts +++ b/extensions/markdown-language-features/src/languageFeatures/diagnostics.ts @@ -39,7 +39,7 @@ export interface DiagnosticOptions { readonly validateReferences: DiagnosticLevel; readonly validateOwnHeaders: DiagnosticLevel; readonly validateFilePaths: DiagnosticLevel; - readonly skipPaths: readonly string[]; + readonly ignoreLinks: readonly string[]; } function toSeverity(level: DiagnosticLevel): vscode.DiagnosticSeverity | undefined { @@ -64,7 +64,7 @@ class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConf || e.affectsConfiguration('markdown.experimental.validate.referenceLinks.enabled') || e.affectsConfiguration('markdown.experimental.validate.headerLinks.enabled') || e.affectsConfiguration('markdown.experimental.validate.fileLinks.enabled') - || e.affectsConfiguration('markdown.experimental.validate.fileLinks.skipPaths') + || e.affectsConfiguration('markdown.experimental.validate.ignoreLinks') ) { this._onDidChange.fire(); } @@ -78,7 +78,7 @@ class VSCodeDiagnosticConfiguration extends Disposable implements DiagnosticConf validateReferences: config.get('experimental.validate.referenceLinks.enabled', DiagnosticLevel.ignore), validateOwnHeaders: config.get('experimental.validate.headerLinks.enabled', DiagnosticLevel.ignore), validateFilePaths: config.get('experimental.validate.fileLinks.enabled', DiagnosticLevel.ignore), - skipPaths: config.get('experimental.validate.fileLinks.skipPaths', []), + ignoreLinks: config.get('experimental.validate.ignoreLinks', []), }; } } @@ -216,13 +216,13 @@ class LinkWatcher extends Disposable { } } -class FileDoesNotExistDiagnostic extends vscode.Diagnostic { +class LinkDoesNotExistDiagnostic extends vscode.Diagnostic { - public readonly path: string; + public readonly link: string; - constructor(range: vscode.Range, message: string, severity: vscode.DiagnosticSeverity, path: string) { + constructor(range: vscode.Range, message: string, severity: vscode.DiagnosticSeverity, link: string) { super(range, message, severity); - this.path = path; + this.link = link; } } @@ -424,10 +424,13 @@ export class DiagnosticComputer { && link.href.fragment && !toc.lookup(link.href.fragment) ) { - diagnostics.push(new vscode.Diagnostic( - link.source.hrefRange, - localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment), - severity)); + if (!this.isIgnoredLink(options, link.source.text)) { + diagnostics.push(new LinkDoesNotExistDiagnostic( + link.source.hrefRange, + localize('invalidHeaderLink', 'No header found: \'{0}\'', link.href.fragment), + severity, + link.source.text)); + } } } @@ -481,8 +484,8 @@ export class DiagnosticComputer { if (!hrefDoc && !await this.workspaceContents.pathExists(path)) { const msg = localize('invalidPathLink', 'File does not exist at path: {0}', path.fsPath); for (const link of links) { - if (!options.skipPaths.some(glob => picomatch.isMatch(link.source.pathText, glob))) { - diagnostics.push(new FileDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.pathText)); + if (!this.isIgnoredLink(options, link.source.pathText)) { + diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.pathText)); } } } else if (hrefDoc) { @@ -491,9 +494,9 @@ export class DiagnosticComputer { if (fragmentLinks.length) { const toc = await TableOfContents.create(this.engine, hrefDoc); for (const link of fragmentLinks) { - if (!toc.lookup(link.fragment)) { + if (!toc.lookup(link.fragment) && !this.isIgnoredLink(options, link.source.text)) { const msg = localize('invalidLinkToHeaderInOtherFile', 'Header does not exist in file: {0}', link.fragment); - diagnostics.push(new vscode.Diagnostic(link.source.hrefRange, msg, severity)); + diagnostics.push(new LinkDoesNotExistDiagnostic(link.source.hrefRange, msg, severity, link.source.text)); } } } @@ -502,11 +505,15 @@ export class DiagnosticComputer { })); return diagnostics; } + + private isIgnoredLink(options: DiagnosticOptions, link: string): boolean { + return options.ignoreLinks.some(glob => picomatch.isMatch(link, glob)); + } } -class AddToSkipPathsQuickFixProvider implements vscode.CodeActionProvider { +class AddToIgnoreLinksQuickFixProvider implements vscode.CodeActionProvider { - private static readonly _addToSkipPathsCommandId = '_markdown.addToSkipPaths'; + private static readonly _addToIgnoreLinksCommandId = '_markdown.addToIgnoreLinks'; private static readonly metadata: vscode.CodeActionProviderMetadata = { providedCodeActionKinds: [ @@ -515,11 +522,11 @@ class AddToSkipPathsQuickFixProvider implements vscode.CodeActionProvider { }; public static register(selector: vscode.DocumentSelector, commandManager: CommandManager): vscode.Disposable { - const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToSkipPathsQuickFixProvider(), AddToSkipPathsQuickFixProvider.metadata); + const reg = vscode.languages.registerCodeActionsProvider(selector, new AddToIgnoreLinksQuickFixProvider(), AddToIgnoreLinksQuickFixProvider.metadata); const commandReg = commandManager.register({ - id: AddToSkipPathsQuickFixProvider._addToSkipPathsCommandId, + id: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId, execute(resource: vscode.Uri, path: string) { - const settingId = 'experimental.validate.fileLinks.skipPaths'; + const settingId = 'experimental.validate.ignoreLinks'; const config = vscode.workspace.getConfiguration('markdown', resource); const paths = new Set(config.get(settingId, [])); paths.add(path); @@ -533,15 +540,15 @@ class AddToSkipPathsQuickFixProvider implements vscode.CodeActionProvider { const fixes: vscode.CodeAction[] = []; for (const diagnostic of context.diagnostics) { - if (diagnostic instanceof FileDoesNotExistDiagnostic) { + if (diagnostic instanceof LinkDoesNotExistDiagnostic) { const fix = new vscode.CodeAction( - localize('skipPathsQuickFix.title', "Add '{0}' to paths that skip link validation.", diagnostic.path), + localize('ignoreLinksQuickFix.title', "Exclude '{0}' from link validation.", diagnostic.link), vscode.CodeActionKind.QuickFix); fix.command = { - command: AddToSkipPathsQuickFixProvider._addToSkipPathsCommandId, + command: AddToIgnoreLinksQuickFixProvider._addToIgnoreLinksCommandId, title: '', - arguments: [document.uri, diagnostic.path] + arguments: [document.uri, diagnostic.link] }; fixes.push(fix); } @@ -563,5 +570,5 @@ export function register( return vscode.Disposable.from( configuration, manager, - AddToSkipPathsQuickFixProvider.register(selector, commandManager)); + AddToIgnoreLinksQuickFixProvider.register(selector, commandManager)); } diff --git a/extensions/markdown-language-features/src/preview/preview.ts b/extensions/markdown-language-features/src/preview/preview.ts index f598dd62fa3..dd3c310a194 100644 --- a/extensions/markdown-language-features/src/preview/preview.ts +++ b/extensions/markdown-language-features/src/preview/preview.ts @@ -109,6 +109,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { private readonly _onScrollEmitter = this._register(new vscode.EventEmitter()); public readonly onScroll = this._onScrollEmitter.event; + private readonly _disposeCts = this._register(new vscode.CancellationTokenSource()); + constructor( webview: vscode.WebviewPanel, resource: vscode.Uri, @@ -202,6 +204,8 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { } override dispose() { + this._disposeCts.cancel(); + super.dispose(); this._disposed = true; @@ -286,7 +290,9 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { try { document = await vscode.workspace.openTextDocument(this._resource); } catch { - await this.showFileNotFoundError(); + if (!this._disposed) { + await this.showFileNotFoundError(); + } return; } @@ -306,7 +312,7 @@ class MarkdownPreview extends Disposable implements WebviewResourceProvider { this.currentVersion = pendingVersion; const content = await (shouldReloadPage - ? this._contentProvider.provideTextDocumentContent(document, this, this._previewConfigurations, this.line, this.state) + ? this._contentProvider.provideTextDocumentContent(document, this, this._previewConfigurations, this.line, this.state, this._disposeCts.token) : this._contentProvider.markdownBody(document, this)); // Another call to `doUpdate` may have happened. diff --git a/extensions/markdown-language-features/src/preview/previewContentProvider.ts b/extensions/markdown-language-features/src/preview/previewContentProvider.ts index b7ff739ce91..8437017dc4f 100644 --- a/extensions/markdown-language-features/src/preview/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/preview/previewContentProvider.ts @@ -66,7 +66,8 @@ export class MarkdownContentProvider { resourceProvider: WebviewResourceProvider, previewConfigurations: MarkdownPreviewConfigurationManager, initialLine: number | undefined = undefined, - state?: any + state: any | undefined, + token: vscode.CancellationToken ): Promise { const sourceUri = markdownDocument.uri; const config = previewConfigurations.loadAndCacheConfiguration(sourceUri); @@ -89,6 +90,10 @@ export class MarkdownContentProvider { const csp = this.getCsp(resourceProvider, sourceUri, nonce); const body = await this.markdownBody(markdownDocument, resourceProvider); + if (token.isCancellationRequested) { + return { html: '', containingImages: [] }; + } + const html = ` diff --git a/extensions/markdown-language-features/src/test/diagnostic.test.ts b/extensions/markdown-language-features/src/test/diagnostic.test.ts index e58dabd983e..1490df29279 100644 --- a/extensions/markdown-language-features/src/test/diagnostic.test.ts +++ b/extensions/markdown-language-features/src/test/diagnostic.test.ts @@ -26,7 +26,7 @@ async function getComputedDiagnostics(doc: InMemoryDocument, workspaceContents: validateFilePaths: DiagnosticLevel.warning, validateOwnHeaders: DiagnosticLevel.warning, validateReferences: DiagnosticLevel.warning, - skipPaths: [], + ignoreLinks: [], }, noopToken) ).diagnostics; } @@ -44,7 +44,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { constructor( private readonly enabled: boolean = true, - private readonly skipPaths: string[] = [], + private readonly ignoreLinks: string[] = [], ) { } getOptions(_resource: vscode.Uri): DiagnosticOptions { @@ -54,7 +54,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { validateFilePaths: DiagnosticLevel.ignore, validateOwnHeaders: DiagnosticLevel.ignore, validateReferences: DiagnosticLevel.ignore, - skipPaths: this.skipPaths, + ignoreLinks: this.ignoreLinks, }; } return { @@ -62,7 +62,7 @@ class MemoryDiagnosticConfiguration implements DiagnosticConfiguration { validateFilePaths: DiagnosticLevel.warning, validateOwnHeaders: DiagnosticLevel.warning, validateReferences: DiagnosticLevel.warning, - skipPaths: this.skipPaths, + ignoreLinks: this.ignoreLinks, }; } } @@ -196,7 +196,7 @@ suite('markdown: Diagnostics', () => { assert.deepStrictEqual(diagnostics.length, 0); }); - test('skipPaths should allow skipping non-existent file', async () => { + test('ignoreLinks should allow skipping link to non-existent file', async () => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[text](/no-such-file#header)`, )); @@ -206,7 +206,7 @@ suite('markdown: Diagnostics', () => { assert.deepStrictEqual(diagnostics.length, 0); }); - test('skipPaths should not consider link fragment', async () => { + test('ignoreLinks should not consider link fragment', async () => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `[text](/no-such-file#header)`, )); @@ -216,7 +216,7 @@ suite('markdown: Diagnostics', () => { assert.deepStrictEqual(diagnostics.length, 0); }); - test('skipPaths should support globs', async () => { + test('ignoreLinks should support globs', async () => { const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( `![i](/images/aaa.png)`, `![i](/images/sub/bbb.png)`, @@ -227,4 +227,33 @@ suite('markdown: Diagnostics', () => { const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); assert.deepStrictEqual(diagnostics.length, 0); }); + + test('ignoreLinks should support ignoring header', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `![i](#no-such)`, + )); + + const manager = createDiagnosticsManager(new InMemoryWorkspaceMarkdownDocuments([doc1]), new MemoryDiagnosticConfiguration(true, ['#no-such'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + }); + + test('ignoreLinks should support ignoring header in file', async () => { + const doc1 = new InMemoryDocument(workspacePath('doc1.md'), joinLines( + `![i](/doc2.md#no-such)`, + )); + const doc2 = new InMemoryDocument(workspacePath('doc2.md'), joinLines('')); + + const contents = new InMemoryWorkspaceMarkdownDocuments([doc1, doc2]); + { + const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md#no-such'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + } + { + const manager = createDiagnosticsManager(contents, new MemoryDiagnosticConfiguration(true, ['/doc2.md#*'])); + const { diagnostics } = await manager.recomputeDiagnosticState(doc1, noopToken); + assert.deepStrictEqual(diagnostics.length, 0); + } + }); }); diff --git a/extensions/markdown-math/package.json b/extensions/markdown-math/package.json index 8267989daee..d3bb21a553a 100644 --- a/extensions/markdown-math/package.json +++ b/extensions/markdown-math/package.json @@ -90,7 +90,7 @@ "build-notebook": "node ./esbuild" }, "dependencies": { - "@iktakahiro/markdown-it-katex": "https://github.com/mjbvz/markdown-it-katex.git" + "@iktakahiro/markdown-it-katex": "mjbvz/markdown-it-katex" }, "devDependencies": { "@types/markdown-it": "^0.0.0", diff --git a/extensions/markdown-math/yarn.lock b/extensions/markdown-math/yarn.lock index 645b0080707..2b979935781 100644 --- a/extensions/markdown-math/yarn.lock +++ b/extensions/markdown-math/yarn.lock @@ -2,9 +2,9 @@ # yarn lockfile v1 -"@iktakahiro/markdown-it-katex@https://github.com/mjbvz/markdown-it-katex.git": +"@iktakahiro/markdown-it-katex@mjbvz/markdown-it-katex": version "4.0.1" - resolved "https://github.com/mjbvz/markdown-it-katex.git#2e3736e4b916ee64ed92ebfabeaa94643612665a" + resolved "https://codeload.github.com/mjbvz/markdown-it-katex/tar.gz/1e0d09f9174b3ee1537de2586ce8d8a460284ce4" dependencies: katex "^0.13.0" diff --git a/extensions/php/cgmanifest.json b/extensions/php/cgmanifest.json index 4b25149ffdd..4fe8f5ebcb5 100644 --- a/extensions/php/cgmanifest.json +++ b/extensions/php/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "language-php", "repositoryUrl": "https://github.com/atom/language-php", - "commitHash": "ff64523c94c014d68f5dec189b05557649c5872a" + "commitHash": "eb28b8aea1214dcbc732f3d9b9ed20c089c648bd" } }, "license": "MIT", - "version": "0.48.0" + "version": "0.48.1" } ], "version": 1 diff --git a/extensions/php/syntaxes/php.tmLanguage.json b/extensions/php/syntaxes/php.tmLanguage.json index 69a11ec2529..542b02023a7 100644 --- a/extensions/php/syntaxes/php.tmLanguage.json +++ b/extensions/php/syntaxes/php.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-php/commit/ff64523c94c014d68f5dec189b05557649c5872a", + "version": "https://github.com/atom/language-php/commit/eb28b8aea1214dcbc732f3d9b9ed20c089c648bd", "scopeName": "source.php", "patterns": [ { @@ -13,62 +13,6 @@ { "include": "#comments" }, - { - "begin": "(?i)^\\s*(interface)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)\\s*(extends)?\\s*", - "beginCaptures": { - "1": { - "name": "storage.type.interface.php" - }, - "2": { - "name": "entity.name.type.interface.php" - }, - "3": { - "name": "storage.modifier.extends.php" - } - }, - "end": "(?i)((?:[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*\\s*,\\s*)*)([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)?\\s*(?:(?={)|$)", - "endCaptures": { - "1": { - "patterns": [ - { - "match": "(?i)[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*", - "name": "entity.other.inherited-class.php" - }, - { - "match": ",", - "name": "punctuation.separator.classes.php" - } - ] - }, - "2": { - "name": "entity.other.inherited-class.php" - } - }, - "name": "meta.interface.php", - "patterns": [ - { - "include": "#namespace" - } - ] - }, - { - "begin": "(?i)^\\s*(trait)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)", - "beginCaptures": { - "1": { - "name": "storage.type.trait.php" - }, - "2": { - "name": "entity.name.type.trait.php" - } - }, - "end": "(?={)", - "name": "meta.trait.php", - "patterns": [ - { - "include": "#comments" - } - ] - }, { "match": "(?i)(?:^|(?<=<\\?php))\\s*(namespace)\\s+([a-z0-9_\\x{7f}-\\x{10ffff}\\\\]+)(?=\\s*;)", "name": "meta.namespace.php", @@ -232,6 +176,149 @@ } ] }, + { + "begin": "(?ix)\n\\b(trait)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)", + "beginCaptures": { + "1": { + "name": "storage.type.trait.php" + }, + "2": { + "name": "entity.name.type.trait.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.trait.end.bracket.curly.php" + } + }, + "name": "meta.trait.php", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.trait.begin.bracket.curly.php" + } + }, + "end": "(?=}|\\?>)", + "contentName": "meta.trait.body.php", + "patterns": [ + { + "include": "$self" + } + ] + } + ] + }, + { + "begin": "(?ix)\n\\b(interface)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)", + "beginCaptures": { + "1": { + "name": "storage.type.interface.php" + }, + "2": { + "name": "entity.name.type.interface.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.interface.end.bracket.curly.php" + } + }, + "name": "meta.interface.php", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#interface-extends" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.interface.begin.bracket.curly.php" + } + }, + "end": "(?=}|\\?>)", + "contentName": "meta.interface.body.php", + "patterns": [ + { + "include": "#class-constant" + }, + { + "include": "$self" + } + ] + } + ] + }, + { + "begin": "(?ix)\n\\b(enum)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)\n(?: \\s* (:) \\s* (int | string) \\b )?", + "beginCaptures": { + "1": { + "name": "storage.type.enum.php" + }, + "2": { + "name": "entity.name.type.enum.php" + }, + "3": { + "name": "keyword.operator.return-value.php" + }, + "4": { + "name": "keyword.other.type.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.enum.end.bracket.curly.php" + } + }, + "name": "meta.enum.php", + "patterns": [ + { + "include": "#comments" + }, + { + "include": "#class-implements" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.enum.begin.bracket.curly.php" + } + }, + "end": "(?=}|\\?>)", + "contentName": "meta.enum.body.php", + "patterns": [ + { + "match": "(?i)\\b(case)\\s*([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)", + "captures": { + "1": { + "name": "storage.modifier.php" + }, + "2": { + "name": "constant.enum.php" + } + } + }, + { + "include": "#class-constant" + }, + { + "include": "$self" + } + ] + } + ] + }, { "begin": "(?ix)\n(?:\n \\b(?:(abstract|final)\\s+)?(class)\\s+([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)\n |\\b(new)\\b\\s*(\\#\\[.*\\])?\\s*\\b(class)\\b # anonymous class\n)", "beginCaptures": { @@ -266,89 +353,37 @@ }, "name": "meta.class.php", "patterns": [ + { + "begin": "(?<=class)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.bracket.round.php" + } + }, + "end": "\\)|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.arguments.end.bracket.round.php" + } + }, + "name": "meta.function-call.php", + "patterns": [ + { + "include": "#named-arguments" + }, + { + "include": "$self" + } + ] + }, { "include": "#comments" }, { - "begin": "(?i)(extends)\\s+", - "beginCaptures": { - "1": { - "name": "storage.modifier.extends.php" - } - }, - "contentName": "meta.other.inherited-class.php", - "end": "(?i)(?=[^a-z0-9_\\x{7f}-\\x{10ffff}\\\\])", - "patterns": [ - { - "begin": "(?i)(?=\\\\?[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*\\\\)", - "end": "(?i)([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)?(?=[^a-z0-9_\\x{7f}-\\x{10ffff}\\\\])", - "endCaptures": { - "1": { - "name": "entity.other.inherited-class.php" - } - }, - "patterns": [ - { - "include": "#namespace" - } - ] - }, - { - "include": "#class-builtin" - }, - { - "include": "#namespace" - }, - { - "match": "(?i)[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*", - "name": "entity.other.inherited-class.php" - } - ] + "include": "#class-extends" }, { - "begin": "(?i)(implements)\\s+", - "beginCaptures": { - "1": { - "name": "storage.modifier.implements.php" - } - }, - "end": "(?i)(?=[;{])", - "patterns": [ - { - "include": "#comments" - }, - { - "begin": "(?i)(?=[a-z0-9_\\x{7f}-\\x{10ffff}\\\\]+)", - "contentName": "meta.other.inherited-class.php", - "end": "(?i)(?:\\s*(?:,|(?=[^a-z0-9_\\x{7f}-\\x{10ffff}\\\\\\s]))\\s*)", - "patterns": [ - { - "begin": "(?i)(?=\\\\?[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*\\\\)", - "end": "(?i)([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)?(?=[^a-z0-9_\\x{7f}-\\x{10ffff}\\\\])", - "endCaptures": { - "1": { - "name": "entity.other.inherited-class.php" - } - }, - "patterns": [ - { - "include": "#namespace" - } - ] - }, - { - "include": "#class-builtin" - }, - { - "include": "#namespace" - }, - { - "match": "(?i)[a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*", - "name": "entity.other.inherited-class.php" - } - ] - } - ] + "include": "#class-implements" }, { "begin": "{", @@ -360,6 +395,9 @@ "end": "(?=}|\\?>)", "contentName": "meta.class.body.php", "patterns": [ + { + "include": "#class-constant" + }, { "include": "$self" } @@ -382,7 +420,7 @@ } }, { - "match": "(?x)\n\\s* # FIXME: Removing this causes specs to fail. Investigate.\n\\b(\n break|case|continue|declare|default|die|do|\n else(if)?|end(declare|for(each)?|if|switch|while)|exit|\n for(each)?|if|return|switch|use|while|yield\n)\\b", + "match": "(?x)\n\\b(\n break|case|continue|declare|default|die|do|\n else(if)?|end(declare|for(each)?|if|switch|while)|exit|\n for(each)?|if|return|switch|use|while|yield\n)\\b", "captures": { "1": { "name": "keyword.control.${1:/downcase}.php" @@ -853,7 +891,7 @@ "name": "storage.type.php" }, { - "match": "(?i)\\b(global|abstract|const|extends|implements|final|private|protected|public|static)\\b", + "match": "(?i)\\b(global|abstract|const|final|private|protected|public|static)\\b", "name": "storage.modifier.php" }, { @@ -975,7 +1013,7 @@ "name": "entity.name.goto-label.php" } }, - "match": "(?i)^\\s*([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*)\\s*:(?!:)" + "match": "(?i)^\\s*([a-z_\\x{7f}-\\x{10ffff}][a-z0-9_\\x{7f}-\\x{10ffff}]*(?You signed out in another tab or window. Reload to refresh your session. - \ No newline at end of file + diff --git a/package.json b/package.json index c9215abb13a..b8992138afc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.68.0", - "distro": "dec8abea6c1191c66ab4a551172264be30a76e26", + "distro": "19bbe01e3aa85625f35f1fb2df6818a8e491670f", "author": { "name": "Microsoft Corporation" }, @@ -84,12 +84,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.47", + "xterm": "4.19.0-beta.49", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36", - "xterm-headless": "4.19.0-beta.47", + "xterm-headless": "4.19.0-beta.49", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, @@ -199,7 +199,7 @@ "style-loader": "^1.0.0", "ts-loader": "^9.2.7", "tsec": "0.1.4", - "typescript": "^4.8.0-dev.20220511", + "typescript": "^4.8.0-dev.20220518", "typescript-formatter": "7.1.0", "underscore": "^1.12.1", "util": "^0.12.4", diff --git a/product.json b/product.json index e80c93598d5..ca0f29df1da 100644 --- a/product.json +++ b/product.json @@ -27,7 +27,7 @@ "licenseFileName": "LICENSE.txt", "reportIssueUrl": "https://github.com/microsoft/vscode/issues/new", "urlProtocol": "code-oss", - "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/181b43c0e2949e36ecb623d8cc6de29d4fa2bae8/out/vs/workbench/contrib/webview/browser/pre/", + "webviewContentExternalBaseUrlTemplate": "https://{{uuid}}.vscode-cdn.net/insider/3c8520fab514b9f56070214496b26ff68d1b1cb5/out/vs/workbench/contrib/webview/browser/pre/", "builtInExtensions": [ { "name": "ms-vscode.js-debug-companion", @@ -61,7 +61,7 @@ }, { "name": "ms-vscode.vscode-js-profile-table", - "version": "1.0.1", + "version": "1.0.2", "repo": "https://github.com/microsoft/vscode-js-profile-visualizer", "metadata": { "id": "7e52b41b-71ad-457b-ab7e-0620f1fc4feb", diff --git a/remote/package.json b/remote/package.json index 75e83acbde5..3e833d3c5f6 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.47", + "xterm": "4.19.0-beta.49", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36", - "xterm-headless": "4.19.0-beta.47", + "xterm-headless": "4.19.0-beta.49", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index ece7c79fe5c..4ec42b01f12 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -10,7 +10,7 @@ "tas-client-umd": "0.1.5", "vscode-oniguruma": "1.6.1", "vscode-textmate": "7.0.1", - "xterm": "4.19.0-beta.47", + "xterm": "4.19.0-beta.49", "xterm-addon-search": "0.9.0-beta.37", "xterm-addon-unicode11": "0.4.0-beta.3", "xterm-addon-webgl": "0.12.0-beta.36" diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 97609688b6d..d8eac016d98 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -128,7 +128,7 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" - integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== +xterm@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" + integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== diff --git a/remote/yarn.lock b/remote/yarn.lock index f023194fa26..52a1cc813ed 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -934,15 +934,15 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm-headless@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.47.tgz#9d5af8145c42b9a6241a040bb244b877c149761b" - integrity sha512-chVTURPMNDEerQIsN4lIRotpXCfJsTHioZGQxDBNdktZO1gMXGvNxDjzsbsJg5ikLN5llz/HLDfXZrjCeb3elg== +xterm-headless@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.49.tgz#8e4178620be9a9dee25a586ef992a6fc621a829a" + integrity sha512-uRnHpR2pv1MJPbE3sa7XbP6x0uHubAWVMbiD26e3a5ICtWJQVVpLojkA0t4qU7CoMX85pRL1rIclg9CKY0B0xA== -xterm@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" - integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== +xterm@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" + integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== yallist@^4.0.0: version "4.0.0" diff --git a/src/buildfile.js b/src/buildfile.js index 8c30339da6e..6b49aa30083 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -38,10 +38,6 @@ exports.base = [ }, { name: 'vs/base/common/worker/simpleWorker', - }, - { - name: 'vs/platform/extensions/node/extensionHostStarterWorker', - exclude: ['vs/base/common/worker/simpleWorker'] } ]; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index d30acf1fff9..d2fa1bd3ba8 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -577,6 +577,24 @@ export function getDomNodePagePosition(domNode: HTMLElement): IDomNodePagePositi }; } +/** + * Returns the effective zoom on a given element before window zoom level is applied + */ +export function getDomNodeZoomLevel(domNode: HTMLElement): number { + let testElement: HTMLElement | null = domNode; + let zoom = 1.0; + do { + const elementZoomLevel = (getComputedStyle(testElement) as any).zoom; + if (elementZoomLevel !== null && elementZoomLevel !== undefined && elementZoomLevel !== '1') { + zoom *= elementZoomLevel; + } + + testElement = testElement.parentElement; + } while (testElement !== null && testElement !== document.documentElement); + + return zoom; +} + export interface IStandardWindow { readonly scrollX: number; readonly scrollY: number; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index c34f711ae9f..1aa24bde272 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -262,11 +262,16 @@ export class ContextView extends Disposable { if (DOM.isHTMLElement(anchor)) { let elementPosition = DOM.getDomNodePagePosition(anchor); + // In areas where zoom is applied to the element or its ancestors, we need to adjust the size of the element + // e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level. + // Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Size Multiplier: 1.5 + const zoom = DOM.getDomNodeZoomLevel(anchor); + around = { - top: elementPosition.top, - left: elementPosition.left, - width: elementPosition.width, - height: elementPosition.height + top: elementPosition.top * zoom, + left: elementPosition.left * zoom, + width: elementPosition.width * zoom, + height: elementPosition.height * zoom }; } else { around = { diff --git a/src/vs/base/parts/quickinput/browser/media/quickInput.css b/src/vs/base/parts/quickinput/browser/media/quickInput.css index 802cc6adfe7..efab21e77bf 100644 --- a/src/vs/base/parts/quickinput/browser/media/quickInput.css +++ b/src/vs/base/parts/quickinput/browser/media/quickInput.css @@ -6,10 +6,11 @@ .quick-input-widget { position: absolute; width: 600px; - z-index: 4000; + z-index: 2550; padding: 0 1px 1px 1px; left: 50%; margin-left: -300px; + -webkit-app-region: no-drag; } .quick-input-titlebar { diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index 0dca8a2bd38..49fb9f19455 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -1233,6 +1233,7 @@ export class QuickInputController extends Disposable { const checkAll = dom.append(headerContainer, $('input.quick-input-check-all')); checkAll.type = 'checkbox'; + checkAll.setAttribute('aria-label', localize('quickInput.checkAll', "Toggle all checkboxes")); this._register(dom.addStandardDisposableListener(checkAll, dom.EventType.CHANGE, e => { const checked = checkAll.checked; list.setAllVisibleChecked(checked); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4bc4a432ceb..651c9ea09e2 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -46,7 +46,7 @@ import { getResolvedShellEnv } from 'vs/platform/shell/node/shellEnv'; import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter'; -import { WorkerMainProcessExtensionHostStarter } from 'vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter'; +import { ExtensionHostStarter } from 'vs/platform/extensions/electron-main/extensionHostStarter'; import { IExternalTerminalMainService } from 'vs/platform/externalTerminal/common/externalTerminal'; import { LinuxExternalTerminalService, MacExternalTerminalService, WindowsExternalTerminalService } from 'vs/platform/externalTerminal/node/externalTerminalService'; import { LOCAL_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/files/common/diskFileSystemProviderClient'; @@ -641,7 +641,7 @@ export class CodeApplication extends Disposable { services.set(IExtensionUrlTrustService, new SyncDescriptor(ExtensionUrlTrustService)); // Extension Host Starter - services.set(IExtensionHostStarter, new SyncDescriptor(WorkerMainProcessExtensionHostStarter)); + services.set(IExtensionHostStarter, new SyncDescriptor(ExtensionHostStarter)); // Storage services.set(IStorageMainService, new SyncDescriptor(StorageMainService)); diff --git a/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts index d801c2c7722..096713ed3b0 100644 --- a/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/browser/outlineModel.test.ts @@ -77,9 +77,9 @@ suite('OutlineModel', function () { }); assert.strictEqual(isCancelled, false); - let s1 = new CancellationTokenSource(); + const s1 = new CancellationTokenSource(); service.getOrCreate(model, s1.token); - let s2 = new CancellationTokenSource(); + const s2 = new CancellationTokenSource(); service.getOrCreate(model, s2.token); s1.cancel(); diff --git a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts index 6b54319a21a..9db0f7f572d 100644 --- a/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/browser/foldingModel.test.ts @@ -347,20 +347,20 @@ suite('Folding Model', () => { let textModel = createTextModel(lines.join('\n')); try { - let foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); + const foldingModel = new FoldingModel(textModel, new TestDecorationProvider(textModel)); - let ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ }); + const ranges = computeRanges(textModel, false, { start: /^\/\/#region$/, end: /^\/\/#endregion$/ }); foldingModel.update(ranges); - let r1 = r(1, 2, false); - let r2 = r(3, 11, false); - let r3 = r(4, 10, false); - let r4 = r(5, 6, false); - let r5 = r(8, 9, false); + const r1 = r(1, 2, false); + const r2 = r(3, 11, false); + const r3 = r(4, 10, false); + const r4 = r(5, 6, false); + const r5 = r(8, 9, false); - let region1 = foldingModel.getRegionAtLine(r1.startLineNumber); - let region2 = foldingModel.getRegionAtLine(r2.startLineNumber); - let region3 = foldingModel.getRegionAtLine(r3.startLineNumber); + const region1 = foldingModel.getRegionAtLine(r1.startLineNumber); + const region2 = foldingModel.getRegionAtLine(r2.startLineNumber); + const region3 = foldingModel.getRegionAtLine(r3.startLineNumber); assertRanges(foldingModel, [r1, r2, r3, r4, r5]); diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index a73af96a7c8..94e9a31abc7 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -10,7 +10,6 @@ import { CancellationError, getErrorMessage } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { isBoolean } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; import { @@ -364,9 +363,11 @@ export abstract class AbstractExtensionManagementService extends Disposable impl throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); } - const deprecated = report.deprecated ? report.deprecated[extension.identifier.id.toLowerCase()] : undefined; - if (deprecated && !isBoolean(deprecated)) { - throw new ExtensionManagementError(nls.localize('unsupported prerelease extension', "Can't install '{0}' extension because it is deprecated. Use '{1}' extension instead.", extension.identifier.id, deprecated.displayName), ExtensionManagementErrorCode.UnsupportedPreRelease); + const deprecated = report.deprecated[extension.identifier.id.toLowerCase()]; + if (deprecated?.disallowInstall) { + const message = deprecated.extension ? nls.localize('unsupported extension with alternative', "Can't install '{0}' extension because it is deprecated. Use {1} extension instead.", extension.identifier.id, deprecated.extension.displayName) + : nls.localize('unsupported extension without alternative and no message', "Can't install '{0}' extension because it is deprecated.", extension.identifier.id); + throw new ExtensionManagementError(message, ExtensionManagementErrorCode.Deprecated); } if (!await this.canInstall(extension)) { @@ -581,7 +582,7 @@ export abstract class AbstractExtensionManagementService extends Disposable impl return manifest; } catch (err) { this.logService.trace('ExtensionManagementService.refreshControlCache - failed to get extension control manifest'); - return { malicious: [] }; + return { malicious: [], deprecated: {} }; } } diff --git a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts index b75a76752b8..3f563b19e40 100644 --- a/src/vs/platform/extensionManagement/common/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/common/extensionGalleryService.ts @@ -16,7 +16,7 @@ import { URI } from 'vs/base/common/uri'; import { IRequestContext, IRequestOptions } from 'vs/base/parts/request/common/request'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { getFallbackTargetPlarforms, getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortBy, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionManifest, TargetPlatform } from 'vs/platform/extensions/common/extensions'; import { isEngineValid } from 'vs/platform/extensions/common/extensionValidator'; @@ -537,7 +537,20 @@ function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGaller interface IRawExtensionsControlManifest { malicious: string[]; - deprecated?: IStringDictionary; + migrateToPreRelease?: IStringDictionary<{ + id: string; + displayName: string; + migrateStorage?: boolean; + engine?: string; + }>; + deprecated?: IStringDictionary; } abstract class AbstractExtensionGalleryService implements IExtensionGalleryService { @@ -666,8 +679,13 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } if (compatible) { - const engine = await this.getEngine(rawGalleryExtensionVersion); - if (!isEngineValid(engine, this.productService.version, this.productService.date)) { + try { + const engine = await this.getEngine(rawGalleryExtensionVersion); + if (!isEngineValid(engine, this.productService.version, this.productService.date)) { + return false; + } + } catch (error) { + this.logService.error(`Error while getting the engine for the version ${rawGalleryExtensionVersion.version}.`, getErrorMessage(error)); return false; } } @@ -1138,7 +1156,7 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi } if (!this.extensionsControlUrl) { - return { malicious: [] }; + return { malicious: [], deprecated: {} }; } const context = await this.requestService.request({ type: 'GET', url: this.extensionsControlUrl }, CancellationToken.None); @@ -1148,13 +1166,36 @@ abstract class AbstractExtensionGalleryService implements IExtensionGalleryServi const result = await asJson(context); const malicious: IExtensionIdentifier[] = []; + const deprecated: IStringDictionary = {}; if (result) { for (const id of result.malicious) { malicious.push({ id }); } + if (result.migrateToPreRelease) { + for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) { + if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) { + deprecated[unsupportedPreReleaseExtensionId.toLowerCase()] = { + disallowInstall: true, + extension: { + id: preReleaseExtensionInfo.id, + displayName: preReleaseExtensionInfo.displayName, + autoMigrate: { storage: !!preReleaseExtensionInfo.migrateStorage }, + preRelease: true + } + }; + } + } + } + if (result.deprecated) { + for (const [deprecatedExtensionId, deprecationInfo] of Object.entries(result.deprecated)) { + if (deprecationInfo) { + deprecated[deprecatedExtensionId.toLowerCase()] = isBoolean(deprecationInfo) ? {} : deprecationInfo; + } + } + } } - return { malicious, deprecated: result?.deprecated }; + return { malicious, deprecated }; } } diff --git a/src/vs/platform/extensionManagement/common/extensionManagement.ts b/src/vs/platform/extensionManagement/common/extensionManagement.ts index 061de6580a9..d041814268b 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagement.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagement.ts @@ -282,9 +282,20 @@ export const enum StatisticType { Uninstall = 'uninstall' } +export interface IDeprecationInfo { + readonly disallowInstall?: boolean; + readonly extension?: { + readonly id: string; + readonly displayName: string; + readonly autoMigrate?: { readonly storage: boolean }; + readonly preRelease?: boolean; + }; + readonly settings?: readonly string[]; +} + export interface IExtensionsControlManifest { - malicious: IExtensionIdentifier[]; - deprecated?: IStringDictionary; + readonly malicious: IExtensionIdentifier[]; + readonly deprecated: IStringDictionary; } export const enum InstallOperation { @@ -349,7 +360,7 @@ export interface DidUninstallExtensionEvent { export enum ExtensionManagementErrorCode { Unsupported = 'Unsupported', - UnsupportedPreRelease = 'UnsupportedPreRelease', + Deprecated = 'Deprecated', Malicious = 'Malicious', Incompatible = 'Incompatible', IncompatiblePreRelease = 'IncompatiblePreRelease', diff --git a/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts b/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts index 87eaf1447a6..57aadd8fc1d 100644 --- a/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts +++ b/src/vs/platform/extensionManagement/common/unsupportedExtensionsMigration.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken } from 'vs/base/common/cancellation'; -import { isBoolean } from 'vs/base/common/types'; import { IExtensionGalleryService, IExtensionManagementService, IGlobalExtensionEnablementService, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions, getExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IExtensionStorageService } from 'vs/platform/extensionManagement/common/extensionStorage'; @@ -26,10 +25,13 @@ export async function migrateUnsupportedExtensions(extensionManagementService: I } const installed = await extensionManagementService.getInstalled(ExtensionType.User); for (const [unsupportedExtensionId, deprecated] of Object.entries(extensionsControlManifest.deprecated)) { - if (isBoolean(deprecated)) { + if (!deprecated?.extension) { + continue; + } + const { id: preReleaseExtensionId, autoMigrate, preRelease } = deprecated.extension; + if (!autoMigrate) { continue; } - const { id: preReleaseExtensionId, migrateStorage, preRelease } = deprecated; const unsupportedExtension = installed.find(i => areSameExtensions(i.identifier, { id: unsupportedExtensionId })); // Unsupported Extension is not installed if (!unsupportedExtension) { @@ -57,7 +59,7 @@ export async function migrateUnsupportedExtensions(extensionManagementService: I await extensionEnablementService.disableExtension(preReleaseExtension.identifier); logService.info(`Disabled the pre-release extension '${preReleaseExtension.identifier.id}' because the unsupported extension '${unsupportedExtension.identifier.id}' is disabled`); } - if (migrateStorage) { + if (autoMigrate.storage) { extensionStorageService.addToMigrationList(getExtensionId(unsupportedExtension.manifest.publisher, unsupportedExtension.manifest.name), getExtensionId(preReleaseExtension.manifest.publisher, preReleaseExtension.manifest.name)); logService.info(`Added pre-release extension to the storage migration list`); } diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts similarity index 78% rename from src/vs/platform/extensions/node/extensionHostStarterWorker.ts rename to src/vs/platform/extensions/electron-main/extensionHostStarter.ts index e0f8d8b03e4..493168507e8 100644 --- a/src/vs/platform/extensions/node/extensionHostStarterWorker.ts +++ b/src/vs/platform/extensions/electron-main/extensionHostStarter.ts @@ -3,141 +3,20 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { canceled, SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; +import { Emitter, Event } from 'vs/base/common/event'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; +import { StopWatch } from 'vs/base/common/stopwatch'; import { ChildProcess, fork } from 'child_process'; import { StringDecoder } from 'string_decoder'; import { Promises, timeout } from 'vs/base/common/async'; -import { SerializedError, transformErrorForSerialization } from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { FileAccess } from 'vs/base/common/network'; import { mixin } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { cwd } from 'vs/base/common/process'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; - -export interface IExtensionHostStarterWorkerHost { - logInfo(message: string): Promise; -} - -class ExtensionHostProcess extends Disposable { - - readonly _onStdout = this._register(new Emitter()); - readonly onStdout = this._onStdout.event; - - readonly _onStderr = this._register(new Emitter()); - readonly onStderr = this._onStderr.event; - - readonly _onMessage = this._register(new Emitter()); - readonly onMessage = this._onMessage.event; - - readonly _onError = this._register(new Emitter<{ error: SerializedError }>()); - readonly onError = this._onError.event; - - readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>()); - readonly onExit = this._onExit.event; - - private _process: ChildProcess | null = null; - private _hasExited: boolean = false; - - constructor( - public readonly id: string, - private readonly _host: IExtensionHostStarterWorkerHost - ) { - super(); - } - - start(opts: IExtensionHostProcessOptions): { pid: number } { - if (platform.isCI) { - this._host.logInfo(`Calling fork to start extension host...`); - } - const sw = StopWatch.create(false); - this._process = fork( - FileAccess.asFileUri('bootstrap-fork', require).fsPath, - ['--type=extensionHost', '--skipWorkspaceStorageLock'], - mixin({ cwd: cwd() }, opts), - ); - const forkTime = sw.elapsed(); - const pid = this._process.pid!; - - this._host.logInfo(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); - - const stdoutDecoder = new StringDecoder('utf-8'); - this._process.stdout?.on('data', (chunk) => { - const strChunk = typeof chunk === 'string' ? chunk : stdoutDecoder.write(chunk); - this._onStdout.fire(strChunk); - }); - - const stderrDecoder = new StringDecoder('utf-8'); - this._process.stderr?.on('data', (chunk) => { - const strChunk = typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk); - this._onStderr.fire(strChunk); - }); - - this._process.on('message', msg => { - this._onMessage.fire(msg); - }); - - this._process.on('error', (err) => { - this._onError.fire({ error: transformErrorForSerialization(err) }); - }); - - this._process.on('exit', (code: number, signal: string) => { - this._hasExited = true; - this._onExit.fire({ pid, code, signal }); - }); - - return { pid }; - } - - enableInspectPort(): boolean { - if (!this._process) { - return false; - } - - this._host.logInfo(`Enabling inspect port on extension host with pid ${this._process.pid}.`); - - interface ProcessExt { - _debugProcess?(n: number): any; - } - - if (typeof (process)._debugProcess === 'function') { - // use (undocumented) _debugProcess feature of node - (process)._debugProcess!(this._process.pid!); - return true; - } else if (!platform.isWindows) { - // use KILL USR1 on non-windows platforms (fallback) - this._process.kill('SIGUSR1'); - return true; - } else { - // not supported... - return false; - } - } - - kill(): void { - if (!this._process) { - return; - } - this._host.logInfo(`Killing extension host with pid ${this._process.pid}.`); - this._process.kill(); - } - - async waitForExit(maxWaitTimeMs: number): Promise { - if (!this._process) { - return; - } - const pid = this._process.pid; - this._host.logInfo(`Waiting for extension host with pid ${pid} to exit.`); - await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); - - if (!this._hasExited) { - // looks like we timed out - this._host.logInfo(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); - this._process.kill(); - } - } -} export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter { _serviceBrand: undefined; @@ -145,11 +24,19 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter private static _lastId: number = 0; protected readonly _extHosts: Map; + private _shutdown = false; constructor( - private readonly _host: IExtensionHostStarterWorkerHost + @ILogService private readonly _logService: ILogService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService ) { this._extHosts = new Map(); + + // On shutdown: gracefully await extension host shutdowns + lifecycleMainService.onWillShutdown((e) => { + this._shutdown = true; + e.join(this._waitForAllExit(6000)); + }); } dispose(): void { @@ -185,11 +72,14 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter } async createExtensionHost(): Promise<{ id: string }> { + if (this._shutdown) { + throw canceled(); + } const id = String(++ExtensionHostStarter._lastId); - const extHost = new ExtensionHostProcess(id, this._host); + const extHost = new ExtensionHostProcess(id, this._logService); this._extHosts.set(id, extHost); extHost.onExit(({ pid, code, signal }) => { - this._host.logInfo(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); + this._logService.info(`Extension host with pid ${pid} exited with code: ${code}, signal: ${signal}.`); setTimeout(() => { extHost.dispose(); this._extHosts.delete(id); @@ -199,10 +89,16 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter } async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { + if (this._shutdown) { + throw canceled(); + } return this._getExtHost(id).start(opts); } async enableInspectPort(id: string): Promise { + if (this._shutdown) { + throw canceled(); + } const extHostProcess = this._extHosts.get(id); if (!extHostProcess) { return false; @@ -211,6 +107,9 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter } async kill(id: string): Promise { + if (this._shutdown) { + throw canceled(); + } const extHostProcess = this._extHosts.get(id); if (!extHostProcess) { // already gone! @@ -219,13 +118,13 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter extHostProcess.kill(); } - async killAllNow(): Promise { + async _killAllNow(): Promise { for (const [, extHost] of this._extHosts) { extHost.kill(); } } - async waitForAllExit(maxWaitTimeMs: number): Promise { + async _waitForAllExit(maxWaitTimeMs: number): Promise { const exitPromises: Promise[] = []; for (const [, extHost] of this._extHosts) { exitPromises.push(extHost.waitForExit(maxWaitTimeMs)); @@ -234,10 +133,121 @@ export class ExtensionHostStarter implements IDisposable, IExtensionHostStarter } } -/** - * The `create` function needs to be there by convention because - * we are loaded via the `vs/base/common/worker/simpleWorker` utility. - */ -export function create(host: IExtensionHostStarterWorkerHost) { - return new ExtensionHostStarter(host); +class ExtensionHostProcess extends Disposable { + + readonly _onStdout = this._register(new Emitter()); + readonly onStdout = this._onStdout.event; + + readonly _onStderr = this._register(new Emitter()); + readonly onStderr = this._onStderr.event; + + readonly _onMessage = this._register(new Emitter()); + readonly onMessage = this._onMessage.event; + + readonly _onError = this._register(new Emitter<{ error: SerializedError }>()); + readonly onError = this._onError.event; + + readonly _onExit = this._register(new Emitter<{ pid: number; code: number; signal: string }>()); + readonly onExit = this._onExit.event; + + private _process: ChildProcess | null = null; + private _hasExited: boolean = false; + + constructor( + public readonly id: string, + @ILogService private readonly _logService: ILogService, + ) { + super(); + } + + start(opts: IExtensionHostProcessOptions): { pid: number } { + if (platform.isCI) { + this._logService.info(`Calling fork to start extension host...`); + } + const sw = StopWatch.create(false); + this._process = fork( + FileAccess.asFileUri('bootstrap-fork', require).fsPath, + ['--type=extensionHost', '--skipWorkspaceStorageLock'], + mixin({ cwd: cwd() }, opts), + ); + const forkTime = sw.elapsed(); + const pid = this._process.pid!; + + this._logService.info(`Starting extension host with pid ${pid} (fork() took ${forkTime} ms).`); + + const stdoutDecoder = new StringDecoder('utf-8'); + this._process.stdout?.on('data', (chunk) => { + const strChunk = typeof chunk === 'string' ? chunk : stdoutDecoder.write(chunk); + this._onStdout.fire(strChunk); + }); + + const stderrDecoder = new StringDecoder('utf-8'); + this._process.stderr?.on('data', (chunk) => { + const strChunk = typeof chunk === 'string' ? chunk : stderrDecoder.write(chunk); + this._onStderr.fire(strChunk); + }); + + this._process.on('message', msg => { + this._onMessage.fire(msg); + }); + + this._process.on('error', (err) => { + this._onError.fire({ error: transformErrorForSerialization(err) }); + }); + + this._process.on('exit', (code: number, signal: string) => { + this._hasExited = true; + this._onExit.fire({ pid, code, signal }); + }); + + return { pid }; + } + + enableInspectPort(): boolean { + if (!this._process) { + return false; + } + + this._logService.info(`Enabling inspect port on extension host with pid ${this._process.pid}.`); + + interface ProcessExt { + _debugProcess?(n: number): any; + } + + if (typeof (process)._debugProcess === 'function') { + // use (undocumented) _debugProcess feature of node + (process)._debugProcess!(this._process.pid!); + return true; + } else if (!platform.isWindows) { + // use KILL USR1 on non-windows platforms (fallback) + this._process.kill('SIGUSR1'); + return true; + } else { + // not supported... + return false; + } + } + + kill(): void { + if (!this._process) { + return; + } + this._logService.info(`Killing extension host with pid ${this._process.pid}.`); + this._process.kill(); + } + + async waitForExit(maxWaitTimeMs: number): Promise { + if (!this._process) { + return; + } + const pid = this._process.pid; + this._logService.info(`Waiting for extension host with pid ${pid} to exit.`); + await Promise.race([Event.toPromise(this.onExit), timeout(maxWaitTimeMs)]); + + if (!this._hasExited) { + // looks like we timed out + this._logService.info(`Extension host with pid ${pid} did not exit within ${maxWaitTimeMs}ms.`); + this._process.kill(); + } + } } diff --git a/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts b/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts deleted file mode 100644 index f78d3e2db1f..00000000000 --- a/src/vs/platform/extensions/electron-main/workerMainProcessExtensionHostStarter.ts +++ /dev/null @@ -1,173 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { canceled, SerializedError } from 'vs/base/common/errors'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { IExtensionHostProcessOptions, IExtensionHostStarter } from 'vs/platform/extensions/common/extensionHostStarter'; -import { Event } from 'vs/base/common/event'; -import { FileAccess } from 'vs/base/common/network'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Worker } from 'worker_threads'; -import { IWorker, IWorkerCallback, IWorkerFactory, SimpleWorkerClient } from 'vs/base/common/worker/simpleWorker'; -import type { ExtensionHostStarter, IExtensionHostStarterWorkerHost } from 'vs/platform/extensions/node/extensionHostStarterWorker'; -import { ILifecycleMainService } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; -import { StopWatch } from 'vs/base/common/stopwatch'; - -class NodeWorker implements IWorker { - - private readonly _worker: Worker; - - public readonly onError: Event; - public readonly onExit: Event; - public readonly onMessageError: Event; - - constructor(callback: IWorkerCallback, onErrorCallback: (err: any) => void) { - this._worker = new Worker( - FileAccess.asFileUri('vs/platform/extensions/node/extensionHostStarterWorkerMain.js', require).fsPath, - ); - this._worker.on('message', callback); - this._worker.on('error', onErrorCallback); - this.onError = Event.fromNodeEventEmitter(this._worker, 'error'); - this.onExit = Event.fromNodeEventEmitter(this._worker, 'exit'); - this.onMessageError = Event.fromNodeEventEmitter(this._worker, 'messageerror'); - } - - getId(): number { - return 1; - } - - postMessage(message: any, transfer: ArrayBuffer[]): void { - this._worker.postMessage(message, transfer); - } - - dispose(): void { - this._worker.terminate(); - } -} - -class ExtensionHostStarterWorkerHost implements IExtensionHostStarterWorkerHost { - constructor( - @ILogService private readonly _logService: ILogService - ) { } - - public async logInfo(message: string): Promise { - this._logService.info(message); - } -} - -export class WorkerMainProcessExtensionHostStarter implements IDisposable, IExtensionHostStarter { - _serviceBrand: undefined; - - private _proxy: ExtensionHostStarter | null; - private readonly _worker: SimpleWorkerClient; - private _shutdown = false; - - constructor( - @ILogService private readonly _logService: ILogService, - @ILifecycleMainService lifecycleMainService: ILifecycleMainService - ) { - this._proxy = null; - - const workerFactory: IWorkerFactory = { - create: (moduleId: string, callback: IWorkerCallback, onErrorCallback: (err: any) => void): IWorker => { - const worker = new NodeWorker(callback, onErrorCallback); - worker.onError((err) => { - this._logService.error(`ExtensionHostStarterWorker has encountered an error:`); - this._logService.error(err); - }); - worker.onMessageError((err) => { - this._logService.error(`ExtensionHostStarterWorker has encountered a message error:`); - this._logService.error(err); - }); - worker.onExit((exitCode) => this._logService.info(`ExtensionHostStarterWorker exited with code ${exitCode}.`)); - worker.postMessage(moduleId, []); - return worker; - } - }; - this._worker = new SimpleWorkerClient( - workerFactory, - 'vs/platform/extensions/node/extensionHostStarterWorker', - new ExtensionHostStarterWorkerHost(this._logService) - ); - this._initialize(); - - // On shutdown: gracefully await extension host shutdowns - lifecycleMainService.onWillShutdown((e) => { - this._shutdown = true; - if (this._proxy) { - e.join(this._proxy.waitForAllExit(6000)); - } - }); - } - - dispose(): void { - // Intentionally not killing the extension host processes - } - - async _initialize(): Promise { - this._proxy = await this._worker.getProxyObject(); - this._logService.info(`ExtensionHostStarterWorker created`); - } - - onDynamicStdout(id: string): Event { - return this._proxy!.onDynamicStdout(id); - } - - onDynamicStderr(id: string): Event { - return this._proxy!.onDynamicStderr(id); - } - - onDynamicMessage(id: string): Event { - return this._proxy!.onDynamicMessage(id); - } - - onDynamicError(id: string): Event<{ error: SerializedError }> { - return this._proxy!.onDynamicError(id); - } - - onDynamicExit(id: string): Event<{ code: number; signal: string }> { - return this._proxy!.onDynamicExit(id); - } - - async createExtensionHost(): Promise<{ id: string }> { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.createExtensionHost(); - } - - async start(id: string, opts: IExtensionHostProcessOptions): Promise<{ pid: number }> { - const sw = StopWatch.create(false); - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - const timeout = setTimeout(() => { - this._logService.info(`ExtensionHostStarterWorker.start() did not return within 30s. This might be a problem.`); - }, 30000); - const result = await proxy.start(id, opts); - const duration = sw.elapsed(); - this._logService.info(`ExtensionHostStarterWorker.start() took ${duration} ms.`); - clearTimeout(timeout); - return result; - } - - async enableInspectPort(id: string): Promise { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.enableInspectPort(id); - } - - async kill(id: string): Promise { - const proxy = await this._worker.getProxyObject(); - if (this._shutdown) { - throw canceled(); - } - return proxy.kill(id); - } -} diff --git a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts b/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts deleted file mode 100644 index b4efa0c798f..00000000000 --- a/src/vs/platform/extensions/node/extensionHostStarterWorkerMain.ts +++ /dev/null @@ -1,66 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -(function () { - 'use strict'; - - const loader = require('../../../loader'); - const bootstrap = require('../../../../bootstrap'); - const path = require('path'); - const parentPort = require('worker_threads').parentPort; - - // Bootstrap: NLS - const nlsConfig = bootstrap.setupNLS(); - - // Bootstrap: Loader - loader.config({ - baseUrl: bootstrap.fileUriFromPath(path.join(__dirname, '../../../../'), { isWindows: process.platform === 'win32' }), - catchError: true, - nodeRequire: require, - nodeMain: __filename, - 'vs/nls': nlsConfig, - amdModulesPattern: /^vs\//, - recordStats: true - }); - - let isFirstMessage = true; - let beforeReadyMessages: any[] = []; - - const initialMessageHandler = (data: any) => { - if (!isFirstMessage) { - beforeReadyMessages.push(data); - return; - } - - isFirstMessage = false; - loadCode(data); - }; - - parentPort.on('message', initialMessageHandler); - - const loadCode = function (moduleId: string) { - loader([moduleId], function (ws: any) { - setTimeout(() => { - - const messageHandler = ws.create((msg: any, transfer?: ArrayBuffer[]) => { - parentPort.postMessage(msg, transfer); - }, null); - parentPort.off('message', initialMessageHandler); - parentPort.on('message', (data: any) => { - messageHandler.onmessage(data); - }); - while (beforeReadyMessages.length > 0) { - const msg = beforeReadyMessages.shift()!; - messageHandler.onmessage(msg); - } - - }); - }, (err: any) => console.error(err)); - }; - - parentPort.on('messageerror', (err: Error) => { - console.error(err); - }); -})(); diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 47e7c042f8f..f278a476e0e 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -1218,6 +1218,7 @@ export interface IFilesConfiguration { autoSaveDelay: number; eol: string; enableTrash: boolean; + excludeGitIgnore: boolean; hotExit: string; saveConflictResolution: 'askUser' | 'overwriteFileOnDisk'; }; diff --git a/src/vs/platform/terminal/node/ptyService.ts b/src/vs/platform/terminal/node/ptyService.ts index a3191a3230c..d9f8ae2b092 100644 --- a/src/vs/platform/terminal/node/ptyService.ts +++ b/src/vs/platform/terminal/node/ptyService.ts @@ -766,8 +766,8 @@ class XtermSerializer implements ITerminalSerializer { return { events: [ { - cols: this._xterm.getOption('cols'), - rows: this._xterm.getOption('rows'), + cols: this._xterm.cols, + rows: this._xterm.rows, data: serialized } ], diff --git a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts index a0c161312fb..3da845d011a 100644 --- a/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts +++ b/src/vs/platform/webview/electron-main/webviewProtocolProvider.ts @@ -14,7 +14,6 @@ export class WebviewProtocolProvider extends Disposable { private static validWebviewFilePaths = new Map([ ['/index.html', 'index.html'], ['/fake.html', 'fake.html'], - ['/main.js', 'main.js'], ['/service-worker.js', 'service-worker.js'], ]); diff --git a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts index 6f247115e34..3be47943f1a 100644 --- a/src/vs/workbench/api/browser/mainThreadCustomEditors.ts +++ b/src/vs/workbench/api/browser/mainThreadCustomEditors.ts @@ -662,6 +662,7 @@ class MainThreadCustomEditorModel extends ResourceWorkingCopy implements ICustom } : undefined, webview: { id: primaryEditor.id, + origin: primaryEditor.webview.origin, options: primaryEditor.webview.options, state: primaryEditor.webview.state, } diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 8ace4020a72..4b4cc8b38f0 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -967,7 +967,7 @@ registerAction2(class extends Action2 { abstract class BaseResizeViewAction extends Action2 { - protected static readonly RESIZE_INCREMENT = 6.5; // This is a media-size percentage + protected static readonly RESIZE_INCREMENT = 60; // This is a css pixel size protected resizePart(widthChange: number, heightChange: number, layoutService: IWorkbenchLayoutService, partToResize?: Parts): void { diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index acb2fc49198..2740f42fde9 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,7 +5,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, getClientArea, Dimension, position, size, IDimension, isAncestorUsingFlowTo, computeScreenAwareSize } from 'vs/base/browser/dom'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { IWorkingCopyBackupService } from 'vs/workbench/services/workingCopy/common/workingCopyBackup'; import { isWindows, isLinux, isMacintosh, isWeb, isNative, isIOS } from 'vs/base/common/platform'; @@ -1328,8 +1328,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } resizePart(part: Parts, sizeChangeWidth: number, sizeChangeHeight: number): void { - const sizeChangePxWidth = this.workbenchGrid.width * sizeChangeWidth / 100; - const sizeChangePxHeight = this.workbenchGrid.height * sizeChangeHeight / 100; + const sizeChangePxWidth = Math.sign(sizeChangeWidth) * computeScreenAwareSize(Math.abs(sizeChangeWidth)); + const sizeChangePxHeight = Math.sign(sizeChangeHeight) * computeScreenAwareSize(Math.abs(sizeChangeHeight)); let viewSize: IViewSize; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 8b516b544a7..1a1d9490d5c 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -81,7 +81,7 @@ /* Window Title Menu */ .monaco-workbench .part.titlebar>.titlebar-container>.window-title>.title-menu { - z-index: 3000; + z-index: 2550; -webkit-app-region: no-drag; padding: 0 8px; } diff --git a/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts b/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts index a68616a47d8..77e1ebb32eb 100644 --- a/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/titleMenuControl.ts @@ -95,7 +95,19 @@ export class TitleMenuControl { } private _updateFromWindowTitle() { - this.workspaceTitle.innerText = windowTitle.workspaceName; + + // label: just workspace name and optional decorations + const { prefix, suffix } = windowTitle.getTitleDecorations(); + let label = windowTitle.workspaceName; + if (prefix) { + label = localize('label1', "{0} {1}", prefix, label); + } + if (suffix) { + label = localize('label2', "{0} {1}", label, suffix); + } + this.workspaceTitle.innerText = label; + + // tooltip: full windowTitle const kb = keybindingService.lookupKeybinding(action.id)?.getLabel(); const title = kb ? localize('title', "Search {0} ({1}) \u2014 {2}", windowTitle.workspaceName, kb, windowTitle.value) diff --git a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts index cdae94ad277..1920790f520 100644 --- a/src/vs/workbench/browser/parts/titlebar/windowTitle.ts +++ b/src/vs/workbench/browser/parts/titlebar/windowTitle.ts @@ -108,28 +108,41 @@ export class WindowTitle extends Disposable { } private getWindowTitle(): string { - let title = this.doGetWindowTitle(); + let title = this.doGetWindowTitle() || this.productService.nameLong; + let { prefix, suffix } = this.getTitleDecorations(); + if (prefix) { + title = `${prefix} ${title}`; + } + if (suffix) { + title = `${title} ${suffix}`; + } + // Replace non-space whitespace + title = title.replace(/[^\S ]/g, ' '); + return title; + } + + getTitleDecorations() { + let prefix: string | undefined; + let suffix: string | undefined; if (this.properties.prefix) { - title = `${this.properties.prefix} ${title || this.productService.nameLong}`; + prefix = this.properties.prefix; + } + if (this.environmentService.isExtensionDevelopment) { + prefix = !prefix + ? WindowTitle.NLS_EXTENSION_HOST + : `${WindowTitle.NLS_EXTENSION_HOST} - ${prefix}`; } if (this.properties.isAdmin) { - title = `${title || this.productService.nameLong} ${WindowTitle.NLS_USER_IS_ADMIN}`; + suffix = WindowTitle.NLS_USER_IS_ADMIN; } - if (!this.properties.isPure) { - title = `${title || this.productService.nameLong} ${WindowTitle.NLS_UNSUPPORTED}`; + suffix = !suffix + ? WindowTitle.NLS_UNSUPPORTED + : `${suffix} ${WindowTitle.NLS_UNSUPPORTED}`; } - - if (this.environmentService.isExtensionDevelopment) { - title = `${WindowTitle.NLS_EXTENSION_HOST} - ${title || this.productService.nameLong}`; - } - - // Replace non-space whitespace - title = title.replace(/[^\S ]/g, ' '); - - return title; + return { prefix, suffix }; } updateProperties(properties: ITitleProperties): void { diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index bf7c818fb94..7258b951aff 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground, treeIndentGuidesStroke, errorForeground, listActiveSelectionBackground, listActiveSelectionForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { registerColor, editorBackground, contrastBorder, transparent, editorWidgetBackground, textLinkForeground, lighten, darken, focusBorder, activeContrastBorder, editorWidgetForeground, editorErrorForeground, editorWarningForeground, editorInfoForeground, treeIndentGuidesStroke, errorForeground, listActiveSelectionBackground, listActiveSelectionForeground, editorForeground, toolbarHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -763,8 +763,8 @@ export const MENUBAR_SELECTION_FOREGROUND = registerColor('menubar.selectionFore }, localize('menubarSelectionForeground', "Foreground color of the selected menu item in the menubar.")); export const MENUBAR_SELECTION_BACKGROUND = registerColor('menubar.selectionBackground', { - dark: transparent(Color.white, 0.1), - light: transparent(Color.black, 0.1), + dark: toolbarHoverBackground, + light: toolbarHoverBackground, hcDark: null, hcLight: null, }, localize('menubarSelectionBackground', "Background color of the selected menu item in the menubar.")); diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts index a4a7f7eea73..a1670e79b80 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInputFactory.ts @@ -32,6 +32,7 @@ export interface CustomDocumentBackupData extends IWorkingCopyBackupMeta { readonly webview: { readonly id: string; + readonly origin: string | undefined; readonly options: SerializedWebviewOptions; readonly state: any; }; @@ -107,9 +108,10 @@ export class CustomEditorInputSerializer extends WebviewEditorInputSerializer { } } -function reviveWebview(webviewService: IWebviewService, data: { id: string; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { +function reviveWebview(webviewService: IWebviewService, data: { id: string; origin: string | undefined; state: any; webviewOptions: WebviewOptions; contentOptions: WebviewContentOptions; extension?: WebviewExtensionDescription }) { const webview = webviewService.createWebviewOverlay({ id: data.id, + origin: data.origin, options: { purpose: WebviewContentPurpose.CustomEditor, enableFindWidget: data.webviewOptions.enableFindWidget, @@ -185,6 +187,7 @@ export class ComplexCustomWorkingCopyEditorHandler extends Disposable implements const extension = reviveWebviewExtensionDescription(backupData.extension?.id, backupData.extension?.location); const webview = reviveWebview(this._webviewService, { id, + origin: backupData.webview.origin, webviewOptions: restoreWebviewOptions(backupData.webview.options), contentOptions: restoreWebviewContentOptions(backupData.webview.options), state: backupData.webview.state, diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index c061b736c55..36390d28f6a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -71,8 +71,7 @@ /* Expressions */ -.monaco-workbench .debug-pane .monaco-list-row .expression, -.monaco-workbench .debug-hover-widget .monaco-list-row .expression { +.monaco-workbench .monaco-list-row .expression { display: flex; } diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index d28e388e474..38c3431d3b3 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -23,6 +23,10 @@ line-height: var(--vscode-repl-line-height); } +.monaco-workbench .repl .repl-tree .monaco-tl-contents .expression .lazy-button { + cursor: pointer; +} + .monaco-workbench .repl .repl-tree .monaco-tl-twistie { background-position-y: calc(100% - (var(--vscode-repl-font-size-for-twistie))); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts index 6ad78716620..4cfe2517ef5 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionEditor.ts @@ -658,7 +658,7 @@ export class ExtensionEditor extends EditorPane { } }; reset(template.recommendation); - if (extension.deprecated || extension.state === ExtensionState.Installed) { + if (extension.deprecationInfo || extension.state === ExtensionState.Installed) { return; } updateRecommendationText(false); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index d3b1573086e..e578b8359cb 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -64,7 +64,6 @@ import { escapeMarkdownSyntaxTokens, IMarkdownString, MarkdownString } from 'vs/ import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { flatten } from 'vs/base/common/arrays'; -import { isBoolean } from 'vs/base/common/types'; import { fromNow } from 'vs/base/common/date'; export class PromptExtensionInstallFailureAction extends Action { @@ -104,7 +103,7 @@ export class PromptExtensionInstallFailureAction extends Action { return; } - if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.ReleaseVersionNotFound, ExtensionManagementErrorCode.UnsupportedPreRelease].includes(this.error.name)) { + if ([ExtensionManagementErrorCode.Incompatible, ExtensionManagementErrorCode.IncompatibleTargetPlatform, ExtensionManagementErrorCode.Malicious, ExtensionManagementErrorCode.ReleaseVersionNotFound, ExtensionManagementErrorCode.Deprecated].includes(this.error.name)) { await this.dialogService.show(Severity.Info, getErrorMessage(this.error)); return; } @@ -276,11 +275,11 @@ export abstract class AbstractInstallAction extends ExtensionAction { return; } - if (this.extension.deprecated && isBoolean(this.extension.deprecated)) { + if (this.extension.deprecationInfo) { const result = await this.dialogService.confirm({ type: 'warning', - title: localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), - message: localize('deprecated message', "This extension is no longer being maintained and is deprecated."), + message: localize('install confirmation', "Are you sure you want to install '{0}'?", this.extension.displayName), + detail: localize('deprecated message', "This extension is no longer being maintained and is deprecated."), primaryButton: localize('install anyway', "Install Anyway"), }); if (!result.confirmed) { @@ -2172,14 +2171,15 @@ export class ExtensionStatusAction extends ExtensionAction { return; } - if (this.extension.deprecated) { - if (isBoolean(this.extension.deprecated)) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported tooltip', "This extension is no longer being maintained and is deprecated.")) }, true); + if (this.extension.deprecationInfo) { + if (this.extension.deprecationInfo.extension) { + const link = `[${this.extension.deprecationInfo.extension.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.extension.id]))}`)})`; + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate extension tooltip', "This extension has been deprecated. Use {0} extension instead.", link)) }, true); + } else if (this.extension.deprecationInfo.settings) { + const link = `[${localize('settings', "settings")}](${URI.parse(`command:workbench.action.openSettings?${encodeURIComponent(JSON.stringify([this.extension.deprecationInfo.settings.map(setting => `@id:${setting}`).join(' ')]))}`)})`; + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated with alternate settings tooltip', "This extension is deprecated and has become a native feature in VS Code. Configure these {0} instead.", link)) }, true); } else { - const link = `[${this.extension.deprecated.displayName}](${URI.parse(`command:extension.open?${encodeURIComponent(JSON.stringify([this.extension.deprecated.id]))}`)})`; - if (this.extension.state !== ExtensionState.Installed) { - this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('unsupported prerelease switch tooltip', "This extension has been deprecated. Use {0} instead.", link)) }, true); - } + this.updateStatus({ icon: warningIcon, message: new MarkdownString(localize('deprecated tooltip', "This extension is no longer being maintained and is deprecated.")) }, true); } return; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts index 4755c672581..9b5c1397ee4 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsList.ts @@ -188,7 +188,7 @@ export class Renderer implements IPagedRenderer { const updateEnablement = async () => { let isDisabled = false; if (extension.state === ExtensionState.Uninstalled) { - isDisabled = !!extension.deprecated || !(await this.extensionsWorkbenchService.canInstall(extension)); + isDisabled = !!extension.deprecationInfo || !(await this.extensionsWorkbenchService.canInstall(extension)); } else if (extension.local && !isLanguagePackExtension(extension.local.manifest)) { const runningExtensions = await this.extensionService.getExtensions(); const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts index e4592383fe6..1760db32044 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWidgets.ts @@ -185,7 +185,7 @@ export class RecommendationWidget extends ExtensionWidget { render(): void { this.clear(); - if (!this.extension || this.extension.state === ExtensionState.Installed || this.extension.deprecated) { + if (!this.extension || this.extension.state === ExtensionState.Installed || this.extension.deprecationInfo) { return; } const extRecommendations = this.extensionRecommendationsService.getAllRecommendationsWithReason(); @@ -549,7 +549,7 @@ export class ExtensionHoverWidget extends ExtensionWidget { if (extension.state === ExtensionState.Installed) { return undefined; } - if (extension.deprecated) { + if (extension.deprecationInfo) { return undefined; } const recommendation = this.extensionRecommendationsService.getAllRecommendationsWithReason()[extension.identifier.id.toLowerCase()]; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 7fda957ef46..ece35b058d1 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -15,7 +15,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, InstallExtensionEvent, DidUninstallExtensionEvent, IExtensionIdentifier, InstallOperation, InstallOptions, WEB_EXTENSION_TAG, InstallExtensionResult, - IExtensionsControlManifest, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions + IExtensionsControlManifest, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, EnablementState, IExtensionManagementServerService, IExtensionManagementServer, IWorkbenchExtensionManagementService, DefaultIconPath } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, groupByExtension, ExtensionKey, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; @@ -198,7 +198,7 @@ export class Extension implements IExtension { } public isMalicious: boolean = false; - public deprecated: boolean | { id: string; displayName: string } = false; + public deprecationInfo: IDeprecationInfo | undefined; get installCount(): number | undefined { return this.gallery ? this.gallery.installCount : undefined; @@ -399,10 +399,8 @@ ${this.description} class Extensions extends Disposable { static updateExtensionFromControlManifest(extension: Extension, extensionsControlManifest: IExtensionsControlManifest): void { - const isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier)); - extension.isMalicious = isMalicious; - const deprecated = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()] : undefined; - extension.deprecated = deprecated ?? false; + extension.isMalicious = extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier)); + extension.deprecationInfo = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()] : undefined; } private readonly _onChange: Emitter<{ extension: Extension; operation?: InstallOperation } | undefined> = this._register(new Emitter<{ extension: Extension; operation?: InstallOperation } | undefined>()); @@ -1166,7 +1164,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension return false; } - if (extension.deprecated && !isBoolean(extension.deprecated)) { + if (extension.deprecationInfo?.disallowInstall) { return false; } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 23744cd4f9b..47be2a6e355 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; -import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IQueryOptions, ILocalExtension, IGalleryExtension, IExtensionIdentifier, InstallOptions, InstallVSIXOptions, IExtensionInfo, IExtensionQueryOptions, IDeprecationInfo } from 'vs/platform/extensionManagement/common/extensionManagement'; import { EnablementState, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -78,7 +78,7 @@ export interface IExtension { readonly local?: ILocalExtension; gallery?: IGalleryExtension; readonly isMalicious: boolean; - readonly deprecated: boolean | { id: string; displayName: string }; + readonly deprecationInfo?: IDeprecationInfo; } export const SERVICE_ID = 'extensionsWorkbenchService'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index 54ddc028ea9..add1a477c3f 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -218,7 +218,7 @@ suite('ExtensionRecommendationsService Test', () => { onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, async canInstall() { return true; }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); } }); instantiationService.stub(IExtensionService, >{ diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 7e5b0d2a156..93b1ba0e871 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -100,7 +100,7 @@ async function setupTest() { onUninstallExtension: uninstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata) { local.identifier.uuid = metadata.id; local.publisherDisplayName = metadata.publisherDisplayName; @@ -2424,7 +2424,7 @@ function createExtensionManagementService(installed: ILocalExtension[] = []): IE return local; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, }; } diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 39f0b2ca026..1212a0ed3b2 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -98,7 +98,7 @@ suite('ExtensionsListView Tests', () => { onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, async canInstall() { return true; }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, async getTargetPlatform() { return getTargetPlatform(platform, arch); } }); instantiationService.stub(IRemoteAgentService, RemoteAgentService); diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 09723bab668..0c7d5816688 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -95,7 +95,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { onUninstallExtension: uninstallEvent.event, onDidUninstallExtension: didUninstallEvent.event, async getInstalled() { return []; }, - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, async updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata) { local.identifier.uuid = metadata.id; local.publisherDisplayName = metadata.publisherDisplayName; @@ -1489,7 +1489,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { return local; }, getTargetPlatform: async () => getTargetPlatform(platform, arch), - async getExtensionsControlManifest() { return { malicious: [] }; }, + async getExtensionsControlManifest() { return { malicious: [], deprecated: {} }; }, }; } }); diff --git a/src/vs/workbench/contrib/files/browser/explorerService.ts b/src/vs/workbench/contrib/files/browser/explorerService.ts index a1d5910f284..5b25d2e5590 100644 --- a/src/vs/workbench/contrib/files/browser/explorerService.ts +++ b/src/vs/workbench/contrib/files/browser/explorerService.ts @@ -156,7 +156,7 @@ export class ExplorerService implements IExplorerService { const items = new Set(this.view.getContext(respectMultiSelection)); items.forEach(item => { - if (this.view?.isItemCollapsed(item) && item.nestedChildren) { + if (respectMultiSelection && this.view?.isItemCollapsed(item) && item.nestedChildren) { for (const child of item.nestedChildren) { items.add(child); } diff --git a/src/vs/workbench/contrib/files/browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts index b162ad2ead5..cd4f1fd7ee5 100644 --- a/src/vs/workbench/contrib/files/browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -186,6 +186,12 @@ configurationRegistry.registerConfiguration({ 'markdownDescription': nls.localize('autoGuessEncoding', "When enabled, the editor will attempt to guess the character set encoding when opening files. This setting can also be configured per language. Note, this setting is not respected by text search. Only `#files.encoding#` is respected."), 'scope': ConfigurationScope.LANGUAGE_OVERRIDABLE }, + 'files.excludeGitIgnore': { + type: 'boolean', + markdownDescription: nls.localize('excludeGitignore', "Controls whether entries in .gitignore should be parsed and excluded from the explorer. Similar to `#files.exclude#`."), + default: false, + scope: ConfigurationScope.RESOURCE + }, 'files.eol': { 'type': 'string', 'enum': [ @@ -500,7 +506,7 @@ configurationRegistry.registerConfiguration({ '*.jsx': '${capture}.js', '*.tsx': '${capture}.ts', 'tsconfig.json': 'tsconfig.*.json', - 'package.json': 'package-lock.json, yarn.lock', + 'package.json': 'package-lock.json, yarn.lock, pnpm-lock.yaml', } } } diff --git a/src/vs/workbench/contrib/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts index b4b01687527..83b5e3b6d6b 100644 --- a/src/vs/workbench/contrib/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -23,7 +23,7 @@ export interface IExplorerService { readonly roots: ExplorerItem[]; readonly sortOrderConfiguration: ISortOrderConfiguration; - getContext(respectMultiSelection: boolean, includeNestedChildren?: boolean): ExplorerItem[]; + getContext(respectMultiSelection: boolean): ExplorerItem[]; hasViewFocus(): boolean; setEditable(stat: ExplorerItem, data: IEditableData | null): Promise; getEditable(): { stat: ExplorerItem; data: IEditableData } | undefined; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index a24f390cf51..73c8913b840 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -9,7 +9,7 @@ import * as glob from 'vs/base/common/glob'; import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { IProgressService, ProgressLocation, } from 'vs/platform/progress/common/progress'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; +import { IFileService, FileKind, FileOperationError, FileOperationResult, FileChangeType } from 'vs/platform/files/common/files'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { isTemporaryWorkspace, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; @@ -58,6 +58,8 @@ import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; import { BrowserFileUpload, ExternalFileImport, getMultipleFilesOverwriteConfirm } from 'vs/workbench/contrib/files/browser/fileImportExport'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAccess'; +import { IgnoreFile } from 'vs/workbench/services/search/common/ignoreFile'; +import { ResourceSet, TernarySearchTree } from 'vs/base/common/map'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -605,20 +607,39 @@ export class FilesFilter implements ITreeFilter { private editorsAffectingFilter = new Set(); private _onDidChange = new Emitter(); private toDispose: IDisposable[] = []; + // List of ignoreFile resources. Used to detect changes to the ignoreFiles. + private ignoreFileResourcesPerRoot = new Map(); + // Ignore tree per root. Similar to `hiddenExpressionPerRoot` + private ignoreTreesPerRoot = new Map>(); constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, @IExplorerService private readonly explorerService: IExplorerService, @IEditorService private readonly editorService: IEditorService, - @IUriIdentityService private readonly uriIdentityService: IUriIdentityService + @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @IFileService private readonly fileService: IFileService ) { this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => this.updateConfiguration())); this.toDispose.push(this.configurationService.onDidChangeConfiguration((e) => { - if (e.affectsConfiguration('files.exclude')) { + if (e.affectsConfiguration('files.exclude') || e.affectsConfiguration('files.excludeGitIgnore')) { this.updateConfiguration(); } })); + this.toDispose.push(this.fileService.onDidFilesChange(e => { + // Check to see if the update contains any of the ignoreFileResources + for (const [root, ignoreFileResourceSet] of this.ignoreFileResourcesPerRoot.entries()) { + ignoreFileResourceSet.forEach(async ignoreResource => { + if (e.contains(ignoreResource, FileChangeType.UPDATED)) { + await this.processIgnoreFile(root, ignoreResource, true); + } + if (e.contains(ignoreResource, FileChangeType.DELETED)) { + this.ignoreTreesPerRoot.get(root)?.delete(ignoreResource); + ignoreFileResourceSet.delete(ignoreResource); + } + }); + } + })); this.toDispose.push(this.editorService.onDidVisibleEditorsChange(() => { const editors = this.editorService.visibleEditors; let shouldFire = false; @@ -658,9 +679,25 @@ export class FilesFilter implements ITreeFilter { private updateConfiguration(): void { let shouldFire = false; + let updatedGitIgnoreSetting = false; this.contextService.getWorkspace().folders.forEach(folder => { const configuration = this.configurationService.getValue({ resource: folder.uri }); const excludesConfig: glob.IExpression = configuration?.files?.exclude || Object.create(null); + const parseIgnoreFile: boolean = configuration.files.excludeGitIgnore; + + // If we should be parsing ignoreFiles for this workspace and don't have an ignore tree initialize one + if (parseIgnoreFile && !this.ignoreTreesPerRoot.has(folder.uri.toString())) { + updatedGitIgnoreSetting = true; + this.ignoreFileResourcesPerRoot.set(folder.uri.toString(), new ResourceSet()); + this.ignoreTreesPerRoot.set(folder.uri.toString(), TernarySearchTree.forUris((uri) => this.uriIdentityService.extUri.ignorePathCasing(uri))); + } + + // If we shouldn't be parsing ignore files but have an ignore tree, clear the ignore tree + if (!parseIgnoreFile && this.ignoreTreesPerRoot.has(folder.uri.toString())) { + updatedGitIgnoreSetting = true; + this.ignoreFileResourcesPerRoot.delete(folder.uri.toString()); + this.ignoreTreesPerRoot.delete(folder.uri.toString()); + } if (!shouldFire) { const cached = this.hiddenExpressionPerRoot.get(folder.uri.toString()); @@ -672,13 +709,54 @@ export class FilesFilter implements ITreeFilter { this.hiddenExpressionPerRoot.set(folder.uri.toString(), { original: excludesConfigCopy, parsed: glob.parse(excludesConfigCopy) }); }); - if (shouldFire) { + if (shouldFire || updatedGitIgnoreSetting) { this.editorsAffectingFilter.clear(); this._onDidChange.fire(); } } + /** + * Given a .gitignore file resource, processes the resource and adds it to the ignore tree which hides explorer items + * @param root The root folder of the workspace as a string. Used for lookup key for ignore tree and resource list + * @param ignoreFileResource The resource of the .gitignore file + * @param update Whether or not we're updating an existing ignore file. If true it deletes the old entry + */ + private async processIgnoreFile(root: string, ignoreFileResource: URI, update?: boolean) { + // Get the name of the directory which the ignore file is in + const dirUri = dirname(ignoreFileResource); + const ignoreTree = this.ignoreTreesPerRoot.get(root); + if (!ignoreTree) { + return; + } + // If it's an update we remove the stale ignore file as we will be adding a new one + if (update) { + ignoreTree.delete(dirUri); + } + // Don't process a directory if we already have it in the tree + if (ignoreTree.has(dirUri)) { + return; + } + // Maybe we need a cancellation token here in case it's super long? + const content = await this.fileService.readFile(ignoreFileResource); + const ignoreParent = ignoreTree.findSubstr(dirUri); + const ignoreFile = new IgnoreFile(content.value.toString(), dirUri.path + path.sep, ignoreParent); + ignoreTree.set(dirUri, ignoreFile); + // If we haven't seen this resource before then we need to add it to the list of resources we're tracking + if (!this.ignoreFileResourcesPerRoot.get(root)?.has(ignoreFileResource)) { + this.ignoreFileResourcesPerRoot.get(root)?.add(ignoreFileResource); + } + // Notify the explorer of the change so we may ignore these files + this._onDidChange.fire(); + } + filter(stat: ExplorerItem, parentVisibility: TreeVisibility): boolean { + // Add newly visited .gitignore files to the ignore tree + if (stat.name === '.gitignore') { + this.processIgnoreFile(stat.root.resource.toString(), stat.resource, false); + // Never hide .gitignore files + return true; + } + return this.isVisible(stat, parentVisibility); } @@ -694,7 +772,13 @@ export class FilesFilter implements ITreeFilter { // Hide those that match Hidden Patterns const cached = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()); - if ((cached && cached.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) || stat.parent?.isExcluded) { + const globMatch = cached?.parsed(path.relative(stat.root.resource.path, stat.resource.path), stat.name, name => !!(stat.parent && stat.parent.getChild(name))); + // Small optimization to only traverse gitIgnore if the globMatch from fileExclude returned nothing + const ignoreFile = globMatch ? undefined : this.ignoreTreesPerRoot.get(stat.root.resource.toString())?.findSubstr(stat.resource); + const isIncludedInTraversal = ignoreFile?.isPathIncludedInTraversal(stat.resource.path, stat.isDirectory); + // Doing !undefined returns true and we want it to be false when undefined because that means it's not included in the ignore file + const isIgnoredByIgnoreFile = isIncludedInTraversal === undefined ? false : !isIncludedInTraversal; + if (isIgnoredByIgnoreFile || globMatch || stat.parent?.isExcluded) { stat.isExcluded = true; const editors = this.editorService.visibleEditors; const editor = editors.find(e => e.resource && this.uriIdentityService.extUri.isEqualOrParent(e.resource, stat.resource)); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts index 81fd6185e59..212b41dbdb5 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/viewportCustomMarkdown/viewportCustomMarkdown.ts @@ -70,7 +70,7 @@ class NotebookViewportContribution extends Disposable implements INotebookEditor return; } - const visibleRanges = this._notebookEditor.getVisibleRangesPlusViewportBelow(); + const visibleRanges = this._notebookEditor.getVisibleRangesPlusViewportAboveAndBelow(); cellRangesToIndexes(visibleRanges).forEach(index => { const cell = this._notebookEditor.cellAt(index); diff --git a/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg b/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg index 909b8665213..6a2f80fc98d 100644 --- a/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg +++ b/src/vs/workbench/contrib/notebook/browser/docs/cell-resize-above-viewport.drawio.svg @@ -1,42 +1,42 @@ - + - - - - - - - - + + + + + + + + - - + + - - + + - - + + - +
-
-
+
+
Notebook List View
- + Notebook List View @@ -44,86 +44,86 @@ - +
-
-
+
+
Webview top -1000
- + Webview top -1000 - - + + - +
-
-
+
+
Viewport
- + Viewport - - - - + + + + - +
-
-
+
+
Notebook List View
- + Notebook List View - - + + - - + + - + - + - +
-
-
+
+
Grow Height by 50
- + Grow Height by 50 @@ -131,16 +131,16 @@ - +
-
-
+
+
scrollTop 1000
- + scrollTop 1000 @@ -148,172 +148,172 @@ - +
-
-
+
+
scrollTop 1000
- + scrollTop 1000 - - + + - - - - + + + + - +
-
-
+
+
Notebook List View
- + Notebook List View - - + + - - + + - + - + - +
-
-
+
+
scrollTop 1050
- + scrollTop 1050 - - + + - - + + - - + + - - + + - - - - + + + + - +
-
-
+
+
Notebook List View
- + Notebook List View - - + + - - + + - + - + - +
-
-
+
+
scrollTop 1050
- + scrollTop 1050 - - + + - +
-
-
+
+
Adjust top
- + Adjust top - - + + - +
-
-
+
+
UpdateScrollTop
- + UpdateScrollTop @@ -323,16 +323,16 @@ - +
-
-
+
+
Webview top -1000
- + Webview top -1000 @@ -340,16 +340,16 @@ - +
-
-
+
+
Webview top -1000
- + Webview top -1000 @@ -357,16 +357,16 @@ - +
-
-
+
+
Webview top -1000
- + Webview top -1000 @@ -374,55 +374,55 @@ - - + + - - - - + + + + - +
-
-
+
+
Notebook List View
- + Notebook List View - - + + - - + + - + - + - +
-
-
+
+
scrollTop 1050
- + scrollTop 1050 @@ -430,16 +430,16 @@ - +
-
-
+
+
Webview top -950
- + Webview top -950 @@ -447,55 +447,55 @@ - - + + - - - - + + + + - +
-
-
+
+
Notebook List View
- + Notebook List View - - + + - - + + - + - + - +
-
-
+
+
scrollTop 1050
- + scrollTop 1050 @@ -503,35 +503,35 @@ - +
-
-
+
+
Webview top -950
- + Webview top -950 - - + + - +
-
-
+
+
Adjust top
- + Adjust top @@ -543,16 +543,16 @@ - +
-
-
+
+
1
- + 1 @@ -560,16 +560,16 @@ - +
-
-
+
+
2
- + 2 @@ -577,16 +577,16 @@ - +
-
-
+
+
3
- + 3 @@ -594,16 +594,16 @@ - +
-
-
+
+
4
- + 4 @@ -611,16 +611,16 @@ - +
-
-
+
+
4'
- + 4' @@ -628,16 +628,16 @@ - +
-
-
+
+
5
- + 5 diff --git a/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md index b289f68fd34..eb757a5a366 100644 --- a/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md +++ b/src/vs/workbench/contrib/notebook/browser/docs/notebook.layout.md @@ -8,7 +8,6 @@ The notebook editor is a virtualized list view rendered in two contexts (mainfra - [Optimizations](#optimizations) - [Avoid flickering on resize of cells above current viewport](#avoid-flickering-on-resize-of-cells-above-current-viewport) - [Executing code cell followed by markdown cells](#executing-code-cell-followed-by-markdown-cells) - - [What's the catch](#whats-the-catch) - [Re-executing code cell followed by markdown cells](#re-executing-code-cell-followed-by-markdown-cells) - [Scrolling](#scrolling) @@ -172,6 +171,6 @@ If the new output is rendered within 200ms, users won't see the UI movement. ## Scrolling -Code cell outputs and markdown cells are rendered in the webview, which are async in nature. In order to have the cell outputs and markdown previews rendered when users scroll to them, we send rendering requests of cells in the next viewport when it's idle. Thus scrolling downwards is smoother. +Code cell outputs and markdown cells are rendered in the webview, which are async in nature. In order to have the cell outputs and markdown previews rendered when users scroll to them, we send rendering requests of cells in the next and previous viewport when it's idle. Thus scrolling is smoother. -However, we **don't** warmup the previous viewport as the cell height change of previous viewport might trigger the flickering of markdown cells in current viewport. Before we optimize this, do not do any warmup of cells before current viewport. +We also warm up all rendered markdown in the document, from top to bottom, when the browser is idle. We don't warm up outputs like this, since they can be more expensive to render than markdown. But outputs do get rendered when the user starts a search in outputs. diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 56012a1a041..75452af0bde 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -442,7 +442,7 @@ export interface INotebookEditor { */ getLayoutInfo(): NotebookLayoutInfo; - getVisibleRangesPlusViewportBelow(): ICellRange[]; + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[]; /** * Focus the container of a cell (the monaco editor inside is not focused). diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 535e00b2aa6..8567bdded40 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -2088,8 +2088,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._listViewInfoAccessor.setHiddenAreas(_ranges); } - getVisibleRangesPlusViewportBelow(): ICellRange[] { - return this._listViewInfoAccessor.getVisibleRangesPlusViewportBelow(); + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[] { + return this._listViewInfoAccessor.getVisibleRangesPlusViewportAboveAndBelow(); } setScrollTop(scrollTop: number) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 859e007d6c9..0318aa6a288 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -600,15 +600,16 @@ export class NotebookCellList extends WorkbenchList implements ID return reduceCellRanges(ranges); } - getVisibleRangesPlusViewportBelow() { + getVisibleRangesPlusViewportAboveAndBelow() { if (this.view.length <= 0) { return []; } - const bottom = clamp(this.getViewScrollBottom() + this.renderHeight, 0, this.scrollHeight); - const topViewIndex = this.firstVisibleIndex; + const top = Math.max(this.getViewScrollTop() - this.renderHeight, 0); + const topViewIndex = this.view.indexAt(top); const topElement = this.view.element(topViewIndex); const topModelIndex = this._viewModel!.getCellIndex(topElement); + const bottom = clamp(this.getViewScrollBottom() + this.renderHeight, 0, this.scrollHeight); const bottomViewIndex = clamp(this.view.indexAt(bottom), 0, this.view.length - 1); const bottomElement = this.view.element(bottomViewIndex); const bottomModelIndex = this._viewModel!.getCellIndex(bottomElement); @@ -1584,8 +1585,8 @@ export class ListViewInfoAccessor extends Disposable { return this.list.setHiddenAreas(_ranges, true); } - getVisibleRangesPlusViewportBelow(): ICellRange[] { - return this.list?.getVisibleRangesPlusViewportBelow() ?? []; + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[] { + return this.list?.getVisibleRangesPlusViewportAboveAndBelow() ?? []; } triggerScroll(event: IMouseWheelEvent) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 0eb419e6d32..0bdb4ff330f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -53,7 +53,7 @@ export interface INotebookCellList { getViewIndex2(modelIndex: number): number | undefined; getModelIndex(cell: CellViewModel): number | undefined; getModelIndex2(viewIndex: number): number | undefined; - getVisibleRangesPlusViewportBelow(): ICellRange[]; + getVisibleRangesPlusViewportAboveAndBelow(): ICellRange[]; focusElement(element: ICellViewModel): void; selectElements(elements: ICellViewModel[]): void; getFocusedElements(): ICellViewModel[]; diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index 6c4169e6b7c..e41f7298554 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -18,8 +18,9 @@ import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_S import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; -import { ConfigurationScope, EditPresentationTypes } from 'vs/platform/configuration/common/configurationRegistry'; +import { ConfigurationScope, EditPresentationTypes, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ILanguageService } from 'vs/editor/common/languages/language'; +import { Registry } from 'vs/platform/registry/common/platform'; export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices'; @@ -221,6 +222,12 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { this.value = displayValue; this.scopeValue = isConfigured && overrideValues[targetSelector]; this.defaultValue = overrideValues.defaultValue ?? inspected.defaultValue; + + const registryValues = Registry.as(Extensions.Configuration).getConfigurationDefaultsOverrides(); + const overrideValueSource = registryValues.get(`[${languageSelector}]`)?.valuesSources?.get(this.setting.key); + if (overrideValueSource) { + this.setting.defaultValueSource = overrideValueSource; + } } else { this.value = displayValue; this.scopeValue = isConfigured && inspected[targetSelector]; diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index a2d8b9671ae..7da92791ee6 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -243,20 +243,20 @@ class InitialRemoteConnectionHealthContribution implements IWorkbenchContributio await timeout(EXT_HOST_LATENCY_DELAY); } - type RemoteConnectionFailureClassification = { + type RemoteConnectionLatencyClassification = { owner: 'connor4312'; comment: 'The latency to the remote extension host'; web: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Whether this is running on web' }; remoteName: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Anonymized remote name' }; latencyMs: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'Latency to the remote, in milliseconds'; isMeasurement: true }; }; - type RemoteConnectionFailureEvent = { + type RemoteConnectionLatencyEvent = { web: boolean; remoteName: string | undefined; latencyMs: number; }; - this._telemetryService.publicLog2('remoteConnectionFailure', { + this._telemetryService.publicLog2('remoteConnectionLatency', { web: isWeb, remoteName: getRemoteName(this._environmentService.remoteAuthority), latencyMs: bestLatency diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index ced0fd8cf8f..3c6dc5ca458 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -5,10 +5,10 @@ import * as nls from 'vs/nls'; import { Codicon } from 'vs/base/common/codicons'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import { AbstractProblemCollector, StartStopProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; -import { TaskEvent, TaskEventKind } from 'vs/workbench/contrib/tasks/common/tasks'; +import { TaskEvent, TaskEventKind, TaskRunType } from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskService, Task } from 'vs/workbench/contrib/tasks/common/taskService'; import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ITerminalStatus } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; @@ -17,15 +17,18 @@ import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; interface TerminalData { terminal: ITerminalInstance; + task: Task; status: ITerminalStatus; problemMatcher: AbstractProblemCollector; + taskRunEnded: boolean; + disposeListener?: IDisposable; } const TASK_TERMINAL_STATUS_ID = 'task_terminal_status'; -const ACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: spinningLoading, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.active', "Task is running") }; -const SUCCEEDED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.check, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.succeeded', "Task succeeded") }; +export const ACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: spinningLoading, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.active', "Task is running") }; +export const SUCCEEDED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.check, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.succeeded', "Task succeeded") }; const SUCCEEDED_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.check, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.succeededInactive', "Task succeeded and waiting...") }; -const FAILED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.error, severity: Severity.Error, tooltip: nls.localize('taskTerminalStatus.errors', "Task has errors") }; +export const FAILED_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.error, severity: Severity.Error, tooltip: nls.localize('taskTerminalStatus.errors', "Task has errors") }; const FAILED_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.error, severity: Severity.Error, tooltip: nls.localize('taskTerminalStatus.errorsInactive', "Task has errors and is waiting...") }; const WARNING_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.warning, severity: Severity.Warning, tooltip: nls.localize('taskTerminalStatus.warnings', "Task has warnings") }; const WARNING_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.warning, severity: Severity.Warning, tooltip: nls.localize('taskTerminalStatus.warningsInactive', "Task has warnings and is waiting...") }; @@ -33,7 +36,7 @@ const INFO_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: C const INFO_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.info, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.infosInactive', "Task has infos and is waiting...") }; export class TaskTerminalStatus extends Disposable { - private terminalMap: Map = new Map(); + private terminalMap: Map = new Map(); constructor(taskService: ITaskService) { super(); @@ -50,15 +53,15 @@ export class TaskTerminalStatus extends Disposable { addTerminal(task: Task, terminal: ITerminalInstance, problemMatcher: AbstractProblemCollector) { const status: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, severity: Severity.Info }; terminal.statusList.add(status); - this.terminalMap.set(task, { terminal, status, problemMatcher }); + this.terminalMap.set(task._id, { terminal, task, status, problemMatcher, taskRunEnded: false }); } private terminalFromEvent(event: TaskEvent): TerminalData | undefined { - if (!event.__task || !this.terminalMap.get(event.__task)) { + if (!event.__task) { return undefined; } - return this.terminalMap.get(event.__task); + return this.terminalMap.get(event.__task._id); } private eventEnd(event: TaskEvent) { @@ -66,13 +69,11 @@ export class TaskTerminalStatus extends Disposable { if (!terminalData) { return; } - - this.terminalMap.delete(event.__task!); - + terminalData.taskRunEnded = true; terminalData.terminal.statusList.remove(terminalData.status); if ((event.exitCode === 0) && (terminalData.problemMatcher.numberOfMatches === 0)) { terminalData.terminal.statusList.add(SUCCEEDED_TASK_STATUS); - } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { + } else if (event.exitCode || terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Error) { terminalData.terminal.statusList.add(FAILED_TASK_STATUS); } else if (terminalData.problemMatcher.maxMarkerSeverity === MarkerSeverity.Warning) { terminalData.terminal.statusList.add(WARNING_TASK_STATUS); @@ -83,7 +84,7 @@ export class TaskTerminalStatus extends Disposable { private eventInactive(event: TaskEvent) { const terminalData = this.terminalFromEvent(event); - if (!terminalData || !terminalData.problemMatcher) { + if (!terminalData || !terminalData.problemMatcher || terminalData.taskRunEnded) { return; } terminalData.terminal.statusList.remove(terminalData.status); @@ -103,10 +104,16 @@ export class TaskTerminalStatus extends Disposable { if (!terminalData) { return; } - + if (!terminalData.disposeListener) { + terminalData.disposeListener = terminalData.terminal.onDisposed(() => { + this.terminalMap.delete(event.__task?._id!); + terminalData.disposeListener?.dispose(); + }); + } + terminalData.taskRunEnded = false; terminalData.terminal.statusList.remove(terminalData.status); // We don't want to show an infinite status for a background task that doesn't have a problem matcher. - if ((terminalData.problemMatcher instanceof StartStopProblemCollector) || (terminalData.problemMatcher?.problemMatchers.length > 0)) { + if ((terminalData.problemMatcher instanceof StartStopProblemCollector) || (terminalData.problemMatcher?.problemMatchers.length > 0) || event.runType === TaskRunType.SingleRun) { terminalData.terminal.statusList.add(ACTIVE_TASK_STATUS); } } diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts new file mode 100644 index 00000000000..3b00f83eaf5 --- /dev/null +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ok } from 'assert'; +import { Emitter, Event } from 'vs/base/common/event'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ACTIVE_TASK_STATUS, FAILED_TASK_STATUS, SUCCEEDED_TASK_STATUS, TaskTerminalStatus } from 'vs/workbench/contrib/tasks/browser/taskTerminalStatus'; +import { AbstractProblemCollector } from 'vs/workbench/contrib/tasks/common/problemCollectors'; +import { CommonTask, TaskEvent, TaskEventKind, TaskRunType } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskService, Task } from 'vs/workbench/contrib/tasks/common/taskService'; +import { ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalStatus, ITerminalStatusList, TerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; + +class TestTaskService implements Partial { + private readonly _onDidStateChange: Emitter = new Emitter(); + public get onDidStateChange(): Event { + return this._onDidStateChange.event; + } + public triggerStateChange(event: TaskEvent): void { + this._onDidStateChange.fire(event); + } +} + +class TestTerminal implements Partial { + statusList: TerminalStatusList = new TerminalStatusList(new TestConfigurationService()); +} + +class TestTask extends CommonTask { + protected getFolderId(): string | undefined { + throw new Error('Method not implemented.'); + } + protected fromObject(object: any): Task { + throw new Error('Method not implemented.'); + } +} + +class TestProblemCollector implements Partial { + +} + +suite('Task Terminal Status', () => { + let instantiationService: TestInstantiationService; + let taskService: TestTaskService; + let taskTerminalStatus: TaskTerminalStatus; + let testTerminal: ITerminalInstance; + let testTask: Task; + let problemCollector: AbstractProblemCollector; + setup(() => { + instantiationService = new TestInstantiationService(); + taskService = new TestTaskService(); + taskTerminalStatus = instantiationService.createInstance(TaskTerminalStatus, taskService); + testTerminal = instantiationService.createInstance(TestTerminal); + testTask = instantiationService.createInstance(TestTask); + problemCollector = instantiationService.createInstance(TestProblemCollector); + }); + test('Should add failed status when there is an exit code on task end', async () => { + taskTerminalStatus.addTerminal(testTask, testTerminal, problemCollector); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted }); + assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.End, exitCode: 2 }); + await poll(async () => Promise.resolve(), () => testTerminal?.statusList.primary?.id === FAILED_TASK_STATUS.id, 'terminal status should be updated'); + }); + test('Should add active status when a non-background task is run for a second time in the same terminal', async () => { + taskTerminalStatus.addTerminal(testTask, testTerminal, problemCollector); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted }); + assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.ProcessStarted, runType: TaskRunType.SingleRun }); + assertStatus(testTerminal.statusList, ACTIVE_TASK_STATUS); + taskService.triggerStateChange({ kind: TaskEventKind.Inactive }); + assertStatus(testTerminal.statusList, SUCCEEDED_TASK_STATUS); + }); +}); + +function assertStatus(actual: ITerminalStatusList, expected: ITerminalStatus): void { + ok(actual.statuses.length === 1, '# of statuses'); + ok(actual.primary?.id === expected.id, 'ID'); + ok(actual.primary?.severity === expected.severity, 'Severity'); +} + +async function poll( + fn: () => Thenable, + acceptFn: (result: T) => boolean, + timeoutMessage: string, + retryCount: number = 200, + retryInterval: number = 10 // millis +): Promise { + let trial = 1; + let lastError: string = ''; + + while (true) { + if (trial > retryCount) { + throw new Error(`Timeout: ${timeoutMessage} after ${(retryCount * retryInterval) / 1000} seconds.\r${lastError}`); + } + + let result; + try { + result = await fn(); + if (acceptFn(result)) { + return result; + } else { + lastError = 'Did not pass accept function'; + } + } catch (e: any) { + lastError = Array.isArray(e.stack) ? e.stack.join('\n') : e.stack; + } + + await new Promise(resolve => setTimeout(resolve, retryInterval)); + trial++; + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts index 043a8472b5e..e03ec4351a5 100644 --- a/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts +++ b/src/vs/workbench/contrib/terminal/browser/links/terminalLinkOpeners.ts @@ -170,7 +170,8 @@ export class TerminalSearchLinkOpener implements ITerminalLinkOpener { let resourceMatch: IResourceMatch | undefined; if (osPathModule(this._os).isAbsolute(sanitizedLink)) { const scheme = this._workbenchEnvironmentService.remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - const uri = URI.from({ scheme, path: sanitizedLink }); + const slashNormalizedPath = this._os === OperatingSystem.Windows ? sanitizedLink.replace(/\\/g, '/') : sanitizedLink; + const uri = URI.from({ scheme, path: slashNormalizedPath }); try { const fileStat = await this._fileService.stat(uri); resourceMatch = { uri, isDirectory: fileStat.isDirectory }; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 6849cc1ff84..3285ae2c363 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -620,6 +620,7 @@ export class XtermTerminal extends DisposableStore implements IXtermTerminal { this._decorationAddon.dispose(); this._decorationAddon = undefined; } + return; } if (this._decorationAddon) { this._decorationAddon.dispose(); diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index a90e8a5bf3b..e1e9d7887a9 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -521,7 +521,7 @@ const terminalConfiguration: IConfigurationNode = { default: true }, [TerminalSettingId.AutoReplies]: { - markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that when encountered in the terminal will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart VS Code if new don't apply.", '`"Terminate batch job (Y/N)": "\\r"`', '`"\\r"`'), + markdownDescription: localize('terminal.integrated.autoReplies', "A set of messages that when encountered in the terminal will be automatically responded to. Provided the message is specific enough, this can help automate away common responses.\n\nRemarks:\n\n- Use {0} to automatically respond to the terminate batch job prompt on Windows.\n- The message includes escape sequences so the reply might not happen with styled text.\n- Each reply can only happen once every second.\n- Use {1} in the reply to mean the enter key.\n- To unset a default key, set the value to null.\n- Restart VS Code if new don't apply.", '`"Terminate batch job (Y/N)": "Y\\r"`', '`"\\r"`'), type: 'object', additionalProperties: { oneOf: [{ diff --git a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts index b6efab0951f..a67d0f4b0d2 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/links/terminalLinkOpeners.test.ts @@ -177,7 +177,7 @@ suite('Workbench - TerminalLinkOpeners', () => { type: TerminalBuiltinLinkType.Search }); deepStrictEqual(activationResult, { - link: 'file:///c%3A%5CUsers%5Chome%5Cfolder%5Cfile.txt', + link: 'file:///c%3A/Users/home/folder/file.txt', source: 'editor' }); diff --git a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts index b1a228fc65c..8c6cc98b2f4 100644 --- a/src/vs/workbench/contrib/webview/browser/overlayWebview.ts +++ b/src/vs/workbench/contrib/webview/browser/overlayWebview.ts @@ -8,6 +8,7 @@ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; +import { generateUuid } from 'vs/base/common/uuid'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; @@ -43,6 +44,7 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { private _findWidgetEnabled: IContextKey | undefined; public readonly id: string; + public readonly origin: string; public constructor( initInfo: WebviewInitInfo, @@ -53,6 +55,8 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { super(); this.id = initInfo.id; + this.origin = initInfo.origin ?? generateUuid(); + this._extension = initInfo.extension; this._options = initInfo.options; this._contentOptions = initInfo.contentOptions; @@ -186,7 +190,13 @@ export class OverlayWebview extends Disposable implements IOverlayWebview { } if (!this._webview.value) { - const webview = this._webviewService.createWebviewElement({ id: this.id, options: this._options, contentOptions: this._contentOptions, extension: this.extension }); + const webview = this._webviewService.createWebviewElement({ + id: this.id, + origin: this.origin, + options: this._options, + contentOptions: this._contentOptions, + extension: this.extension, + }); this._webview.value = webview; webview.state = this._state; diff --git a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html index 6885c2488cd..625c46f3115 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index-no-csp.html @@ -12,7 +12,1136 @@ - + diff --git a/src/vs/workbench/contrib/webview/browser/pre/index.html b/src/vs/workbench/contrib/webview/browser/pre/index.html index 3f2ade8478f..457447af38e 100644 --- a/src/vs/workbench/contrib/webview/browser/pre/index.html +++ b/src/vs/workbench/contrib/webview/browser/pre/index.html @@ -4,7 +4,8 @@ - + - + diff --git a/src/vs/workbench/contrib/webview/browser/pre/main.js b/src/vs/workbench/contrib/webview/browser/pre/main.js deleted file mode 100644 index d2c57f4aa10..00000000000 --- a/src/vs/workbench/contrib/webview/browser/pre/main.js +++ /dev/null @@ -1,1133 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -// @ts-check - -/// - -const isSafari = ( - navigator.vendor && navigator.vendor.indexOf('Apple') > -1 && - navigator.userAgent && - navigator.userAgent.indexOf('CriOS') === -1 && - navigator.userAgent.indexOf('FxiOS') === -1 -); - -const isFirefox = ( - navigator.userAgent && - navigator.userAgent.indexOf('Firefox') >= 0 -); - -const searchParams = new URL(location.toString()).searchParams; -const ID = searchParams.get('id'); -const onElectron = searchParams.get('platform') === 'electron'; -const expectedWorkerVersion = parseInt(searchParams.get('swVersion')); - -/** - * Use polling to track focus of main webview and iframes within the webview - * - * @param {Object} handlers - * @param {() => void} handlers.onFocus - * @param {() => void} handlers.onBlur - */ -const trackFocus = ({ onFocus, onBlur }) => { - const interval = 250; - let isFocused = document.hasFocus(); - setInterval(() => { - const isCurrentlyFocused = document.hasFocus(); - if (isCurrentlyFocused === isFocused) { - return; - } - isFocused = isCurrentlyFocused; - if (isCurrentlyFocused) { - onFocus(); - } else { - onBlur(); - } - }, interval); -}; - -const getActiveFrame = () => { - return /** @type {HTMLIFrameElement | undefined} */ (document.getElementById('active-frame')); -}; - -const getPendingFrame = () => { - return /** @type {HTMLIFrameElement | undefined} */ (document.getElementById('pending-frame')); -}; - -/** - * @template T - * @param {T | undefined | null} obj - * @return {T} - */ -function assertIsDefined(obj) { - if (typeof obj === 'undefined' || obj === null) { - throw new Error('Found unexpected null'); - } - return obj; -} - -const vscodePostMessageFuncName = '__vscode_post_message__'; - -const defaultStyles = document.createElement('style'); -defaultStyles.id = '_defaultStyles'; -defaultStyles.textContent = ` - html { - scrollbar-color: var(--vscode-scrollbarSlider-background) var(--vscode-editor-background); - } - - body { - background-color: transparent; - color: var(--vscode-editor-foreground); - font-family: var(--vscode-font-family); - font-weight: var(--vscode-font-weight); - font-size: var(--vscode-font-size); - margin: 0; - padding: 0 20px; - } - - img { - max-width: 100%; - max-height: 100%; - } - - a, a code { - color: var(--vscode-textLink-foreground); - } - - a:hover { - color: var(--vscode-textLink-activeForeground); - } - - a:focus, - input:focus, - select:focus, - textarea:focus { - outline: 1px solid -webkit-focus-ring-color; - outline-offset: -1px; - } - - code { - color: var(--vscode-textPreformat-foreground); - } - - blockquote { - background: var(--vscode-textBlockQuote-background); - border-color: var(--vscode-textBlockQuote-border); - } - - kbd { - color: var(--vscode-editor-foreground); - border-radius: 3px; - vertical-align: middle; - padding: 1px 3px; - - background-color: hsla(0,0%,50%,.17); - border: 1px solid rgba(71,71,71,.4); - border-bottom-color: rgba(88,88,88,.4); - box-shadow: inset 0 -1px 0 rgba(88,88,88,.4); - } - .vscode-light kbd { - background-color: hsla(0,0%,87%,.5); - border: 1px solid hsla(0,0%,80%,.7); - border-bottom-color: hsla(0,0%,73%,.7); - box-shadow: inset 0 -1px 0 hsla(0,0%,73%,.7); - } - - ::-webkit-scrollbar { - width: 10px; - height: 10px; - } - - ::-webkit-scrollbar-corner { - background-color: var(--vscode-editor-background); - } - - ::-webkit-scrollbar-thumb { - background-color: var(--vscode-scrollbarSlider-background); - } - ::-webkit-scrollbar-thumb:hover { - background-color: var(--vscode-scrollbarSlider-hoverBackground); - } - ::-webkit-scrollbar-thumb:active { - background-color: var(--vscode-scrollbarSlider-activeBackground); - } - ::highlight(find-highlight) { - background-color: var(--vscode-editor-findMatchHighlightBackground); - } - ::highlight(current-find-highlight) { - background-color: var(--vscode-editor-findMatchBackground); - }`; - -/** - * @param {boolean} allowMultipleAPIAcquire - * @param {*} [state] - * @return {string} - */ -function getVsCodeApiScript(allowMultipleAPIAcquire, state) { - const encodedState = state ? encodeURIComponent(state) : undefined; - return /* js */` - globalThis.acquireVsCodeApi = (function() { - const originalPostMessage = window.parent['${vscodePostMessageFuncName}'].bind(window.parent); - const doPostMessage = (channel, data, transfer) => { - originalPostMessage(channel, data, transfer); - }; - - let acquired = false; - - let state = ${state ? `JSON.parse(decodeURIComponent("${encodedState}"))` : undefined}; - - return () => { - if (acquired && !${allowMultipleAPIAcquire}) { - throw new Error('An instance of the VS Code API has already been acquired'); - } - acquired = true; - return Object.freeze({ - postMessage: function(message, transfer) { - doPostMessage('onmessage', { message, transfer }, transfer); - }, - setState: function(newState) { - state = newState; - doPostMessage('do-update-state', JSON.stringify(newState)); - return newState; - }, - getState: function() { - return state; - } - }); - }; - })(); - delete window.parent; - delete window.top; - delete window.frameElement; - `; -} - -/** @type {Promise} */ -const workerReady = new Promise((resolve, reject) => { - if (!areServiceWorkersEnabled()) { - return reject(new Error('Service Workers are not enabled. Webviews will not work. Try disabling private/incognito mode.')); - } - - const swPath = `service-worker.js?v=${expectedWorkerVersion}&vscode-resource-base-authority=${searchParams.get('vscode-resource-base-authority')}&remoteAuthority=${searchParams.get('remoteAuthority') ?? ''}`; - navigator.serviceWorker.register(swPath) - .then(() => navigator.serviceWorker.ready) - .then(async registration => { - /** - * @param {MessageEvent} event - */ - const versionHandler = async (event) => { - if (event.data.channel !== 'version') { - return; - } - - navigator.serviceWorker.removeEventListener('message', versionHandler); - if (event.data.version === expectedWorkerVersion) { - return resolve(); - } else { - console.log(`Found unexpected service worker version. Found: ${event.data.version}. Expected: ${expectedWorkerVersion}`); - console.log(`Attempting to reload service worker`); - - // If we have the wrong version, try once (and only once) to unregister and re-register - // Note that `.update` doesn't seem to work desktop electron at the moment so we use - // `unregister` and `register` here. - return registration.unregister() - .then(() => navigator.serviceWorker.register(swPath)) - .then(() => navigator.serviceWorker.ready) - .finally(() => { resolve(); }); - } - }; - navigator.serviceWorker.addEventListener('message', versionHandler); - - const postVersionMessage = (/** @type {ServiceWorker} */ controller) => { - controller.postMessage({ channel: 'version' }); - }; - - // At this point, either the service worker is ready and - // became our controller, or we need to wait for it. - // Note that navigator.serviceWorker.controller could be a - // controller from a previously loaded service worker. - const currentController = navigator.serviceWorker.controller; - if (currentController?.scriptURL.endsWith(swPath)) { - // service worker already loaded & ready to receive messages - postVersionMessage(currentController); - } else { - // either there's no controlling service worker, or it's an old one: - // wait for it to change before posting the message - const onControllerChange = () => { - navigator.serviceWorker.removeEventListener('controllerchange', onControllerChange); - postVersionMessage(navigator.serviceWorker.controller); - }; - navigator.serviceWorker.addEventListener('controllerchange', onControllerChange); - } - }).catch(error => { - reject(new Error(`Could not register service workers: ${error}.`)); - }); -}); - -const hostMessaging = new class HostMessaging { - - constructor() { - this.channel = new MessageChannel(); - - /** @type {Map void>>} */ - this.handlers = new Map(); - - this.channel.port1.onmessage = (e) => { - const channel = e.data.channel; - const handlers = this.handlers.get(channel); - if (handlers) { - for (const handler of handlers) { - handler(e, e.data.args); - } - } else { - console.log('no handler for ', e); - } - }; - } - - /** - * @param {string} channel - * @param {any} data - * @param {any} [transfer] - */ - postMessage(channel, data, transfer) { - this.channel.port1.postMessage({ channel, data }, transfer); - } - - /** - * @param {string} channel - * @param {(event: MessageEvent, data: any) => void} handler - */ - onMessage(channel, handler) { - let handlers = this.handlers.get(channel); - if (!handlers) { - handlers = []; - this.handlers.set(channel, handlers); - } - handlers.push(handler); - } - - async signalReady() { - const start = (/** @type {string} */ parentOrigin) => { - window.parent.postMessage({ target: ID, channel: 'webview-ready', data: {} }, parentOrigin, [this.channel.port2]); - }; - - const parentOrigin = searchParams.get('parentOrigin'); - const id = searchParams.get('id'); - - const hostname = location.hostname; - - if (!crypto.subtle) { - // cannot validate, not running in a secure context - throw new Error(`Cannot validate in current context!`); - } - - // Here the `parentOriginHash()` function from `src/vs/workbench/common/webview.ts` is inlined - // compute a sha-256 composed of `parentOrigin` and `salt` converted to base 32 - let parentOriginHash; - try { - const strData = JSON.stringify({ parentOrigin, salt: id }); - const encoder = new TextEncoder(); - const arrData = encoder.encode(strData); - const hash = await crypto.subtle.digest('sha-256', arrData); - const hashArray = Array.from(new Uint8Array(hash)); - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - // sha256 has 256 bits, so we need at most ceil(lg(2^256-1)/lg(32)) = 52 chars to represent it in base 32 - parentOriginHash = BigInt(`0x${hashHex}`).toString(32).padStart(52, '0'); - } catch (err) { - throw err instanceof Error ? err : new Error(String(err)); - } - - if (hostname === parentOriginHash || hostname.startsWith(parentOriginHash + '.')) { - // validation succeeded! - return start(parentOrigin); - } - - throw new Error(`Expected '${parentOriginHash}' as hostname or subdomain!`); - } -}(); - -const unloadMonitor = new class { - - constructor() { - this.confirmBeforeClose = 'keyboardOnly'; - this.isModifierKeyDown = false; - - hostMessaging.onMessage('set-confirm-before-close', (_e, /** @type {string} */ data) => { - this.confirmBeforeClose = data; - }); - - hostMessaging.onMessage('content', (_e, /** @type {any} */ data) => { - this.confirmBeforeClose = data.confirmBeforeClose; - }); - - window.addEventListener('beforeunload', (event) => { - if (onElectron) { - return; - } - - switch (this.confirmBeforeClose) { - case 'always': { - event.preventDefault(); - event.returnValue = ''; - return ''; - } - case 'never': { - break; - } - case 'keyboardOnly': - default: { - if (this.isModifierKeyDown) { - event.preventDefault(); - event.returnValue = ''; - return ''; - } - break; - } - } - }); - } - - onIframeLoaded(/** @type {HTMLIFrameElement} */ frame) { - frame.contentWindow.addEventListener('keydown', e => { - this.isModifierKeyDown = e.metaKey || e.ctrlKey || e.altKey; - }); - - frame.contentWindow.addEventListener('keyup', () => { - this.isModifierKeyDown = false; - }); - } -}; - -// state -let firstLoad = true; -/** @type {any} */ -let loadTimeout; -let styleVersion = 0; - -/** @type {Array<{ readonly message: any, transfer?: ArrayBuffer[] }>} */ -let pendingMessages = []; - -const initData = { - /** @type {number | undefined} */ - initialScrollProgress: undefined, - - /** @type {{ [key: string]: string } | undefined} */ - styles: undefined, - - /** @type {string | undefined} */ - activeTheme: undefined, - - /** @type {string | undefined} */ - themeName: undefined, - - /** @type {boolean} */ - screenReader: false, - - /** @type {boolean} */ - reduceMotion: false, -}; - -hostMessaging.onMessage('did-load-resource', (_event, data) => { - navigator.serviceWorker.ready.then(registration => { - assertIsDefined(registration.active).postMessage({ channel: 'did-load-resource', data }, data.data?.buffer ? [data.data.buffer] : []); - }); -}); - -hostMessaging.onMessage('did-load-localhost', (_event, data) => { - navigator.serviceWorker.ready.then(registration => { - assertIsDefined(registration.active).postMessage({ channel: 'did-load-localhost', data }); - }); -}); - -navigator.serviceWorker.addEventListener('message', event => { - switch (event.data.channel) { - case 'load-resource': - case 'load-localhost': - hostMessaging.postMessage(event.data.channel, event.data); - return; - } -}); -/** - * @param {HTMLDocument?} document - * @param {HTMLElement?} body - */ -const applyStyles = (document, body) => { - if (!document) { - return; - } - - if (body) { - body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast', 'vscode-reduce-motion', 'vscode-using-screen-reader'); - if (initData.activeTheme) { - body.classList.add(initData.activeTheme); - } - - if (initData.reduceMotion) { - body.classList.add('vscode-reduce-motion'); - } - - if (initData.screenReader) { - body.classList.add('vscode-using-screen-reader'); - } - - body.dataset.vscodeThemeKind = initData.activeTheme; - body.dataset.vscodeThemeName = initData.themeName || ''; - } - - if (initData.styles) { - const documentStyle = document.documentElement.style; - - // Remove stale properties - for (let i = documentStyle.length - 1; i >= 0; i--) { - const property = documentStyle[i]; - - // Don't remove properties that the webview might have added separately - if (property && property.startsWith('--vscode-')) { - documentStyle.removeProperty(property); - } - } - - // Re-add new properties - for (const variable of Object.keys(initData.styles)) { - documentStyle.setProperty(`--${variable}`, initData.styles[variable]); - } - } -}; - -/** - * @param {MouseEvent} event - */ -const handleInnerClick = (event) => { - if (!event?.view?.document) { - return; - } - - const baseElement = event.view.document.querySelector('base'); - - for (const pathElement of event.composedPath()) { - /** @type {any} */ - const node = pathElement; - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - if (node.getAttribute('href') === '#') { - event.view.scrollTo(0, 0); - } else if (node.hash && (node.getAttribute('href') === node.hash || (baseElement && node.href === baseElement.href + node.hash))) { - const fragment = node.hash.slice(1); - const scrollTarget = event.view.document.getElementById(fragment) ?? event.view.document.getElementById(decodeURIComponent(fragment)); - scrollTarget?.scrollIntoView(); - } else { - hostMessaging.postMessage('did-click-link', node.href.baseVal || node.href); - } - event.preventDefault(); - return; - } - } -}; - -/** - * @param {MouseEvent} event - */ -const handleAuxClick = (event) => { - // Prevent middle clicks opening a broken link in the browser - if (!event?.view?.document) { - return; - } - - if (event.button === 1) { - for (const pathElement of event.composedPath()) { - /** @type {any} */ - const node = pathElement; - if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - event.preventDefault(); - return; - } - } - } -}; - -/** - * @param {KeyboardEvent} e - */ -const handleInnerKeydown = (e) => { - // If the keypress would trigger a browser event, such as copy or paste, - // make sure we block the browser from dispatching it. Instead VS Code - // handles these events and will dispatch a copy/paste back to the webview - // if needed - if (isUndoRedo(e) || isPrint(e) || isFindEvent(e)) { - e.preventDefault(); - } else if (isCopyPasteOrCut(e)) { - if (onElectron) { - e.preventDefault(); - } else { - return; // let the browser handle this - } - } - - hostMessaging.postMessage('did-keydown', { - key: e.key, - keyCode: e.keyCode, - code: e.code, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - repeat: e.repeat - }); -}; -/** - * @param {KeyboardEvent} e - */ -const handleInnerUp = (e) => { - hostMessaging.postMessage('did-keyup', { - key: e.key, - keyCode: e.keyCode, - code: e.code, - shiftKey: e.shiftKey, - altKey: e.altKey, - ctrlKey: e.ctrlKey, - metaKey: e.metaKey, - repeat: e.repeat - }); -}; - -/** - * @param {KeyboardEvent} e - * @return {boolean} - */ -function isCopyPasteOrCut(e) { - const hasMeta = e.ctrlKey || e.metaKey; - const shiftInsert = e.shiftKey && e.key.toLowerCase() === 'insert'; - return (hasMeta && ['c', 'v', 'x'].includes(e.key.toLowerCase())) || shiftInsert; -} - -/** - * @param {KeyboardEvent} e - * @return {boolean} - */ -function isUndoRedo(e) { - const hasMeta = e.ctrlKey || e.metaKey; - return hasMeta && ['z', 'y'].includes(e.key.toLowerCase()); -} - -/** - * @param {KeyboardEvent} e - * @return {boolean} - */ -function isPrint(e) { - const hasMeta = e.ctrlKey || e.metaKey; - return hasMeta && e.key.toLowerCase() === 'p'; -} - -/** - * @param {KeyboardEvent} e - * @return {boolean} - */ -function isFindEvent(e) { - const hasMeta = e.ctrlKey || e.metaKey; - return hasMeta && e.key.toLowerCase() === 'f'; -} - -let isHandlingScroll = false; - -/** - * @param {WheelEvent} event - */ -const handleWheel = (event) => { - if (isHandlingScroll) { - return; - } - - hostMessaging.postMessage('did-scroll-wheel', { - deltaMode: event.deltaMode, - deltaX: event.deltaX, - deltaY: event.deltaY, - deltaZ: event.deltaZ, - detail: event.detail, - type: event.type - }); -}; - -/** - * @param {Event} event - */ -const handleInnerScroll = (event) => { - if (isHandlingScroll) { - return; - } - - const target = /** @type {HTMLDocument | null} */ (event.target); - const currentTarget = /** @type {Window | null} */ (event.currentTarget); - if (!currentTarget || !target?.body) { - return; - } - - const progress = currentTarget.scrollY / target.body.clientHeight; - if (isNaN(progress)) { - return; - } - - isHandlingScroll = true; - window.requestAnimationFrame(() => { - try { - hostMessaging.postMessage('did-scroll', progress); - } catch (e) { - // noop - } - isHandlingScroll = false; - }); -}; - -function handleInnerDragStartEvent(/** @type {DragEvent} */ e) { - if (e.defaultPrevented) { - // Extension code has already handled this event - return; - } - - if (!e.dataTransfer || e.shiftKey) { - return; - } - - // Only handle drags from outside editor for now - if (e.dataTransfer.items.length && Array.prototype.every.call(e.dataTransfer.items, item => item.kind === 'file')) { - hostMessaging.postMessage('drag-start'); - } -} - -/** - * @param {() => void} callback - */ -function onDomReady(callback) { - if (document.readyState === 'interactive' || document.readyState === 'complete') { - callback(); - } else { - document.addEventListener('DOMContentLoaded', callback); - } -} - -function areServiceWorkersEnabled() { - try { - return !!navigator.serviceWorker; - } catch (e) { - return false; - } -} - -/** - * @typedef {{ - * contents: string; - * options: { - * readonly allowScripts: boolean; - * readonly allowForms: boolean; - * readonly allowMultipleAPIAcquire: boolean; - * } - * state: any; - * cspSource: string; - * }} ContentUpdateData - */ - -/** - * @param {ContentUpdateData} data - * @return {string} - */ -function toContentHtml(data) { - const options = data.options; - const text = data.contents; - const newDocument = new DOMParser().parseFromString(text, 'text/html'); - - newDocument.querySelectorAll('a').forEach(a => { - if (!a.title) { - const href = a.getAttribute('href'); - if (typeof href === 'string') { - a.title = href; - } - } - }); - - // Set default aria role - if (!newDocument.body.hasAttribute('role')) { - newDocument.body.setAttribute('role', 'document'); - } - - // Inject default script - if (options.allowScripts) { - const defaultScript = newDocument.createElement('script'); - defaultScript.id = '_vscodeApiScript'; - defaultScript.textContent = getVsCodeApiScript(options.allowMultipleAPIAcquire, data.state); - newDocument.head.prepend(defaultScript); - } - - // Inject default styles - newDocument.head.prepend(defaultStyles.cloneNode(true)); - - applyStyles(newDocument, newDocument.body); - - // Strip out unsupported http-equiv tags - for (const metaElement of Array.from(newDocument.querySelectorAll('meta'))) { - const httpEquiv = metaElement.getAttribute('http-equiv'); - if (httpEquiv && !/^(content-security-policy|default-style|content-type)$/i.test(httpEquiv)) { - console.warn(`Removing unsupported meta http-equiv: ${httpEquiv}`); - metaElement.remove(); - } - } - - // Check for CSP - const csp = newDocument.querySelector('meta[http-equiv="Content-Security-Policy"]'); - if (!csp) { - hostMessaging.postMessage('no-csp-found'); - } else { - try { - // Attempt to rewrite CSPs that hardcode old-style resource endpoint - const cspContent = csp.getAttribute('content'); - if (cspContent) { - const newCsp = cspContent.replace(/(vscode-webview-resource|vscode-resource):(?=(\s|;|$))/g, data.cspSource); - csp.setAttribute('content', newCsp); - } - } catch (e) { - console.error(`Could not rewrite csp: ${e}`); - } - } - - // set DOCTYPE for newDocument explicitly as DOMParser.parseFromString strips it off - // and DOCTYPE is needed in the iframe to ensure that the user agent stylesheet is correctly overridden - return '\n' + newDocument.documentElement.outerHTML; -} - -onDomReady(() => { - if (!document.body) { - return; - } - - hostMessaging.onMessage('styles', (_event, data) => { - ++styleVersion; - - initData.styles = data.styles; - initData.activeTheme = data.activeTheme; - initData.themeName = data.themeName; - initData.reduceMotion = data.reduceMotion; - initData.screenReader = data.screenReader; - - const target = getActiveFrame(); - if (!target) { - return; - } - - if (target.contentDocument) { - applyStyles(target.contentDocument, target.contentDocument.body); - } - }); - - // propagate focus - hostMessaging.onMessage('focus', () => { - const activeFrame = getActiveFrame(); - if (!activeFrame || !activeFrame.contentWindow) { - // Focus the top level webview instead - window.focus(); - return; - } - - if (document.activeElement === activeFrame) { - // We are already focused on the iframe (or one of its children) so no need - // to refocus. - return; - } - - activeFrame.contentWindow.focus(); - }); - - // update iframe-contents - let updateId = 0; - hostMessaging.onMessage('content', async (_event, /** @type {ContentUpdateData} */ data) => { - const currentUpdateId = ++updateId; - try { - await workerReady; - } catch (e) { - console.error(`Webview fatal error: ${e}`); - hostMessaging.postMessage('fatal-error', { message: e + '' }); - return; - } - - if (currentUpdateId !== updateId) { - return; - } - - const options = data.options; - const newDocument = toContentHtml(data); - - const initialStyleVersion = styleVersion; - - const frame = getActiveFrame(); - const wasFirstLoad = firstLoad; - // keep current scrollY around and use later - /** @type {(body: HTMLElement, window: Window) => void} */ - let setInitialScrollPosition; - if (firstLoad) { - firstLoad = false; - setInitialScrollPosition = (body, window) => { - if (typeof initData.initialScrollProgress === 'number' && !isNaN(initData.initialScrollProgress)) { - if (window.scrollY === 0) { - window.scroll(0, body.clientHeight * initData.initialScrollProgress); - } - } - }; - } else { - const scrollY = frame && frame.contentDocument && frame.contentDocument.body ? assertIsDefined(frame.contentWindow).scrollY : 0; - setInitialScrollPosition = (body, window) => { - if (window.scrollY === 0) { - window.scroll(0, scrollY); - } - }; - } - - // Clean up old pending frames and set current one as new one - const previousPendingFrame = getPendingFrame(); - if (previousPendingFrame) { - previousPendingFrame.setAttribute('id', ''); - document.body.removeChild(previousPendingFrame); - } - if (!wasFirstLoad) { - pendingMessages = []; - } - - const newFrame = document.createElement('iframe'); - newFrame.setAttribute('id', 'pending-frame'); - newFrame.setAttribute('frameborder', '0'); - - const sandboxRules = new Set(['allow-same-origin', 'allow-pointer-lock']); - if (options.allowScripts) { - sandboxRules.add('allow-scripts'); - sandboxRules.add('allow-downloads'); - } - if (options.allowForms) { - sandboxRules.add('allow-forms'); - } - newFrame.setAttribute('sandbox', Array.from(sandboxRules).join(' ')); - if (!isFirefox) { - newFrame.setAttribute('allow', options.allowScripts ? 'clipboard-read; clipboard-write;' : ''); - } - // We should just be able to use srcdoc, but I wasn't - // seeing the service worker applying properly. - // Fake load an empty on the correct origin and then write real html - // into it to get around this. - newFrame.src = `./fake.html?id=${ID}`; - - newFrame.style.cssText = 'display: block; margin: 0; overflow: hidden; position: absolute; width: 100%; height: 100%; visibility: hidden'; - document.body.appendChild(newFrame); - - /** - * @param {Document} contentDocument - */ - function onFrameLoaded(contentDocument) { - // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=978325 - setTimeout(() => { - contentDocument.open(); - contentDocument.write(newDocument); - contentDocument.close(); - hookupOnLoadHandlers(newFrame); - - if (initialStyleVersion !== styleVersion) { - applyStyles(contentDocument, contentDocument.body); - } - }, 0); - } - - if (!options.allowScripts && isSafari) { - // On Safari for iframes with scripts disabled, the `DOMContentLoaded` never seems to be fired: https://bugs.webkit.org/show_bug.cgi?id=33604 - // Use polling instead. - const interval = setInterval(() => { - // If the frame is no longer mounted, loading has stopped - if (!newFrame.parentElement) { - clearInterval(interval); - return; - } - - const contentDocument = assertIsDefined(newFrame.contentDocument); - if (contentDocument.location.pathname.endsWith('/fake.html') && contentDocument.readyState !== 'loading') { - clearInterval(interval); - onFrameLoaded(contentDocument); - } - }, 10); - } else { - assertIsDefined(newFrame.contentWindow).addEventListener('DOMContentLoaded', e => { - const contentDocument = e.target ? (/** @type {HTMLDocument} */ (e.target)) : undefined; - onFrameLoaded(assertIsDefined(contentDocument)); - }); - } - - /** - * @param {Document} contentDocument - * @param {Window} contentWindow - */ - const onLoad = (contentDocument, contentWindow) => { - if (contentDocument && contentDocument.body) { - // Workaround for https://github.com/microsoft/vscode/issues/12865 - // check new scrollY and reset if necessary - setInitialScrollPosition(contentDocument.body, contentWindow); - } - - const newFrame = getPendingFrame(); - if (newFrame && newFrame.contentDocument && newFrame.contentDocument === contentDocument) { - const wasFocused = document.hasFocus(); - const oldActiveFrame = getActiveFrame(); - if (oldActiveFrame) { - document.body.removeChild(oldActiveFrame); - } - // Styles may have changed since we created the element. Make sure we re-style - if (initialStyleVersion !== styleVersion) { - applyStyles(newFrame.contentDocument, newFrame.contentDocument.body); - } - newFrame.setAttribute('id', 'active-frame'); - newFrame.style.visibility = 'visible'; - - contentWindow.addEventListener('scroll', handleInnerScroll); - contentWindow.addEventListener('wheel', handleWheel); - - if (wasFocused) { - contentWindow.focus(); - } - - pendingMessages.forEach((message) => { - contentWindow.postMessage(message.message, window.origin, message.transfer); - }); - pendingMessages = []; - } - }; - - /** - * @param {HTMLIFrameElement} newFrame - */ - function hookupOnLoadHandlers(newFrame) { - clearTimeout(loadTimeout); - loadTimeout = undefined; - loadTimeout = setTimeout(() => { - clearTimeout(loadTimeout); - loadTimeout = undefined; - onLoad(assertIsDefined(newFrame.contentDocument), assertIsDefined(newFrame.contentWindow)); - }, 200); - - const contentWindow = assertIsDefined(newFrame.contentWindow); - - contentWindow.addEventListener('load', function (e) { - const contentDocument = /** @type {Document} */ (e.target); - - if (loadTimeout) { - clearTimeout(loadTimeout); - loadTimeout = undefined; - onLoad(contentDocument, this); - } - }); - - // Bubble out various events - contentWindow.addEventListener('click', handleInnerClick); - contentWindow.addEventListener('auxclick', handleAuxClick); - contentWindow.addEventListener('keydown', handleInnerKeydown); - contentWindow.addEventListener('keyup', handleInnerUp); - contentWindow.addEventListener('contextmenu', e => { - if (e.defaultPrevented) { - // Extension code has already handled this event - return; - } - - e.preventDefault(); - hostMessaging.postMessage('did-context-menu', { - clientX: e.clientX, - clientY: e.clientY, - }); - }); - - contentWindow.addEventListener('dragenter', handleInnerDragStartEvent); - contentWindow.addEventListener('dragover', handleInnerDragStartEvent); - - unloadMonitor.onIframeLoaded(newFrame); - } - }); - - // Forward message to the embedded iframe - hostMessaging.onMessage('message', (_event, /** @type {{message: any, transfer?: ArrayBuffer[] }} */ data) => { - const pending = getPendingFrame(); - if (!pending) { - const target = getActiveFrame(); - if (target) { - assertIsDefined(target.contentWindow).postMessage(data.message, window.origin, data.transfer); - return; - } - } - pendingMessages.push(data); - }); - - hostMessaging.onMessage('initial-scroll-position', (_event, progress) => { - initData.initialScrollProgress = progress; - }); - - hostMessaging.onMessage('execCommand', (_event, data) => { - const target = getActiveFrame(); - if (!target) { - return; - } - assertIsDefined(target.contentDocument).execCommand(data); - }); - - /** @type {string | undefined} */ - let lastFindValue = undefined; - - hostMessaging.onMessage('find', (_event, data) => { - const target = getActiveFrame(); - if (!target) { - return; - } - - if (!data.previous && lastFindValue !== data.value) { - // Reset selection so we start search at the head of the last search - const selection = target.contentWindow.getSelection(); - selection.collapse(selection.anchorNode); - } - lastFindValue = data.value; - - const didFind = (/** @type {any} */ (target.contentWindow)).find( - data.value, - /* caseSensitive*/ false, - /* backwards*/ data.previous, - /* wrapAround*/ true, - /* wholeWord */ false, - /* searchInFrames*/ false, - false); - hostMessaging.postMessage('did-find', didFind); - }); - - hostMessaging.onMessage('find-stop', (_event, data) => { - const target = getActiveFrame(); - if (!target) { - return; - } - - lastFindValue = undefined; - - if (!data.clearSelection) { - const selection = target.contentWindow.getSelection(); - for (let i = 0; i < selection.rangeCount; i++) { - selection.removeRange(selection.getRangeAt(i)); - } - } - }); - - trackFocus({ - onFocus: () => hostMessaging.postMessage('did-focus'), - onBlur: () => hostMessaging.postMessage('did-blur') - }); - - (/** @type {any} */ (window))[vscodePostMessageFuncName] = (/** @type {string} */ command, /** @type {any} */ data) => { - switch (command) { - case 'onmessage': - case 'do-update-state': - hostMessaging.postMessage(command, data); - break; - } - }; - - // Also forward events before the contents of the webview have loaded - window.addEventListener('keydown', handleInnerKeydown); - window.addEventListener('dragenter', handleInnerDragStartEvent); - window.addEventListener('dragover', handleInnerDragStartEvent); - - hostMessaging.signalReady(); -}); diff --git a/src/vs/workbench/contrib/webview/browser/resourceLoading.ts b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts index 9d717dd0c1c..cdff87cccd0 100644 --- a/src/vs/workbench/contrib/webview/browser/resourceLoading.ts +++ b/src/vs/workbench/contrib/webview/browser/resourceLoading.ts @@ -65,7 +65,7 @@ export async function loadLocalResource( const mime = getWebviewContentMimeType(requestUri); // Use the original path for the mime try { - const result = await fileService.readFileStream(resourceToLoad, { etag: options.ifNoneMatch }); + const result = await fileService.readFileStream(resourceToLoad, { etag: options.ifNoneMatch }, token); return new WebviewResourceResponse.StreamSuccess(result.value, result.etag, result.mtime, mime); } catch (err) { if (err instanceof FileOperationError) { diff --git a/src/vs/workbench/contrib/webview/browser/webview.ts b/src/vs/workbench/contrib/webview/browser/webview.ts index 3d50fee82a0..e9e90223d4e 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.ts @@ -151,8 +151,16 @@ export interface WebviewMessageReceivedEvent { export interface IWebview extends IDisposable { + /** + * External identifier of this webview. + */ readonly id: string; + /** + * The origin this webview itself is loaded from. May not be unique + */ + readonly origin: string; + html: string; contentOptions: WebviewContentOptions; localResourcesRoot: readonly URI[]; diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index fe286d9a832..77aaf6683f9 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -102,6 +102,7 @@ namespace WebviewState { export interface WebviewInitInfo { readonly id: string; + readonly origin?: string; readonly options: WebviewOptions; readonly contentOptions: WebviewContentOptions; @@ -118,7 +119,12 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD public readonly id: string; /** - * Unique identifier of this webview iframe element. + * The origin this webview itself is loaded from. May not be unique + */ + public readonly origin: string; + + /** + * Unique internal identifier of this webview's iframe element. */ private readonly iframeId: string; @@ -194,7 +200,9 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD this.id = initInfo.id; this.iframeId = generateUuid(); - this.encodedWebviewOriginPromise = parentOriginHash(window.origin, this.iframeId).then(id => this.encodedWebviewOrigin = id); + this.origin = initInfo.origin ?? this.iframeId; + + this.encodedWebviewOriginPromise = parentOriginHash(window.origin, this.origin).then(id => this.encodedWebviewOrigin = id); this.options = initInfo.options; this.extension = initInfo.extension; @@ -484,6 +492,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD // The extensionId and purpose in the URL are used for filtering in js-debug: const params: { [key: string]: string } = { id: this.iframeId, + origin: this.origin, swVersion: String(this._expectedServiceWorkerVersion), extensionId: extension?.id.value ?? '', platform: this.platform, diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts index abed8dcdb16..ec4f4c9ef91 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewEditorInputSerializer.ts @@ -21,6 +21,7 @@ interface SerializedIconPath { export interface SerializedWebview { readonly id: string; + readonly origin: string | undefined; readonly viewType: string; readonly title: string; readonly options: SerializedWebviewOptions; @@ -33,6 +34,7 @@ export interface SerializedWebview { export interface DeserializedWebview { readonly id: string; + readonly origin: string | undefined; readonly viewType: string; readonly title: string; readonly webviewOptions: WebviewOptions; @@ -76,6 +78,7 @@ export class WebviewEditorInputSerializer implements IEditorSerializer { return this._webviewWorkbenchService.reviveWebview({ webviewInitInfo: { id: data.id, + origin: data.origin, options: data.webviewOptions, contentOptions: data.contentOptions, extension: data.extension, @@ -102,6 +105,7 @@ export class WebviewEditorInputSerializer implements IEditorSerializer { protected toJson(input: WebviewInput): SerializedWebview { return { id: input.id, + origin: input.webview.origin, viewType: input.viewType, title: input.getName(), options: { ...input.webview.options, ...input.webview.contentOptions }, diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index b491ce7079e..b4c0e11cbeb 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -105,15 +105,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService // In areas where zoom is applied to the element or its ancestors, we need to adjust accordingly // e.g. The title bar has counter zoom behavior meaning it applies the inverse of zoom level. // Window Zoom Level: 1.5, Title Bar Zoom: 1/1.5, Coordinate Multiplier: 1.5 * 1.0 / 1.5 = 1.0 - let testElement: HTMLElement | null = anchor; - do { - const elementZoomLevel = (dom.getComputedStyle(testElement) as any).zoom; - if (elementZoomLevel !== null && elementZoomLevel !== undefined && elementZoomLevel !== '1') { - zoom *= elementZoomLevel; - } - - testElement = testElement.parentElement; - } while (testElement !== null && testElement !== document.documentElement); + zoom *= dom.getDomNodeZoomLevel(anchor); x = elementPosition.left; y = elementPosition.top + elementPosition.height; diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 85cda949313..ae666036856 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -180,7 +180,7 @@ export class BrowserWorkbenchEnvironmentService implements IBrowserWorkbenchEnvi const webviewExternalEndpointCommit = this.payload?.get('webviewExternalEndpointCommit'); return endpoint - .replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? '181b43c0e2949e36ecb623d8cc6de29d4fa2bae8') + .replace('{{commit}}', webviewExternalEndpointCommit ?? this.productService.commit ?? '3c8520fab514b9f56070214496b26ff68d1b1cb5') .replace('{{quality}}', (webviewExternalEndpointCommit ? 'insider' : this.productService.quality) ?? 'insider'); } diff --git a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts index aa1eb99bc0e..1124b843b01 100644 --- a/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/webExtensionsScannerService.ts @@ -21,7 +21,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls'; import { localize } from 'vs/nls'; import * as semver from 'vs/base/common/semver/semver'; -import { isBoolean, isString } from 'vs/base/common/types'; +import { isString } from 'vs/base/common/types'; import { getErrorMessage } from 'vs/base/common/errors'; import { ResourceMap } from 'vs/base/common/map'; import { IExtensionManifestPropertiesService } from 'vs/workbench/services/extensions/common/extensionManifestPropertiesService'; @@ -140,11 +140,11 @@ export class WebExtensionsScannerService extends Disposable implements IWebExten this.logService.info(`Checking additional builtin extensions: Ignoring '${extension.id}' because it is reported to be malicious.`); continue; } - const deprecated = extensionsControlManifest.deprecated ? extensionsControlManifest.deprecated[extension.id.toLowerCase()] : undefined; - if (deprecated && !isBoolean(deprecated)) { - const preReleaseExtensionId = deprecated.id; + const deprecationInfo = extensionsControlManifest.deprecated[extension.id.toLowerCase()]; + if (deprecationInfo?.extension?.autoMigrate) { + const preReleaseExtensionId = deprecationInfo.extension.id; this.logService.info(`Checking additional builtin extensions: '${extension.id}' is deprecated, instead using '${preReleaseExtensionId}'`); - result.push({ id: preReleaseExtensionId, preRelease: !!deprecated.preRelease }); + result.push({ id: preReleaseExtensionId, preRelease: !!extension.preRelease }); } else { result.push(extension); } diff --git a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts index cc9bcdd1045..46c26ac1795 100644 --- a/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts +++ b/src/vs/workbench/services/extensionManagement/common/extensionManagementService.ts @@ -394,7 +394,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench if (this.extensionManagementServerService.webExtensionManagementServer) { return this.extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getExtensionsControlManifest(); } - return Promise.resolve({ malicious: [] }); + return Promise.resolve({ malicious: [], deprecated: {} }); } private getServer(extension: ILocalExtension): IExtensionManagementServer | null { diff --git a/src/vs/workbench/services/hover/browser/hoverWidget.ts b/src/vs/workbench/services/hover/browser/hoverWidget.ts index 837a4bdfd52..24f4173e3f8 100644 --- a/src/vs/workbench/services/hover/browser/hoverWidget.ts +++ b/src/vs/workbench/services/hover/browser/hoverWidget.ts @@ -230,7 +230,19 @@ export class HoverWidget extends Widget { this._hover.containerDomNode.classList.remove('right-aligned'); this._hover.contentsDomNode.style.maxHeight = ''; - const targetBounds = this._target.targetElements.map(e => e.getBoundingClientRect()); + const getZoomAccountedBoundingClientRect = (e: HTMLElement) => { + const zoom = dom.getDomNodeZoomLevel(e); + + const boundingRect = e.getBoundingClientRect(); + return { + top: boundingRect.top * zoom, + bottom: boundingRect.bottom * zoom, + right: boundingRect.right * zoom, + left: boundingRect.left * zoom, + }; + }; + + const targetBounds = this._target.targetElements.map(e => getZoomAccountedBoundingClientRect(e)); const top = Math.min(...targetBounds.map(e => e.top)); const right = Math.max(...targetBounds.map(e => e.right)); const bottom = Math.max(...targetBounds.map(e => e.bottom)); diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 000a74b77ca..d93923deb81 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -707,17 +707,19 @@ export class DefaultSettings extends Disposable { }); } - const registeredConfigurationProp = prop as IRegisteredConfigurationPropertySchema; - let defaultValueSource: string | IExtensionInfo | undefined; - if (registeredConfigurationProp && registeredConfigurationProp.defaultValueSource) { - defaultValueSource = registeredConfigurationProp.defaultValueSource; - } - let isLanguageTagSetting = false; if (OVERRIDE_PROPERTY_REGEX.test(key)) { isLanguageTagSetting = true; } + let defaultValueSource: string | IExtensionInfo | undefined; + if (!isLanguageTagSetting) { + const registeredConfigurationProp = prop as IRegisteredConfigurationPropertySchema; + if (registeredConfigurationProp && registeredConfigurationProp.defaultValueSource) { + defaultValueSource = registeredConfigurationProp.defaultValueSource; + } + } + result.push({ key, value, diff --git a/test/integration/browser/src/index.ts b/test/integration/browser/src/index.ts index 18350685d5e..39a996b6ff4 100644 --- a/test/integration/browser/src/index.ts +++ b/test/integration/browser/src/index.ts @@ -65,7 +65,7 @@ async function runTestsInBrowser(browserType: BrowserType, endpoint: url.UrlWith const testExtensionUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionDevelopmentPath)).path, protocol, host, slashes: true }); const testFilesUri = url.format({ pathname: URI.file(path.resolve(optimist.argv.extensionTestsPath)).path, protocol, host, slashes: true }); - const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","181b43c0e2949e36ecb623d8cc6de29d4fa2bae8"],["skipWelcome","true"]]`; + const payloadParam = `[["extensionDevelopmentPath","${testExtensionUri}"],["extensionTestsPath","${testFilesUri}"],["enableProposedApi",""],["webviewExternalEndpointCommit","3c8520fab514b9f56070214496b26ff68d1b1cb5"],["skipWelcome","true"]]`; if (path.extname(testWorkspacePath) === '.code-workspace') { await page.goto(`${endpoint.href}&workspace=${testWorkspacePath}&payload=${payloadParam}`); diff --git a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts index a5f5842b4c6..b94b40045f7 100644 --- a/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts +++ b/test/smoke/src/areas/terminal/terminal-shellIntegration.test.ts @@ -26,8 +26,7 @@ export function setup() { describe('Shell integration', function () { // TODO: Fix on Linux, some distros use sh as the default shell in which case shell integration will fail - // TODO: Fix on macOS, not sure reason for failing https://github.com/microsoft/vscode/issues/149757 - describe.skip('Decorations', function () { + (process.platform === 'win32' || process.platform === 'linux' ? describe.skip : describe)('Decorations', function () { describe('Should show default icons', function () { it('Placeholder', async () => { await terminal.createTerminal(); diff --git a/yarn.lock b/yarn.lock index 21813ce9737..70623ba39e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2563,16 +2563,7 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bl@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" - integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -bl@^4.0.3: +bl@^4.0.2, bl@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -3957,9 +3948,9 @@ decompress@^4.2.1: strip-dirs "^2.0.0" deemon@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/deemon/-/deemon-1.4.0.tgz#01c09cc23eec41e5d7ddac082eb52c3611d38dff" - integrity sha512-S0zK5tNTdVFsJZVUeKi/CYJn4zzhW0Y55lwXzv2hVxb7ajzAHf91BhE5y2xvx1X7czIZ6PHLPDj00TVAmylVXw== + version "1.7.1" + resolved "https://registry.yarnpkg.com/deemon/-/deemon-1.7.1.tgz#46cf8313fd320fac6ee944bbb1bea3e8c2a978ee" + integrity sha512-UcBiu6+4sZgkDrs7GT78FDkqmt9ZVN412XXT4Bm9sn5Hcrwv/M9HmCay3108Yvxl4Ti5n1UjkkuSVA1y3+FIvg== dependencies: bl "^4.0.2" tree-kill "^1.2.2" @@ -11394,10 +11385,10 @@ typescript@^2.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" integrity sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q= -typescript@^4.8.0-dev.20220511: - version "4.8.0-dev.20220511" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220511.tgz#42dd5ad99dcd345277c784949cc36a7b5d43fa18" - integrity sha512-MDo0tI/TRHCJ1sxochoU4LhT41C6jhEIkWneehqsxNYV84+d+C4HvD7Nq4DQrI03cIGvPJbdW2s3ZladROXE+A== +typescript@^4.8.0-dev.20220518: + version "4.8.0-dev.20220518" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220518.tgz#3082c89c764daece904480552b9f2c3f5ce79c56" + integrity sha512-yczRLiowXD4THxpe2DrClYXsmIRt9VPDft1dat4Le50mQwuUcmvdqD43o4hmlbNP7HpyeEYX51KXozGq1s7zlw== typical@^4.0.0: version "4.0.0" @@ -12214,15 +12205,15 @@ xterm-addon-webgl@0.12.0-beta.36: resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.36.tgz#460f80829a78c979a448d5b764699af3f0366ff1" integrity sha512-sgX7OHSGZQZE5b4xtPqd/5NEcll0Z+00tnTVxKZlXf5XEENcG0tnBF4I4f+k9K3cmjE1UIUVG2yYPrqWlYCdpA== -xterm-headless@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.47.tgz#9d5af8145c42b9a6241a040bb244b877c149761b" - integrity sha512-chVTURPMNDEerQIsN4lIRotpXCfJsTHioZGQxDBNdktZO1gMXGvNxDjzsbsJg5ikLN5llz/HLDfXZrjCeb3elg== +xterm-headless@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.49.tgz#8e4178620be9a9dee25a586ef992a6fc621a829a" + integrity sha512-uRnHpR2pv1MJPbE3sa7XbP6x0uHubAWVMbiD26e3a5ICtWJQVVpLojkA0t4qU7CoMX85pRL1rIclg9CKY0B0xA== -xterm@4.19.0-beta.47: - version "4.19.0-beta.47" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.47.tgz#f5931201346a485e3bcb29d24e83583727e55707" - integrity sha512-c8EcCSWCHnFYNdRMTnJIpYTV9J2Ze7kdB1GhTcPO2ipMEsqaP/u6Ca8rgBWwV9HeEVfe4rGRqWW2qcSy4U9hMQ== +xterm@4.19.0-beta.49: + version "4.19.0-beta.49" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.49.tgz#96d0d748c97a2f0cfa4c6112bc4de8b0d5d559fe" + integrity sha512-qPhOeGtnB357mZOvfDckVlPDR7EDkSASQrB3aujhjgJlOiBBqcNRJcuSvF7v1DOho7xdEI9UQxiSiT5diHhMlg== y18n@^3.2.1: version "3.2.2"