diff --git a/.eslintrc.json b/.eslintrc.json index 1085686e464..e21cd93df02 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -985,7 +985,8 @@ "CustomEditorProvider", "CustomReadonlyEditorProvider", "TerminalLinkProvider", - "AuthenticationProvider" + "AuthenticationProvider", + "NotebookContentProvider" ] } ], diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000000..a050f362c15 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index 50294f89cac..3b553d5997b 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -77,9 +77,9 @@ jsonc-parser@^2.3.0: integrity sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg== vscode-emmet-helper@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.4.0.tgz#12f82fd05fb1df11ef7e78f85df63724de54a05b" - integrity sha512-aMbUL3oHBWM/Ux/C035a6KKLgIV8+GNGuu1b7ztuHXM6VmrdFFLNI5+tfFjfYQ3GBpD+Q7ZQU7CX/JaGlBbBiA== + version "2.4.1" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-2.4.1.tgz#7e020f66cbd72cf8e107b0ac7235cb806a728126" + integrity sha512-Sz3QbEgD1h05+sK3ltVLOUl2KKqwFVGUFSLS7ZlVqZoKPCWiFnd0dgf3miyipt6lGOadrsqvn7mCRbiSXJAMqA== dependencies: emmet "^2.3.0" jsonc-parser "^2.3.0" diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts index 9f75f92f43f..06458587116 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.test.ts @@ -1240,6 +1240,12 @@ suite('Notebook API tests', function () { assert.strictEqual(document.cells[0].metadata.executionOrder, executionOrder); assert.strictEqual(document.cells[0].metadata.runState, vscode.NotebookCellRunState.Success); }); + test('Opening a notebook should fire activeNotebook event changed only once', async function () { + const openedEditor = asPromise(vscode.window.onDidChangeActiveNotebookEditor); + const resource = await createRandomFile('', undefined, '.vsctestnb'); + await vscode.notebook.openNotebookDocument(resource); + assert.ok(await openedEditor); + }); // }); diff --git a/extensions/yaml/package.json b/extensions/yaml/package.json index 0673aeda746..fc7503a482b 100644 --- a/extensions/yaml/package.json +++ b/extensions/yaml/package.json @@ -13,6 +13,22 @@ }, "contributes": { "languages": [ + { + "id": "dockercompose", + "aliases": [ + "Compose", + "compose" + ], + "filenamePatterns": [ + "compose.yml", + "compose.yaml", + "compose.*.yml", + "compose.*.yaml", + "*docker*compose*.yml", + "*docker*compose*.yaml" + ], + "configuration": "./language-configuration.json" + }, { "id": "yaml", "aliases": [ @@ -30,6 +46,11 @@ } ], "grammars": [ + { + "language": "dockercompose", + "scopeName": "source.yaml", + "path": "./syntaxes/yaml.tmLanguage.json" + }, { "language": "yaml", "scopeName": "source.yaml", diff --git a/package.json b/package.json index 663f0e02a2a..2356dea1bec 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.55.0", - "distro": "17365b1560dc5051788f2ec8b388b44b67ec1365", + "distro": "0a442d49008152919d1ca15e5d21c03a1536c80d", "author": { "name": "Microsoft Corporation" }, diff --git a/resources/linux/snap/snapcraft.yaml b/resources/linux/snap/snapcraft.yaml index c24d0af3ea7..71eff238e12 100644 --- a/resources/linux/snap/snapcraft.yaml +++ b/resources/linux/snap/snapcraft.yaml @@ -9,6 +9,7 @@ description: | architectures: - build-on: amd64 run-on: @@ARCHITECTURE@@ +compression: lzo grade: stable confinement: classic diff --git a/src/vs/base/browser/ui/splitview/paneview.css b/src/vs/base/browser/ui/splitview/paneview.css index a7d76d81055..a2ab94660b4 100644 --- a/src/vs/base/browser/ui/splitview/paneview.css +++ b/src/vs/base/browser/ui/splitview/paneview.css @@ -73,6 +73,7 @@ align-items: center; justify-content: center; color: inherit; + outline-offset: -2px; } .monaco-pane-view .pane > .pane-header .monaco-action-bar .action-item.select-container { diff --git a/src/vs/base/node/shell.ts b/src/vs/base/node/shell.ts index cacc2da4db2..98861c0dd07 100644 --- a/src/vs/base/node/shell.ts +++ b/src/vs/base/node/shell.ts @@ -13,7 +13,7 @@ import * as processes from 'vs/base/node/processes'; * shell that the terminal uses by default. * @param p The platform to detect the shell of. */ -export async function getSystemShell(p: platform.Platform, env = process.env as platform.IProcessEnvironment): Promise { +export async function getSystemShell(p: platform.Platform, env: platform.IProcessEnvironment): Promise { if (p === platform.Platform.Windows) { if (platform.isWindows) { return getSystemShellWindows(); @@ -25,7 +25,7 @@ export async function getSystemShell(p: platform.Platform, env = process.env as return getSystemShellUnixLike(p, env); } -export function getSystemShellSync(p: platform.Platform, env = process.env as platform.IProcessEnvironment): string { +export function getSystemShellSync(p: platform.Platform, env: platform.IProcessEnvironment): string { if (p === platform.Platform.Windows) { if (platform.isWindows) { return getSystemShellWindowsSync(env); diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 75b1c60e454..28de366c453 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -53,6 +53,7 @@ export interface IssueReporterConfiguration extends IWindowConfiguration { commit: string | undefined; date: string | undefined; reportIssueUrl: string | undefined; + reportMarketplaceIssueUrl: string | undefined; } } @@ -83,6 +84,7 @@ export class IssueReporter extends Disposable { super(); this.initServices(configuration); + console.log(configuration); const targetExtension = configuration.data.extensionId ? configuration.data.enabledExtensions.find(extension => extension.id === configuration.data.extensionId) : undefined; this.issueReporterModel = new IssueReporterModel({ @@ -353,7 +355,8 @@ export class IssueReporter extends Disposable { this.addEventListener('issue-title', 'input', (e: Event) => { const title = (e.target).value; const lengthValidationMessage = this.getElementById('issue-title-length-validation-error'); - if (title && this.getIssueUrlWithTitle(title).length > MAX_URL_LENGTH) { + const issueUrl = this.getIssueUrl(); + if (title && this.getIssueUrlWithTitle(title, issueUrl).length > MAX_URL_LENGTH) { show(lengthValidationMessage); } else { hide(lengthValidationMessage); @@ -468,6 +471,10 @@ export class IssueReporter extends Disposable { return true; } + if (issueType === IssueType.Marketplace) { + return true; + } + return false; } @@ -633,11 +640,18 @@ export class IssueReporter extends Disposable { const typeSelect = this.getElementById('issue-type')! as HTMLSelectElement; const { issueType } = this.issueReporterModel.getData(); - reset(typeSelect, - makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), - makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), - makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")) - ); + this.configuration.product.reportMarketplaceIssueUrl + ? reset(typeSelect, + makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), + makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), + makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")), + makeOption(IssueType.Marketplace, localize('marketplaceIssue', "Extensions Marketplace Issue")) + ) + : reset(typeSelect, + makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")), + makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")), + makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")), + ); typeSelect.value = issueType.toString(); @@ -751,6 +765,9 @@ export class IssueReporter extends Disposable { if (fileOnExtension) { show(extensionSelector); } + } else if (issueType === IssueType.Marketplace) { + reset(descriptionTitle, localize('description', "Description"), $('span.required-input', undefined, '*')); + reset(descriptionSubtitle, localize('marketplaceDescription', "Please describe the feature you would like added to the marketplace, or steps to reliably reproduce the problem if you are reporting a bug. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub.")); } } @@ -770,10 +787,14 @@ export class IssueReporter extends Disposable { private validateInputs(): boolean { let isValid = true; - ['issue-title', 'description', 'issue-source'].forEach(elementId => { + ['issue-title', 'description'].forEach(elementId => { isValid = this.validateInput(elementId) && isValid; }); + if (this.issueReporterModel.getData().issueType !== IssueType.Marketplace) { + isValid = this.validateInput('issue-source') && isValid; + } + if (this.issueReporterModel.fileOnExtension()) { isValid = this.validateInput('extension-selector') && isValid; } @@ -845,13 +866,13 @@ export class IssueReporter extends Disposable { const issueTitle = (this.getElementById('issue-title')).value; const issueBody = this.issueReporterModel.serialize(); - const issueUrl = this.issueReporterModel.fileOnExtension() ? this.getExtensionGitHubUrl() : this.configuration.product.reportIssueUrl!; + const issueUrl = this.getIssueUrl(); const gitHubDetails = this.parseGitHubUrl(issueUrl); if (this.configuration.data.githubAccessToken && gitHubDetails) { return this.submitToGitHub(issueTitle, issueBody, gitHubDetails); } - const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value); + const baseUrl = this.getIssueUrlWithTitle((this.getElementById('issue-title')).value, issueUrl); let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`; if (url.length > MAX_URL_LENGTH) { @@ -881,6 +902,14 @@ export class IssueReporter extends Disposable { }); } + private getIssueUrl(): string { + return this.issueReporterModel.fileOnExtension() + ? this.getExtensionGitHubUrl() + : this.issueReporterModel.getData().issueType === IssueType.Marketplace + ? this.configuration.product.reportMarketplaceIssueUrl! + : this.configuration.product.reportIssueUrl!; + } + private parseGitHubUrl(url: string): undefined | { repositoryName: string, owner: string } { // Assumes a GitHub url to a particular repo, https://github.com/repositoryName/owner. // Repository name and owner cannot contain '/' @@ -909,16 +938,12 @@ export class IssueReporter extends Disposable { return repositoryUrl; } - private getIssueUrlWithTitle(issueTitle: string): string { - let repositoryUrl = this.configuration.product.reportIssueUrl; + private getIssueUrlWithTitle(issueTitle: string, repositoryUrl: string): string { if (this.issueReporterModel.fileOnExtension()) { - const extensionGitHubUrl = this.getExtensionGitHubUrl(); - if (extensionGitHubUrl) { - repositoryUrl = extensionGitHubUrl + '/issues/new'; - } + repositoryUrl = repositoryUrl + '/issues/new'; } - const queryStringPrefix = this.configuration.product.reportIssueUrl && this.configuration.product.reportIssueUrl.indexOf('?') === -1 ? '?' : '&'; + const queryStringPrefix = repositoryUrl.indexOf('?') === -1 ? '?' : '&'; return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`; } @@ -1156,7 +1181,7 @@ export class IssueReporter extends Disposable { ), ...extensions.map(extension => $('tr', undefined, $('td', undefined, extension.name), - $('td', undefined, extension.publisher.substr(0, 3)), + $('td', undefined, extension.publisher?.substr(0, 3) ?? 'N/A'), $('td', undefined, extension.version), )) ); diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index f850e4d8093..82b7dd39bb5 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -238,7 +238,7 @@ ${this._data.experimentInfo} const tableHeader = `Extension|Author (truncated)|Version ---|---|---`; const table = this._data.enabledNonThemeExtesions.map(e => { - return `${e.name}|${e.publisher.substr(0, 3)}|${e.version}`; + return `${e.name}|${e.publisher?.substr(0, 3) ?? 'N/A'}|${e.version}`; }).join('\n'); return `
Extensions (${this._data.enabledNonThemeExtesions.length}) diff --git a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts index 7718ae4003a..56875381773 100644 --- a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts +++ b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts @@ -121,9 +121,9 @@ export class RangeUtil { startChildIndex = Math.min(max, Math.max(min, startChildIndex)); endChildIndex = Math.min(max, Math.max(min, endChildIndex)); - if (startChildIndex === endChildIndex && startOffset === endOffset && startOffset === 0) { + if (startChildIndex === endChildIndex && startOffset === endOffset && startOffset === 0 && !domNode.children[startChildIndex].firstChild) { // We must find the position at the beginning of a - // To cover cases of empty s, aboid using a range and use the 's bounding box + // To cover cases of empty s, avoid using a range and use the 's bounding box const clientRects = domNode.children[startChildIndex].getClientRects(); return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft); } diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 9014c904a2a..918d6b64632 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -90,10 +90,10 @@ class CodeLensContentWidget implements IContentWidget { if (lens.command) { const title = renderLabelWithIcons(lens.command.title.trim()); if (lens.command.id) { - children.push(dom.$('a', { id: String(i) }, ...title)); + children.push(dom.$('a', { id: String(i), title: lens.command.tooltip }, ...title)); this._commands.set(String(i), lens.command); } else { - children.push(dom.$('span', undefined, ...title)); + children.push(dom.$('span', { title: lens.command.tooltip }, ...title)); } if (i + 1 < lenses.length) { children.push(dom.$('span', undefined, '\u00a0|\u00a0')); diff --git a/src/vs/platform/environment/node/shellEnv.ts b/src/vs/platform/environment/node/shellEnv.ts index 9454f1d4730..c2d7594b124 100644 --- a/src/vs/platform/environment/node/shellEnv.ts +++ b/src/vs/platform/environment/node/shellEnv.ts @@ -79,7 +79,7 @@ async function doResolveUnixShellEnv(logService: ILogService): Promise; + + // todo@API remove! against separation of data provider and renderer + // eslint-disable-next-line vscode-dts-cancellation + resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Thenable; + /** * Content providers should always use [file system providers](#FileSystemProvider) to * resolve the raw content for `uri` as the resouce is not necessarily a file on disk. */ - // eslint-disable-next-line vscode-dts-provider-naming - openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Thenable; - // eslint-disable-next-line vscode-dts-provider-naming - // eslint-disable-next-line vscode-dts-cancellation - resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Thenable; - // eslint-disable-next-line vscode-dts-provider-naming - saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Thenable; - // eslint-disable-next-line vscode-dts-provider-naming - saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Thenable; - // eslint-disable-next-line vscode-dts-provider-naming - backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Thenable; + openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext, token: CancellationToken): NotebookData | Thenable; - // ??? - // provideKernels(document: NotebookDocument, token: CancellationToken): ProviderResult; + saveNotebook(document: NotebookDocument, token: CancellationToken): Thenable; + + saveNotebookAs(targetResource: Uri, document: NotebookDocument, token: CancellationToken): Thenable; + + backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, token: CancellationToken): Thenable; } export namespace notebook { diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 0788ef388a5..02742173831 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -403,8 +403,8 @@ export class MainThreadNotebooks implements MainThreadNotebookShape { contentOptions.transientOutputs = newOptions.transientOutputs; }, viewOptions: options.viewOptions, - openNotebook: async (viewType: string, uri: URI, backupId?: string) => { - const data = await this._proxy.$openNotebook(viewType, uri, backupId); + openNotebook: async (viewType: string, uri: URI, backupId: string | undefined, token: CancellationToken) => { + const data = await this._proxy.$openNotebook(viewType, uri, backupId, token); return { data, transientOptions: contentOptions diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 297d95919fb..7269a63dcfd 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1863,7 +1863,7 @@ export interface ExtHostNotebookShape { $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise; $cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise; $onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void; - $openNotebook(viewType: string, uri: UriComponents, backupId?: string): Promise; + $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, token: CancellationToken): Promise; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise; $saveNotebookAs(viewType: string, uri: UriComponents, target: UriComponents, token: CancellationToken): Promise; $backupNotebook(viewType: string, uri: UriComponents, cancellation: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostAuthentication.ts b/src/vs/workbench/api/common/extHostAuthentication.ts index d32ec2ad1dc..be7640ce1d9 100644 --- a/src/vs/workbench/api/common/extHostAuthentication.ts +++ b/src/vs/workbench/api/common/extHostAuthentication.ts @@ -114,7 +114,7 @@ export class ExtHostAuthentication implements ExtHostAuthenticationShape { this._proxy.$sendDidChangeSessions(id, { added: e.added ?? [], changed: e.changed ?? [], - removed: e.changed ?? [] + removed: e.removed ?? [] }); }); diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index c027bc91d59..144390d8790 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -497,9 +497,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { // --- open, save, saveAs, backup - async $openNotebook(viewType: string, uri: UriComponents, backupId?: string): Promise { + async $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, token: CancellationToken): Promise { const { provider } = this._getProviderData(viewType); - const data = await provider.openNotebook(URI.revive(uri), { backupId }); + const data = await provider.openNotebook(URI.revive(uri), { backupId }, token); return { metadata: { ...notebookDocumentMetadataDefaults, @@ -776,7 +776,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape { } else if (delta.newActiveEditor) { this._activeNotebookEditor = this._editors.get(delta.newActiveEditor)?.editor; } - this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor?.editor); + if (delta.newActiveEditor !== undefined) { + this._onDidChangeActiveNotebookEditor.fire(this._activeNotebookEditor?.editor); + } } createNotebookCellStatusBarItemInternal(cell: vscode.NotebookCell, alignment: extHostTypes.NotebookCellStatusBarAlignment | undefined, priority: number | undefined) { diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index d2b9958bfbd..a799d988111 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -41,7 +41,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { // Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous // and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are // starting up but if not, we run getSystemShellSync below which gets a sane default. - getSystemShell(platform.platform).then(s => this._defaultShell = s); + getSystemShell(platform.platform, process.env as platform.IProcessEnvironment).then(s => this._defaultShell = s); this._updateLastActiveWorkspace(); this._updateVariableResolver(); @@ -83,7 +83,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService { return terminalEnvironment.getDefaultShell( fetchSetting, this._isWorkspaceShellAllowed, - this._defaultShell ?? getSystemShellSync(platform.platform), + this._defaultShell ?? getSystemShellSync(platform.platform, process.env as platform.IProcessEnvironment), process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), process.env.windir, terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver), diff --git a/src/vs/workbench/contrib/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts index 8aef4812c8c..e86421e0896 100644 --- a/src/vs/workbench/contrib/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -384,12 +384,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, primary: KeyCode.F5, when: ContextKeyExpr.and(CONTEXT_DEBUGGERS_AVAILABLE, CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing))), - handler: async (accessor: ServicesAccessor, debugStartOptions?: { noDebug: boolean }) => { + handler: async (accessor: ServicesAccessor, debugStartOptions: { config?: Partial; noDebug?: boolean } = {}) => { const debugService = accessor.get(IDebugService); let { launch, name, getConfig } = debugService.getConfigurationManager().selectedConfiguration; const config = await getConfig(); - const clonedConfig = deepClone(config); - await debugService.startDebugging(launch, clonedConfig || name, { noDebug: debugStartOptions && debugStartOptions.noDebug }); + const configOrName = config ? Object.assign(deepClone(config), debugStartOptions.config) : name; + await debugService.startDebugging(launch, configOrName, { noDebug: debugStartOptions.noDebug }); } }); diff --git a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts index d26321962a5..380bda0cdb3 100644 --- a/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugConfigurationManager.ts @@ -374,6 +374,10 @@ export class ConfigurationManager implements IConfigurationManager { let type = config?.type; if (name && names.indexOf(name) >= 0) { this.setSelectedLaunchName(name); + if (!config && name && launch) { + config = launch.getConfiguration(name); + type = config?.type; + } } else if (dynamicConfig && dynamicConfig.type) { // We could not find the previously used name and config is not passed. We should get all dynamic configurations from providers // And potentially auto select the previously used dynamic configuration #96293 diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index 8ae3c2eb16e..0bc759b8caf 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -852,14 +852,12 @@ registerAction2(class extends ViewAction { const stopppedChildSession = debugService.getModel().getSessions().find(s => s.parentSession === session && s.state === State.Stopped); if (stopppedChildSession) { session = stopppedChildSession; - } else { - await view.selectSession(session); } } await debugService.focusStackFrame(undefined, undefined, session, true); - } else { - await view.selectSession(session); } + // Need to select the session in the view since the focussed session might not have changed + await view.selectSession(session); } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index 94fdc443a78..39750747195 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -272,7 +272,7 @@ export class ExtensionsTree extends WorkbenchAsyncDataTree>{ getAriaLabel(extensionData: IExtensionData): string { const extension = extensionData.extension; - return localize('extension-arialabel', "{0}, {1}, {2}, {3}", extension.displayName, extension.version, extension.publisherDisplayName, extension.description); + return localize('extension.arialabel', "{0}, {1}, {2}, {3}", extension.displayName, extension.version, extension.publisherDisplayName, extension.description); }, getWidgetAriaLabel(): string { return localize('extensions', "Extensions"); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index dc2515142c5..f9a525c8466 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -165,7 +165,7 @@ export class ExtensionsListView extends ViewPane { horizontalScrolling: false, accessibilityProvider: >{ getAriaLabel(extension: IExtension | null): string { - return extension ? localize('extension-arialabel', "{0}, {1}, {2}, {3}", extension.displayName, extension.version, extension.publisherDisplayName, extension.description) : ''; + return extension ? localize('extension.arialabel', "{0}, {1}, {2}, {3}", extension.displayName, extension.version, extension.publisherDisplayName, extension.description) : ''; }, getWidgetAriaLabel(): string { return localize('extensions', "Extensions"); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts index f3561117874..1fb836f9fea 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts @@ -109,7 +109,7 @@ export class OutputElement extends Disposable { if (result.type !== RenderOutputType.Mainframe) { // this.viewCell.selfSizeMonitoring = true; - this._notebookEditor.createInset( + this._notebookEditor.createOutput( this._diffElementViewModel, this._nestedCell, result, diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index dd0920e45d3..0ca043aacff 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -33,7 +33,7 @@ export interface INotebookTextDiffEditor extends ICommonNotebookEditor { getLayoutInfo(): NotebookLayoutInfo; layoutNotebookCell(cell: DiffElementViewModelBase, height: number): void; getOutputRenderer(): OutputRenderer; - createInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void; + createOutput(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void; showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide): void; removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel, diffSide: DiffSide): void; hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel): void; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 3448adfd9c7..8fbe0f1dc72 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -590,7 +590,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD this._list.triggerScrollFromMouseWheelEvent(event); } - createInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void { + createOutput(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void { this._insetModifyQueueByOutputId.queue(output.source.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; if (!activeWebview) { @@ -599,7 +599,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD if (!activeWebview.insetMapping.has(output.source)) { const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); - await activeWebview.createInset({ diffElement: cellDiffViewModel, cellHandle: cellViewModel.handle, cellId: cellViewModel.id, cellUri: cellViewModel.uri }, output, cellTop, getOffset()); + await activeWebview.createOutput({ diffElement: cellDiffViewModel, cellHandle: cellViewModel.handle, cellId: cellViewModel.id, cellUri: cellViewModel.uri }, output, cellTop, getOffset()); } else { const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); const scrollTop = this._list.scrollTop; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index de4f972fb02..27010d49513 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -479,7 +479,7 @@ export interface INotebookEditor extends ICommonNotebookEditor { /** * Render the output in webview layer */ - createInset(cell: ICellViewModel, output: IInsetRenderOutput, offset: number): Promise; + createOutput(cell: ICellViewModel, output: IInsetRenderOutput, offset: number): Promise; /** * Remove the output from the webview layer @@ -748,7 +748,8 @@ export enum CellRevealType { export enum CellRevealPosition { Top, - Center + Center, + Bottom } export enum CellEditState { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 270abab7746..4df6ab24776 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -1693,7 +1693,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor await this._webview?.updateMarkdownPreviewSelectionState(cell.id, isSelected); } - async createInset(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise { + async createOutput(cell: CodeCellViewModel, output: IInsetRenderOutput, offset: number): Promise { this._insetModifyQueueByOutputId.queue(output.source.model.outputId, async () => { if (!this._webview) { return; @@ -1703,7 +1703,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (!this._webview!.insetMapping.has(output.source)) { const cellTop = this._list.getAbsoluteTopOfElement(cell); - await this._webview!.createInset({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset); + await this._webview!.createOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset); } else { const cellTop = this._list.getAbsoluteTopOfElement(cell); const scrollTop = this._list.scrollTop; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 619c18136d8..60c702c929f 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -512,12 +512,12 @@ export class NotebookService extends Disposable implements INotebookService, IEd return result; } - async fetchNotebookRawData(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions }> { + async fetchNotebookRawData(viewType: string, uri: URI, backupId: string | undefined, token: CancellationToken): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions }> { if (!await this.canResolve(viewType)) { throw new Error(`CANNOT fetch notebook data, there is NO provider for '${viewType}'`); } const provider = this._withProvider(viewType)!; - return await provider.controller.openNotebook(viewType, uri, backupId); + return await provider.controller.openNotebook(viewType, uri, backupId, token); } async save(viewType: string, resource: URI, token: CancellationToken): Promise { diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 67381a6fdba..093773bfa00 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -24,6 +24,7 @@ import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/ import { diff, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, CellKind, ICellRange, NOTEBOOK_EDITOR_CURSOR_BEGIN_END, cellRangesToIndexes, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { clamp } from 'vs/base/common/numbers'; import { SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; +import { ISplice } from 'vs/base/common/sequence'; export interface IFocusNextPreviousDelegate { onFocusNext(applyFocusNext: () => void): void; @@ -313,53 +314,14 @@ export class NotebookCellList extends WorkbenchList implements ID }); if (e.synchronous) { - viewDiffs.reverse().forEach((diff) => { - // remove output in the webview - const hideOutputs: ICellOutputViewModel[] = []; - const deletedOutputs: ICellOutputViewModel[] = []; - - for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { - const cell = this.element(i); - if (cell.cellKind === CellKind.Code) { - if (this._viewModel!.hasCell(cell.handle)) { - hideOutputs.push(...cell?.outputsViewModels); - } else { - deletedOutputs.push(...cell?.outputsViewModels); - } - } - } - - this.splice2(diff.start, diff.deleteCount, diff.toInsert); - - hideOutputs.forEach(output => this._onDidHideOutput.fire(output)); - deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output)); - }); + this._updateElementsInWebview(viewDiffs); } else { this._viewModelStore.add(DOM.scheduleAtNextAnimationFrame(() => { if (this._isDisposed) { return; } - viewDiffs.reverse().forEach((diff) => { - const hideOutputs: ICellOutputViewModel[] = []; - const deletedOutputs: ICellOutputViewModel[] = []; - - for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { - const cell = this.element(i); - if (cell.cellKind === CellKind.Code) { - if (this._viewModel!.hasCell(cell.handle)) { - hideOutputs.push(...cell?.outputsViewModels); - } else { - deletedOutputs.push(...cell?.outputsViewModels); - } - } - } - - this.splice2(diff.start, diff.deleteCount, diff.toInsert); - - hideOutputs.forEach(output => this._onDidHideOutput.fire(output)); - deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output)); - }); + this._updateElementsInWebview(viewDiffs); })); } })); @@ -393,6 +355,33 @@ export class NotebookCellList extends WorkbenchList implements ID this.splice2(0, 0, viewCells); } + private _updateElementsInWebview(viewDiffs: ISplice[]) { + viewDiffs.reverse().forEach((diff) => { + const hideOutputs: ICellOutputViewModel[] = []; + const deletedOutputs: ICellOutputViewModel[] = []; + const removedMarkdownCells: ICellViewModel[] = []; + + for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { + const cell = this.element(i); + if (cell.cellKind === CellKind.Code) { + if (this._viewModel!.hasCell(cell.handle)) { + hideOutputs.push(...cell?.outputsViewModels); + } else { + deletedOutputs.push(...cell?.outputsViewModels); + } + } else { + removedMarkdownCells.push(cell); + } + } + + this.splice2(diff.start, diff.deleteCount, diff.toInsert); + + hideOutputs.forEach(output => this._onDidHideOutput.fire(output)); + deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output)); + removedMarkdownCells.forEach(cell => this._onDidRemoveCellFromView.fire(cell)); + }); + } + clear() { super.splice(0, this.length); } @@ -473,31 +462,7 @@ export class NotebookCellList extends WorkbenchList implements ID return oldViewCellMapping.has(a.uri.toString()); }); - viewDiffs.reverse().forEach((diff) => { - // remove output in the webview - const hideOutputs: ICellOutputViewModel[] = []; - const deletedOutputs: ICellOutputViewModel[] = []; - const removedMarkdownCells: ICellViewModel[] = []; - - for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { - const cell = this.element(i); - if (cell.cellKind === CellKind.Code) { - if (this._viewModel!.hasCell(cell.handle)) { - hideOutputs.push(...cell?.outputsViewModels); - } else { - deletedOutputs.push(...cell?.outputsViewModels); - } - } else { - removedMarkdownCells.push(cell); - } - } - - this.splice2(diff.start, diff.deleteCount, diff.toInsert); - - hideOutputs.forEach(output => this._onDidHideOutput.fire(output)); - deletedOutputs.forEach(output => this._onDidRemoveOutput.fire(output)); - removedMarkdownCells.forEach(cell => this._onDidRemoveCellFromView.fire(cell)); - }); + this._updateElementsInWebview(viewDiffs); } splice2(start: number, deleteCount: number, elements: CellViewModel[] = []): void { @@ -698,7 +663,7 @@ export class NotebookCellList extends WorkbenchList implements ID return; } - const endIndex = this._getViewIndexUpperBound2(range.end); + const endIndex = this._getViewIndexUpperBound2(range.end - 1); const scrollTop = this.getViewScrollTop(); const wrapperBottom = this.getViewScrollBottom(); @@ -1074,18 +1039,31 @@ export class NotebookCellList extends WorkbenchList implements ID } } - // first render - const viewItemOffset = revealPosition === CellRevealPosition.Top ? elementTop : (elementTop - this.view.renderHeight / 2); - this.view.setScrollTop(viewItemOffset); - - // second scroll as markdown cell is dynamic - const newElementTop = this.view.elementTop(viewIndex); - const newViewItemOffset = revealPosition === CellRevealPosition.Top ? newElementTop : (newElementTop - this.view.renderHeight / 2); - this.view.setScrollTop(newViewItemOffset); + switch (revealPosition) { + case CellRevealPosition.Top: + this.view.setScrollTop(elementTop); + this.view.setScrollTop(this.view.elementTop(viewIndex)); + break; + case CellRevealPosition.Center: + this.view.setScrollTop(elementTop - this.view.renderHeight / 2); + this.view.setScrollTop(this.view.elementTop(viewIndex) - this.view.renderHeight / 2); + break; + case CellRevealPosition.Bottom: + this.view.setScrollTop(elementBottom - this.view.renderHeight); + this.view.setScrollTop(this.view.elementTop(viewIndex) + this.view.elementHeight(viewIndex) - this.view.renderHeight); + break; + default: + break; + } } private _revealInView(viewIndex: number) { - this._revealInternal(viewIndex, true, CellRevealPosition.Top); + const firstIndex = this.view.firstVisibleIndex; + if (viewIndex < firstIndex) { + this._revealInternal(viewIndex, true, CellRevealPosition.Top); + } else { + this._revealInternal(viewIndex, true, CellRevealPosition.Bottom); + } } private _revealInCenter(viewIndex: number) { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 509cd5498a2..a7bb989709c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -1163,7 +1163,7 @@ var requirejs = (function() { await p; } - async createInset(cellInfo: T, content: IInsetRenderOutput, cellTop: number, offset: number) { + async createOutput(cellInfo: T, content: IInsetRenderOutput, cellTop: number, offset: number) { if (this._disposed) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts index 7604b08bca3..3aade6390a6 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts @@ -150,7 +150,7 @@ export class CellOutputElement extends Disposable { } if (renderResult.type !== RenderOutputType.Mainframe) { - this.notebookEditor.createInset(this.viewCell, renderResult, this.viewCell.getOutputOffset(index)); + this.notebookEditor.createOutput(this.viewCell, renderResult, this.viewCell.getOutputOffset(index)); this.domNode.classList.add('background'); } else { this.domNode.classList.add('foreground', 'output-element'); @@ -388,7 +388,7 @@ export class CellOutputContainer extends Disposable { const renderedOutput = this.outputEntries.get(currOutput); if (renderedOutput && renderedOutput.renderResult) { if (renderedOutput.renderResult.type !== RenderOutputType.Mainframe) { - this.notebookEditor.createInset(this.viewCell, renderedOutput.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index)); + this.notebookEditor.createOutput(this.viewCell, renderedOutput.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index)); } else { this.viewCell.updateOutputHeight(index, renderedOutput.domClientHeight); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts index 4d0112043d2..0312488eb30 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts @@ -276,7 +276,6 @@ export class StatefulMarkdownCell extends Disposable { } dispose() { - this.notebookEditor.removeMarkdownPreview(this.viewCell); this.localDisposables.dispose(); this.viewCell.detachTextEditor(); super.dispose(); diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts index 13748942e86..75406e7a644 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModel.ts @@ -170,7 +170,7 @@ export class NotebookEditorModel extends EditorModel implements INotebookEditorM private async _loadFromProvider(backupId: string | undefined): Promise { - const data = await this._notebookService.fetchNotebookRawData(this.viewType, this.resource, backupId); + const data = await this._notebookService.fetchNotebookRawData(this.viewType, this.resource, backupId, CancellationToken.None); this._lastResolvedFileStat = await this._resolveStats(this.resource); if (this.isDisposed()) { diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index db6cea79b0e..e4a628741bf 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -22,9 +22,10 @@ export const INotebookService = createDecorator('notebookServi export interface IMainNotebookController { viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; }; options: { transientOutputs: boolean; transientMetadata: TransientMetadata; }; - openNotebook(viewType: string, uri: URI, backupId?: string): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>; resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise; onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void; + + openNotebook(viewType: string, uri: URI, backupId: string | undefined, token: CancellationToken): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>; save(uri: URI, token: CancellationToken): Promise; saveAs(uri: URI, target: URI, token: CancellationToken): Promise; backup(uri: URI, token: CancellationToken): Promise; @@ -66,7 +67,7 @@ export interface INotebookService { getNotebookProviderResourceRoots(): URI[]; destoryNotebookDocument(viewType: string, notebook: INotebookTextModel): void; - fetchNotebookRawData(viewType: string, uri: URI, backupId?: string): Promise; + fetchNotebookRawData(viewType: string, uri: URI, backupId: string | undefined, token: CancellationToken): Promise; save(viewType: string, resource: URI, token: CancellationToken): Promise; saveAs(viewType: string, resource: URI, target: URI, token: CancellationToken): Promise; backup(viewType: string, uri: URI, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 43296a32a9c..fed3e68aa04 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -309,7 +309,7 @@ export class TestNotebookEditor implements INotebookEditor { // throw new Error('Method not implemented.'); return; } - createInset(cell: CellViewModel, output: IInsetRenderOutput, offset: number): Promise { + createOutput(cell: CellViewModel, output: IInsetRenderOutput, offset: number): Promise { return Promise.resolve(); } createMarkdownPreview(cell: ICellViewModel): Promise { diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index 057347da4d8..d8a34d36a1c 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -303,10 +303,6 @@ box-sizing: border-box; } -.monaco-workbench .search-view a.prominent { - text-decoration: underline; -} - /* Theming */ .monaco-workbench.vs .search-view .search-widget .toggle-replace-button:hover { diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 76d4e0b1ae9..134c7d220b2 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -255,6 +255,7 @@ export class ExcludePatternInputWidget extends PatternInputWidget { setUseExcludesAndIgnoreFiles(value: boolean) { this.useExcludesAndIgnoreFilesBox.checked = value; + this._onChangeIgnoreBoxEmitter.fire(); } protected getSubcontrolsWidth(): number { diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index cd392813418..7d282f99aad 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -17,7 +17,7 @@ import * as errors from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -45,7 +45,7 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IProgress, IProgressService, IProgressStep } from 'vs/platform/progress/common/progress'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, foreground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; +import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, foreground, listActiveSelectionForeground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; @@ -118,7 +118,7 @@ export class SearchView extends ViewPane { private treeLabels!: ResourceLabels; private viewletState: MementoObject; private messagesElement!: HTMLElement; - private messageDisposables: IDisposable[] = []; + private readonly messageDisposables: DisposableStore = new DisposableStore(); private searchWidgetsContainerElement!: HTMLElement; private searchWidget!: SearchWidget; private size!: dom.Dimension; @@ -680,8 +680,7 @@ export class SearchView extends ViewPane { const wasHidden = this.messagesElement.style.display === 'none'; dom.clearNode(this.messagesElement); dom.show(this.messagesElement); - dispose(this.messageDisposables); - this.messageDisposables = []; + this.messageDisposables.clear(); const newMessage = dom.append(this.messagesElement, $('.message')); if (wasHidden) { @@ -1478,32 +1477,23 @@ export class SearchView extends ViewPane { const p = dom.append(messageEl, $('p', undefined, message)); if (!completed) { - const searchAgainLink = dom.append(p, $('a.pointer.prominent', undefined, nls.localize('rerunSearch.message', "Search again"))); - this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => { - dom.EventHelper.stop(e, false); - this.triggerQueryChange({ preserveFocus: false }); - })); + const searchAgainButton = this.messageDisposables.add(new SearchLinkButton( + nls.localize('rerunSearch.message', "Search again"), + () => this.triggerQueryChange({ preserveFocus: false }))); + dom.append(p, searchAgainButton.element); } else if (hasIncludes || hasExcludes) { - const searchAgainLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('rerunSearchInAll.message', "Search again in all files"))); - this.messageDisposables.push(dom.addDisposableListener(searchAgainLink, dom.EventType.CLICK, (e: MouseEvent) => { - dom.EventHelper.stop(e, false); - - this.inputPatternExcludes.setValue(''); - this.inputPatternIncludes.setValue(''); - this.inputPatternIncludes.setOnlySearchInOpenEditors(false); - - this.triggerQueryChange({ preserveFocus: false }); - })); + const searchAgainButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('rerunSearchInAll.message', "Search again in all files"), this.onSearchAgain.bind(this))); + dom.append(p, searchAgainButton.element); } else { - const openSettingsLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.message', "Open Settings"))); - this.addClickEvents(openSettingsLink, this.onOpenSettings); + const openSettingsButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('openSettings.message', "Open Settings"), this.onOpenSettings.bind(this))); + dom.append(p, openSettingsButton.element); } if (completed) { dom.append(p, $('span', undefined, ' - ')); - const learnMoreLink = dom.append(p, $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openSettings.learnMore', "Learn More"))); - this.addClickEvents(learnMoreLink, this.onLearnMore); + const learnMoreButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('openSettings.learnMore', "Learn More"), this.onLearnMore.bind(this))); + dom.append(p, learnMoreButton.element); } if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { @@ -1555,30 +1545,10 @@ export class SearchView extends ViewPane { .then(onComplete, onError); } - private addClickEvents = (element: HTMLElement, handler: (event: any) => void): void => { - this.messageDisposables.push(dom.addDisposableListener(element, dom.EventType.CLICK, handler)); - this.messageDisposables.push(dom.addDisposableListener(element, dom.EventType.KEY_DOWN, e => { - const event = new StandardKeyboardEvent(e); - let eventHandled = true; - - if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) { - handler(e); - } else { - eventHandled = false; - } - - if (eventHandled) { - event.preventDefault(); - event.stopPropagation(); - } - })); - }; - - private onOpenSettings = (e: dom.EventLike): void => { + private onOpenSettings(e: dom.EventLike): void { dom.EventHelper.stop(e, false); - - this.openSettings('.exclude'); - }; + this.openSettings('@id:files.exclude,search.exclude,search.useGlobalIgnoreFiles,search.useIgnoreFiles'); + } private openSettings(query: string): Promise { const options: ISettingsEditorOptions = { query }; @@ -1587,11 +1557,22 @@ export class SearchView extends ViewPane { this.preferencesService.openGlobalSettings(undefined, options); } - private onLearnMore = (e: MouseEvent): void => { - dom.EventHelper.stop(e, false); - + private onLearnMore(): void { this.openerService.open(URI.parse('https://go.microsoft.com/fwlink/?linkid=853977')); - }; + } + + private onSearchAgain(): void { + this.inputPatternExcludes.setValue(''); + this.inputPatternIncludes.setValue(''); + this.inputPatternIncludes.setOnlySearchInOpenEditors(false); + + this.triggerQueryChange({ preserveFocus: false }); + } + + private onEnableExcludes(): void { + this.toggleQueryDetails(false, true); + this.searchExcludePattern.setUseExcludesAndIgnoreFiles(true); + } private updateSearchResultCount(disregardExcludesAndIgnores?: boolean): void { const fileCount = this.viewModel.searchResult.fileCount(); @@ -1600,26 +1581,27 @@ export class SearchView extends ViewPane { const msgWasHidden = this.messagesElement.style.display === 'none'; const messageEl = this.clearMessage(); - let resultMsg = this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount); + const resultMsg = this.buildResultCountMessage(this.viewModel.searchResult.count(), fileCount); this.tree.ariaLabel = resultMsg + nls.localize('forTerm', " - Search: {0}", this.searchResult.query?.contentPattern.pattern ?? ''); + dom.append(messageEl, resultMsg); if (fileCount > 0) { if (disregardExcludesAndIgnores) { - resultMsg += nls.localize('useIgnoresAndExcludesDisabled', " - exclude settings and ignore files are disabled"); + const excludesDisabledMessage = ' - ' + nls.localize('useIgnoresAndExcludesDisabled', "exclude settings and ignore files are disabled") + ' '; + const enableExcludesButton = this.messageDisposables.add(new SearchLinkButton(nls.localize('excludes.enable', "enable"), this.onEnableExcludes.bind(this), nls.localize('useExcludesAndIgnoreFilesDescription', "Use Exclude Settings and Ignore Files"))); + dom.append(messageEl, $('span', undefined, excludesDisabledMessage, '(', enableExcludesButton.element, ')')); } - dom.append(messageEl, $('span', undefined, resultMsg + ' - ')); - const span = dom.append(messageEl, $('span')); - const openInEditorLink = dom.append(span, $('a.pointer.prominent', undefined, nls.localize('openInEditor.message', "Open in editor"))); + dom.append(messageEl, ' - '); - openInEditorLink.title = appendKeyBindingLabel( + const openInEditorTooltip = appendKeyBindingLabel( nls.localize('openInEditor.tooltip', "Copy current search results to an editor"), this.keybindingService.lookupKeybinding(Constants.OpenInEditorCommandId), this.keybindingService); - - this.messageDisposables.push(dom.addDisposableListener(openInEditorLink, dom.EventType.CLICK, (e: MouseEvent) => { - dom.EventHelper.stop(e, false); - this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.searchIncludePattern.onlySearchInOpenEditors()); - })); + const openInEditorButton = this.messageDisposables.add(new SearchLinkButton( + nls.localize('openInEditor.message', "Open in editor"), + () => this.instantiationService.invokeFunction(createEditorFromSearchResult, this.searchResult, this.searchIncludePattern.getValue(), this.searchExcludePattern.getValue(), this.searchIncludePattern.onlySearchInOpenEditors()), + openInEditorTooltip)); + dom.append(messageEl, openInEditorButton.element); this.reLayout(); } else if (!msgWasHidden) { @@ -1645,24 +1627,22 @@ export class SearchView extends ViewPane { const textEl = dom.append(this.searchWithoutFolderMessageElement, $('p', undefined, nls.localize('searchWithoutFolder', "You have not opened or specified a folder. Only open files are currently searched - "))); - const openFolderLink = dom.append(textEl, - $('a.pointer.prominent', { tabindex: 0 }, nls.localize('openFolder', "Open Folder"))); + const actionRunner = this.messageDisposables.add(new ActionRunner()); + const openFolderButton = this.messageDisposables.add(new SearchLinkButton( + nls.localize('openFolder', "Open Folder"), + () => { + const action = env.isMacintosh ? + this.instantiationService.createInstance(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL) : + this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL); - const actionRunner = new ActionRunner(); - this.messageDisposables.push(dom.addDisposableListener(openFolderLink, dom.EventType.CLICK, (e: MouseEvent) => { - dom.EventHelper.stop(e, false); - - const action = env.isMacintosh ? - this.instantiationService.createInstance(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL) : - this.instantiationService.createInstance(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL); - - actionRunner.run(action).then(() => { - action.dispose(); - }, err => { - action.dispose(); - errors.onUnexpectedError(err); - }); - })); + actionRunner.run(action).then(() => { + action.dispose(); + }, err => { + action.dispose(); + errors.onUnexpectedError(err); + }); + })); + dom.append(textEl, openFolderButton.element); } private showEmptyStage(forceHideMessages = false): void { @@ -1930,4 +1910,42 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) = collector.addRule(`.search-view .message { color: ${fgWithOpacity}; }`); } } + + const link = theme.getColor(textLinkForeground); + if (link) { + collector.addRule(`.monaco-workbench .search-view .message a { color: ${link}; }`); + } + + const activeLink = theme.getColor(textLinkActiveForeground); + if (activeLink) { + collector.addRule(`.monaco-workbench .search-view .message a:hover, + .monaco-workbench .search-view .message a:active { color: ${activeLink}; }`); + } }); + +class SearchLinkButton extends Disposable { + public readonly element: HTMLElement; + + constructor(label: string, handler: (e: dom.EventLike) => unknown, tooltip?: string) { + super(); + this.element = $('a.pointer', { tabindex: 0, title: tooltip }, label); + this.addEventHandlers(handler); + } + + private addEventHandlers(handler: (e: dom.EventLike) => unknown): void { + const wrappedHandler = (e: dom.EventLike) => { + dom.EventHelper.stop(e, false); + handler(e); + }; + + this._register(dom.addDisposableListener(this.element, dom.EventType.CLICK, wrappedHandler)); + this._register(dom.addDisposableListener(this.element, dom.EventType.KEY_DOWN, e => { + const event = new StandardKeyboardEvent(e); + if (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) { + wrappedHandler(e); + event.preventDefault(); + event.stopPropagation(); + } + })); + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index 9a7ce1fa8b1..ac123a8626d 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -364,6 +364,8 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } private _setupPtyHostListeners(offProcessTerminalService: IOffProcessTerminalService) { + // Mark the process as disconnected is the pty host is unresponsive, the responsive event + // will fire only when the pty host was already unresponsive this._register(offProcessTerminalService.onPtyHostUnresponsive(() => { this.isDisconnected = true; this._onPtyDisconnect.fire(); @@ -373,6 +375,9 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce this._onPtyReconnect.fire(); }); this._register(toDisposable(() => this._ptyResponsiveListener?.dispose())); + + // When the pty host restarts, reconnect is no longer possible so dispose the responsive + // listener this._register(offProcessTerminalService.onPtyHostRestart(() => { // When the pty host restarts, reconnect is no longer possible if (!this.isDisconnected) { diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts index 0c11faaf0b3..6b8955477c1 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts @@ -13,6 +13,8 @@ import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/co import { getTerminalShellConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { getSystemShell } from 'vs/base/node/shell'; +import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals'; +import { Platform } from 'vs/base/common/platform'; // This file contains additional desktop-only contributions on top of those in browser/ @@ -25,4 +27,5 @@ workbenchRegistry.registerWorkbenchContribution(TerminalNativeContribution, Life // Register configurations const configurationRegistry = Registry.as(Extensions.Configuration); -getTerminalShellConfiguration(getSystemShell).then(config => configurationRegistry.registerConfiguration(config)); +const systemShell = async (p: Platform) => getSystemShell(p, await process.getShellEnv()); +getTerminalShellConfiguration(systemShell).then(config => configurationRegistry.registerConfiguration(config)); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts index 32673f92f70..facc10fc01b 100644 --- a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -13,6 +13,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IShellEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import type { Terminal as XTermTerminal } from 'xterm'; import type { SearchAddon as XTermSearchAddon } from 'xterm-addon-search'; @@ -34,7 +35,8 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst @IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IHistoryService private readonly _historyService: IHistoryService, - @ILogService private readonly _logService: ILogService + @ILogService private readonly _logService: ILogService, + @IShellEnvironmentService private readonly _shellEnvironmentService: IShellEnvironmentService ) { super(); } @@ -76,10 +78,11 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(); let lastActiveWorkspace = activeWorkspaceRootUri ? this._workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) : undefined; lastActiveWorkspace = lastActiveWorkspace === null ? undefined : lastActiveWorkspace; + const shell = getDefaultShell( (key) => this._configurationService.inspect(key), isWorkspaceShellAllowed, - await getSystemShell(platformOverride), + await getSystemShell(platformOverride, await this._shellEnvironmentService.getShellEnv()), process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'), process.env.windir, createVariableResolver(lastActiveWorkspace, this._configurationResolverService), @@ -87,6 +90,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst useAutomationShell, platformOverride ); + const args = getDefaultShellArgs( (key) => this._configurationService.inspect(key), isWorkspaceShellAllowed, @@ -95,6 +99,7 @@ export class TerminalInstanceService extends Disposable implements ITerminalInst this._logService, platformOverride ); + return Promise.resolve({ shell, args }); } diff --git a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts index 5816ad9f40a..81a8d4e5afa 100644 --- a/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts +++ b/src/vs/workbench/contrib/webviewPanel/browser/webviewPanel.contribution.ts @@ -5,11 +5,16 @@ import { localize } from 'vs/nls'; import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { EditorOverride, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; -import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { Extensions as EditorInputExtensions, IEditorInput, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { HideWebViewEditorFindCommand, ReloadWebviewAction, ShowWebViewEditorFindWidgetAction, WebViewEditorFindNextCommand, WebViewEditorFindPreviousCommand } from './webviewCommands'; import { WebviewEditor } from './webviewEditor'; import { WebviewInput } from './webviewEditorInput'; @@ -22,6 +27,53 @@ import { IWebviewWorkbenchService, WebviewEditorService } from './webviewWorkben localize('webview.editor.label', "webview editor")), [new SyncDescriptor(WebviewInput)]); +class WebviewPanelContribution implements IWorkbenchContribution { + constructor( + @IEditorService private readonly editorService: IEditorService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + ) { + this.editorService.overrideOpenEditor({ + open: (editor, options, group) => this.onEditorOpening(editor, options, group) + }); + } + + private onEditorOpening( + editor: IEditorInput, + options: ITextEditorOptions | undefined, + group: IEditorGroup + ): IOpenEditorOverride | undefined { + if (!(editor instanceof WebviewInput) || editor.getTypeId() !== WebviewInput.typeId) { + return undefined; + } + + if (group.isOpened(editor)) { + return undefined; + } + + let previousGroup: IEditorGroup | undefined; + const groups = this.editorGroupService.groups; + for (const group of groups) { + if (group.isOpened(editor)) { + previousGroup = group; + break; + } + } + + if (!previousGroup) { + return undefined; + } + + previousGroup.closeEditor(editor); + + return { + override: this.editorService.openEditor(editor, { ...options, override: EditorOverride.DISABLED }, group) + }; + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(WebviewPanelContribution, LifecyclePhase.Starting); + Registry.as(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory( WebviewEditorInputFactory.ID, WebviewEditorInputFactory); diff --git a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts index 1014b49cd96..0a80acfb1a9 100644 --- a/src/vs/workbench/test/browser/api/extHostNotebook.test.ts +++ b/src/vs/workbench/test/browser/api/extHostNotebook.test.ts @@ -353,4 +353,40 @@ suite('NotebookCell#Document', function () { assert.strictEqual(event.changes[0].items[0].document.isClosed, false); assert.strictEqual(event.changes[0].items[1].document.isClosed, false); }); + + + test('Opening a notebook results in VS Code firing the event onDidChangeActiveNotebookEditor twice #118470', function () { + let count = 0; + extHostNotebooks.onDidChangeActiveNotebookEditor(() => count += 1); + + extHostNotebooks.$acceptDocumentAndEditorsDelta({ + addedEditors: [{ + documentUri: notebookUri, + id: '_notebook_editor_2', + selections: [{ start: 0, end: 1 }], + visibleRanges: [] + }] + }); + + extHostNotebooks.$acceptDocumentAndEditorsDelta({ + newActiveEditor: '_notebook_editor_2' + }); + + assert.strictEqual(count, 1); + }); + + test('unset active notebook editor', function () { + + const editor = extHostNotebooks.activeNotebookEditor; + assert.ok(editor !== undefined); + + extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: undefined }); + assert.ok(extHostNotebooks.activeNotebookEditor === editor); + + extHostNotebooks.$acceptDocumentAndEditorsDelta({}); + assert.ok(extHostNotebooks.activeNotebookEditor === editor); + + extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: null }); + assert.ok(extHostNotebooks.activeNotebookEditor === undefined); + }); }); diff --git a/test/automation/src/extensions.ts b/test/automation/src/extensions.ts index b2bedb8f5c8..3e71563a202 100644 --- a/test/automation/src/extensions.ts +++ b/test/automation/src/extensions.ts @@ -24,10 +24,6 @@ export class Extensions extends Viewlet { await this.code.waitForActiveElement(SEARCH_BOX); } - async waitForExtensionsViewlet(): Promise { - await this.code.waitForElement(SEARCH_BOX); - } - async searchForExtension(id: string): Promise { await this.code.waitAndClick(SEARCH_BOX); await this.code.waitForActiveElement(SEARCH_BOX); @@ -40,6 +36,10 @@ export class Extensions extends Viewlet { await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[data-extension-id="${id}"]`); } + async closeExtension(title: string): Promise { + await this.code.waitAndClick(`.tabs-container div.tab[title="Extension: ${title}"] div.tab-actions a.action-label.codicon.codicon-close`); + } + async installExtension(id: string, waitUntilEnabled: boolean): Promise { await this.searchForExtension(id); await this.code.waitAndClick(`div.extensions-viewlet[id="workbench.view.extensions"] .monaco-list-row[data-extension-id="${id}"] .extension-list-item .monaco-action-bar .action-item:not(.disabled) .extension-action.install`); diff --git a/test/smoke/src/areas/extensions/extensions.test.ts b/test/smoke/src/areas/extensions/extensions.test.ts index 1abdd6ee13c..8154cc465f0 100644 --- a/test/smoke/src/areas/extensions/extensions.test.ts +++ b/test/smoke/src/areas/extensions/extensions.test.ts @@ -7,35 +7,22 @@ import { Application, Quality } from '../../../../automation'; export function setup() { describe('Extensions', () => { - it(`install and activate vscode-smoketest-check extension`, async function () { + it(`install and enable vscode-smoketest-check extension`, async function () { const app = this.app as Application; - if (app.quality === Quality.Dev || app.web /* https://github.com/microsoft/vscode/issues/118443 */) { + if (app.quality === Quality.Dev) { this.skip(); - return; - } - - if (!app.web) { - await app.workbench.settingsEditor.addUserSetting('webview.experimental.useIframes', 'true'); } await app.workbench.extensions.openExtensionsViewlet(); await app.workbench.extensions.installExtension('michelkaporin.vscode-smoketest-check', true); - await app.workbench.extensions.waitForExtensionsViewlet(); + // Close extension editor because keybindings dispatch is not working when web views are opened and focused + // https://github.com/microsoft/vscode/issues/110276 + await app.workbench.extensions.closeExtension('vscode-smoketest-check'); await app.workbench.quickaccess.runCommand('Smoke Test Check'); - await app.workbench.statusbar.waitForStatusbarText('smoke test', 'VS Code Smoke Test Check'); - }); - - after(async function () { - const app = this.app as Application; - if (app.web) { - return; - } - - await app.workbench.settingsEditor.clearUserSettings(); }); }); diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 444a9157aa2..77bcc40efb4 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -35,7 +35,7 @@ export function setup() { }); // https://github.com/microsoft/vscode/issues/115244 - it('dismisses result & checks for correct result number', async function () { + it.skip('dismisses result & checks for correct result number', async function () { const app = this.app as Application; await app.workbench.search.searchFor('body'); await app.workbench.search.removeFileMatch('app.js');