From c6bcad53df7bef121bf804aa7add13f861469caf Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Mon, 26 Jun 2023 21:02:27 +0000 Subject: [PATCH 001/216] Initial code for go telemetry --- .../electron-sandbox/workspaceTagsService.ts | 71 ++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 39a65eac89638..b832b1d639dec 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -244,6 +244,32 @@ const PyModulesToLookFor = [ 'playwright' ]; +const GoMetaModulesToLookFor = [ + 'github.com/Azure/azure-sdk-for-go/sdk/' +]; + +const GoModulesToLookFor = [ + 'github.com/Azure/azure-sdk-for-go/sdk/storage/azblob', + 'github.com/Azure/azure-sdk-for-go/sdk/storage/azfile', + 'github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue', + 'github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel', + 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin', + 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates', + 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys', + 'github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets', + 'github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery', + 'github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs', + 'github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus', + 'github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig', + 'github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos', + 'github.com/Azure/azure-sdk-for-go/sdk/data/aztables', + 'github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry', + 'github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai', + 'github.com/Azure/azure-sdk-for-go/sdk/azidentity', + 'github.com/Azure/azure-sdk-for-go/sdk/azcore' +]; + + export class WorkspaceTagsService implements IWorkspaceTagsService { declare readonly _serviceBrand: undefined; private _tags: Tags | undefined; @@ -566,6 +592,24 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.azure-security-attestation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-data-nspkg" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-data-tables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/aztables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azidentity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azcore" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ private async resolveWorkspaceTags(): Promise { @@ -624,6 +668,8 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { tags['workspace.py.app'] = nameSet.has('app.py'); tags['workspace.py.pyproject'] = nameSet.has('pyproject.toml'); + tags['workspace.go.mod'] = nameSet.has('go.mod'); + const mainActivity = nameSet.has('mainactivity.cs') || nameSet.has('mainactivity.fs'); const appDelegate = nameSet.has('appdelegate.cs') || nameSet.has('appdelegate.fs'); const androidManifest = nameSet.has('androidmanifest.xml'); @@ -752,6 +798,29 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { } }); + const goModPromises = getFilePromises('go.mod', this.fileService, this.textFileService, content => { + // TODO: Richard to write the code for parsing the go.mod file + // look for everything in require() and get the string value only discard version + const dependencies: string[] = splitLines(content.value); + for (const dependency of dependencies) { + // Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo` + const format1 = dependency.split('=='); + const format2 = dependency.split('>='); + const packageName = (format1.length === 2 ? format1[0] : format2[0]).trim(); + + // copied from line 728 function addPythonTags + if (GoModulesToLookFor.indexOf(packageName) > -1) { + tags['workspace.go.mod' + packageName] = true; + } + // not sure if we should keep this + for (const metaModule of GoMetaModulesToLookFor) { + if (packageName.startsWith(metaModule)) { + tags['workspace.go.mod' + metaModule] = true; + } + } + } + }); + const pomPromises = getFilePromises('pom.xml', this.fileService, this.textFileService, content => { try { let dependenciesContent; @@ -792,7 +861,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { }); }); - return Promise.all([...packageJsonPromises, ...requirementsTxtPromises, ...pipfilePromises, ...pomPromises, ...gradlePromises, ...androidPromises]).then(() => tags); + return Promise.all([...packageJsonPromises, ...requirementsTxtPromises, ...pipfilePromises, ...pomPromises, ...gradlePromises, ...androidPromises, ...goModPromises]).then(() => tags); }); } From b62087b19d5fd3525dab5481500cde91c795a4cb Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Mon, 26 Jun 2023 21:58:54 +0000 Subject: [PATCH 002/216] Adding go mod parser --- .../electron-sandbox/workspaceTagsService.ts | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index b832b1d639dec..4d5ff69fa1d91 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -801,21 +801,36 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { const goModPromises = getFilePromises('go.mod', this.fileService, this.textFileService, content => { // TODO: Richard to write the code for parsing the go.mod file // look for everything in require() and get the string value only discard version - const dependencies: string[] = splitLines(content.value); - for (const dependency of dependencies) { - // Dependencies in requirements.txt can have 3 formats: `foo==3.1, foo>=3.1, foo` - const format1 = dependency.split('=='); - const format2 = dependency.split('>='); - const packageName = (format1.length === 2 ? format1[0] : format2[0]).trim(); + const lines: string[] = splitLines(content.value); + let firstRequireBlockFound: boolean = false; + + for (let i = 0; i < lines.length; i++) { + const line: string = lines[i].trim(); + + if (line.startsWith('require (')) { + if (!firstRequireBlockFound) { + firstRequireBlockFound = true; + continue; + } else { + break; + } + } - // copied from line 728 function addPythonTags - if (GoModulesToLookFor.indexOf(packageName) > -1) { - tags['workspace.go.mod' + packageName] = true; + if (line.startsWith(')')) { + break; } - // not sure if we should keep this - for (const metaModule of GoMetaModulesToLookFor) { - if (packageName.startsWith(metaModule)) { - tags['workspace.go.mod' + metaModule] = true; + + if (firstRequireBlockFound && line !== '') { + const packageName: string = line.split(' ')[0].trim(); + // copied from line 728 function addPythonTags + if (GoModulesToLookFor.indexOf(packageName) > -1) { + tags['workspace.go.mod' + packageName] = true; + } + // not sure if we should keep this + for (const metaModule of GoMetaModulesToLookFor) { + if (packageName.startsWith(metaModule)) { + tags['workspace.go.mod' + metaModule] = true; + } } } } From af15aac09c3ed2df82bdc774fa539aa9e4751417 Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Tue, 27 Jun 2023 20:54:56 +0000 Subject: [PATCH 003/216] Removing meta modules as Go does not need it --- .../electron-sandbox/workspaceTagsService.ts | 56 ++++++++----------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 4d5ff69fa1d91..10be76329c4b6 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -244,10 +244,6 @@ const PyModulesToLookFor = [ 'playwright' ]; -const GoMetaModulesToLookFor = [ - 'github.com/Azure/azure-sdk-for-go/sdk/' -]; - const GoModulesToLookFor = [ 'github.com/Azure/azure-sdk-for-go/sdk/storage/azblob', 'github.com/Azure/azure-sdk-for-go/sdk/storage/azfile', @@ -799,41 +795,33 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { }); const goModPromises = getFilePromises('go.mod', this.fileService, this.textFileService, content => { - // TODO: Richard to write the code for parsing the go.mod file - // look for everything in require() and get the string value only discard version - const lines: string[] = splitLines(content.value); - let firstRequireBlockFound: boolean = false; - - for (let i = 0; i < lines.length; i++) { - const line: string = lines[i].trim(); - - if (line.startsWith('require (')) { - if (!firstRequireBlockFound) { - firstRequireBlockFound = true; - continue; - } else { - break; - } - } - - if (line.startsWith(')')) { - break; - } - - if (firstRequireBlockFound && line !== '') { - const packageName: string = line.split(' ')[0].trim(); - // copied from line 728 function addPythonTags - if (GoModulesToLookFor.indexOf(packageName) > -1) { - tags['workspace.go.mod' + packageName] = true; + try { + const lines: string[] = splitLines(content.value); + let firstRequireBlockFound: boolean = false; + for (let i = 0; i < lines.length; i++) { + const line: string = lines[i].trim(); + if (line.startsWith('require (')) { + if (!firstRequireBlockFound) { + firstRequireBlockFound = true; + continue; + } else { + break; + } } - // not sure if we should keep this - for (const metaModule of GoMetaModulesToLookFor) { - if (packageName.startsWith(metaModule)) { - tags['workspace.go.mod' + metaModule] = true; + if (line.startsWith(')')) { + break; + } + if (firstRequireBlockFound && line !== '') { + const packageName: string = line.split(' ')[0].trim(); + if (GoModulesToLookFor.indexOf(packageName) > -1) { + tags['workspace.go.mod' + packageName] = true; } } } } + catch (e) { + // Ignore errors when resolving file or parsing file contents + } }); const pomPromises = getFilePromises('pom.xml', this.fileService, this.textFileService, content => { From a98595156a09ccdad70b12844653e3c0632be59c Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Tue, 27 Jun 2023 21:57:31 -0700 Subject: [PATCH 004/216] adding missing commas to GDPR segment --- .../electron-sandbox/workspaceTagsService.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 10be76329c4b6..04d93982b2a86 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -587,24 +587,24 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.py.azure-communication-administration" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-security-attestation" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.py.azure-data-nspkg" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspace.py.azure-data-tables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/aztables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azidentity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "workspace.py.azure-data-tables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azfile" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/storage/azqueue" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/tracing/azotel" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azadmin" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azcertificates" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/monitor/azquery" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azeventhubs" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/azcosmos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/data/aztables" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/containers/azcontainerregistry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/cognitiveservices/azopenai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azidentity" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.go.mod.github.com/Azure/azure-sdk-for-go/sdk/azcore" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } } */ From 721629bd1a78ef1f3facfae766f3ba2f05556eab Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 30 Jun 2023 17:15:37 +0600 Subject: [PATCH 005/216] removing the assert type on the active session to be able to spawn several sessions one after the other --- .../workbench/contrib/inlineChat/browser/inlineChatController.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index ba825ebb44a9f..1e975917f3dc6 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -214,7 +214,6 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { - assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); let session: Session | undefined = options.existingSession; From cc92e04a08a1d4f0b94ac26696a2aa1bebbbf142 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 30 Jun 2023 17:32:10 +0600 Subject: [PATCH 006/216] adding some code, need to verify what causes the error in dev tools --- .../contrib/inlineChat/browser/inlineChatController.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 1e975917f3dc6..8f6bc67f54a3c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -214,6 +214,12 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { + if (this._activeSession) { + this._sessionStore.clear(); + this._inlineChatSessionService.releaseSession(this._activeSession); + await this[State.PAUSE](); + } + assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); let session: Session | undefined = options.existingSession; From 7eaae680dc341be01f6563bdd0e43c7519ecd086 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 10 Jul 2023 10:59:42 +0200 Subject: [PATCH 007/216] adding some comments that will be removed later --- .../inlineChat/browser/inlineChatController.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 8f6bc67f54a3c..7a9d3d2ee993d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -215,10 +215,14 @@ export class InlineChatController implements IEditorContribution { private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { if (this._activeSession) { + console.log('before clearing the session store'); this._sessionStore.clear(); + console.log('before releasing teh session'); this._inlineChatSessionService.releaseSession(this._activeSession); + console.log('before calling pause'); await this[State.PAUSE](); } + console.log('this._activeSession after the cleaning : ', this._activeSession); assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -282,6 +286,8 @@ export class InlineChatController implements IEditorContribution { } private async [State.INIT_UI](options: InlineChatRunOptions): Promise { + console.log('inside of init ui'); + console.log('this._activeSession : ', this._activeSession); assertType(this._activeSession); // hide/cancel inline completions when invoking IE @@ -347,11 +353,14 @@ export class InlineChatController implements IEditorContribution { })); if (!this._activeSession.lastExchange) { + console.log('before waiting for input'); return State.WAIT_FOR_INPUT; } else if (options.isUnstashed) { delete options.isUnstashed; + console.log('before apply response'); return State.APPLY_RESPONSE; } else { + console.log('before show response'); return State.SHOW_RESPONSE; } } @@ -631,6 +640,8 @@ export class InlineChatController implements IEditorContribution { private async [State.PAUSE]() { + console.log('entered into pause state'); + this._ctxDidEdit.reset(); this._ctxUserDidEdit.reset(); this._ctxLastResponseType.reset(); @@ -650,6 +661,7 @@ export class InlineChatController implements IEditorContribution { } private async [State.ACCEPT]() { + console.log('inside of accept'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); From 55613b5f13d009a2c0d32eb95b45b88024d4eecd Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Mon, 10 Jul 2023 15:31:45 +0200 Subject: [PATCH 008/216] adding some console logs, added todo --- .../browser/inlineChatController.ts | 50 ++++++++++++++++--- .../browser/inlineChatStrategies.ts | 5 +- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 7a9d3d2ee993d..05cb6b8e8df7c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -134,6 +134,7 @@ export class InlineChatController implements IEditorContribution { } this._log('session RESUMING', e); + console.log('before calling create session of the constructor'); await this._nextState(State.CREATE_SESSION, { existingSession }); this._log('session done or paused'); })); @@ -141,6 +142,7 @@ export class InlineChatController implements IEditorContribution { } dispose(): void { + console.log('inside of dispose'); this._stashedSession.clear(); this.finishExistingSession(); this._store.dispose(); @@ -174,10 +176,10 @@ export class InlineChatController implements IEditorContribution { } async run(options: InlineChatRunOptions | undefined = {}): Promise { - this._log('session starting'); + this._log('session starting inside of run'); await this.finishExistingSession(); this._stashedSession.clear(); - + console.log('before calling create session inside of run'); await this._nextState(State.CREATE_SESSION, options); this._log('session done or paused'); } @@ -214,6 +216,8 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { + console.log('inside of CREATE_SESSION'); + console.log('this._activeSession : ', this._activeSession); if (this._activeSession) { console.log('before clearing the session store'); this._sessionStore.clear(); @@ -235,6 +239,7 @@ export class InlineChatController implements IEditorContribution { if (!session) { const createSessionCts = new CancellationTokenSource(); const msgListener = Event.once(this._messages.event)(m => { + console.log('inside of the msgListener code of CREATE_SESSION'); this._log('state=_createSession) message received', m); if (m === Message.ACCEPT_INPUT) { // user accepted the input before having a session @@ -348,6 +353,7 @@ export class InlineChatController implements IEditorContribution { if (editIsOutsideOfWholeRange) { this._log('text changed outside of whole range, FINISH session'); + console.log('before the third finish existing session'); this.finishExistingSession(); } })); @@ -379,7 +385,7 @@ export class InlineChatController implements IEditorContribution { } - private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { + private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { assertType(this._activeSession); assertType(this._strategy); @@ -400,6 +406,7 @@ export class InlineChatController implements IEditorContribution { } else { const barrier = new Barrier(); const msgListener = Event.once(this._messages.event)(m => { + console.log('inside of msgListener of WAIT FOR INPUT'); this._log('state=_waitForInput) message received', m); message = m; barrier.open(); @@ -411,11 +418,19 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.selectAll(); if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) { - return State.CANCEL; + console.log('inside of wait for input'); + console.log('entered into the case when message cancel session'); + await this[State.CANCEL](); + return; + // return State.CANCEL; } if (message & Message.ACCEPT_SESSION) { - return State.ACCEPT; + console.log('inside of wait for input'); + console.log('entered into the case when message accept'); + await this[State.ACCEPT](); + return; + // return State.ACCEPT; } if (message & Message.PAUSE_SESSION) { @@ -467,6 +482,7 @@ export class InlineChatController implements IEditorContribution { let message = Message.NONE; const msgListener = Event.once(this._messages.event)(m => { + console.log('inside of msgListener of MAKE REQUEST'); this._log('state=_makeRequest) message received', m); message = m; requestCts.cancel(); @@ -521,10 +537,14 @@ export class InlineChatController implements IEditorContribution { this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); if (message & Message.CANCEL_SESSION) { + console.log('inside of make request'); + console.log('cancelling the session'); return State.CANCEL; } else if (message & Message.PAUSE_SESSION) { return State.PAUSE; } else if (message & Message.ACCEPT_SESSION) { + console.log('inside of make request'); + console.log('accepting'); return State.ACCEPT; } else { return State.APPLY_RESPONSE; @@ -665,21 +685,31 @@ export class InlineChatController implements IEditorContribution { assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); + console.log('after assert type'); try { + console.log('before strategy apply'); await this._strategy.apply(); + console.log('after strategy apply'); + // TODO: ASK WHY DESPITE AWAIT AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION } catch (err) { + console.log('when error obtained'); this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err))); this._log('FAILED to apply changes'); this._log(err); } - this._inlineChatSessionService.releaseSession(this._activeSession); + console.log('before release session'); - this[State.PAUSE](); + await this._inlineChatSessionService.releaseSession(this._activeSession); + + console.log('before state pause'); + await this[State.PAUSE](); } private async [State.CANCEL]() { + console.log('inside of cancel the session'); + assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -694,7 +724,7 @@ export class InlineChatController implements IEditorContribution { this._log(err); } - this[State.PAUSE](); + await this[State.PAUSE](); this._stashedSession.clear(); if (!mySession.isUnstashed && mySession.lastExchange) { @@ -780,10 +810,12 @@ export class InlineChatController implements IEditorContribution { } acceptSession(): void { + console.log('inside of acceptSession method'); this._messages.fire(Message.ACCEPT_SESSION); } cancelSession() { + console.log('inside of cancelSession'); let result: string | undefined; if (this._strategy && this._activeSession) { const changedText = this._activeSession.asChangedText(); @@ -798,6 +830,8 @@ export class InlineChatController implements IEditorContribution { } async finishExistingSession(): Promise { + console.log('inside of finish existing session'); + console.log(this._activeSession); if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 36e64ebf09af5..019a4ecfd58d5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -86,7 +86,7 @@ export class PreviewStrategy extends EditModeStrategy { } async apply() { - + console.log('at the beginning of the apply method of preview strategy'); if (!(this._session.lastExchange?.response instanceof EditResponse)) { return; } @@ -107,6 +107,7 @@ export class PreviewStrategy extends EditModeStrategy { modelN.pushStackElement(); } } + console.log('at the end of the apply method of preview strategy'); } async cancel(): Promise { @@ -279,6 +280,7 @@ export class LiveStrategy extends EditModeStrategy { } async apply() { + console.log('inside of apply of live strategy'); if (this._editCount > 0) { this._editor.pushUndoStop(); } @@ -286,6 +288,7 @@ export class LiveStrategy extends EditModeStrategy { await this._bulkEditService.apply(this._lastResponse.workspaceEdits); this._instaService.invokeFunction(showSingleCreateFile, this._lastResponse); } + console.log('at the end of apply for live strategy'); } async cancel() { From 369585db99845a28851b0bcfde7a5e66aba43397 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 12:12:23 +0200 Subject: [PATCH 009/216] work in progress --- .../browser/inlineChatController.ts | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 05cb6b8e8df7c..4a5c801b3da91 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -217,16 +217,16 @@ export class InlineChatController implements IEditorContribution { private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { console.log('inside of CREATE_SESSION'); - console.log('this._activeSession : ', this._activeSession); + console.log('inside of CREATE_SESSION, this._activeSession : ', this._activeSession); if (this._activeSession) { - console.log('before clearing the session store'); + console.log('inside of CREATE_SESSION, before clearing the session store'); this._sessionStore.clear(); - console.log('before releasing teh session'); + console.log('inside of CREATE_SESSION, before releasing the session'); this._inlineChatSessionService.releaseSession(this._activeSession); - console.log('before calling pause'); + console.log('inside of CREATE_SESSION, before calling pause'); await this[State.PAUSE](); } - console.log('this._activeSession after the cleaning : ', this._activeSession); + console.log('inside of CREATE_SESSION, this._activeSession after the cleaning : ', this._activeSession); assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -239,7 +239,7 @@ export class InlineChatController implements IEditorContribution { if (!session) { const createSessionCts = new CancellationTokenSource(); const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of the msgListener code of CREATE_SESSION'); + console.log('inside of CREATE_SESSION, inside of the msgListener code of CREATE_SESSION'); this._log('state=_createSession) message received', m); if (m === Message.ACCEPT_INPUT) { // user accepted the input before having a session @@ -292,7 +292,7 @@ export class InlineChatController implements IEditorContribution { private async [State.INIT_UI](options: InlineChatRunOptions): Promise { console.log('inside of init ui'); - console.log('this._activeSession : ', this._activeSession); + console.log('inside of INIT_UI, this._activeSession : ', this._activeSession); assertType(this._activeSession); // hide/cancel inline completions when invoking IE @@ -353,20 +353,20 @@ export class InlineChatController implements IEditorContribution { if (editIsOutsideOfWholeRange) { this._log('text changed outside of whole range, FINISH session'); - console.log('before the third finish existing session'); + console.log('inside of INIT_UI, before the third finish existing session'); this.finishExistingSession(); } })); if (!this._activeSession.lastExchange) { - console.log('before waiting for input'); + console.log('inside of INIT_UI,before waiting for input'); return State.WAIT_FOR_INPUT; } else if (options.isUnstashed) { delete options.isUnstashed; - console.log('before apply response'); + console.log('inside of INIT_UI,before apply response'); return State.APPLY_RESPONSE; } else { - console.log('before show response'); + console.log('inside of INIT_UI,before show response'); return State.SHOW_RESPONSE; } } @@ -386,6 +386,8 @@ export class InlineChatController implements IEditorContribution { private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { + console.log('inside of wait for input'); + assertType(this._activeSession); assertType(this._strategy); @@ -406,7 +408,7 @@ export class InlineChatController implements IEditorContribution { } else { const barrier = new Barrier(); const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of msgListener of WAIT FOR INPUT'); + console.log('inside of WAIT_FOR_INPUT, inside of msgListener'); this._log('state=_waitForInput) message received', m); message = m; barrier.open(); @@ -418,16 +420,14 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.selectAll(); if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) { - console.log('inside of wait for input'); - console.log('entered into the case when message cancel session'); + console.log('inside of WAIT_FOR_INPUT,entered into the case when message cancel session'); await this[State.CANCEL](); return; // return State.CANCEL; } if (message & Message.ACCEPT_SESSION) { - console.log('inside of wait for input'); - console.log('entered into the case when message accept'); + console.log('inside of WAIT_FOR_INPUT,entered into the case when message accept'); await this[State.ACCEPT](); return; // return State.ACCEPT; @@ -681,7 +681,7 @@ export class InlineChatController implements IEditorContribution { } private async [State.ACCEPT]() { - console.log('inside of accept'); + console.log('inside of State.ACCEPT'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -835,12 +835,15 @@ export class InlineChatController implements IEditorContribution { if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); + console.log('before cancelling inside of finish existing session'); this.cancelSession(); } else { this._log('finishing existing session, using APPLY', this._activeSession.editMode); + console.log('before accepting inside of finish existing session'); this.acceptSession(); } } + console.log('at the end of finish existing session'); } unstashLastSession(): Session | undefined { From a07bb86a618f10d43eb3b82d155b3408f37c4080 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 14:18:17 +0200 Subject: [PATCH 010/216] adding all the comments --- .../browser/inlineChatController.ts | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 4a5c801b3da91..58067e15c590d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -482,7 +482,7 @@ export class InlineChatController implements IEditorContribution { let message = Message.NONE; const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of msgListener of MAKE REQUEST'); + console.log('inside of MAKE_REQUEST, inside of msgListener of MAKE REQUEST'); this._log('state=_makeRequest) message received', m); message = m; requestCts.cancel(); @@ -537,14 +537,12 @@ export class InlineChatController implements IEditorContribution { this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); if (message & Message.CANCEL_SESSION) { - console.log('inside of make request'); - console.log('cancelling the session'); + console.log('inside of MAKE_REQUEST, cancelling the session'); return State.CANCEL; } else if (message & Message.PAUSE_SESSION) { return State.PAUSE; } else if (message & Message.ACCEPT_SESSION) { - console.log('inside of make request'); - console.log('accepting'); + console.log('inside of MAKE_REQUEST, accepting'); return State.ACCEPT; } else { return State.APPLY_RESPONSE; @@ -685,25 +683,25 @@ export class InlineChatController implements IEditorContribution { assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); - console.log('after assert type'); + console.log('inside of State.ACCEPT, after assert type'); try { - console.log('before strategy apply'); + console.log('inside of State.ACCEPT, before strategy apply'); await this._strategy.apply(); - console.log('after strategy apply'); + console.log('inside of State.ACCEPT, after strategy apply'); // TODO: ASK WHY DESPITE AWAIT AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION } catch (err) { - console.log('when error obtained'); + console.log('inside of State.ACCEPT, when error obtained'); this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err))); this._log('FAILED to apply changes'); this._log(err); } - console.log('before release session'); + console.log('inside of State.ACCEPT, before release session'); await this._inlineChatSessionService.releaseSession(this._activeSession); - console.log('before state pause'); + console.log('inside of State.ACCEPT, before state pause'); await this[State.PAUSE](); } From d80f78877bdd9f4228f2153d0502f3e964b736d4 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 14:30:47 +0200 Subject: [PATCH 011/216] adding some more comments --- .../contrib/inlineChat/browser/inlineChatController.ts | 7 ++++++- .../contrib/inlineChat/browser/inlineChatSession.ts | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 58067e15c590d..6059b0c35a70f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -144,6 +144,7 @@ export class InlineChatController implements IEditorContribution { dispose(): void { console.log('inside of dispose'); this._stashedSession.clear(); + console.log('inside of dispose before calling finishExistingSession'); this.finishExistingSession(); this._store.dispose(); this._log('controller disposed'); @@ -177,6 +178,7 @@ export class InlineChatController implements IEditorContribution { async run(options: InlineChatRunOptions | undefined = {}): Promise { this._log('session starting inside of run'); + console.log('before finishExistingSession inside of run'); await this.finishExistingSession(); this._stashedSession.clear(); console.log('before calling create session inside of run'); @@ -353,7 +355,7 @@ export class InlineChatController implements IEditorContribution { if (editIsOutsideOfWholeRange) { this._log('text changed outside of whole range, FINISH session'); - console.log('inside of INIT_UI, before the third finish existing session'); + console.log('inside of INIT_UI, before calling finish existing session'); this.finishExistingSession(); } })); @@ -729,6 +731,7 @@ export class InlineChatController implements IEditorContribution { // only stash sessions that had edits this._stashedSession.value = this._instaService.createInstance(StashedSession, this._editor, mySession); } else { + console.log('before releasing the session of cancel'); this._inlineChatSessionService.releaseSession(mySession); } } @@ -870,6 +873,7 @@ class StashedSession { this._ctxHasStashedSession.set(true); this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { this._session = undefined; + console.log('before release session of constructor'); this._sessionService.releaseSession(session); this._ctxHasStashedSession.reset(); }); @@ -879,6 +883,7 @@ class StashedSession { this._listener.dispose(); this._ctxHasStashedSession.reset(); if (this._session) { + console.log('before release session of dispose'); this._sessionService.releaseSession(this._session); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 83aa7b064ff7d..79ac5e76633a1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -477,6 +477,7 @@ export class InlineChatSessionService implements IInlineChatSessionService { } releaseSession(session: Session): void { + console.log('at the beginning of the release session'); const { editor } = session; @@ -498,6 +499,7 @@ export class InlineChatSessionService implements IInlineChatSessionService { // send telemetry this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); + console.log('at the end of the release session'); } getSession(editor: ICodeEditor, uri: URI): Session | undefined { From 3c632446d5052f7e4f7c8b9b464efaf2a2d7a02e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 14:47:21 +0200 Subject: [PATCH 012/216] go over this and try to understand why it does not work --- .../browser/inlineChatController.ts | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 6059b0c35a70f..f15eff338cd8d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -180,6 +180,7 @@ export class InlineChatController implements IEditorContribution { this._log('session starting inside of run'); console.log('before finishExistingSession inside of run'); await this.finishExistingSession(); + console.log('after finish existing session inside of run'); this._stashedSession.clear(); console.log('before calling create session inside of run'); await this._nextState(State.CREATE_SESSION, options); @@ -220,14 +221,15 @@ export class InlineChatController implements IEditorContribution { private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { console.log('inside of CREATE_SESSION'); console.log('inside of CREATE_SESSION, this._activeSession : ', this._activeSession); - if (this._activeSession) { - console.log('inside of CREATE_SESSION, before clearing the session store'); - this._sessionStore.clear(); - console.log('inside of CREATE_SESSION, before releasing the session'); - this._inlineChatSessionService.releaseSession(this._activeSession); - console.log('inside of CREATE_SESSION, before calling pause'); - await this[State.PAUSE](); - } + // if (this._activeSession) { + // console.log('inside of CREATE_SESSION, before clearing the session store'); + // this._sessionStore.clear(); + // console.log('inside of CREATE_SESSION, before releasing the session'); + // this._inlineChatSessionService.releaseSession(this._activeSession); + // console.log('inside of CREATE_SESSION, before calling pause'); + // // Doesn't appear to be properly awaited? + // await this[State.PAUSE](); + // } console.log('inside of CREATE_SESSION, this._activeSession after the cleaning : ', this._activeSession); assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -691,7 +693,7 @@ export class InlineChatController implements IEditorContribution { console.log('inside of State.ACCEPT, before strategy apply'); await this._strategy.apply(); console.log('inside of State.ACCEPT, after strategy apply'); - // TODO: ASK WHY DESPITE AWAIT AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION + // TODO: ASK WHY DESPITE AWAIT, AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION } catch (err) { console.log('inside of State.ACCEPT, when error obtained'); this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err))); @@ -812,6 +814,7 @@ export class InlineChatController implements IEditorContribution { acceptSession(): void { console.log('inside of acceptSession method'); + // Will fire a message which will be picked up by the controller and some other code will be run this._messages.fire(Message.ACCEPT_SESSION); } From d88605d5423555c91ec5876b4f20bc33884dbb38 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 17:13:14 +0200 Subject: [PATCH 013/216] cleaning the code --- .../inlineChat/browser/inlineChatActions.ts | 1 + .../browser/inlineChatController.ts | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 83b481739a1cc..b65aab455b63a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -67,6 +67,7 @@ export class StartSessionAction extends EditorAction2 { options = arg; } InlineChatController.get(editor)?.run(options); + InlineChatController.get(editor)?.run(options); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index f15eff338cd8d..55b8744a5f885 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -176,15 +176,17 @@ export class InlineChatController implements IEditorContribution { return this._zone.value.position; } + private _currentRun?: Promise; + async run(options: InlineChatRunOptions | undefined = {}): Promise { - this._log('session starting inside of run'); - console.log('before finishExistingSession inside of run'); - await this.finishExistingSession(); - console.log('after finish existing session inside of run'); + this.finishExistingSession(); + if (this._currentRun) { + await this._currentRun; + } this._stashedSession.clear(); - console.log('before calling create session inside of run'); - await this._nextState(State.CREATE_SESSION, options); - this._log('session done or paused'); + this._currentRun = this._nextState(State.CREATE_SESSION, options); + await this._currentRun; + this._currentRun = undefined; } // ---- state machine @@ -833,7 +835,7 @@ export class InlineChatController implements IEditorContribution { return result; } - async finishExistingSession(): Promise { + finishExistingSession(): void { console.log('inside of finish existing session'); console.log(this._activeSession); if (this._activeSession) { @@ -846,6 +848,7 @@ export class InlineChatController implements IEditorContribution { console.log('before accepting inside of finish existing session'); this.acceptSession(); } + } console.log('at the end of finish existing session'); } From 26151020a859156fb692dd82c298915de618a5fa Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 17:16:14 +0200 Subject: [PATCH 014/216] cleaning the code --- .../workbench/contrib/inlineChat/browser/inlineChatSession.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts index 79ac5e76633a1..83aa7b064ff7d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatSession.ts @@ -477,7 +477,6 @@ export class InlineChatSessionService implements IInlineChatSessionService { } releaseSession(session: Session): void { - console.log('at the beginning of the release session'); const { editor } = session; @@ -499,7 +498,6 @@ export class InlineChatSessionService implements IInlineChatSessionService { // send telemetry this._telemetryService.publicLog2('interactiveEditor/session', session.asTelemetryData()); - console.log('at the end of the release session'); } getSession(editor: ICodeEditor, uri: URI): Session | undefined { From 15db6060dbea7e707191c96fb62f63dedfda335f Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 13 Jul 2023 17:16:24 +0200 Subject: [PATCH 015/216] cleaning the code --- .../inlineChat/browser/inlineChatActions.ts | 1 - .../browser/inlineChatController.ts | 71 ++----------------- .../browser/inlineChatStrategies.ts | 5 +- 3 files changed, 7 insertions(+), 70 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index b65aab455b63a..83b481739a1cc 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -67,7 +67,6 @@ export class StartSessionAction extends EditorAction2 { options = arg; } InlineChatController.get(editor)?.run(options); - InlineChatController.get(editor)?.run(options); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 55b8744a5f885..39436818fa9aa 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -134,7 +134,6 @@ export class InlineChatController implements IEditorContribution { } this._log('session RESUMING', e); - console.log('before calling create session of the constructor'); await this._nextState(State.CREATE_SESSION, { existingSession }); this._log('session done or paused'); })); @@ -142,9 +141,7 @@ export class InlineChatController implements IEditorContribution { } dispose(): void { - console.log('inside of dispose'); this._stashedSession.clear(); - console.log('inside of dispose before calling finishExistingSession'); this.finishExistingSession(); this._store.dispose(); this._log('controller disposed'); @@ -221,18 +218,6 @@ export class InlineChatController implements IEditorContribution { } private async [State.CREATE_SESSION](options: InlineChatRunOptions): Promise { - console.log('inside of CREATE_SESSION'); - console.log('inside of CREATE_SESSION, this._activeSession : ', this._activeSession); - // if (this._activeSession) { - // console.log('inside of CREATE_SESSION, before clearing the session store'); - // this._sessionStore.clear(); - // console.log('inside of CREATE_SESSION, before releasing the session'); - // this._inlineChatSessionService.releaseSession(this._activeSession); - // console.log('inside of CREATE_SESSION, before calling pause'); - // // Doesn't appear to be properly awaited? - // await this[State.PAUSE](); - // } - console.log('inside of CREATE_SESSION, this._activeSession after the cleaning : ', this._activeSession); assertType(this._activeSession === undefined); assertType(this._editor.hasModel()); @@ -245,7 +230,6 @@ export class InlineChatController implements IEditorContribution { if (!session) { const createSessionCts = new CancellationTokenSource(); const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of CREATE_SESSION, inside of the msgListener code of CREATE_SESSION'); this._log('state=_createSession) message received', m); if (m === Message.ACCEPT_INPUT) { // user accepted the input before having a session @@ -297,8 +281,6 @@ export class InlineChatController implements IEditorContribution { } private async [State.INIT_UI](options: InlineChatRunOptions): Promise { - console.log('inside of init ui'); - console.log('inside of INIT_UI, this._activeSession : ', this._activeSession); assertType(this._activeSession); // hide/cancel inline completions when invoking IE @@ -359,20 +341,16 @@ export class InlineChatController implements IEditorContribution { if (editIsOutsideOfWholeRange) { this._log('text changed outside of whole range, FINISH session'); - console.log('inside of INIT_UI, before calling finish existing session'); this.finishExistingSession(); } })); if (!this._activeSession.lastExchange) { - console.log('inside of INIT_UI,before waiting for input'); return State.WAIT_FOR_INPUT; } else if (options.isUnstashed) { delete options.isUnstashed; - console.log('inside of INIT_UI,before apply response'); return State.APPLY_RESPONSE; } else { - console.log('inside of INIT_UI,before show response'); return State.SHOW_RESPONSE; } } @@ -391,9 +369,7 @@ export class InlineChatController implements IEditorContribution { } - private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { - console.log('inside of wait for input'); - + private async [State.WAIT_FOR_INPUT](options: InlineChatRunOptions): Promise { assertType(this._activeSession); assertType(this._strategy); @@ -414,7 +390,6 @@ export class InlineChatController implements IEditorContribution { } else { const barrier = new Barrier(); const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of WAIT_FOR_INPUT, inside of msgListener'); this._log('state=_waitForInput) message received', m); message = m; barrier.open(); @@ -426,17 +401,11 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.selectAll(); if (message & (Message.CANCEL_INPUT | Message.CANCEL_SESSION)) { - console.log('inside of WAIT_FOR_INPUT,entered into the case when message cancel session'); - await this[State.CANCEL](); - return; - // return State.CANCEL; + return State.CANCEL; } if (message & Message.ACCEPT_SESSION) { - console.log('inside of WAIT_FOR_INPUT,entered into the case when message accept'); - await this[State.ACCEPT](); - return; - // return State.ACCEPT; + return State.ACCEPT; } if (message & Message.PAUSE_SESSION) { @@ -488,7 +457,6 @@ export class InlineChatController implements IEditorContribution { let message = Message.NONE; const msgListener = Event.once(this._messages.event)(m => { - console.log('inside of MAKE_REQUEST, inside of msgListener of MAKE REQUEST'); this._log('state=_makeRequest) message received', m); message = m; requestCts.cancel(); @@ -543,12 +511,10 @@ export class InlineChatController implements IEditorContribution { this._activeSession.addExchange(new SessionExchange(this._activeSession.lastInput, response)); if (message & Message.CANCEL_SESSION) { - console.log('inside of MAKE_REQUEST, cancelling the session'); return State.CANCEL; } else if (message & Message.PAUSE_SESSION) { return State.PAUSE; } else if (message & Message.ACCEPT_SESSION) { - console.log('inside of MAKE_REQUEST, accepting'); return State.ACCEPT; } else { return State.APPLY_RESPONSE; @@ -664,8 +630,6 @@ export class InlineChatController implements IEditorContribution { private async [State.PAUSE]() { - console.log('entered into pause state'); - this._ctxDidEdit.reset(); this._ctxUserDidEdit.reset(); this._ctxLastResponseType.reset(); @@ -685,35 +649,24 @@ export class InlineChatController implements IEditorContribution { } private async [State.ACCEPT]() { - console.log('inside of State.ACCEPT'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); - console.log('inside of State.ACCEPT, after assert type'); try { - console.log('inside of State.ACCEPT, before strategy apply'); await this._strategy.apply(); - console.log('inside of State.ACCEPT, after strategy apply'); - // TODO: ASK WHY DESPITE AWAIT, AFTER STRATEFY NOT PRINTED BEFORE CREATE SESSION } catch (err) { - console.log('inside of State.ACCEPT, when error obtained'); this._dialogService.error(localize('err.apply', "Failed to apply changes.", toErrorMessage(err))); this._log('FAILED to apply changes'); this._log(err); } - console.log('inside of State.ACCEPT, before release session'); - - await this._inlineChatSessionService.releaseSession(this._activeSession); + this._inlineChatSessionService.releaseSession(this._activeSession); - console.log('inside of State.ACCEPT, before state pause'); - await this[State.PAUSE](); + this[State.PAUSE](); } private async [State.CANCEL]() { - console.log('inside of cancel the session'); - assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -728,14 +681,13 @@ export class InlineChatController implements IEditorContribution { this._log(err); } - await this[State.PAUSE](); + this[State.PAUSE](); this._stashedSession.clear(); if (!mySession.isUnstashed && mySession.lastExchange) { // only stash sessions that had edits this._stashedSession.value = this._instaService.createInstance(StashedSession, this._editor, mySession); } else { - console.log('before releasing the session of cancel'); this._inlineChatSessionService.releaseSession(mySession); } } @@ -815,13 +767,10 @@ export class InlineChatController implements IEditorContribution { } acceptSession(): void { - console.log('inside of acceptSession method'); - // Will fire a message which will be picked up by the controller and some other code will be run this._messages.fire(Message.ACCEPT_SESSION); } cancelSession() { - console.log('inside of cancelSession'); let result: string | undefined; if (this._strategy && this._activeSession) { const changedText = this._activeSession.asChangedText(); @@ -836,21 +785,15 @@ export class InlineChatController implements IEditorContribution { } finishExistingSession(): void { - console.log('inside of finish existing session'); - console.log(this._activeSession); if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); - console.log('before cancelling inside of finish existing session'); this.cancelSession(); } else { this._log('finishing existing session, using APPLY', this._activeSession.editMode); - console.log('before accepting inside of finish existing session'); this.acceptSession(); } - } - console.log('at the end of finish existing session'); } unstashLastSession(): Session | undefined { @@ -879,7 +822,6 @@ class StashedSession { this._ctxHasStashedSession.set(true); this._listener = Event.once(Event.any(editor.onDidChangeCursorSelection, editor.onDidChangeModelContent, editor.onDidChangeModel))(() => { this._session = undefined; - console.log('before release session of constructor'); this._sessionService.releaseSession(session); this._ctxHasStashedSession.reset(); }); @@ -889,7 +831,6 @@ class StashedSession { this._listener.dispose(); this._ctxHasStashedSession.reset(); if (this._session) { - console.log('before release session of dispose'); this._sessionService.releaseSession(this._session); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 019a4ecfd58d5..36e64ebf09af5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -86,7 +86,7 @@ export class PreviewStrategy extends EditModeStrategy { } async apply() { - console.log('at the beginning of the apply method of preview strategy'); + if (!(this._session.lastExchange?.response instanceof EditResponse)) { return; } @@ -107,7 +107,6 @@ export class PreviewStrategy extends EditModeStrategy { modelN.pushStackElement(); } } - console.log('at the end of the apply method of preview strategy'); } async cancel(): Promise { @@ -280,7 +279,6 @@ export class LiveStrategy extends EditModeStrategy { } async apply() { - console.log('inside of apply of live strategy'); if (this._editCount > 0) { this._editor.pushUndoStop(); } @@ -288,7 +286,6 @@ export class LiveStrategy extends EditModeStrategy { await this._bulkEditService.apply(this._lastResponse.workspaceEdits); this._instaService.invokeFunction(showSingleCreateFile, this._lastResponse); } - console.log('at the end of apply for live strategy'); } async cancel() { From 670b39eb0acb9d67789514ffd7a41072b4f07f75 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 10:04:05 +0200 Subject: [PATCH 016/216] adding console logs to understand where the error comes from --- src/vs/editor/browser/widget/diffEditorWidget.ts | 3 +++ .../contrib/inlineChat/browser/inlineChatStrategies.ts | 1 + .../contrib/inlineChat/browser/inlineChatWidget.ts | 7 +++++++ .../inlineChat/test/browser/inlineChatController.test.ts | 1 + 4 files changed, 12 insertions(+) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index e21c1e9f3beb2..47afe24c19605 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -709,6 +709,8 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public override dispose(): void { + console.log('inside of the dispose method'); + this._codeEditorService.removeDiffEditor(this); if (this._beginUpdateDecorationsTimeout !== -1) { @@ -884,6 +886,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._onDidChangeModel.fire(); // Diff navigator + console.log('right before the final _register'); this._diffNavigator = this._register(this._instantiationService.createInstance(DiffNavigator, this, { alwaysRevealFirst: false, findResultLoop: this.getModifiedEditor().getOption(EditorOption.find).loop diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 36e64ebf09af5..bbc2aabacb852 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -126,6 +126,7 @@ export class PreviewStrategy extends EditModeStrategy { const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); this._widget.showEditsPreview(this._session.textModel0, edits, this._session.lastTextModelChanges); } else { + console.log('inside of render changes'); this._widget.hideEditsPreview(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index adb0b77635e8f..a7cc7801921c5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -521,6 +521,7 @@ export class InlineChatWidget { this._elements.statusToolbar.classList.add('hidden'); this._elements.feedbackToolbar.classList.add('hidden'); this.hideCreatePreview(); + console.log('inside of reset'); this.hideEditsPreview(); this._onDidChangeHeight.fire(); } @@ -537,6 +538,7 @@ export class InlineChatWidget { showEditsPreview(textModelv0: ITextModel, edits: ISingleEditOperation[], changes: readonly LineRangeMapping[]) { if (changes.length === 0) { + console.log('inside of show edits preview'); this.hideEditsPreview(); return; } @@ -546,6 +548,7 @@ export class InlineChatWidget { const languageSelection: ILanguageSelection = { languageId: textModelv0.getLanguageId(), onDidChange: Event.None }; const modified = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModelv0.createSnapshot()), languageSelection, undefined, true); modified.applyEdits(edits, false); + console.log('inside of show edits preview'); this._previewDiffEditor.value.setModel({ original: textModelv0, modified }); // joined ranges @@ -577,7 +580,10 @@ export class InlineChatWidget { } hideEditsPreview() { + // Error happens because this is called after the diff editor widget is already disposed. + console.log('inside of hide edits preview'); this._elements.previewDiff.classList.add('hidden'); + // TODO: error is happening here this._previewDiffEditor.value.setModel(null); this._previewDiffModel.clear(); this._onDidChangeHeight.fire(); @@ -834,6 +840,7 @@ export class InlineChatZoneWidget extends ZoneWidget { } override hide(): void { + console.log('inside of hide'); this.container!.classList.remove('inside-selection'); this._ctxVisible.reset(); this._ctxCursorPosition.reset(); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index f4ba0f5b182ac..7733da149efea 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -156,6 +156,7 @@ suite('InteractiveChatController', function () { await run; + console.log('ctrl.getWidgetPosition() : ', ctrl.getWidgetPosition()); assert.ok(ctrl.getWidgetPosition() === undefined); }); From bd27bb08e5d3984ee288c4a9c4eba233625acd3c Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Fri, 14 Jul 2023 10:33:19 +0200 Subject: [PATCH 017/216] not sure what is causing the test errors --- .../browser/inlineChatController.ts | 4 +++ .../test/browser/inlineChatController.test.ts | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 39436818fa9aa..3306e796ce37e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -176,6 +176,7 @@ export class InlineChatController implements IEditorContribution { private _currentRun?: Promise; async run(options: InlineChatRunOptions | undefined = {}): Promise { + console.log('inside of run'); this.finishExistingSession(); if (this._currentRun) { await this._currentRun; @@ -667,6 +668,7 @@ export class InlineChatController implements IEditorContribution { } private async [State.CANCEL]() { + console.log('inside of cancel function'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -771,6 +773,7 @@ export class InlineChatController implements IEditorContribution { } cancelSession() { + console.log('inside of the cancel session method'); let result: string | undefined; if (this._strategy && this._activeSession) { const changedText = this._activeSession.asChangedText(); @@ -785,6 +788,7 @@ export class InlineChatController implements IEditorContribution { } finishExistingSession(): void { + console.log('inside of finish existing session'); if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 7733da149efea..1ace231c30b32 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -141,27 +141,39 @@ suite('InteractiveChatController', function () { }); test('creation, not showing anything', function () { + console.log('*** at the begining of creation'); ctrl = instaService.createInstance(TestController, editor); assert.ok(ctrl); assert.strictEqual(ctrl.getWidgetPosition(), undefined); + console.log('*** at the end of creation'); }); test('run (show/hide)', async function () { + console.log('*** at the beginning of run (show/hide)'); + ctrl = instaService.createInstance(TestController, editor); + console.log('right before run'); const run = ctrl.run({ message: 'Hello', autoSend: true }); + console.log('right before waitFor INIT_SEQUENCE_AUTO_SEND'); await ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); assert.ok(ctrl.getWidgetPosition() !== undefined); + + console.log('right before cancel session'); ctrl.cancelSession(); + console.log('right before run'); await run; console.log('ctrl.getWidgetPosition() : ', ctrl.getWidgetPosition()); assert.ok(ctrl.getWidgetPosition() === undefined); + console.log('*** at the end of run (show/hide)'); }); test('wholeRange expands to whole lines, editor selection default', async function () { + console.log('*** at the beginning of wholeRange, editor selection default'); + editor.setSelection(new Range(1, 1, 1, 3)); ctrl = instaService.createInstance(TestController, editor); @@ -186,10 +198,14 @@ suite('InteractiveChatController', function () { ctrl.cancelSession(); d.dispose(); + + console.log('*** at the end of wholeRange, editor selection default'); }); test('wholeRange expands to whole lines, session provided', async function () { + console.log('*** at the beginning of wholeRange, editor selection provided'); + editor.setSelection(new Range(1, 1, 1, 1)); ctrl = instaService.createInstance(TestController, editor); @@ -215,9 +231,14 @@ suite('InteractiveChatController', function () { ctrl.cancelSession(); d.dispose(); + + console.log('*** at the end of wholeRange, editor selection provided'); }); test('typing outside of wholeRange finishes session', async function () { + + console.log('*** at the beginning of typing outside'); + ctrl = instaService.createInstance(TestController, editor); ctrl.run({ message: 'Hello', autoSend: true }); @@ -231,10 +252,14 @@ suite('InteractiveChatController', function () { editor.trigger('test', 'type', { text: 'a' }); await ctrl.waitFor([State.ACCEPT]); + + console.log('*** at the end of typing outside'); }); test('\'whole range\' isn\'t updated for edits outside whole range #4346', async function () { + console.log('*** at the beginning of whole range isnt updated'); + editor.setSelection(new Range(3, 1, 3, 1)); const d = inlineChatService.addProvider({ @@ -271,9 +296,14 @@ suite('InteractiveChatController', function () { await ctrl.waitFor([State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 4, 12)); + + console.log('*** at the end of whole range isnt updated'); }); test('Stuck inline chat widget #211', async function () { + + console.log('*** at the beginning of stuck inline chat'); + const d = inlineChatService.addProvider({ debugName: 'Unit Test', prepareInlineChatSession() { @@ -306,5 +336,7 @@ suite('InteractiveChatController', function () { await p; assert.strictEqual(ctrl.getWidgetPosition(), undefined); + + console.log('*** at the end of stuck inline chat'); }); }); From 2a19176aa6719e12975e4730990ef81fb5e757e8 Mon Sep 17 00:00:00 2001 From: yshaojun Date: Sun, 16 Jul 2023 14:55:48 +0800 Subject: [PATCH 018/216] fix: modified editor width(#175397) --- src/vs/editor/browser/widget/diffEditorWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index e21c1e9f3beb2..123bbc901c0bb 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -1368,7 +1368,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._originalDomNode.style.width = splitPoint + 'px'; this._originalDomNode.style.left = '0px'; - this._modifiedDomNode.style.width = (width - splitPoint) + 'px'; + this._modifiedDomNode.style.width = (width - splitPoint - DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH) + 'px'; this._modifiedDomNode.style.left = splitPoint + 'px'; this._overviewDomElement.style.top = '0px'; From 99e31ae2973504e8bcb478991724af814afb7a71 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 18 Jul 2023 09:47:46 -0700 Subject: [PATCH 019/216] Add terminal selection API Part of #188173 --- .../src/singlefolder-tests/terminal.test.ts | 23 ++++++++++++++++++- .../api/browser/mainThreadTerminalService.ts | 1 + .../workbench/api/common/extHost.protocol.ts | 1 + .../api/common/extHostTerminalService.ts | 12 ++++++++++ .../contrib/terminal/browser/terminal.ts | 2 ++ .../terminal/browser/terminalInstance.ts | 3 +++ .../terminal/browser/terminalService.ts | 5 +++- .../common/extensionsApiProposals.ts | 1 + .../vscode.proposed.terminalSelection.d.ts | 16 +++++++++++++ 9 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.terminalSelection.d.ts diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index b2a8bb3a9cc8c..ef0becabd912d 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { deepStrictEqual, doesNotThrow, equal, ok, strictEqual, throws } from 'assert'; -import { ConfigurationTarget, Disposable, env, EnvironmentVariableCollection, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EnvironmentVariableScope, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode'; +import { commands, ConfigurationTarget, Disposable, env, EnvironmentVariableCollection, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EnvironmentVariableScope, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode'; import { assertNoRpc, poll } from '../utils'; // Disable terminal tests: @@ -347,6 +347,27 @@ import { assertNoRpc, poll } from '../utils'; }); }); + suite('selection', () => { + test('should be undefined immediately after creation', async () => { + const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); + equal(terminal.selection, undefined); + terminal.dispose(); + }); + test('should be defined after selecting all content', async () => { + const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); + await commands.executeCommand('workbench.action.terminal.selectAll'); + // TODO: Need to poll? + terminal.dispose(); + }); + test('should be undefined after clearing a selection', async () => { + const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); + await commands.executeCommand('workbench.action.terminal.selectAll'); + // TODO: Need to poll? + await commands.executeCommand('workbench.action.terminal.clearSelection'); + terminal.dispose(); + }); + }); + suite('window.onDidWriteTerminalData', () => { test('should listen to all future terminal data events', (done) => { const openEvents: string[] = []; diff --git a/src/vs/workbench/api/browser/mainThreadTerminalService.ts b/src/vs/workbench/api/browser/mainThreadTerminalService.ts index b2f7c9a7dd300..ffa8985978f8b 100644 --- a/src/vs/workbench/api/browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/browser/mainThreadTerminalService.ts @@ -85,6 +85,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape this._store.add(_terminalService.onDidChangeActiveInstance(instance => this._onActiveTerminalChanged(instance ? instance.instanceId : null))); this._store.add(_terminalService.onDidChangeInstanceTitle(instance => instance && this._onTitleChanged(instance.instanceId, instance.title))); this._store.add(_terminalService.onDidInputInstanceData(instance => this._proxy.$acceptTerminalInteraction(instance.instanceId))); + this._store.add(_terminalService.onDidChangeSelection(instance => this._proxy.$acceptTerminalSelection(instance.instanceId, instance.selection))); // Set initial ext host state for (const instance of this._terminalService.instances) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 0dc33e49a04ca..3ea49c6ad1ee0 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2012,6 +2012,7 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalDimensions(id: number, cols: number, rows: number): void; $acceptTerminalMaximumDimensions(id: number, cols: number, rows: number): void; $acceptTerminalInteraction(id: number): void; + $acceptTerminalSelection(id: number, selection: string | undefined): void; $startExtensionTerminal(id: number, initialDimensions: ITerminalDimensionsDto | undefined): Promise; $acceptProcessAckDataEvent(id: number, charCount: number): void; $acceptProcessInput(id: number, data: string): void; diff --git a/src/vs/workbench/api/common/extHostTerminalService.ts b/src/vs/workbench/api/common/extHostTerminalService.ts index a6c7fd37e18b4..96e5d9671353b 100644 --- a/src/vs/workbench/api/common/extHostTerminalService.ts +++ b/src/vs/workbench/api/common/extHostTerminalService.ts @@ -75,6 +75,7 @@ export class ExtHostTerminal { private _rows: number | undefined; private _exitStatus: vscode.TerminalExitStatus | undefined; private _state: vscode.TerminalState = { isInteractedWith: false }; + private _selection: string | undefined; public isOpen: boolean = false; @@ -106,6 +107,9 @@ export class ExtHostTerminal { get state(): vscode.TerminalState { return that._state; }, + get selection(): string | undefined { + return that._selection; + }, sendText(text: string, addNewLine: boolean = true): void { that._checkDisposed(); that._proxy.$sendText(that._id, text, addNewLine); @@ -233,6 +237,10 @@ export class ExtHostTerminal { return false; } + public setSelection(selection: string | undefined): void { + this._selection = selection; + } + public _setProcessId(processId: number | undefined): void { // The event may fire 2 times when the panel is restored if (this._pidPromiseComplete) { @@ -615,6 +623,10 @@ export abstract class BaseExtHostTerminalService extends Disposable implements I } } + public $acceptTerminalSelection(id: number, selection: string | undefined): void { + this._getTerminalById(id)?.setSelection(selection); + } + public $acceptProcessResize(id: number, cols: number, rows: number): void { try { this._terminalProcesses.get(id)?.resize(cols, rows); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index f57623f7ca81c..ec146c02cf6b1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -176,6 +176,7 @@ export interface ITerminalService extends ITerminalInstanceHost { readonly onDidChangeInstanceColor: Event<{ instance: ITerminalInstance; userInitiated: boolean }>; readonly onDidChangeInstancePrimaryStatus: Event; readonly onDidInputInstanceData: Event; + readonly onDidChangeSelection: Event; readonly onDidRegisterProcessSupport: Event; readonly onDidChangeConnectionState: Event; @@ -546,6 +547,7 @@ export interface ITerminalInstance { onDidRequestFocus: Event; onDidBlur: Event; onDidInputData: Event; + onDidChangeSelection: Event; /** * An event that fires when a terminal is dropped on this instance via drag and drop. diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 6889de6378d64..711504aef6c59 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -316,6 +316,8 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { readonly onDidBlur = this._onDidBlur.event; private readonly _onDidInputData = this._register(new Emitter()); readonly onDidInputData = this._onDidInputData.event; + private readonly _onDidChangeSelection = this._register(new Emitter()); + readonly onDidChangeSelection = this._onDidChangeSelection.event; private readonly _onRequestAddInstanceToGroup = this._register(new Emitter()); readonly onRequestAddInstanceToGroup = this._onRequestAddInstanceToGroup.event; private readonly _onDidChangeHasChildProcesses = this._register(new Emitter()); @@ -1722,6 +1724,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } private async _onSelectionChange(): Promise { + this._onDidChangeSelection.fire(this); if (this._configurationService.getValue(TerminalSettingId.CopyOnSelection)) { if (this.hasSelection()) { await this.copySelection(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index b0e8a2c08cdc7..27ff2a40b233c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -152,6 +152,8 @@ export class TerminalService implements ITerminalService { get onDidChangeInstancePrimaryStatus(): Event { return this._onDidChangeInstancePrimaryStatus.event; } private readonly _onDidInputInstanceData = new Emitter(); get onDidInputInstanceData(): Event { return this._onDidInputInstanceData.event; } + private readonly _onDidChangeSelection = new Emitter(); + get onDidChangeSelection(): Event { return this._onDidChangeSelection.event; } private readonly _onDidDisposeGroup = new Emitter(); get onDidDisposeGroup(): Event { return this._onDidDisposeGroup.event; } private readonly _onDidChangeGroups = new Emitter(); @@ -836,7 +838,8 @@ export class TerminalService implements ITerminalService { instance.onMaximumDimensionsChanged(() => this._onDidMaxiumumDimensionsChange.fire(instance)), instance.onDidInputData(this._onDidInputInstanceData.fire, this._onDidInputInstanceData), instance.onDidFocus(this._onDidChangeActiveInstance.fire, this._onDidChangeActiveInstance), - instance.onRequestAddInstanceToGroup(async e => await this._addInstanceToGroup(instance, e)) + instance.onRequestAddInstanceToGroup(async e => await this._addInstanceToGroup(instance, e)), + instance.onDidChangeSelection(this._onDidChangeSelection.fire, this._onDidChangeSelection) ]; instance.onDisposed(() => dispose(instanceDisposables)); } diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index d42bf233a5c69..51dd25db0e5ad 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -86,6 +86,7 @@ export const allApiProposals = Object.freeze({ terminalDataWriteEvent: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDataWriteEvent.d.ts', terminalDimensions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalDimensions.d.ts', terminalQuickFixProvider: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalQuickFixProvider.d.ts', + terminalSelection: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.terminalSelection.d.ts', testCoverage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testCoverage.d.ts', testInvalidateResults: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testInvalidateResults.d.ts', testObserver: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.testObserver.d.ts', diff --git a/src/vscode-dts/vscode.proposed.terminalSelection.d.ts b/src/vscode-dts/vscode.proposed.terminalSelection.d.ts new file mode 100644 index 0000000000000..706bcdafe104f --- /dev/null +++ b/src/vscode-dts/vscode.proposed.terminalSelection.d.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + // https://github.com/microsoft/vscode/issues/188173 + + export interface Terminal { + /** + * The selected text of the terminal or undefined if there is no selection. + */ + readonly selection: string | undefined; + } +} From 775102bd8297c6b2daa133d11b2d0af2cbf335b7 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 18 Jul 2023 09:58:47 -0700 Subject: [PATCH 020/216] Implement tests --- .../src/singlefolder-tests/terminal.test.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index ef0becabd912d..1728c3263ef62 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { deepStrictEqual, doesNotThrow, equal, ok, strictEqual, throws } from 'assert'; +import { deepStrictEqual, doesNotThrow, equal, notEqual, ok, strictEqual, throws } from 'assert'; import { commands, ConfigurationTarget, Disposable, env, EnvironmentVariableCollection, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EnvironmentVariableScope, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode'; import { assertNoRpc, poll } from '../utils'; @@ -349,21 +349,25 @@ import { assertNoRpc, poll } from '../utils'; suite('selection', () => { test('should be undefined immediately after creation', async () => { - const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); + const terminal = window.createTerminal({ name: 'selection test' }); + terminal.show(); equal(terminal.selection, undefined); terminal.dispose(); }); test('should be defined after selecting all content', async () => { - const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); + const terminal = window.createTerminal({ name: 'selection test' }); + terminal.show(); await commands.executeCommand('workbench.action.terminal.selectAll'); - // TODO: Need to poll? + await poll(() => Promise.resolve(), () => terminal.selection !== undefined, 'selection should be defined'); terminal.dispose(); }); test('should be undefined after clearing a selection', async () => { - const terminal = window.createTerminal({ name: 'bg', hideFromUser: true }); + const terminal = window.createTerminal({ name: 'selection test' }); + terminal.show(); await commands.executeCommand('workbench.action.terminal.selectAll'); - // TODO: Need to poll? + await poll(() => Promise.resolve(), () => terminal.selection !== undefined, 'selection should be defined'); await commands.executeCommand('workbench.action.terminal.clearSelection'); + await poll(() => Promise.resolve(), () => terminal.selection === undefined, 'selection should not be defined'); terminal.dispose(); }); }); From 49df64f6acbd7d9b48fb49bd1b5d92c433070621 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 18 Jul 2023 10:17:00 -0700 Subject: [PATCH 021/216] Remove unused import --- .../vscode-api-tests/src/singlefolder-tests/terminal.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 1728c3263ef62..50197899110a7 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { deepStrictEqual, doesNotThrow, equal, notEqual, ok, strictEqual, throws } from 'assert'; +import { deepStrictEqual, doesNotThrow, equal, ok, strictEqual, throws } from 'assert'; import { commands, ConfigurationTarget, Disposable, env, EnvironmentVariableCollection, EnvironmentVariableMutator, EnvironmentVariableMutatorOptions, EnvironmentVariableMutatorType, EnvironmentVariableScope, EventEmitter, ExtensionContext, extensions, ExtensionTerminalOptions, Pseudoterminal, Terminal, TerminalDimensions, TerminalExitReason, TerminalOptions, TerminalState, UIKind, Uri, window, workspace } from 'vscode'; import { assertNoRpc, poll } from '../utils'; From 1e0a1f58214354089193a0ed03e5b5b722ba4132 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 18 Jul 2023 20:28:08 +0200 Subject: [PATCH 022/216] Introduces onlyShowAccessibleDiffViewer. Fixes #182789 --- .../editor/browser/widget/diffEditorWidget.ts | 2 + .../diffEditorWidget2/accessibleDiffViewer.ts | 41 +++++++++++-------- .../diffEditorWidget2/diffEditorOptions.ts | 3 ++ .../diffEditorWidget2/diffEditorWidget2.ts | 9 +++- src/vs/editor/common/config/editorOptions.ts | 5 +++ src/vs/monaco.d.ts | 4 ++ .../inlineChat/browser/inlineChatActions.ts | 4 +- .../browser/inlineChatLivePreviewWidget.ts | 5 ++- .../inlineChat/browser/inlineChatWidget.ts | 5 ++- 9 files changed, 55 insertions(+), 23 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index e21c1e9f3beb2..95c5bea3fe891 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -299,6 +299,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE collapseUnchangedRegions: false, }, isInEmbeddedEditor: false, + onlyShowAccessibleDiffViewer: false, }); this.isEmbeddedDiffEditorKey = EditorContextKeys.isEmbeddedDiffEditor.bindTo(this._contextKeyService); @@ -2743,6 +2744,7 @@ function validateDiffEditorOptions(options: Readonly, defaul collapseUnchangedRegions: false, }, isInEmbeddedEditor: validateBooleanOption(options.isInEmbeddedEditor, defaults.isInEmbeddedEditor), + onlyShowAccessibleDiffViewer: false, }; } diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts index 346f847085edf..94c20eb68da9c 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts @@ -42,7 +42,9 @@ const diffReviewCloseIcon = registerIcon('diff-review-close', Codicon.close, loc export class AccessibleDiffViewer extends Disposable { constructor( private readonly _parentNode: HTMLElement, - private readonly _visible: ISettableObservable, + private readonly _visible: IObservable, + private readonly _setVisible: (visible: boolean, tx: ITransaction | undefined) => void, + private readonly _canClose: IObservable, private readonly _width: IObservable, private readonly _height: IObservable, private readonly _diffs: IObservable, @@ -59,7 +61,7 @@ export class AccessibleDiffViewer extends Disposable { if (!visible) { return null; } - const model = store.add(this._instantiationService.createInstance(ViewModel, this._diffs, this._editors, this._visible)); + const model = store.add(this._instantiationService.createInstance(ViewModel, this._diffs, this._editors, this._setVisible, this._canClose)); const view = store.add(this._instantiationService.createInstance(View, this._parentNode, model, this._width, this._height, this._editors)); return { model, @@ -70,7 +72,7 @@ export class AccessibleDiffViewer extends Disposable { next(): void { transaction(tx => { const isVisible = this._visible.get(); - this._visible.set(true, tx); + this._setVisible(true, tx); if (isVisible) { this.model.get()!.model.nextGroup(tx); } @@ -79,14 +81,14 @@ export class AccessibleDiffViewer extends Disposable { prev(): void { transaction(tx => { - this._visible.set(true, tx); + this._setVisible(true, tx); this.model.get()!.model.previousGroup(tx); }); } close(): void { transaction(tx => { - this._visible.set(false, tx); + this._setVisible(false, tx); }); } } @@ -104,12 +106,11 @@ class ViewModel extends Disposable { public readonly currentElement: IObservable = this._currentElementIdx.map((idx, r) => this.currentGroup.read(r)?.lines[idx]); - public readonly canClose: IObservable = constObservable(true); - constructor( private readonly _diffs: IObservable, private readonly _editors: DiffEditorEditors, - private readonly _visible: ISettableObservable, + private readonly _setVisible: (visible: boolean, tx: ITransaction | undefined) => void, + public readonly canClose: IObservable, @IAudioCueService private readonly _audioCueService: IAudioCueService, ) { super(); @@ -192,7 +193,7 @@ class ViewModel extends Disposable { } revealCurrentElementInEditor(): void { - this._visible.set(false, undefined); + this._setVisible(false, undefined); const curElem = this.currentElement.get(); if (curElem) { @@ -211,7 +212,7 @@ class ViewModel extends Disposable { } close(): void { - this._visible.set(false, undefined); + this._setVisible(false, undefined); this._editors.modified.focus(); } } @@ -338,14 +339,18 @@ class View extends Disposable { this._actionBar = this._register(new ActionBar( actionBarContainer )); - this._actionBar.push(new Action( - 'diffreview.close', - localize('label.close', "Close"), - 'close-diff-review ' + ThemeIcon.asClassName(diffReviewCloseIcon), - true, - async () => _model.close() - ), { label: false, icon: true }); - + this._register(autorun('update actions', reader => { + this._actionBar.clear(); + if (this._model.canClose.read(reader)) { + this._actionBar.push(new Action( + 'diffreview.close', + localize('label.close', "Close"), + 'close-diff-review ' + ThemeIcon.asClassName(diffReviewCloseIcon), + true, + async () => _model.close() + ), { label: false, icon: true }); + } + })); this._content = document.createElement('div'); this._content.className = 'diff-review-content'; diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorOptions.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorOptions.ts index fcec4e86f0926..eed16b994722a 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorOptions.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorOptions.ts @@ -46,6 +46,7 @@ export class DiffEditorOptions { public readonly accessibilityVerbose = derived('accessibilityVerbose', reader => this._options.read(reader).accessibilityVerbose); public readonly diffAlgorithm = derived('diffAlgorithm', reader => this._options.read(reader).diffAlgorithm); public readonly showEmptyDecorations = derived('showEmptyDecorations', reader => this._options.read(reader).experimental.showEmptyDecorations!); + public readonly onlyShowAccessibleDiffViewer = derived('onlyShowAccessibleDiffViewer', reader => this._options.read(reader).onlyShowAccessibleDiffViewer); public updateOptions(changedOptions: IDiffEditorOptions): void { const newDiffEditorOptions = validateDiffEditorOptions(changedOptions, this._options.get()); @@ -75,6 +76,7 @@ const diffEditorDefaultOptions: ValidDiffEditorBaseOptions = { showEmptyDecorations: true, }, isInEmbeddedEditor: false, + onlyShowAccessibleDiffViewer: false, }; function validateDiffEditorOptions(options: Readonly, defaults: ValidDiffEditorBaseOptions): ValidDiffEditorBaseOptions { @@ -99,5 +101,6 @@ function validateDiffEditorOptions(options: Readonly, defaul showEmptyDecorations: validateBooleanOption(options.experimental?.showEmptyDecorations, defaults.experimental.showEmptyDecorations!), }, isInEmbeddedEditor: validateBooleanOption(options.isInEmbeddedEditor, defaults.isInEmbeddedEditor), + onlyShowAccessibleDiffViewer: validateBooleanOption(options.onlyShowAccessibleDiffViewer, defaults.onlyShowAccessibleDiffViewer), }; } diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts index 82c59dd4ddcc0..86d7edb47b866 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts @@ -66,7 +66,12 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { private unchangedRangesFeature!: UnchangedRangesFeature; - private _accessibleDiffViewerVisible = observableValue('accessibleDiffViewerVisible', false); + private _accessibleDiffViewerShouldBeVisible = observableValue('accessibleDiffViewerShouldBeVisible', false); + private _accessibleDiffViewerVisible = derived('accessibleDiffViewerVisible', reader => + this._options.onlyShowAccessibleDiffViewer.read(reader) + ? true + : this._accessibleDiffViewerShouldBeVisible.read(reader) + ); private _accessibleDiffViewer!: AccessibleDiffViewer; private readonly _options: DiffEditorOptions; private readonly _editors: DiffEditorEditors; @@ -163,6 +168,8 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { readHotReloadableExport(AccessibleDiffViewer, reader), this.elements.accessibleDiffViewer, this._accessibleDiffViewerVisible, + (visible, tx) => this._accessibleDiffViewerShouldBeVisible.set(visible, tx), + this._options.onlyShowAccessibleDiffViewer.map(v => !v), this._rootSizeObserver.width, this._rootSizeObserver.height, this._diffModel.map((m, r) => m?.diff.read(r)?.mappings.map(m => m.lineRangeMapping)), diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 96dbb05c27523..5f30ccd744d22 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -827,6 +827,11 @@ export interface IDiffEditorBaseOptions { * Defaults to false */ isInEmbeddedEditor?: boolean; + + /** + * If the diff editor should only show the difference review mode. + */ + onlyShowAccessibleDiffViewer?: boolean; } /** diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index dbcada860e609..5011ecde3ccfc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3975,6 +3975,10 @@ declare namespace monaco.editor { * Defaults to false */ isInEmbeddedEditor?: boolean; + /** + * If the diff editor should only show the difference review mode. + */ + onlyShowAccessibleDiffViewer?: boolean; } /** diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 95fef85528594..341a38fb18111 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -7,7 +7,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; -import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; +import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget2 } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, InlineChatResponseType, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChateResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, MENU_INLINE_CHAT_WIDGET_TOGGLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -121,7 +121,7 @@ abstract class AbstractInlineChatAction extends EditorAction2 { if (!ctrl) { for (const diffEditor of accessor.get(ICodeEditorService).listDiffEditors()) { if (diffEditor.getOriginalEditor() === editor || diffEditor.getModifiedEditor() === editor) { - if (diffEditor instanceof EmbeddedDiffEditorWidget) { + if (diffEditor instanceof EmbeddedDiffEditorWidget2) { this.runEditorCommand(accessor, diffEditor.getParentEditor(), ..._args); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index 3f38c66ae4ea3..a743c224dee84 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -34,6 +34,7 @@ import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSessi import { ILanguageService } from 'vs/editor/common/languages/language'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { WordHighlighterContribution } from 'vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export class InlineChatLivePreviewWidget extends ZoneWidget { @@ -53,6 +54,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @ILogService private readonly _logService: ILogService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, ) { super(editor, { showArrow: false, showFrame: false, isResizeable: false, isAccessible: true, allowUnlimitedHeight: true, showInHiddenAreas: true, ordinal: 10000 + 1 }); super.create(); @@ -78,7 +80,8 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { stickyScroll: { enabled: false }, minimap: { enabled: false }, isInEmbeddedEditor: true, - overflowWidgetsDomNode: editor.getOverflowWidgetsDomNode() + overflowWidgetsDomNode: editor.getOverflowWidgetsDomNode(), + onlyShowAccessibleDiffViewer: this.accessibilityService.isScreenReaderOptimized(), }, { originalEditor: { contributions: diffContributions }, modifiedEditor: { contributions: diffContributions } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 4cde6edcb51c8..b0cc6198c47b2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -329,7 +329,10 @@ export class InlineChatWidget { this._store.add(feedbackToolbar); // preview editors - this._previewDiffEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget2, this._elements.previewDiff, _previewEditorEditorOptions, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor))); + this._previewDiffEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedDiffEditorWidget2, this._elements.previewDiff, { + ..._previewEditorEditorOptions, + onlyShowAccessibleDiffViewer: this._accessibilityService.isScreenReaderOptimized(), + }, { modifiedEditor: codeEditorWidgetOptions, originalEditor: codeEditorWidgetOptions }, parentEditor))); this._previewCreateTitle = this._store.add(_instantiationService.createInstance(ResourceLabel, this._elements.previewCreateTitle, { supportIcons: true })); this._previewCreateEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, parentEditor))); From c01bc972800eed802e72a0b02fc156bdbcf5a620 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 19 Jul 2023 01:36:43 +0200 Subject: [PATCH 023/216] Fixes CI --- .../browser/widget/diffEditorWidget2/accessibleDiffViewer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts index 94c20eb68da9c..cc0f3a5a7a04c 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts @@ -10,7 +10,7 @@ import { Action } from 'vs/base/common/actions'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, ISettableObservable, ITransaction, autorun, constObservable, derived, keepAlive, observableValue, transaction } from 'vs/base/common/observable'; +import { IObservable, ITransaction, autorun, derived, keepAlive, observableValue, transaction } from 'vs/base/common/observable'; import { autorunWithStore2 } from 'vs/base/common/observableImpl/autorun'; import { subtransaction } from 'vs/base/common/observableImpl/base'; import { derivedWithStore } from 'vs/base/common/observableImpl/derived'; From a323ac942e90027cd4097d5cb1a2b2101a8ee5b3 Mon Sep 17 00:00:00 2001 From: yshaojun Date: Wed, 19 Jul 2023 20:29:20 +0800 Subject: [PATCH 024/216] fix: modified editor width(diffEditor v2) --- .../widget/diffEditorWidget2/diffEditorWidget2.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts index 2e3980aac9292..e2119ef415ff9 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts @@ -235,19 +235,16 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { const sashLeft = this._sash.read(reader)?.sashLeft.read(reader); const originalWidth = sashLeft ?? Math.max(5, this._editors.original.getLayoutInfo().decorationsLeft); + const modifiedWidth = width - originalWidth - (this._options.renderOverviewRuler.read(reader) ? OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH : 0); this.elements.original.style.width = originalWidth + 'px'; this.elements.original.style.left = '0px'; - this.elements.modified.style.width = (width - originalWidth) + 'px'; + this.elements.modified.style.width = modifiedWidth + 'px'; this.elements.modified.style.left = originalWidth + 'px'; - this._editors.original.layout({ width: originalWidth, height: height }); - this._editors.modified.layout({ - width: width - originalWidth - - (this._options.renderOverviewRuler.read(reader) ? OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH : 0), - height - }); + this._editors.original.layout({ width: originalWidth, height }); + this._editors.modified.layout({ width: modifiedWidth, height }); this._reviewPane.layout(0, width, height); return { From 61611e4fe2ba3b192a8a992e46fb0715faaceedb Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 19 Jul 2023 16:01:20 +0200 Subject: [PATCH 025/216] Fixes Code QL alert (#188269) --- scripts/playground-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/playground-server.ts b/scripts/playground-server.ts index 9468087409fdd..1c2074ee19134 100644 --- a/scripts/playground-server.ts +++ b/scripts/playground-server.ts @@ -280,7 +280,7 @@ function makeLoaderJsHotReloadable(loaderJsCode: string, fileChangesUrl: URL): s if (___globalModuleManager._modules2[moduleId]) { const srcUrl = ___globalModuleManager._config.moduleIdToPaths(data.moduleId); const newSrc = await (await fetch(srcUrl)).text(); - (new Function('define', newSrc))(function (deps, callback) { + (new Function('define', newSrc))(function (deps, callback) { // CodeQL [SM01632] This code is only executed during development (as part of the dev-only playground-server). It is required for the hot-reload functionality. const oldModule = ___globalModuleManager._modules2[moduleId]; delete ___globalModuleManager._modules2[moduleId]; From 2b53df759299a2de97d5e483963300870a08f335 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 19 Jul 2023 17:08:47 +0200 Subject: [PATCH 026/216] Enables diffEditor.experimental.useVersion2 to be configured by experiments. (#188271) --- src/vs/editor/common/config/editorConfigurationSchema.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/editor/common/config/editorConfigurationSchema.ts b/src/vs/editor/common/config/editorConfigurationSchema.ts index 70dd0b515dbd5..e127ddc675edb 100644 --- a/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -216,6 +216,7 @@ const editorConfiguration: IConfigurationNode = { type: 'boolean', default: false, description: nls.localize('useVersion2', "Controls whether the diff editor uses the new or the old implementation."), + tags: ['experimental'], }, 'diffEditor.experimental.showEmptyDecorations': { type: 'boolean', From 53e7651c526f982591f8d134f791a643005f040d Mon Sep 17 00:00:00 2001 From: Robo Date: Wed, 19 Jul 2023 08:30:17 -0700 Subject: [PATCH 027/216] chore: update electron@22.3.17 (#188272) * chore: update electron@22.3.17 * chore: bump distro --- .yarnrc | 4 +-- build/checksums/electron.txt | 54 ++++++++++++++++++------------------ cgmanifest.json | 4 +-- package.json | 4 +-- yarn.lock | 8 +++--- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/.yarnrc b/.yarnrc index 3af2059dbd78d..4b56fb47de5f1 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,5 +1,5 @@ disturl "https://electronjs.org/headers" -target "22.3.14" -ms_build_id "21893604" +target "22.3.17" +ms_build_id "22432899" runtime "electron" build_from_source "true" diff --git a/build/checksums/electron.txt b/build/checksums/electron.txt index beb2b9730b077..d5265cab426d0 100644 --- a/build/checksums/electron.txt +++ b/build/checksums/electron.txt @@ -1,27 +1,27 @@ -3ba067c6f338f9a525c4b697e9cf8e3c3b3d9f6abfdfb11fba47e053da0f3496 *electron-v22.3.14-darwin-arm64-symbols.zip -c08bf19e11c006346b210585cf0803cd0b07107a362a2414cc185f6a228afbf2 *electron-v22.3.14-darwin-arm64.zip -72ced94e7230d3138dd84acbf38dc593d4a93ec796a3a478f99aa6974030d79c *electron-v22.3.14-darwin-x64-symbols.zip -77c1c96411326b00d3ef7c9f6af96a3b4c2fa2314196fce3374fcf734dd8dc67 *electron-v22.3.14-darwin-x64.zip -d847c59f3835749dcdd5376daefb3a5992f1ed5d7693f871328296e1388fb69d *electron-v22.3.14-linux-arm64-symbols.zip -95bb9ee160c60b50ff25b307fb8bc36bdb5297d43c6e366f0b835f36c4f327c9 *electron-v22.3.14-linux-arm64.zip -38a51d81f9ffe6e2ebf25844999fddbeb4edc63ae22136af1502db373bb024ab *electron-v22.3.14-linux-armv7l-symbols.zip -bf589c74f07fe11586ffcf8c122d34b91c5ced08d54532ee883d1e025b6d1b02 *electron-v22.3.14-linux-armv7l.zip -28d0eda61ea736375c549d0955f36b7d3e3c2019453ef83d793dae8b0d74f461 *electron-v22.3.14-linux-x64-symbols.zip -89b72e40fb8b9106deda3e6ffa30dd80beaa8f2e2a9d037b55c034a5a27a7b60 *electron-v22.3.14-linux-x64.zip -b9ba15fcf7c60cf57e95fae731bc0c336e131ed4fac91b4c59d50a28407ca0b0 *electron-v22.3.14-win32-arm64-pdb.zip -9f375d01feeb9e28f9c0913a4e22be900c0a7ff4e51449bb9b859ce1bd18f9f3 *electron-v22.3.14-win32-arm64-symbols.zip -17e354aca0683f79d79f7fa7ecfa8a4381b356d04fa45ec0aa85b5f048151c10 *electron-v22.3.14-win32-arm64.zip -900ca316ce939547ab62847c8833a78c1002a69b936be7e9af328a3518a7d379 *electron-v22.3.14-win32-ia32-pdb.zip -90af7a48b4e722a3436b6a8893540fb746d99b4832ca48c355a63fa0930f6446 *electron-v22.3.14-win32-ia32-symbols.zip -487d811c7cf3282f4c3a17b5ab7ab1fd71dbc585449d77da3a9bf052657ac4ad *electron-v22.3.14-win32-ia32.zip -41ce6c3d87c89f6b48aac74649657a120c28c78513908996dc20e57a640d4653 *electron-v22.3.14-win32-x64-pdb.zip -57b35bfa186b64a9dd1eb2bb85141bb998d0378bb20ac8038718b41d16deb978 *electron-v22.3.14-win32-x64-symbols.zip -f45eba3faa7e10fb1c6e5cf044dd42733a7c8cb455de57647b74e7510b0b94b6 *electron-v22.3.14-win32-x64.zip -16a75de6e3e4643589237e6e1c680c43b4e77fe04918bfbe4408775b7e616afc *ffmpeg-v22.3.14-darwin-arm64.zip -92db0c163c326d33a516ebfc56c7bd4faae9456f4238dde916c580b459b8dc8d *ffmpeg-v22.3.14-darwin-x64.zip -59d2e2b2f2cc515a86a4e0cfd1116d10a8b25a8d58d45bb04de3512e156c944b *ffmpeg-v22.3.14-linux-arm64.zip -b9d3b227bee17666d395ee7882ef477a733c3eeef3f1d9f2e3616d2d02eb3376 *ffmpeg-v22.3.14-linux-armv7l.zip -fa07ef910b23a4ef4b6761bc16d20c0e70ff0259325c4d523129e2d9c5084174 *ffmpeg-v22.3.14-linux-x64.zip -7f744b657ae7c26f80cae0f2771a00edd368350229b85118a246573987dd6ff1 *ffmpeg-v22.3.14-win32-arm64.zip -562e04d2cf1c970b6128d66d08dfe8d88a28e54adf599293eee2bd6c292fd16b *ffmpeg-v22.3.14-win32-ia32.zip -f69510384ef912fd9b4961f97357789a4a36e8df6ff382aeeab23fbb063def9a *ffmpeg-v22.3.14-win32-x64.zip +9ba54e68520fe94b15ab6224333f5e63538f78aa0356545130ac06e308b54a72 *electron-v22.3.17-darwin-arm64-symbols.zip +37aa86e637c1306aa0e7b382d3d3d23717ac4769114f00f5abd98a7b95a97a00 *electron-v22.3.17-darwin-arm64.zip +bfd7ffb2a4b2fa8f47a72ea35340c51b21bc3f0f6e85679e48eb30615110e7dd *electron-v22.3.17-darwin-x64-symbols.zip +87e063025bfd11b60cbb637ecf077dc52d53ba5ac76b828ab06a8c5a66b0b590 *electron-v22.3.17-darwin-x64.zip +1e885e3e10fc952e7c898b91fe0b327147d5ba2effa291a8dff63b84ed6f7f09 *electron-v22.3.17-linux-arm64-symbols.zip +d810449d93ddbe7ec81b3792dc4c7237c7ad52e03d15503316029afe95281ef6 *electron-v22.3.17-linux-arm64.zip +b1112192a53388754fc55018f1f5f116cd342d78cc2beedcd115f313c669975e *electron-v22.3.17-linux-armv7l-symbols.zip +647f375119b8611d9a6ec6b4837c246f2e02d6b5ac75eb21a7a0e8bd07717cc6 *electron-v22.3.17-linux-armv7l.zip +8d4c2743c8a8b4b48364bd27e05eb4e1d22b46ed93c978646fad37def2047722 *electron-v22.3.17-linux-x64-symbols.zip +030c540a88998112f8848f2ecc1429790bb90a19f1826eda0b4e89d2b6e2459c *electron-v22.3.17-linux-x64.zip +afa64d9f5523564d48a77c5e53f9ffdf9b9f144bd4ccf55577a8aa1532e7f5d8 *electron-v22.3.17-win32-arm64-pdb.zip +fd8dc02edf2b7120d9b79e06701001d192dc0007503961769bda3e680ad9cbc2 *electron-v22.3.17-win32-arm64-symbols.zip +80dc7fd40f47832757fb150b63cf3a6d14aad3d64b4891a7a3e2785bd4c98f2d *electron-v22.3.17-win32-arm64.zip +e30077316165162d8954e7af1bb0e3454587efb82c26a65269b0e5a0237a739e *electron-v22.3.17-win32-ia32-pdb.zip +feb05e2ee1555de1721b93613db52a4b42f3caa287b28b5e0918dee7d1d321ca *electron-v22.3.17-win32-ia32-symbols.zip +221e988045f9fd299bb00b27cdde31a7a6621a40cea3ad34efe584e988046766 *electron-v22.3.17-win32-ia32.zip +295c820c5f47ad02bdd5f23d5071ff551e14aa3b018a4daff4cb9b18694862a1 *electron-v22.3.17-win32-x64-pdb.zip +4fe6e8f71fe7ade774ff92a5f3dbb3d667185da3c4be10cf6ade5e2700e32cc6 *electron-v22.3.17-win32-x64-symbols.zip +9a3847ff2a2702a62b66a309368f7feced29294168155de6271d1409191441bf *electron-v22.3.17-win32-x64.zip +b1d258f2378b326d52ba1b4dbd55d49b8190e23ef00d3ccdec9407114242a8d8 *ffmpeg-v22.3.17-darwin-arm64.zip +7ad943f4bacff4379b751fd64db85adb68c2a0456c0fe4fe3d0eb2c50257bccf *ffmpeg-v22.3.17-darwin-x64.zip +59d2e2b2f2cc515a86a4e0cfd1116d10a8b25a8d58d45bb04de3512e156c944b *ffmpeg-v22.3.17-linux-arm64.zip +b9d3b227bee17666d395ee7882ef477a733c3eeef3f1d9f2e3616d2d02eb3376 *ffmpeg-v22.3.17-linux-armv7l.zip +fa07ef910b23a4ef4b6761bc16d20c0e70ff0259325c4d523129e2d9c5084174 *ffmpeg-v22.3.17-linux-x64.zip +a83e1314a9161ebf7ffae9c67bb9332f231f3b82e6ffb68de4704dd81489301c *ffmpeg-v22.3.17-win32-arm64.zip +725b025bbe8664733c5e5d6ee5d2baa36acabd415e0fbd21b056de968a1fe2cd *ffmpeg-v22.3.17-win32-ia32.zip +e4c76b9bb3826e8b56c720220cff0bd1fa1a34e69a26617cd3650bda452bff71 *ffmpeg-v22.3.17-win32-x64.zip diff --git a/cgmanifest.json b/cgmanifest.json index d5669cdf185e9..c2812272b5064 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -528,12 +528,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "4ade2a6fb65e4b723feb7c09a5df765e5006b378" + "commitHash": "047fdbf3ca1958e4e489f0e777bf4022540f5ec2" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "22.3.14" + "version": "22.3.17" }, { "component": { diff --git a/package.json b/package.json index a12ce8628838d..2708da44717e5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.81.0", - "distro": "97db68abc58fce4ef0a70cdaa6255836e6a8d085", + "distro": "183ff774d6e9be194f3ef3014b582d92a3b1ce9d", "author": { "name": "Microsoft Corporation" }, @@ -148,7 +148,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.8.0", - "electron": "22.3.14", + "electron": "22.3.17", "eslint": "8.36.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^39.3.2", diff --git a/yarn.lock b/yarn.lock index 4983356750544..393430cea74c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3582,10 +3582,10 @@ electron-to-chromium@^1.4.202: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.207.tgz#9c3310ebace2952903d05dcaba8abe3a4ed44c01" integrity sha512-piH7MJDJp4rJCduWbVvmUd59AUne1AFBJ8JaRQvk0KzNTSUnZrVXHCZc+eg+CGE4OujkcLJznhGKD6tuAshj5Q== -electron@22.3.14: - version "22.3.14" - resolved "https://registry.yarnpkg.com/electron/-/electron-22.3.14.tgz#539fc7d7b6df37483aaa351856a28e43092d550e" - integrity sha512-WxVcLnC4DrkBLN1/BwpxNkGvVq8iq1hM7lae5nvjnSYg/bwVbuo1Cwc80Keft4MIWKlYCXNiKKqs3qCXV4Aiaw== +electron@22.3.17: + version "22.3.17" + resolved "https://registry.yarnpkg.com/electron/-/electron-22.3.17.tgz#90a75f78cc761ed536d8210dd001e142fca78691" + integrity sha512-mo9qD1pOkiibvH+pgETsq9RZF0p3O5ACwxzjk3E2ozMYb9cnJenZyE3jxbs4WqzDCFi+rsm6WWahw3hlPhANXw== dependencies: "@electron/get" "^2.0.0" "@types/node" "^16.11.26" From 87209068c4bcc81dcd8d2fe3eb078d78d8d3a88f Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 09:03:58 -0700 Subject: [PATCH 028/216] fix #188278 --- src/vs/editor/browser/controller/textAreaHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index bc95af4fe9e61..3f7b62eb70675 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -553,7 +553,7 @@ export class TextAreaHandler extends ViewPart { private _getAriaLabel(options: IComputedEditorOptions): string { const accessibilitySupport = options.get(EditorOption.accessibilitySupport); if (accessibilitySupport === AccessibilitySupport.Disabled) { - return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press {0} for options.", platform.isLinux ? 'Shift+Alt+F1' : 'Alt+F1'); + return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Open the quick pick with {0} and run the command: Toggle Screen Reader Accessibility Mode for a screen reader optimized experience.", platform.isMacintosh ? 'Cmd+Shift+P' : 'Ctrl+Shift+P'); } return options.get(EditorOption.ariaLabel); } From 10ad83ca0f7f55da79f94f8326a09fa9635cc2f7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 09:22:25 -0700 Subject: [PATCH 029/216] add default keybinding for toggling screen reader mode --- src/vs/editor/browser/controller/textAreaHandler.ts | 2 +- .../codeEditor/browser/accessibility/accessibility.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 3f7b62eb70675..cefaa886f9967 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -553,7 +553,7 @@ export class TextAreaHandler extends ViewPart { private _getAriaLabel(options: IComputedEditorOptions): string { const accessibilitySupport = options.get(EditorOption.accessibilitySupport); if (accessibilitySupport === AccessibilitySupport.Disabled) { - return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Open the quick pick with {0} and run the command: Toggle Screen Reader Accessibility Mode for a screen reader optimized experience.", platform.isMacintosh ? 'Cmd+Shift+P' : 'Ctrl+Shift+P'); + return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. To enable screen reader optimized mode, use {0}", platform.isMacintosh ? 'Cmd+Shift+f1' : 'Ctrl+Shift+f1'); } return options.get(EditorOption.ariaLabel); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 903406a294778..ca83446c98c5d 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -22,11 +22,15 @@ class ToggleScreenReaderMode extends Action2 { id: 'editor.action.toggleScreenReaderAccessibilityMode', title: { value: nls.localize('toggleScreenReaderMode', "Toggle Screen Reader Accessibility Mode"), original: 'Toggle Screen Reader Accessibility Mode' }, f1: true, - keybinding: { + keybinding: [{ primary: KeyMod.CtrlCmd | KeyCode.KeyE, weight: KeybindingWeight.WorkbenchContrib + 10, when: accessibilityHelpIsShown - } + }, + { + primary: KeyMod.CtrlCmd | KeyCode.F1 | KeyMod.Shift, + weight: KeybindingWeight.WorkbenchContrib + 10, + }] }); } From 4d9df9a19b6757d3691e04b8f92b5312f7db30a1 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 19 Jul 2023 18:33:06 +0200 Subject: [PATCH 030/216] Fixes #186417 (#188279) --- .../browser/widget/diffEditorWidget2/diffEditorEditors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors.ts index 645205e061f81..34ee3e06cdf3d 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorEditors.ts @@ -159,6 +159,6 @@ export class DiffEditorEditors extends Disposable { } else if (ariaLabel) { return ariaLabel.replaceAll(ariaNavigationTip, ''); } - return undefined; + return ''; } } From 87340a153f48d3466e874b85616777270e47c4dd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Jul 2023 18:35:49 +0200 Subject: [PATCH 031/216] fix Getting suspended from Settings Sync #188275 (#188288) --- .../userDataSync/common/extensionsMerge.ts | 5 +- .../test/common/extensionsMerge.test.ts | 106 +++++++++++++++--- 2 files changed, 92 insertions(+), 19 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index 49c0d23827171..905a48be4e3d6 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -395,7 +395,7 @@ function isSameExtensionState(a: IStringDictionary = {}, b: IStringDictiona // massage incoming extension - add optional properties function massageIncomingExtension(extension: ISyncExtension): ISyncExtension { - return { ...extension, ...{ disabled: !!extension.disabled, installed: !!extension.installed } }; + return { ...extension, ...{ disabled: !!extension.disabled, installed: !!extension.installed, isApplicationScoped: !!extension.isApplicationScoped } }; } // massage outgoing extension - remove optional properties @@ -408,7 +408,8 @@ function massageOutgoingExtension(extension: ISyncExtension, key: string): ISync version: extension.version, /* set following always so that to differentiate with older clients */ preRelease: !!extension.preRelease, - pinned: !!extension.pinned + pinned: !!extension.pinned, + isApplicationScoped: !!extension.isApplicationScoped, }; if (extension.disabled) { massagedExtension.disabled = true; diff --git a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index 94bb9f900527a..1b2663593ec51 100644 --- a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -69,7 +69,7 @@ suite('ExtensionsMerge', () => { aLocalSyncExtension({ identifier: { id: 'c', uuid: 'c' } }), ]; const skippedExtension = [ - anSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), + aSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), ]; const expected = [...localExtensions]; @@ -88,7 +88,7 @@ suite('ExtensionsMerge', () => { aLocalSyncExtension({ identifier: { id: 'c', uuid: 'c' } }), ]; const skippedExtension = [ - anSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), + aSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), ]; const expected = [localExtensions[1], localExtensions[2]]; @@ -106,8 +106,8 @@ suite('ExtensionsMerge', () => { aLocalSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const remoteExtensions = [ - anSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), - anSyncExtension({ identifier: { id: 'c', uuid: 'c' } }), + aSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), + aSyncExtension({ identifier: { id: 'c', uuid: 'c' } }), ]; const expected = [ anExpectedSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), @@ -133,8 +133,8 @@ suite('ExtensionsMerge', () => { aLocalSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const remoteExtensions = [ - anSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), - anSyncExtension({ identifier: { id: 'c', uuid: 'c' } }), + aSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), + aSyncExtension({ identifier: { id: 'c', uuid: 'c' } }), ]; const expected = [ anExpectedSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), @@ -155,16 +155,16 @@ suite('ExtensionsMerge', () => { test('merge local and remote extensions when remote is moved forwarded', () => { const baseExtensions = [ - anSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), - anSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), + aSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + aSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const localExtensions = [ aLocalSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), aLocalSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const remoteExtensions = [ - anSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), - anSyncExtension({ identifier: { id: 'c', uuid: 'c' } }), + aSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), + aSyncExtension({ identifier: { id: 'c', uuid: 'c' } }), ]; const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], [], []); @@ -238,7 +238,7 @@ suite('ExtensionsMerge', () => { aLocalSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const skippedExtensions = [ - anSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + aSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), ]; const remoteExtensions = [ aRemoteSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), @@ -265,7 +265,7 @@ suite('ExtensionsMerge', () => { aLocalSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const skippedExtensions = [ - anSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + aSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), ]; const remoteExtensions = [ aRemoteSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), @@ -364,7 +364,7 @@ suite('ExtensionsMerge', () => { aRemoteSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const skippedExtensions = [ - anSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), + aSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const localExtensions = [ aLocalSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), @@ -394,7 +394,7 @@ suite('ExtensionsMerge', () => { aRemoteSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const skippedExtensions = [ - anSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), + aSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const localExtensions = [ aLocalSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), @@ -481,7 +481,7 @@ suite('ExtensionsMerge', () => { aRemoteSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const skippedExtensions = [ - anSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + aSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), ]; const localExtensions = [ aLocalSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), @@ -512,7 +512,7 @@ suite('ExtensionsMerge', () => { aRemoteSyncExtension({ identifier: { id: 'd', uuid: 'd' } }), ]; const skippedExtensions = [ - anSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + aSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), ]; const localExtensions = [ aLocalSyncExtension({ identifier: { id: 'b', uuid: 'b' } }), @@ -1317,6 +1317,74 @@ suite('ExtensionsMerge', () => { assert.deepStrictEqual(actual.remote, null); }); + test('sync adding local application scoped extension', () => { + const localExtensions = [ + aLocalSyncExtension({ identifier: { id: 'a', uuid: 'a' }, isApplicationScoped: true }), + ]; + + const actual = merge(localExtensions, null, null, [], [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote?.all, localExtensions); + }); + + test('sync merging local extension with isApplicationScoped property and remote does not has isApplicationScoped property', () => { + const localExtensions = [ + aLocalSyncExtension({ identifier: { id: 'a', uuid: 'a' }, isApplicationScoped: false }), + ]; + + const baseExtensions = [ + aSyncExtension({ identifier: { id: 'a', uuid: 'a' } }), + ]; + + const actual = merge(localExtensions, baseExtensions, baseExtensions, [], [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote?.all, localExtensions); + }); + + test('sync merging when applicaiton scope is changed locally', () => { + const localExtensions = [ + aLocalSyncExtension({ identifier: { id: 'a', uuid: 'a' }, isApplicationScoped: true }), + ]; + + const baseExtensions = [ + aRemoteSyncExtension({ identifier: { id: 'a', uuid: 'a' }, isApplicationScoped: false }), + ]; + + const actual = merge(localExtensions, baseExtensions, baseExtensions, [], [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, []); + assert.deepStrictEqual(actual.remote?.all, localExtensions); + }); + + test('sync merging when applicaiton scope is changed remotely', () => { + const localExtensions = [ + aLocalSyncExtension({ identifier: { id: 'a', uuid: 'a' }, isApplicationScoped: false }), + ]; + + const baseExtensions = [ + aRemoteSyncExtension({ identifier: { id: 'a', uuid: 'a' }, isApplicationScoped: false }), + ]; + + const remoteExtensions = [ + aRemoteSyncExtension({ identifier: { id: 'a', uuid: 'a' }, isApplicationScoped: true }), + ]; + + const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], [], []); + + assert.deepStrictEqual(actual.local.added, []); + assert.deepStrictEqual(actual.local.removed, []); + assert.deepStrictEqual(actual.local.updated, [anExpectedSyncExtension({ identifier: { id: 'a', uuid: 'a' }, isApplicationScoped: true })]); + assert.deepStrictEqual(actual.remote, null); + }); + function anExpectedSyncExtension(extension: Partial): ISyncExtension { return { identifier: { id: 'a', uuid: 'a' }, @@ -1324,6 +1392,7 @@ suite('ExtensionsMerge', () => { pinned: false, preRelease: false, installed: true, + isApplicationScoped: false, ...extension }; } @@ -1334,6 +1403,7 @@ suite('ExtensionsMerge', () => { version: '1.0.0', pinned: false, preRelease: false, + isApplicationScoped: false, ...extension }; } @@ -1345,6 +1415,7 @@ suite('ExtensionsMerge', () => { pinned: false, preRelease: false, installed: true, + isApplicationScoped: false, ...extension }; } @@ -1356,11 +1427,12 @@ suite('ExtensionsMerge', () => { pinned: false, preRelease: false, installed: true, + isApplicationScoped: false, ...extension }; } - function anSyncExtension(extension: Partial): ISyncExtension { + function aSyncExtension(extension: Partial): ISyncExtension { return { identifier: { id: 'a', uuid: 'a' }, version: '1.0.0', From 37c5709c9613012f67f09ba23db7e1482b7db1fd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 19 Jul 2023 18:58:18 +0200 Subject: [PATCH 032/216] fix using defaults when creating from other profiles or templates (#188290) #156144 fix using defaults when creating from other profiles or templates --- .../browser/userDataProfileImportExportService.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index e45feb0cc50d6..1215f0c55dd91 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -490,27 +490,27 @@ export class UserDataProfileImportExportService extends Disposable implements IU return undefined; } - if (profileTemplate.settings) { + if (profileTemplate.settings && !profile.useDefaultFlags?.settings) { progress(localize('progress settings', "Applying Settings...")); await this.instantiationService.createInstance(SettingsResource).apply(profileTemplate.settings, profile); } - if (profileTemplate.keybindings) { + if (profileTemplate.keybindings && !profile.useDefaultFlags?.keybindings) { progress(localize('progress keybindings', "Applying Keyboard Shortcuts...")); await this.instantiationService.createInstance(KeybindingsResource).apply(profileTemplate.keybindings, profile); } - if (profileTemplate.tasks) { + if (profileTemplate.tasks && !profile.useDefaultFlags?.tasks) { progress(localize('progress tasks', "Applying Tasks...")); await this.instantiationService.createInstance(TasksResource).apply(profileTemplate.tasks, profile); } - if (profileTemplate.snippets) { + if (profileTemplate.snippets && !profile.useDefaultFlags?.snippets) { progress(localize('progress snippets', "Applying Snippets...")); await this.instantiationService.createInstance(SnippetsResource).apply(profileTemplate.snippets, profile); } - if (profileTemplate.globalState) { + if (profileTemplate.globalState && !profile.useDefaultFlags?.globalState) { progress(localize('progress global state', "Applying State...")); await this.instantiationService.createInstance(GlobalStateResource).apply(profileTemplate.globalState, profile); } - if (profileTemplate.extensions && extensions) { + if (profileTemplate.extensions && extensions && !profile.useDefaultFlags?.extensions) { progress(localize('progress extensions', "Applying Extensions...")); await this.instantiationService.createInstance(ExtensionsResource).apply(profileTemplate.extensions, profile); } From 2e615c0542cea8d9a1da65f7cf526df1de165cc2 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Wed, 19 Jul 2023 10:13:39 -0700 Subject: [PATCH 033/216] Announce chat slash command deletion (#188215) * Announce chat slash command deletion * Fix localization formatting --- .../contrib/chat/browser/chatSlashCommandContentWidget.ts | 7 ++++++- .../contrib/chat/browser/contrib/chatInputEditorContrib.ts | 4 +++- .../contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts index a2fa834d90551..51324840b88ff 100644 --- a/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget.ts @@ -9,12 +9,14 @@ import { Range } from 'vs/editor/common/core/range'; import { Disposable } from 'vs/base/common/lifecycle'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget } from 'vs/editor/browser/editorBrowser'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { localize } from 'vs/nls'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export class SlashCommandContentWidget extends Disposable implements IContentWidget { private _domNode = document.createElement('div'); private _lastSlashCommandText: string | undefined; - constructor(private _editor: ICodeEditor) { + constructor(private _editor: ICodeEditor, private _accessibilityService: IAccessibilityService) { super(); this._domNode.toggleAttribute('hidden', true); @@ -65,5 +67,8 @@ export class SlashCommandContentWidget extends Disposable implements IContentWid range: new Range(1, 1, 1, selection.startColumn), text: null }]); + + // Announce the deletion + this._accessibilityService.alert(localize('exited slash command mode', 'Exited {0} mode', this._lastSlashCommandText)); } } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 80d2c1989f18d..da99185ded16e 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -24,6 +24,7 @@ import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; import { SubmitAction } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; const decorationDescription = 'chat'; const slashCommandPlaceholderDecorationType = 'chat-session-detail'; @@ -39,6 +40,7 @@ class InputEditorDecorations extends Disposable { @ICodeEditorService private readonly codeEditorService: ICodeEditorService, @IThemeService private readonly themeService: IThemeService, @IChatService private readonly chatService: IChatService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, ) { super(); @@ -139,7 +141,7 @@ class InputEditorDecorations extends Disposable { if (command && inputValue.startsWith(`/${command.command} `)) { if (!this._slashCommandContentWidget) { - this._slashCommandContentWidget = new SlashCommandContentWidget(this.widget.inputEditor); + this._slashCommandContentWidget = new SlashCommandContentWidget(this.widget.inputEditor, this.accessibilityService); this._store.add(this._slashCommandContentWidget); } this._slashCommandContentWidget.setCommandText(command.command); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index b0cc6198c47b2..28f192ef209e4 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -286,7 +286,7 @@ export class InlineChatWidget { // slash command content widget - this._slashCommandContentWidget = new SlashCommandContentWidget(this._inputEditor); + this._slashCommandContentWidget = new SlashCommandContentWidget(this._inputEditor, this._accessibilityService); this._store.add(this._slashCommandContentWidget); // toolbars From 65f9f6a862dad32633ad152d018a2474918d0f31 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 10:36:44 -0700 Subject: [PATCH 034/216] use keybinding label --- .../browser/controller/textAreaHandler.ts | 24 +++++++++++++++++-- src/vs/editor/browser/view.ts | 6 +++-- .../editor/browser/widget/codeEditorWidget.ts | 3 ++- .../browser/accessibility/accessibility.ts | 2 +- 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index cefaa886f9967..34ce1ad8bb34e 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -35,6 +35,7 @@ import { TokenizationRegistry } from 'vs/editor/common/languages'; import { ColorId, ITokenPresentation } from 'vs/editor/common/encodedTokenAttributes'; import { Color } from 'vs/base/common/color'; import { IME } from 'vs/base/common/ime'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export interface IVisibleRangeProvider { visibleRangeForPosition(position: Position): HorizontalPosition | null; @@ -140,7 +141,12 @@ export class TextAreaHandler extends ViewPart { public readonly textAreaCover: FastDomNode; private readonly _textAreaInput: TextAreaInput; - constructor(context: ViewContext, viewController: ViewController, visibleRangeProvider: IVisibleRangeProvider) { + constructor( + context: ViewContext, + viewController: ViewController, + visibleRangeProvider: IVisibleRangeProvider, + @IKeybindingService private readonly _keybindingService: IKeybindingService + ) { super(context); this._viewController = viewController; @@ -553,7 +559,21 @@ export class TextAreaHandler extends ViewPart { private _getAriaLabel(options: IComputedEditorOptions): string { const accessibilitySupport = options.get(EditorOption.accessibilitySupport); if (accessibilitySupport === AccessibilitySupport.Disabled) { - return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. To enable screen reader optimized mode, use {0}", platform.isMacintosh ? 'Cmd+Shift+f1' : 'Ctrl+Shift+f1'); + + const toggleKeybindingLabel = this._keybindingService.lookupKeybinding('editor.action.toggleScreenReaderAccessibilityMode')?.getAriaLabel(); + const runCommandKeybindingLabel = this._keybindingService.lookupKeybinding('workbench.action.showCommands')?.getAriaLabel(); + const keybindingEditorKeybindingLabel = this._keybindingService.lookupKeybinding('workbench.action.openGlobalKeybindings')?.getAriaLabel(); + const editorNotAccessibleMessage = nls.localize('accessibilityModeOff', "The editor is not accessible at this time."); + if (toggleKeybindingLabel) { + return nls.localize('accessibilityOffAriaLabel', "{0} To enable screen reader optimized mode, use {1}", editorNotAccessibleMessage, toggleKeybindingLabel); + } else if (runCommandKeybindingLabel) { + return nls.localize('accessibilityOffAriaLabelNoKb', "{0} The editor is not accessible at this time. To enable screen reader optimized mode, open the quick pick with {1} and run the command Toggle Screen Reader Accessibility Mode, which is currently not triggerable via keyboard.", editorNotAccessibleMessage, runCommandKeybindingLabel); + } else if (keybindingEditorKeybindingLabel) { + return nls.localize('accessibilityOffAriaLabelNoKbs', "{0} Please assign a keybinding for the command Toggle Screen Reader Accessibility Mode by accessing the keybindings editor with {1} and run it.", editorNotAccessibleMessage, keybindingEditorKeybindingLabel); + } else { + // SOS + return editorNotAccessibleMessage; + } } return options.get(EditorOption.ariaLabel); } diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 82d1fb7a26969..0e791b1430570 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -54,6 +54,7 @@ import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { WhitespaceOverlay } from 'vs/editor/browser/viewParts/whitespace/whitespace'; import { GlyphMarginWidgets } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin'; import { GlyphMarginLane } from 'vs/editor/common/model'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export interface IContentWidgetData { @@ -106,7 +107,8 @@ export class View extends ViewEventHandler { colorTheme: IColorTheme, model: IViewModel, userInputEvents: ViewUserInputEvents, - overflowWidgetsDomNode: HTMLElement | undefined + overflowWidgetsDomNode: HTMLElement | undefined, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); this._selections = [new Selection(1, 1, 1, 1)]; @@ -123,7 +125,7 @@ export class View extends ViewEventHandler { this._viewParts = []; // Keyboard handler - this._textAreaHandler = new TextAreaHandler(this._context, viewController, this._createTextAreaHandlerHelper()); + this._textAreaHandler = this._instantiationService.createInstance(TextAreaHandler, this._context, viewController, this._createTextAreaHandlerHelper()); this._viewParts.push(this._textAreaHandler); // These two dom nodes must be constructed up front, since references are needed in the layout provider (scrolling & co.) diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index c05cb46f268b9..f6389bf5410f0 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -1853,7 +1853,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._themeService.getColorTheme(), viewModel, viewUserInputEvents, - this._overflowWidgetsDomNode + this._overflowWidgetsDomNode, + this._instantiationService ); return [view, true]; diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index ca83446c98c5d..1b57154caed3f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -28,7 +28,7 @@ class ToggleScreenReaderMode extends Action2 { when: accessibilityHelpIsShown }, { - primary: KeyMod.CtrlCmd | KeyCode.F1 | KeyMod.Shift, + primary: KeyMod.Alt | KeyCode.F3, weight: KeybindingWeight.WorkbenchContrib + 10, }] }); From 9a29bef923188f6f0e312f341fa8bb187079878e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 10:38:59 -0700 Subject: [PATCH 035/216] rm one --- src/vs/editor/browser/controller/textAreaHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 34ce1ad8bb34e..94fa1a8bdb173 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -567,7 +567,7 @@ export class TextAreaHandler extends ViewPart { if (toggleKeybindingLabel) { return nls.localize('accessibilityOffAriaLabel', "{0} To enable screen reader optimized mode, use {1}", editorNotAccessibleMessage, toggleKeybindingLabel); } else if (runCommandKeybindingLabel) { - return nls.localize('accessibilityOffAriaLabelNoKb', "{0} The editor is not accessible at this time. To enable screen reader optimized mode, open the quick pick with {1} and run the command Toggle Screen Reader Accessibility Mode, which is currently not triggerable via keyboard.", editorNotAccessibleMessage, runCommandKeybindingLabel); + return nls.localize('accessibilityOffAriaLabelNoKb', "{0} To enable screen reader optimized mode, open the quick pick with {1} and run the command Toggle Screen Reader Accessibility Mode, which is currently not triggerable via keyboard.", editorNotAccessibleMessage, runCommandKeybindingLabel); } else if (keybindingEditorKeybindingLabel) { return nls.localize('accessibilityOffAriaLabelNoKbs', "{0} Please assign a keybinding for the command Toggle Screen Reader Accessibility Mode by accessing the keybindings editor with {1} and run it.", editorNotAccessibleMessage, keybindingEditorKeybindingLabel); } else { From 210f6d517421f99722dc84c1fc8e68d1a7ee7b06 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 19 Jul 2023 19:46:54 +0200 Subject: [PATCH 036/216] Add category to "Go to Next/Previous Difference" commands (#188292) --- .../browser/widget/diffEditor.contribution.ts | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor.contribution.ts index 456dc90b2930a..2deb1fbc1338f 100644 --- a/src/vs/editor/browser/widget/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor.contribution.ts @@ -5,59 +5,67 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; +import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; +import { registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -export class DiffReviewNext extends EditorAction { + +const accessibleDiffViewerCategory: ILocalizedString = { + value: localize('accessibleDiffViewer', 'Accessible Diff Viewer'), + original: 'Accessible Diff Viewer', +}; + +export class DiffReviewNext extends EditorAction2 { public static id = 'editor.action.diffReview.next'; constructor() { super({ id: DiffReviewNext.id, - label: localize('editor.action.diffReview.next', "Go to Next Difference"), - alias: 'Go to Next Difference', + title: { value: localize('editor.action.diffReview.next', "Go to Next Difference"), original: 'Go to Next Difference' }, + category: accessibleDiffViewerCategory, precondition: ContextKeyExpr.has('isInDiffEditor'), - kbOpts: { - kbExpr: null, + keybinding: { primary: KeyCode.F7, weight: KeybindingWeight.EditorContrib - } + }, + f1: true, }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { const diffEditor = findFocusedDiffEditor(accessor); diffEditor?.diffReviewNext(); } } -export class DiffReviewPrev extends EditorAction { +export class DiffReviewPrev extends EditorAction2 { public static id = 'editor.action.diffReview.prev'; constructor() { super({ id: DiffReviewPrev.id, - label: localize('editor.action.diffReview.prev', "Go to Previous Difference"), - alias: 'Go to Previous Difference', + title: { value: localize('editor.action.diffReview.prev', "Go to Previous Difference"), original: 'Go to Previous Difference' }, + category: accessibleDiffViewerCategory, precondition: ContextKeyExpr.has('isInDiffEditor'), - kbOpts: { - kbExpr: null, + keybinding: { primary: KeyMod.Shift | KeyCode.F7, weight: KeybindingWeight.EditorContrib - } + }, + f1: true, }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): void { + public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { const diffEditor = findFocusedDiffEditor(accessor); diffEditor?.diffReviewPrev(); } } -function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | null { +export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | null { const codeEditorService = accessor.get(ICodeEditorService); const diffEditors = codeEditorService.listDiffEditors(); const activeCodeEditor = codeEditorService.getFocusedCodeEditor() ?? codeEditorService.getActiveCodeEditor(); @@ -74,5 +82,5 @@ function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | null { return null; } -registerEditorAction(DiffReviewNext); -registerEditorAction(DiffReviewPrev); +registerAction2(DiffReviewNext); +registerAction2(DiffReviewPrev); From 6ae441b56fc9ca54b53a4d38810619f326b56bec Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 19 Jul 2023 20:11:00 +0200 Subject: [PATCH 037/216] Kerberos auth for proxies (#188130) --- .devcontainer/install-vscode.sh | 2 +- .github/workflows/ci.yml | 2 +- .github/workflows/monaco-editor.yml | 3 + build/.moduleignore | 6 ++ .../alpine/product-build-alpine.yml | 5 ++ .../linux/product-build-linux-test.yml | 2 +- .../linux/product-build-linux.yml | 5 ++ build/azure-pipelines/product-compile.yml | 2 +- .../azure-pipelines/web/product-build-web.yml | 5 ++ build/linux/debian/dep-lists.js | 8 +- build/linux/debian/dep-lists.ts | 6 ++ build/linux/rpm/dep-lists.js | 14 ++- build/linux/rpm/dep-lists.ts | 12 +++ package.json | 4 +- remote/package.json | 3 +- remote/yarn.lock | 35 +++++++- .../node/nativeModules.integrationTest.ts | 5 ++ src/vs/workbench/api/node/proxyResolver.ts | 87 +++++++++++-------- yarn.lock | 40 ++++++++- 19 files changed, 192 insertions(+), 54 deletions(-) diff --git a/.devcontainer/install-vscode.sh b/.devcontainer/install-vscode.sh index 9d4b52755d9d2..cc70d527acdfb 100755 --- a/.devcontainer/install-vscode.sh +++ b/.devcontainer/install-vscode.sh @@ -9,4 +9,4 @@ sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.mi rm -f packages.microsoft.gpg apt update -apt install -y code-insiders libsecret-1-dev libxkbfile-dev +apt install -y code-insiders libsecret-1-dev libxkbfile-dev libkrb5-dev diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dac5f9e073b79..8a4d93dfa770c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,7 @@ jobs: - 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 apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libkrb5-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 diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index a86f94bc331f9..46ece33df5c7c 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -45,6 +45,9 @@ jobs: path: ${{ steps.yarnCacheDirPath.outputs.dir }} key: ${{ runner.os }}-yarnCacheDir-${{ steps.nodeModulesCacheKey.outputs.value }} restore-keys: ${{ runner.os }}-yarnCacheDir- + - name: Install libkrb5-dev + if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} + run: sudo apt install -y libkrb5-dev - name: Execute yarn if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} env: diff --git a/build/.moduleignore b/build/.moduleignore index abc37e3138c4c..76942a81a0582 100644 --- a/build/.moduleignore +++ b/build/.moduleignore @@ -73,6 +73,12 @@ windows-foreground-love/build/** windows-foreground-love/src/** !windows-foreground-love/**/*.node +kerberos/binding.gyp +kerberos/build/** +kerberos/src/** +kerberos/node_modules/** +!kerberos/**/*.node + keytar/binding.gyp keytar/build/** keytar/src/** diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index eed3623addcf1..1a7d33972686f 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -67,6 +67,11 @@ steps: displayName: "Pull image" condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | + sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install libkrb5-dev + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | set -e for i in {1..5}; do # try 5 times diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index 7532c43c7c9b6..a80139473f6d5 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -18,7 +18,7 @@ steps: - script: | set -e sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libxss1 dbus xvfb libgtk-3-0 libgbm1 + sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libkrb5-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 diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 99bbd4ca6bd98..f366cf672f7ea 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -110,6 +110,11 @@ steps: displayName: Register Docker QEMU condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_ARCH'], 'arm64')) + - script: | + sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install libkrb5-dev + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | set -e diff --git a/build/azure-pipelines/product-compile.yml b/build/azure-pipelines/product-compile.yml index 8471cfdec3dea..d83852784744a 100644 --- a/build/azure-pipelines/product-compile.yml +++ b/build/azure-pipelines/product-compile.yml @@ -49,7 +49,7 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Authentication - - script: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libsecret-1-dev libnotify-bin + - script: sudo apt update -y && sudo apt install -y build-essential pkg-config libx11-dev libx11-xcb-dev libxkbfile-dev libsecret-1-dev libnotify-bin libkrb5-dev displayName: Install build tools condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index a941ae9adfe66..8fcebf5b259af 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -53,6 +53,11 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Authentication + - script: | + sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install libkrb5-dev + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | set -e for i in {1..5}; do # try 5 times diff --git a/build/linux/debian/dep-lists.js b/build/linux/debian/dep-lists.js index 2444e401703de..0a818aded80c3 100644 --- a/build/linux/debian/dep-lists.js +++ b/build/linux/debian/dep-lists.js @@ -38,8 +38,10 @@ exports.referenceGeneratedDepsByArch = { 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.16.0)', 'libglib2.0-0 (>= 2.39.4)', + 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.22)', 'libnss3 (>= 3.26)', @@ -76,8 +78,10 @@ exports.referenceGeneratedDepsByArch = { 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.12.0)', 'libglib2.0-0 (>= 2.39.4)', + 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.22)', 'libnss3 (>= 3.26)', @@ -113,8 +117,10 @@ exports.referenceGeneratedDepsByArch = { 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.12.0)', 'libglib2.0-0 (>= 2.39.4)', + 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.22)', 'libnss3 (>= 3.26)', @@ -136,4 +142,4 @@ exports.referenceGeneratedDepsByArch = { 'xdg-utils (>= 1.0.2)' ] }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGtIQUFrSDtBQUNsSCw0REFBNEQ7QUFDL0MsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHFDQUFxQztJQUNyQyxtQkFBbUI7SUFDbkIsc0RBQXNEO0lBQ3RELHNCQUFzQixDQUFDLGlCQUFpQjtDQUN4QyxDQUFDO0FBRUYsb0hBQW9IO0FBQ3BILDBDQUEwQztBQUMxQyw4REFBOEQ7QUFDakQsUUFBQSxlQUFlLEdBQUc7SUFDOUIsWUFBWSxDQUFDLHlFQUF5RTtDQUN0RixDQUFDO0FBRVcsUUFBQSw0QkFBNEIsR0FBRztJQUMzQyxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsa0JBQWtCO1FBQ2xCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQiwwQkFBMEI7UUFDMUIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyx3QkFBd0I7UUFDeEIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQiw0QkFBNEI7UUFDNUIseUJBQXlCO1FBQ3pCLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsZ0JBQWdCO1FBQ2hCLGdCQUFnQjtRQUNoQixnQkFBZ0I7UUFDaEIsc0JBQXNCO1FBQ3RCLHNEQUFzRDtRQUN0RCx5QkFBeUI7UUFDekIscUJBQXFCO1FBQ3JCLHNCQUFzQjtRQUN0Qix5QkFBeUI7UUFDekIsMEJBQTBCO1FBQzFCLDBCQUEwQjtRQUMxQix3QkFBd0I7UUFDeEIscUNBQXFDO1FBQ3JDLHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLDRCQUE0QjtRQUM1Qix5QkFBeUI7UUFDekIsbUJBQW1CO1FBQ25CLHFCQUFxQjtRQUNyQixtQkFBbUI7UUFDbkIsVUFBVTtRQUNWLDBCQUEwQjtRQUMxQixvQkFBb0I7UUFDcEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QixVQUFVO1FBQ1YsWUFBWTtRQUNaLDBCQUEwQjtRQUMxQixhQUFhO1FBQ2IsWUFBWTtRQUNaLHNCQUFzQjtLQUN0QjtJQUNELE9BQU8sRUFBRTtRQUNSLGlCQUFpQjtRQUNqQix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QiwyQkFBMkI7UUFDM0IsaUJBQWlCO1FBQ2pCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQsd0JBQXdCO1FBQ3hCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQiwwQkFBMEI7UUFDMUIsd0JBQXdCO1FBQ3hCLHFDQUFxQztRQUNyQyx3QkFBd0I7UUFDeEIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQiw0QkFBNEI7UUFDNUIseUJBQXlCO1FBQ3pCLG1CQUFtQjtRQUNuQixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7Q0FDRCxDQUFDIn0= \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLGtIQUFrSDtBQUNsSCw0REFBNEQ7QUFDL0MsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHFDQUFxQztJQUNyQyxtQkFBbUI7SUFDbkIsc0RBQXNEO0lBQ3RELHNCQUFzQixDQUFDLGlCQUFpQjtDQUN4QyxDQUFDO0FBRUYsb0hBQW9IO0FBQ3BILDBDQUEwQztBQUMxQyw4REFBOEQ7QUFDakQsUUFBQSxlQUFlLEdBQUc7SUFDOUIsWUFBWSxDQUFDLHlFQUF5RTtDQUN0RixDQUFDO0FBRVcsUUFBQSw0QkFBNEIsR0FBRztJQUMzQyxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixpQkFBaUI7UUFDakIsa0JBQWtCO1FBQ2xCLHNCQUFzQjtRQUN0QixzREFBc0Q7UUFDdEQseUJBQXlCO1FBQ3pCLHFCQUFxQjtRQUNyQixzQkFBc0I7UUFDdEIseUJBQXlCO1FBQ3pCLDBCQUEwQjtRQUMxQiwwQkFBMEI7UUFDMUIsNEJBQTRCO1FBQzVCLHdCQUF3QjtRQUN4QixxQ0FBcUM7UUFDckMsMkJBQTJCO1FBQzNCLHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLDRCQUE0QjtRQUM1Qix5QkFBeUI7UUFDekIsVUFBVTtRQUNWLDBCQUEwQjtRQUMxQixvQkFBb0I7UUFDcEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QixVQUFVO1FBQ1YsWUFBWTtRQUNaLDBCQUEwQjtRQUMxQixhQUFhO1FBQ2IsWUFBWTtRQUNaLHNCQUFzQjtLQUN0QjtJQUNELE9BQU8sRUFBRTtRQUNSLGlCQUFpQjtRQUNqQix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLHdCQUF3QjtRQUN4QiwyQkFBMkI7UUFDM0IsaUJBQWlCO1FBQ2pCLGlCQUFpQjtRQUNqQixnQkFBZ0I7UUFDaEIsZ0JBQWdCO1FBQ2hCLGdCQUFnQjtRQUNoQixzQkFBc0I7UUFDdEIsc0RBQXNEO1FBQ3RELHlCQUF5QjtRQUN6QixxQkFBcUI7UUFDckIsc0JBQXNCO1FBQ3RCLHlCQUF5QjtRQUN6QiwwQkFBMEI7UUFDMUIsMEJBQTBCO1FBQzFCLDRCQUE0QjtRQUM1Qix3QkFBd0I7UUFDeEIscUNBQXFDO1FBQ3JDLDJCQUEyQjtRQUMzQix3QkFBd0I7UUFDeEIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQiw0QkFBNEI7UUFDNUIseUJBQXlCO1FBQ3pCLG1CQUFtQjtRQUNuQixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7SUFDRCxPQUFPLEVBQUU7UUFDUixpQkFBaUI7UUFDakIsd0JBQXdCO1FBQ3hCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsMkJBQTJCO1FBQzNCLGlCQUFpQjtRQUNqQixzQkFBc0I7UUFDdEIsc0RBQXNEO1FBQ3RELHdCQUF3QjtRQUN4QixxQkFBcUI7UUFDckIsc0JBQXNCO1FBQ3RCLHlCQUF5QjtRQUN6QiwwQkFBMEI7UUFDMUIsMEJBQTBCO1FBQzFCLDRCQUE0QjtRQUM1Qix3QkFBd0I7UUFDeEIscUNBQXFDO1FBQ3JDLDJCQUEyQjtRQUMzQix3QkFBd0I7UUFDeEIscUJBQXFCO1FBQ3JCLG1CQUFtQjtRQUNuQiw0QkFBNEI7UUFDNUIseUJBQXlCO1FBQ3pCLG1CQUFtQjtRQUNuQixxQkFBcUI7UUFDckIsbUJBQW1CO1FBQ25CLFVBQVU7UUFDViwwQkFBMEI7UUFDMUIsb0JBQW9CO1FBQ3BCLCtCQUErQjtRQUMvQix3QkFBd0I7UUFDeEIsVUFBVTtRQUNWLFlBQVk7UUFDWiwwQkFBMEI7UUFDMUIsYUFBYTtRQUNiLFlBQVk7UUFDWixzQkFBc0I7S0FDdEI7Q0FDRCxDQUFDIn0= \ No newline at end of file diff --git a/build/linux/debian/dep-lists.ts b/build/linux/debian/dep-lists.ts index 7f6cd6ca8ccfb..72af03d75bd42 100644 --- a/build/linux/debian/dep-lists.ts +++ b/build/linux/debian/dep-lists.ts @@ -38,8 +38,10 @@ export const referenceGeneratedDepsByArch = { 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.16.0)', 'libglib2.0-0 (>= 2.39.4)', + 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.22)', 'libnss3 (>= 3.26)', @@ -76,8 +78,10 @@ export const referenceGeneratedDepsByArch = { 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.12.0)', 'libglib2.0-0 (>= 2.39.4)', + 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.22)', 'libnss3 (>= 3.26)', @@ -113,8 +117,10 @@ export const referenceGeneratedDepsByArch = { 'libgbm1 (>= 17.1.0~rc2)', 'libglib2.0-0 (>= 2.12.0)', 'libglib2.0-0 (>= 2.39.4)', + 'libgssapi-krb5-2 (>= 1.17)', 'libgtk-3-0 (>= 3.9.10)', 'libgtk-3-0 (>= 3.9.10) | libgtk-4-1', + 'libkrb5-3 (>= 1.6.dfsg.2)', 'libnspr4 (>= 2:4.9-2~)', 'libnss3 (>= 2:3.22)', 'libnss3 (>= 3.26)', diff --git a/build/linux/rpm/dep-lists.js b/build/linux/rpm/dep-lists.js index c836348ef5105..bfbcb6763d50c 100644 --- a/build/linux/rpm/dep-lists.js +++ b/build/linux/rpm/dep-lists.js @@ -65,7 +65,11 @@ exports.referenceGeneratedDepsByArch = { 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', + 'libgssapi_krb5.so.2()(64bit)', + 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)(64bit)', 'libgtk-3.so.0()(64bit)', + 'libkrb5.so.3()(64bit)', + 'libkrb5.so.3(krb5_3_MIT)(64bit)', 'libm.so.6()(64bit)', 'libm.so.6(GLIBC_2.2.5)(64bit)', 'libnspr4.so()(64bit)', @@ -146,8 +150,12 @@ exports.referenceGeneratedDepsByArch = { 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', + 'libgssapi_krb5.so.2', + 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)', 'libgtk-3.so.0', 'libgtk-3.so.0()(64bit)', + 'libkrb5.so.3', + 'libkrb5.so.3(krb5_3_MIT)', 'libm.so.6', 'libm.so.6(GLIBC_2.4)', 'libnspr4.so', @@ -236,7 +244,11 @@ exports.referenceGeneratedDepsByArch = { 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', + 'libgssapi_krb5.so.2()(64bit)', + 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)(64bit)', 'libgtk-3.so.0()(64bit)', + 'libkrb5.so.3()(64bit)', + 'libkrb5.so.3(krb5_3_MIT)(64bit)', 'libm.so.6()(64bit)', 'libm.so.6(GLIBC_2.17)(64bit)', 'libnspr4.so()(64bit)', @@ -289,4 +301,4 @@ exports.referenceGeneratedDepsByArch = { 'xdg-utils' ] }; -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLCtHQUErRztBQUMvRywrREFBK0Q7QUFDbEQsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHdCQUF3QjtJQUN4Qiw2QkFBNkI7SUFDN0IsNkJBQTZCO0lBQzdCLGdDQUFnQztJQUNoQyx5QkFBeUI7SUFDekIsdUJBQXVCO0lBQ3ZCLFdBQVcsQ0FBQyxpQkFBaUI7Q0FDN0IsQ0FBQztBQUVXLFFBQUEsNEJBQTRCLEdBQUc7SUFDM0MsUUFBUSxFQUFFO1FBQ1QsaUJBQWlCO1FBQ2pCLCtCQUErQjtRQUMvQiwwQ0FBMEM7UUFDMUMsd0NBQXdDO1FBQ3hDLHNCQUFzQjtRQUN0Qiw2QkFBNkI7UUFDN0IsMEJBQTBCO1FBQzFCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIseUJBQXlCO1FBQ3pCLHlCQUF5QjtRQUN6QixpQ0FBaUM7UUFDakMsc0NBQXNDO1FBQ3RDLDBCQUEwQjtRQUMxQixpQ0FBaUM7UUFDakMsd0JBQXdCO1FBQ3hCLG9CQUFvQjtRQUNwQiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5Qiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5QiwrQkFBK0I7UUFDL0IsNkJBQTZCO1FBQzdCLCtCQUErQjtRQUMvQiwrQkFBK0I7UUFDL0IsK0JBQStCO1FBQy9CLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0IsNkJBQTZCO1FBQzdCLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0Isd0JBQXdCO1FBQ3hCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIscUJBQXFCO1FBQ3JCLGdDQUFnQztRQUNoQyxzQkFBc0I7UUFDdEIsd0JBQXdCO1FBQ3hCLHNCQUFzQjtRQUN0Qix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLDBCQUEwQjtR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 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVwLWxpc3RzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiZGVwLWxpc3RzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7O2dHQUdnRzs7O0FBRWhHLCtHQUErRztBQUMvRywrREFBK0Q7QUFDbEQsUUFBQSxjQUFjLEdBQUc7SUFDN0IsaUJBQWlCO0lBQ2pCLHdCQUF3QjtJQUN4Qiw2QkFBNkI7SUFDN0IsNkJBQTZCO0lBQzdCLGdDQUFnQztJQUNoQyx5QkFBeUI7SUFDekIsdUJBQXVCO0lBQ3ZCLFdBQVcsQ0FBQyxpQkFBaUI7Q0FDN0IsQ0FBQztBQUVXLFFBQUEsNEJBQTRCLEdBQUc7SUFDM0MsUUFBUSxFQUFFO1FBQ1QsaUJBQWlCO1FBQ2pCLCtCQUErQjtRQUMvQiwwQ0FBMEM7UUFDMUMsd0NBQXdDO1FBQ3hDLHNCQUFzQjtRQUN0Qiw2QkFBNkI7UUFDN0IsMEJBQTBCO1FBQzFCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIseUJBQXlCO1FBQ3pCLHlCQUF5QjtRQUN6QixpQ0FBaUM7UUFDakMsc0NBQXNDO1FBQ3RDLDBCQUEwQjtRQUMxQixpQ0FBaUM7UUFDakMsd0JBQXdCO1FBQ3hCLG9CQUFvQjtRQUNwQiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5Qiw4QkFBOEI7UUFDOUIsOEJBQThCO1FBQzlCLDhCQUE4QjtRQUM5QiwrQkFBK0I7UUFDL0IsNkJBQTZCO1FBQzdCLCtCQUErQjtRQUMvQiwrQkFBK0I7UUFDL0IsK0JBQStCO1FBQy9CLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0IsNkJBQTZCO1FBQzdCLDZCQUE2QjtRQUM3Qiw2QkFBNkI7UUFDN0Isd0JBQXdCO1FBQ3hCLHVCQUF1QjtRQUN2Qix5QkFBeUI7UUFDekIscUJBQXFCO1FBQ3JCLGdDQUFnQztRQUNoQyxzQkFBc0I7UUFDdEIsd0JBQXdCO1FBQ3hCLHNCQUFzQjtRQUN0Qix3QkFBd0I7UUFDeEIsK0JBQStCO1FBQy9CLDBCQUEwQjtR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 \ No newline at end of file diff --git a/build/linux/rpm/dep-lists.ts b/build/linux/rpm/dep-lists.ts index c262448c318e8..5788ec30f9486 100644 --- a/build/linux/rpm/dep-lists.ts +++ b/build/linux/rpm/dep-lists.ts @@ -64,7 +64,11 @@ export const referenceGeneratedDepsByArch = { 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', + 'libgssapi_krb5.so.2()(64bit)', + 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)(64bit)', 'libgtk-3.so.0()(64bit)', + 'libkrb5.so.3()(64bit)', + 'libkrb5.so.3(krb5_3_MIT)(64bit)', 'libm.so.6()(64bit)', 'libm.so.6(GLIBC_2.2.5)(64bit)', 'libnspr4.so()(64bit)', @@ -145,8 +149,12 @@ export const referenceGeneratedDepsByArch = { 'libgio-2.0.so.0', 'libglib-2.0.so.0', 'libgobject-2.0.so.0', + 'libgssapi_krb5.so.2', + 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)', 'libgtk-3.so.0', 'libgtk-3.so.0()(64bit)', + 'libkrb5.so.3', + 'libkrb5.so.3(krb5_3_MIT)', 'libm.so.6', 'libm.so.6(GLIBC_2.4)', 'libnspr4.so', @@ -235,7 +243,11 @@ export const referenceGeneratedDepsByArch = { 'libgio-2.0.so.0()(64bit)', 'libglib-2.0.so.0()(64bit)', 'libgobject-2.0.so.0()(64bit)', + 'libgssapi_krb5.so.2()(64bit)', + 'libgssapi_krb5.so.2(gssapi_krb5_2_MIT)(64bit)', 'libgtk-3.so.0()(64bit)', + 'libkrb5.so.3()(64bit)', + 'libkrb5.so.3(krb5_3_MIT)(64bit)', 'libm.so.6()(64bit)', 'libm.so.6(GLIBC_2.17)(64bit)', 'libnspr4.so()(64bit)', diff --git a/package.json b/package.json index 2708da44717e5..78579fcedf4d5 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", - "@vscode/proxy-agent": "^0.16.0", + "@vscode/proxy-agent": "^0.17.0", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.10", "@vscode/sqlite3": "5.1.6-vscode", @@ -83,6 +83,7 @@ "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", "jschardet": "3.0.0", + "kerberos": "^2.0.1", "keytar": "7.9.0", "minimist": "^1.2.6", "native-is-elevated": "0.7.0", @@ -116,6 +117,7 @@ "@types/gulp-postcss": "^8.0.0", "@types/gulp-svgmin": "^1.2.1", "@types/http-proxy-agent": "^2.0.1", + "@types/kerberos": "^1.1.2", "@types/keytar": "^4.4.0", "@types/minimist": "^1.2.1", "@types/mocha": "^9.1.1", diff --git a/remote/package.json b/remote/package.json index e10bd518df423..d5ccd114e2579 100644 --- a/remote/package.json +++ b/remote/package.json @@ -7,7 +7,7 @@ "@microsoft/1ds-post-js": "^3.2.2", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.16.0", + "@vscode/proxy-agent": "^0.17.0", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.10", "@vscode/vscode-languagedetection": "1.0.21", @@ -18,6 +18,7 @@ "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.3", "jschardet": "3.0.0", + "kerberos": "^2.0.1", "keytar": "7.9.0", "minimist": "^1.2.6", "native-watchdog": "^1.4.1", diff --git a/remote/yarn.lock b/remote/yarn.lock index 7c74dbf3e57f9..15557fa622ef2 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -58,10 +58,10 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/proxy-agent@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.16.0.tgz#32054387f7aaf26d1b5d53f553d53bfd8489eab8" - integrity sha512-b8yBHgdngDrP+9HPJtnPUJjPHd+zfEvOYoc8KioWJVs0rFVT2U77nFDVC70Mrrscf87ya2a/sPY32nTrwFfOQQ== +"@vscode/proxy-agent@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.0.tgz#e60d43e2779c07c223d3bad9b7de8eedf7ca1294" + integrity sha512-p4gJ57KeWGw0CEG9R13dmsgmWmszoOQ836pf/PVbAf+ZRF27il3QcFvOhA10XE2QFHaOcRxuJnnIpUD1lSMvqQ== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" @@ -454,6 +454,15 @@ jschardet@3.0.0: resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-3.0.0.tgz#898d2332e45ebabbdb6bf2feece9feea9a99e882" integrity sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ== +kerberos@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-2.0.1.tgz#663b0b46883b4da84495f60f2e9e399a43a33ef5" + integrity sha512-O/jIgbdGK566eUhFwIcgalbqirYU/r76MW7/UFw06Fd9x5bSwgyZWL/Vm26aAmezQww/G9KYkmmJBkEkPk5HLw== + dependencies: + bindings "^1.5.0" + node-addon-api "^4.3.0" + prebuild-install "7.1.1" + keytar@7.9.0: version "7.9.0" resolved "https://registry.yarnpkg.com/keytar/-/keytar-7.9.0.tgz#4c6225708f51b50cbf77c5aae81721964c2918cb" @@ -595,6 +604,24 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +prebuild-install@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prebuild-install@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.1.tgz#c10075727c318efe72412f333e0ef625beaf3870" diff --git a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts index bd10be78b29ff..9ed588ca6f1d8 100644 --- a/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts +++ b/src/vs/platform/environment/test/node/nativeModules.integrationTest.ts @@ -14,6 +14,11 @@ function testErrorMessage(module: string): string { flakySuite('Native Modules (all platforms)', () => { + test('kerberos', async () => { + const kerberos = await import('kerberos'); + assert.ok(typeof kerberos.initializeClient === 'function', testErrorMessage('kerberos')); + }); + test('native-is-elevated', async () => { const isElevated = await import('native-is-elevated'); assert.ok(typeof isElevated === 'function', testErrorMessage('native-is-elevated ')); diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index 133cffc590035..d8a79683c1c03 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -30,7 +30,11 @@ export function connectProxyResolver( const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; const params: ProxyAgentParams = { resolveProxy: url => extHostWorkspace.resolveProxy(url), - getHttpProxySetting: () => configProvider.getConfiguration('http').get('proxy'), + lookupProxyAuthorization: lookupProxyAuthorization.bind(undefined, extHostLogService, configProvider, {}, {}), + getProxyURL: () => configProvider.getConfiguration('http').get('proxy'), + getProxySupport: () => configProvider.getConfiguration('http').get('proxySupport') || 'off', + getSystemCertificatesV1: () => certSettingV1(configProvider), + getSystemCertificatesV2: () => certSettingV2(configProvider), log: (level, message, ...args) => { switch (level) { case LogLevel.Trace: extHostLogService.trace(message, ...args); break; @@ -51,49 +55,18 @@ export function connectProxyResolver( // TODO @chrmarti Remove this from proxy agent proxyResolveTelemetry: () => { }, useHostProxy: doUseHostProxy, - useSystemCertificatesV2: certSettingV2(configProvider), addCertificates: [], env: process.env, }; - configProvider.onDidChangeConfiguration(e => { - params.useSystemCertificatesV2 = certSettingV2(configProvider); - }); const resolveProxy = createProxyResolver(params); - const lookup = createPatchedModules(params, configProvider, resolveProxy); + const lookup = createPatchedModules(params, resolveProxy); return configureModuleLoading(extensionService, lookup); } -function createPatchedModules(params: ProxyAgentParams, configProvider: ExtHostConfigProvider, resolveProxy: ReturnType) { - const proxySetting = { - config: configProvider.getConfiguration('http') - .get('proxySupport') || 'off' - }; - configProvider.onDidChangeConfiguration(e => { - proxySetting.config = configProvider.getConfiguration('http') - .get('proxySupport') || 'off'; - }); - const certSetting = { - config: certSettingV1(configProvider) - }; - configProvider.onDidChangeConfiguration(e => { - certSetting.config = certSettingV1(configProvider); - }); - +function createPatchedModules(params: ProxyAgentParams, resolveProxy: ReturnType) { return { - http: { - off: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'off' }, certSetting, true)), - on: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'on' }, certSetting, true)), - override: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'override' }, certSetting, true)), - onRequest: Object.assign({}, http, createHttpPatch(http, resolveProxy, proxySetting, certSetting, true)), - default: Object.assign(http, createHttpPatch(http, resolveProxy, proxySetting, certSetting, false)) // run last - } as Record, - https: { - off: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'off' }, certSetting, true)), - on: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'on' }, certSetting, true)), - override: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'override' }, certSetting, true)), - onRequest: Object.assign({}, https, createHttpPatch(https, resolveProxy, proxySetting, certSetting, true)), - default: Object.assign(https, createHttpPatch(https, resolveProxy, proxySetting, certSetting, false)) // run last - } as Record, + http: Object.assign(http, createHttpPatch(params, http, resolveProxy)), + https: Object.assign(https, createHttpPatch(params, https, resolveProxy)), net: Object.assign(net, createNetPatch(params, net)), tls: Object.assign(tls, createTlsPatch(params, tls)) }; @@ -128,17 +101,55 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku return original.apply(this, arguments); } - const modules = lookup[request]; const ext = extensionPaths.findSubstr(URI.file(parent.filename)); let cache = modulesCache.get(ext); if (!cache) { modulesCache.set(ext, cache = {}); } if (!cache[request]) { - const mod = modules.default; + const mod = lookup[request]; cache[request] = { ...mod }; // Copy to work around #93167. } return cache[request]; }; }); } + +async function lookupProxyAuthorization( + extHostLogService: ILogService, + configProvider: ExtHostConfigProvider, + proxyAuthenticateCache: Record, + pendingLookups: Record>, + proxyURL: string, + proxyAuthenticate?: string | string[] +): Promise { + const cached = proxyAuthenticateCache[proxyURL]; + if (proxyAuthenticate) { + proxyAuthenticateCache[proxyURL] = proxyAuthenticate; + } + extHostLogService.trace('ProxyResolver#lookupProxyAuthorization callback', `proxyURL:${proxyURL}`, `proxyAuthenticate:${proxyAuthenticate}`, `proxyAuthenticateCache:${cached}`); + const header = proxyAuthenticate || cached; + const authenticate = Array.isArray(header) ? header : typeof header === 'string' ? [header] : []; + if (authenticate.some(a => /^(Negotiate|Kerberos)( |$)/i.test(a))) { + const lookupKey = `${proxyURL}:Negotiate`; + return pendingLookups[lookupKey] ??= (async () => { + try { + const kerberos = await import('kerberos'); + const url = new URL(proxyURL); + // TODO: Add core user setting. + const spn = configProvider.getConfiguration('github.copilot')?.advanced?.kerberosServicePrincipal as string | undefined + || (process.platform === 'win32' ? `HTTP/${url.hostname}` : `HTTP@${url.hostname}`); + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Kerberos authentication lookup', `proxyURL:${proxyURL}`, `spn:${spn}`); + const client = await kerberos.initializeClient(spn); + const response = await client.step(''); + return 'Negotiate ' + response; + } catch (err) { + extHostLogService.error('ProxyResolver#lookupProxyAuthorization Kerberos authentication failed', err); + return undefined; + } finally { + delete pendingLookups[lookupKey]; + } + })(); + } + return undefined; +} diff --git a/yarn.lock b/yarn.lock index 393430cea74c2..974dcf393b48e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1001,6 +1001,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/kerberos@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@types/kerberos/-/kerberos-1.1.2.tgz#2a774abd48f727852f697d74241e9de3aea8e646" + integrity sha512-cLixfcXjdj7qohLasmC1G4fh+en4e4g7mFZiG38D+K9rS9BRKFlq1JH5dGkQzICckbu4wM+RcwSa4VRHlBg7Rg== + "@types/keytar@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@types/keytar/-/keytar-4.4.0.tgz#ca24e6ee6d0df10c003aafe26e93113b8faf0d8e" @@ -1304,10 +1309,10 @@ bindings "^1.5.0" node-addon-api "^6.0.0" -"@vscode/proxy-agent@^0.16.0": - version "0.16.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.16.0.tgz#32054387f7aaf26d1b5d53f553d53bfd8489eab8" - integrity sha512-b8yBHgdngDrP+9HPJtnPUJjPHd+zfEvOYoc8KioWJVs0rFVT2U77nFDVC70Mrrscf87ya2a/sPY32nTrwFfOQQ== +"@vscode/proxy-agent@^0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.0.tgz#e60d43e2779c07c223d3bad9b7de8eedf7ca1294" + integrity sha512-p4gJ57KeWGw0CEG9R13dmsgmWmszoOQ836pf/PVbAf+ZRF27il3QcFvOhA10XE2QFHaOcRxuJnnIpUD1lSMvqQ== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" @@ -6205,6 +6210,15 @@ just-extend@^4.0.2: resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744" integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg== +kerberos@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/kerberos/-/kerberos-2.0.1.tgz#663b0b46883b4da84495f60f2e9e399a43a33ef5" + integrity sha512-O/jIgbdGK566eUhFwIcgalbqirYU/r76MW7/UFw06Fd9x5bSwgyZWL/Vm26aAmezQww/G9KYkmmJBkEkPk5HLw== + dependencies: + bindings "^1.5.0" + node-addon-api "^4.3.0" + prebuild-install "7.1.1" + keygrip@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" @@ -8194,6 +8208,24 @@ postcss@^8.4.19: picocolors "^1.0.0" source-map-js "^1.0.2" +prebuild-install@7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + prebuild-install@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.1.tgz#c10075727c318efe72412f333e0ef625beaf3870" From 31b9e9aa9bef01641c3f12014c93a0b3d917ac1a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:14:31 -0700 Subject: [PATCH 038/216] Wait for terminal data before using terminal.selection APIs --- .../src/singlefolder-tests/terminal.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 50197899110a7..36237a54de7ba 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -357,6 +357,13 @@ import { assertNoRpc, poll } from '../utils'; test('should be defined after selecting all content', async () => { const terminal = window.createTerminal({ name: 'selection test' }); terminal.show(); + // Wait for some terminal data + await new Promise(r => { + const disposable = window.onDidWriteTerminalData(() => { + disposable.dispose(); + r(); + }); + }); await commands.executeCommand('workbench.action.terminal.selectAll'); await poll(() => Promise.resolve(), () => terminal.selection !== undefined, 'selection should be defined'); terminal.dispose(); @@ -364,6 +371,13 @@ import { assertNoRpc, poll } from '../utils'; test('should be undefined after clearing a selection', async () => { const terminal = window.createTerminal({ name: 'selection test' }); terminal.show(); + // Wait for some terminal data + await new Promise(r => { + const disposable = window.onDidWriteTerminalData(() => { + disposable.dispose(); + r(); + }); + }); await commands.executeCommand('workbench.action.terminal.selectAll'); await poll(() => Promise.resolve(), () => terminal.selection !== undefined, 'selection should be defined'); await commands.executeCommand('workbench.action.terminal.clearSelection'); From fd68fa632cea9ab627b52c1ec9a4ad908f6200c6 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 11:30:47 -0700 Subject: [PATCH 039/216] add basics --- .../accessibility/browser/accessibleView.ts | 24 ++++++++++++++++++- .../contrib/chat/browser/chatListRenderer.ts | 20 +++++++++++++--- .../contrib/chat/browser/chatWidget.ts | 5 ++-- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 52e818673beb9..897352f3dd65b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -25,6 +25,8 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { AccessibilityVerbositySettingId, AccessibleViewAction, AccessibleViewNextAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; const enum DEFAULT { WIDTH = 800, @@ -48,6 +50,8 @@ export interface IAccessibleViewService { show(provider: IAccessibleContentProvider): void; next(): void; previous(): void; + getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string; + getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string; } export const enum AccessibleViewType { @@ -242,7 +246,9 @@ export class AccessibleViewService extends Disposable implements IAccessibleView private _accessibleView: AccessibleView | undefined; constructor( - @IInstantiationService private readonly _instantiationService: IInstantiationService + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IKeybindingService private readonly _keybindingService: IKeybindingService ) { super(); } @@ -259,4 +265,20 @@ export class AccessibleViewService extends Disposable implements IAccessibleView previous(): void { this._accessibleView?.previous(); } + getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string { + let hint = ''; + const keybinding = this._keybindingService.lookupKeybinding(AccessibleViewAction.id)?.getAriaLabel(); + if (this._configurationService.getValue(verbositySettingKey)) { + hint = keybinding ? localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); + } + return hint; + } + getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string { + let hint = ''; + const nextKeybinding = this._keybindingService.lookupKeybinding(AccessibleViewNextAction.id)?.getAriaLabel(); + if (this._configurationService.getValue(verbositySettingKey)) { + hint = nextKeybinding ? localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", nextKeybinding) : localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); + } + return hint; + } } diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index bbac658da6f45..ee93693be68a5 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -50,6 +50,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { ILogService } from 'vs/platform/log/common/log'; import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { IChatCodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; import { ChatTreeItem, IChatCodeBlockInfo } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatFollowups } from 'vs/workbench/contrib/chat/browser/chatFollowups'; @@ -513,6 +514,12 @@ export class ChatListDelegate implements IListVirtualDelegate { } export class ChatAccessibilityProvider implements IListAccessibilityProvider { + + constructor( + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService + ) { + + } getWidgetRole(): AriaRole { return 'list'; } @@ -542,15 +549,22 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider token.type === 'code')?.length ?? 0; switch (codeBlockCount) { case 0: - return element.response.value; + label = localize('noCodeBlocks', "{0} {1}", element.response.value, accessibleViewHint); + break; case 1: - return localize('singleCodeBlock', "1 code block: {0}", element.response.value); + label = localize('singleCodeBlock', "1 code block: {0} {1}", element.response.value, accessibleViewHint); + break; default: - return localize('multiCodeBlock', "{0} code blocks: {1}", codeBlockCount, element.response.value); + label = localize('multiCodeBlock', "{0} code blocks: {1}", codeBlockCount, element.response.value, accessibleViewHint); + break; } + label = label.trim(); + return label; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 38b3a62da6e2a..0792427a5c020 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -114,7 +114,8 @@ export class ChatWidget extends Disposable implements IChatWidget { @IChatService private readonly chatService: IChatService, @IChatWidgetService chatWidgetService: IChatWidgetService, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService + @IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService, + @IInstantiationService private readonly _instantiationService: IInstantiationService ) { super(); CONTEXT_IN_CHAT_SESSION.bindTo(contextKeyService).set(true); @@ -274,7 +275,7 @@ export class ChatWidget extends Disposable implements IChatWidget { horizontalScrolling: false, supportDynamicHeights: true, hideTwistiesOfChildlessElements: true, - accessibilityProvider: new ChatAccessibilityProvider(), + accessibilityProvider: this._instantiationService.createInstance(ChatAccessibilityProvider), keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: (e: ChatTreeItem) => isRequestVM(e) ? e.message : isResponseVM(e) ? e.response.value : '' }, // TODO setRowLineHeight: false, overrideStyles: { From 3eed9319874b7ca037128962593b6a8630869253 Mon Sep 17 00:00:00 2001 From: Matt Bierner Date: Wed, 19 Jul 2023 11:43:39 -0700 Subject: [PATCH 040/216] Decode relative links in markdown notebook cells (#188211) Fixes #188209 --- .../browser/view/renderers/backLayerWebView.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 189bd55748887..fe247bf9a854e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -767,7 +767,8 @@ export class BackLayerWebView extends Themable { const uri = URI.parse(data.href); this._handleNotebookCellResource(uri); } else if (!/^[\w\-]+:/.test(data.href)) { - this._handleResourceOpening(data.href); + // Uri without scheme, such as a file path + this._handleResourceOpening(tryDecodeURIComponent(data.href)); } else { // uri with scheme if (osPath.isAbsolute(data.href)) { @@ -1795,3 +1796,11 @@ function getTokenizationCss() { const tokenizationCss = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; return tokenizationCss; } + +function tryDecodeURIComponent(uri: string) { + try { + return decodeURIComponent(uri); + } catch { + return uri; + } +} From b0689b4d885ff3cb743d632dc5105e094f585f3c Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Wed, 19 Jul 2023 13:04:32 -0700 Subject: [PATCH 041/216] Try a dynamic height rendering approach (#188126) * Try a dynamic height rendering approach * Working implementation Summary: 1. dispose the view model & ChatInputPart when we dispose the ChatWidget 2. handle async/next tick code better using disposables 3. layout onDidChangeItems & when a ChatItem height changes for better accuracy 4. use new useDynamicMessageLayout for inputOnTop --- .../multipleByScrollQuickQuestionAction.ts | 53 +++++++++------ .../contrib/chat/browser/chatListRenderer.ts | 4 ++ .../contrib/chat/browser/chatWidget.ts | 65 +++++++++++++++++-- .../contrib/chat/common/chatViewModel.ts | 1 + 4 files changed, 98 insertions(+), 25 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts index 095b8c2df8f88..450e309742bf0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts @@ -22,13 +22,18 @@ import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget'; import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +interface IChatQuickQuestionModeOptions { + renderInputOnTop: boolean; + useDynamicMessageLayout: boolean; +} + class BaseChatQuickQuestionMode implements IQuickQuestionMode { private _currentTimer: any | undefined; private _input: IQuickPick | undefined; private _currentChat: QuickChat | undefined; constructor( - private readonly renderInputOnTop: boolean + private readonly _options: IChatQuickQuestionModeOptions ) { } run(accessor: ServicesAccessor, query: string): void { @@ -62,12 +67,17 @@ class BaseChatQuickQuestionMode implements IQuickQuestionMode { this._input.hideInput = true; - const containerList = dom.$('.interactive-list'); - const containerSession = dom.$('.interactive-session', undefined, containerList); - containerSession.style.height = '500px'; - containerList.style.position = 'relative'; + const containerSession = dom.$('.interactive-session'); this._input.widget = containerSession; + this._currentChat ??= instantiationService.createInstance(QuickChat, { + providerId: providerInfo.id, + ...this._options + }); + // show needs to come before the current chat rendering + this._input.show(); + this._currentChat.render(containerSession); + const clearButton = { iconClass: ThemeIcon.asClassName(Codicon.clearAll), tooltip: localize('clear', "Clear"), @@ -92,12 +102,6 @@ class BaseChatQuickQuestionMode implements IQuickQuestionMode { //#endregion - this._currentChat ??= instantiationService.createInstance(QuickChat, { - providerId: providerInfo.id, - renderInputOnTop: this.renderInputOnTop, - }); - this._currentChat.render(containerSession); - disposableStore.add(this._input.onDidAccept(() => { this._currentChat?.acceptInput(); })); @@ -110,7 +114,6 @@ class BaseChatQuickQuestionMode implements IQuickQuestionMode { } })); - this._input.show(); this._currentChat.layout(); this._currentChat.focus(); @@ -134,7 +137,7 @@ class QuickChat extends Disposable { private _currentParentElement?: HTMLElement; constructor( - private readonly chatViewOptions: IChatViewOptions & { renderInputOnTop: boolean }, + private readonly _options: IChatViewOptions & IChatQuickQuestionModeOptions, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IChatService private readonly chatService: IChatService, @@ -157,14 +160,15 @@ class QuickChat extends Disposable { } render(parent: HTMLElement): void { - this.widget?.dispose(); this._currentParentElement = parent; + this._scopedContextKeyService?.dispose(); this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); + this.widget?.dispose(); this.widget = this._register( scopedInstantiationService.createInstance( ChatWidget, - { resource: true, renderInputOnTop: this.chatViewOptions.renderInputOnTop }, + { resource: true, renderInputOnTop: this._options.renderInputOnTop }, { listForeground: editorForeground, listBackground: editorBackground, @@ -173,6 +177,9 @@ class QuickChat extends Disposable { })); this.widget.render(parent); this.widget.setVisible(true); + if (this._options.useDynamicMessageLayout) { + this.widget.setDynamicChatTreeItemLayout(2, 600); + } this.updateModel(); if (this._currentQuery) { this.widget.inputEditor.setSelection({ @@ -203,7 +210,7 @@ class QuickChat extends Disposable { } async openChatView(): Promise { - const widget = await this._chatWidgetService.revealViewForProvider(this.chatViewOptions.providerId); + const widget = await this._chatWidgetService.revealViewForProvider(this._options.providerId); if (!widget?.viewModel || !this.model) { return; } @@ -233,13 +240,13 @@ class QuickChat extends Disposable { } layout(): void { - if (this._currentParentElement) { + if (!this._options.useDynamicMessageLayout && this._currentParentElement) { this.widget.layout(500, this._currentParentElement.offsetWidth); } } private updateModel(): void { - this.model ??= this.chatService.startSession(this.chatViewOptions.providerId, CancellationToken.None); + this.model ??= this.chatService.startSession(this._options.providerId, CancellationToken.None); if (!this.model) { throw new Error('Could not start chat session'); } @@ -252,7 +259,10 @@ AskQuickQuestionAction.registerMode( QuickQuestionMode.InputOnTopChat, class InputOnTopChatQuickQuestionMode extends BaseChatQuickQuestionMode { constructor() { - super(true); + super({ + renderInputOnTop: true, + useDynamicMessageLayout: true + }); } } ); @@ -261,7 +271,10 @@ AskQuickQuestionAction.registerMode( QuickQuestionMode.InputOnBottomChat, class InputOnBottomChatQuickQuestionMode extends BaseChatQuickQuestionMode { constructor() { - super(false); + super({ + renderInputOnTop: false, + useDynamicMessageLayout: false + }); } } ); diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index bbac658da6f45..1307054639e88 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -305,6 +305,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { - this.onDidChangeItems(); + if (!this._isDisposed) { + this.onDidChangeItems(); + } }); this._onDidChangeViewModel.fire(); @@ -135,6 +139,12 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.inputPart.inputUri; } + private _isDisposed: boolean = false; + public override dispose(): void { + this._isDisposed = true; + super.dispose(); + } + render(parent: HTMLElement): void { const viewId = 'viewId' in this.viewContext ? this.viewContext.viewId : undefined; this.editorOptions = this._register(this.instantiationService.createInstance(ChatEditorOptions, viewId, this.styles.listForeground, this.styles.inputEditorBackground, this.styles.resultEditorBackground)); @@ -193,6 +203,10 @@ export class ChatWidget extends Disposable implements IChatWidget { } }); + if (this._dynamicMessageLayoutData) { + this.layoutDynamicChatTreeItemMode(); + } + const lastItem = treeItems[treeItems.length - 1]?.element; if (lastItem && isResponseVM(lastItem) && lastItem.isComplete) { this.renderFollowups(lastItem.replyFollowups); @@ -216,13 +230,13 @@ export class ChatWidget extends Disposable implements IChatWidget { this.renderer.setVisible(visible); if (visible) { - setTimeout(() => { + this._register(disposableTimeout(() => { // Progressive rendering paused while hidden, so start it up again. // Do it after a timeout because the container is not visible yet (it should be but offsetHeight returns 0 here) if (this.visible) { this.onDidChangeItems(); } - }, 0); + }, 0)); } } @@ -336,7 +350,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } private createInput(container: HTMLElement, options?: { renderFollowups: boolean }): void { - this.inputPart = this.instantiationService.createInstance(ChatInputPart, { renderFollowups: options?.renderFollowups ?? true }); + this.inputPart = this._register(this.instantiationService.createInstance(ChatInputPart, { renderFollowups: options?.renderFollowups ?? true })); this.inputPart.render(container, '', this); this._register(this.inputPart.onDidFocus(() => this._onDidFocus.fire())); @@ -471,6 +485,47 @@ export class ChatWidget extends Disposable implements IChatWidget { this.listContainer.style.height = `${height - inputPartHeight}px`; } + private _dynamicMessageLayoutData?: { numOfMessages: number; maxHeight: number }; + + // An alternative to layout, this allows you to specify the number of ChatTreeItems + // you want to show, and the max height of the container. It will then layout the + // tree to show that many items. + setDynamicChatTreeItemLayout(numOfChatTreeItems: number, maxHeight: number) { + this._dynamicMessageLayoutData = { numOfMessages: numOfChatTreeItems, maxHeight }; + this._register(this.renderer.onDidChangeItemHeight(() => this.layoutDynamicChatTreeItemMode())); + } + + layoutDynamicChatTreeItemMode(allowRecurse = true): void { + if (!this.viewModel) { + return; + } + const inputHeight = this.inputPart.layout(this._dynamicMessageLayoutData!.maxHeight, this.container.offsetWidth); + + const totalMessages = this.viewModel.getItems(); + // grab the last N messages + const messages = totalMessages.slice(-this._dynamicMessageLayoutData!.numOfMessages); + + const needsRerender = messages.some(m => m.currentRenderedHeight === undefined); + const listHeight = needsRerender + ? this._dynamicMessageLayoutData!.maxHeight + : messages.reduce((acc, message) => acc + message.currentRenderedHeight!, 0); + + this.layout( + Math.min( + // we add an additional 25px in order to show that there is scrollable content + inputHeight + listHeight + (totalMessages.length > 2 ? 25 : 0), + this._dynamicMessageLayoutData!.maxHeight + ), + this.container.offsetWidth + ); + + if (needsRerender && allowRecurse) { + // TODO: figure out a better place to reveal the last element + revealLastElement(this.tree); + this.layoutDynamicChatTreeItemMode(false); + } + } + saveState(): void { this.inputPart.saveState(); } diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index 5c3998f7fd07d..14199983c071c 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -368,4 +368,5 @@ export interface IChatWelcomeMessageViewModel { readonly username: string; readonly avatarIconUri?: URI; readonly content: IChatWelcomeMessageContent[]; + currentRenderedHeight?: number; } From 924ae6cdf0256e05c71960c8fc2e92c55871161a Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 13:10:27 -0700 Subject: [PATCH 042/216] add a bunch more --- .../browser/accessibility.contribution.ts | 10 +-- .../browser/accessibilityContribution.ts | 17 +++- .../accessibility/browser/accessibleView.ts | 30 ++++--- .../browser/actions/chatAccessibilityHelp.ts | 3 +- .../contrib/chat/browser/chat.contribution.ts | 83 +++++++++++-------- src/vs/workbench/contrib/chat/browser/chat.ts | 2 + .../contrib/chat/browser/chatWidget.ts | 44 +++++++++- .../codeEditor/browser/diffEditorHelper.ts | 4 +- .../browser/notebookAccessibilityHelp.ts | 3 +- .../browser/terminalAccessibilityHelp.ts | 3 +- 10 files changed, 138 insertions(+), 61 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 63adda5f1fa69..29976a108a529 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -12,7 +12,7 @@ import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/b import { localize } from 'vs/nls'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { AccessibilityHelpAction, AccessibleViewAction, AccessibleViewNextAction, AccessibleViewPreviousAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { AccessibilityHelpAction, AccessibilityVerbositySettingId, AccessibleViewAction, AccessibleViewNextAction, AccessibleViewPreviousAction, registerAccessibilityConfiguration } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import * as strings from 'vs/base/common/strings'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -36,7 +36,7 @@ class AccessibilityHelpProvider implements IAccessibleContentProvider { this._editor.focus(); } options: IAccessibleViewOptions = { type: AccessibleViewType.HelpMenu, ariaLabel: localize('editor-help', "editor accessibility help"), readMoreUrl: 'https://go.microsoft.com/fwlink/?linkid=851010' }; - verbositySettingKey: string = 'editor'; + verbositySettingKey = AccessibilityVerbositySettingId.Editor; constructor( private readonly _editor: ICodeEditor, @IKeybindingService private readonly _keybindingService: IKeybindingService @@ -118,7 +118,7 @@ class HoverAccessibleViewContribution extends Disposable { return false; } accessibleViewService.show({ - verbositySettingKey: 'hover', + verbositySettingKey: AccessibilityVerbositySettingId.Hover, provideContent() { return editorHoverContent; }, onClose() { }, options: this._options @@ -135,7 +135,7 @@ class HoverAccessibleViewContribution extends Disposable { return false; } accessibleViewService.show({ - verbositySettingKey: 'hover', + verbositySettingKey: AccessibilityVerbositySettingId.Hover, provideContent() { return extensionHoverContent; }, onClose() { }, options: this._options @@ -208,7 +208,7 @@ class NotificationAccessibleViewContribution extends Disposable { list.focusPrevious(); renderAccessibleView(); }, - verbositySettingKey: 'notifications', + verbositySettingKey: AccessibilityVerbositySettingId.Notification, options: { ariaLabel: localize('notification', "Notification Accessible View"), type: AccessibleViewType.View diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts index 9dc863f3efd31..80e55ef368ed2 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityContribution.ts @@ -17,7 +17,10 @@ export const enum AccessibilityVerbositySettingId { Chat = 'accessibility.verbosity.panelChat', InlineChat = 'accessibility.verbosity.inlineChat', KeybindingsEditor = 'accessibility.verbosity.keybindingsEditor', - Notebook = 'accessibility.verbosity.notebook' + Notebook = 'accessibility.verbosity.notebook', + Editor = 'accessibility.verbosity.editor', + Hover = 'accessibility.verbosity.hover', + Notification = 'accessibility.verbosity.notification' } const baseProperty: object = { @@ -54,6 +57,14 @@ const configuration: IConfigurationNode = { [AccessibilityVerbositySettingId.Notebook]: { description: localize('verbosity.notebook', 'Provide information about how to focus the cell container or inner editor when a notebook cell is focused.'), ...baseProperty + }, + [AccessibilityVerbositySettingId.Hover]: { + description: localize('verbosity.hover', 'Provide information about how to open the hover in an accessible view.'), + ...baseProperty + }, + [AccessibilityVerbositySettingId.Notification]: { + description: localize('verbosity.notification', 'Provide information about how to open the notification in an accessible view.'), + ...baseProperty } } }; @@ -118,7 +129,7 @@ export const AccessibleViewNextAction = registerCommand(new MultiCommand({ menuOpts: [{ menuId: MenuId.CommandPalette, group: '', - title: localize('editor.action.accessibleViewNext', "Next Accessible View"), + title: localize('editor.action.accessibleViewNext', "Show Next in Accessible View"), order: 1 }], })); @@ -133,7 +144,7 @@ export const AccessibleViewPreviousAction = registerCommand(new MultiCommand({ menuOpts: [{ menuId: MenuId.CommandPalette, group: '', - title: localize('editor.action.accessibleViewPrevious', "Previous Accessible View"), + title: localize('editor.action.accessibleViewPrevious', "Show Previous in Accessible View"), order: 1 }], })); diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 897352f3dd65b..df10b0f0cae3f 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -34,7 +34,7 @@ const enum DEFAULT { } export interface IAccessibleContentProvider { - verbositySettingKey: string; + verbositySettingKey: AccessibilityVerbositySettingId; provideContent(): string; onClose(): void; onKeyDown?(e: IKeyboardEvent): void; @@ -51,7 +51,6 @@ export interface IAccessibleViewService { next(): void; previous(): void; getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string; - getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string; } export const enum AccessibleViewType { @@ -83,7 +82,8 @@ class AccessibleView extends Disposable { @IModelService private readonly _modelService: IModelService, @IContextViewService private readonly _contextViewService: IContextViewService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, - @IAccessibilityService private readonly _accessibilityService: IAccessibilityService + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, + @IKeybindingService private readonly _keybindingService: IKeybindingService ) { super(); this._accessiblityHelpIsShown = accessibilityHelpIsShown.bindTo(this._contextKeyService); @@ -173,7 +173,7 @@ class AccessibleView extends Disposable { ? AccessibilityHelpNLS.changeConfigToOnMac : AccessibilityHelpNLS.changeConfigToOnWinLinux ); - if (accessibilitySupport && provider.verbositySettingKey === 'editor') { + if (accessibilitySupport && provider.verbositySettingKey === AccessibilityVerbositySettingId.Editor) { message = AccessibilityHelpNLS.auto_on; message += '\n'; } else if (!accessibilitySupport) { @@ -197,7 +197,7 @@ class AccessibleView extends Disposable { model.setLanguage(provider.options.language); } container.appendChild(this._editorContainer); - this._editorWidget.updateOptions({ ariaLabel: provider.options.ariaLabel }); + this._editorWidget.updateOptions({ ariaLabel: provider.next && provider.previous ? localize('accessibleViewAriaLabelWithNav', "{0} {1}", provider.options.ariaLabel, this._getNavigationAriaHint(provider.verbositySettingKey)) : localize('accessibleViewAriaLabel', "{0}", provider.options.ariaLabel) }); this._editorWidget.focus(); }); const disposableStore = new DisposableStore(); @@ -239,6 +239,16 @@ class AccessibleView extends Disposable { } return this._modelService.createModel(resource.fragment, null, resource, false); } + + private _getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string { + let hint = ''; + const nextKeybinding = this._keybindingService.lookupKeybinding(AccessibleViewNextAction.id)?.getAriaLabel(); + const previousKeybinding = this._keybindingService.lookupKeybinding(AccessibleViewNextAction.id)?.getAriaLabel(); + if (this._configurationService.getValue(verbositySettingKey)) { + hint = (nextKeybinding && previousKeybinding) ? localize('chatAccessibleViewNextPreviousHint', "Focus the next {0} or previous {1} item without leaving the accessible view", nextKeybinding, previousKeybinding) : localize('chatAccessibleViewNextPreviousHintNoKb', "Focus the next or previous item without leaving the accessible view by configuring keybindings for Show Next / Previous in Accessible View"); + } + return hint; + } } export class AccessibleViewService extends Disposable implements IAccessibleViewService { @@ -273,12 +283,6 @@ export class AccessibleViewService extends Disposable implements IAccessibleView } return hint; } - getNavigationAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string { - let hint = ''; - const nextKeybinding = this._keybindingService.lookupKeybinding(AccessibleViewNextAction.id)?.getAriaLabel(); - if (this._configurationService.getValue(verbositySettingKey)) { - hint = nextKeybinding ? localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", nextKeybinding) : localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); - } - return hint; - } } + + diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index fd147f172eab9..b1aeb25637897 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -12,6 +12,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string { const keybindingService = accessor.get(IKeybindingService); @@ -70,7 +71,7 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi inputEditor.getSupportedActions(); const helpText = getAccessibilityHelpText(accessor, type); accessibleViewService.show({ - verbositySettingKey: type, + verbositySettingKey: type as AccessibilityVerbositySettingId, provideContent: () => helpText, onClose: () => { if (type === 'panelChat' && cachedPosition) { diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 656485ed70a90..d27847fd437f0 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -37,7 +37,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import '../common/chatColors'; import { registerMoveActions } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { registerClearActions } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; -import { AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { AccessibilityVerbositySettingId, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; @@ -135,48 +135,63 @@ class ChatAccessibleViewContribution extends Disposable { const accessibleViewService = accessor.get(IAccessibleViewService); const widgetService = accessor.get(IChatWidgetService); const codeEditorService = accessor.get(ICodeEditorService); + return renderAccessibleView(false, accessibleViewService, widgetService, codeEditorService); + function renderAccessibleView(ignoreChatInput: boolean, accessibleViewService: IAccessibleViewService, widgetService: IChatWidgetService, codeEditorService: ICodeEditorService): boolean { - let widget = widgetService.lastFocusedWidget; - if (!widget) { - return false; - } + let widget = widgetService.lastFocusedWidget; + if (!widget) { + return false; + } - const chatInputFocused = !!(codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor()); + const chatInputFocused = !ignoreChatInput && !!(codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor()); - if (chatInputFocused) { - widget.focusLastMessage(); - widget = widgetService.lastFocusedWidget; - } + if (chatInputFocused) { + widget.focusLastMessage(); + widget = widgetService.lastFocusedWidget; + } - if (!widget) { - return false; - } + if (!widget) { + return false; + } - const verifiedWidget: IChatWidget = widget; - const focusedItem = verifiedWidget.getFocus(); + const verifiedWidget: IChatWidget = widget; + const focusedItem = verifiedWidget.getFocus(); - if (!focusedItem) { - return false; - } + if (!focusedItem) { + return false; + } - const responseContent = isResponseVM(focusedItem) ? focusedItem.response.value : undefined; - if (!responseContent) { - return false; - } + widget.focus(focusedItem); + + const responseContent = isResponseVM(focusedItem) ? focusedItem.response.value : undefined; + if (!responseContent) { + return false; + } - accessibleViewService.show({ - verbositySettingKey: 'panelChat', - provideContent(): string { return responseContent; }, - onClose() { - if (chatInputFocused) { - verifiedWidget.focusInput(); - } else { + accessibleViewService.show({ + verbositySettingKey: AccessibilityVerbositySettingId.Chat, + provideContent(): string { return responseContent; }, + onClose() { + if (chatInputFocused) { + verifiedWidget.focusInput(); + } else { + verifiedWidget.focus(focusedItem); + } + }, + next() { + verifiedWidget.focus(focusedItem); + verifiedWidget.focusNext(focusedItem.id); + renderAccessibleView(true, accessibleViewService, widgetService, codeEditorService); + }, + previous() { verifiedWidget.focus(focusedItem); - } - }, - options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } - }); - return true; + verifiedWidget.focusPrevious(focusedItem.id); + renderAccessibleView(true, accessibleViewService, widgetService, codeEditorService); + }, + options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } + }); + return true; + } }, CONTEXT_IN_CHAT_SESSION)); } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 996a999286cce..b454aaf079562 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -56,6 +56,8 @@ export interface IChatWidget { reveal(item: ChatTreeItem): void; focus(item: ChatTreeItem): void; + focusNext(id: string): void; + focusPrevious(id: string): void; getFocus(): ChatTreeItem | undefined; acceptInput(query?: string): void; focusLastMessage(): void; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 0792427a5c020..523a497438245 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -168,6 +168,46 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.focus(); } + focusNext(id: string): void { + const items = this.viewModel?.getItems(); + if (!items) { + return; + } + const focusedElement = items?.find(i => i.id === id) as any as IChatResponseViewModel; + const responseItems = items?.filter(i => isResponseVM(i)); + if (!focusedElement) { + return; + } + const currentlyFocusedIndex = responseItems?.indexOf(focusedElement); + if (currentlyFocusedIndex === undefined || !responseItems) { + return; + } + if (currentlyFocusedIndex === responseItems.length - 1) { + return; + } + this.focus(responseItems[currentlyFocusedIndex + 1], true); + } + + focusPrevious(id: string): void { + const items = this.viewModel?.getItems(); + if (!items) { + return; + } + const focusedElement = items.find(i => i.id === id) as any as IChatResponseViewModel; + const responseItems = items.filter(i => isResponseVM(i)); + if (!focusedElement) { + return; + } + const currentlyFocusedIndex = responseItems?.indexOf(focusedElement); + if (!currentlyFocusedIndex || !responseItems) { + return; + } + if (currentlyFocusedIndex - 1 < 0) { + return; + } + this.focus(responseItems[currentlyFocusedIndex - 1], true); + } + private onDidChangeItems() { if (this.tree && this.visible) { const treeItems = (this.viewModel?.getItems() ?? []) @@ -387,7 +427,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.reveal(item); } - focus(item: ChatTreeItem): void { + focus(item: ChatTreeItem, noDomFocus?: boolean): void { const items = this.tree.getNode(null).children; const node = items.find(i => i.element?.id === item.id); if (!node) { @@ -395,7 +435,9 @@ export class ChatWidget extends Disposable implements IChatWidget { } this.tree.setFocus([node.element]); + // if (!noDomFocus) { this.tree.domFocus(); + // } } async acceptInput(query?: string | IChatReplyFollowup): Promise { diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index 7f017f54f5cbf..c759bc6f8b535 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -17,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { FloatingClickWidget } from 'vs/workbench/browser/codeeditor'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { AccessibilityHelpAction, AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { localize } from 'vs/nls'; @@ -101,7 +101,7 @@ function createScreenReaderHelp(): IDisposable { const keys = ['audioCues.diffLineDeleted', 'audioCues.diffLineInserted', 'audioCues.diffLineModified']; accessibleViewService.show({ - verbositySettingKey: 'diffEditor', + verbositySettingKey: AccessibilityVerbositySettingId.DiffEditor, provideContent: () => [ localize('msg1', "You are in a diff editor."), localize('msg2', "Press {0} or {1} to view the next or previous diff in the diff review mode that is optimized for screen readers.", next, previous), diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts index 717b6167614d7..c5671a2487701 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts @@ -9,6 +9,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); @@ -46,7 +47,7 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi const accessibleViewService = accessor.get(IAccessibleViewService); const helpText = getAccessibilityHelpText(accessor); accessibleViewService.show({ - verbositySettingKey: 'notebook', + verbositySettingKey: AccessibilityVerbositySettingId.Notebook, provideContent: () => helpText, onClose: () => { editor.focus(); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts index d5d73c308a1e2..a442d9933fa3e 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibilityHelp.ts @@ -10,6 +10,7 @@ import { IAccessibilityService } from 'vs/platform/accessibility/common/accessib import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ShellIntegrationStatus, WindowsShellType } from 'vs/platform/terminal/common/terminal'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { AccessibleViewType, IAccessibleContentProvider, IAccessibleViewOptions } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; @@ -34,7 +35,7 @@ export class TerminalAccessibleContentProvider extends Disposable implements IAc ariaLabel: localize('terminal-help-label', "terminal accessibility help"), readMoreUrl: 'https://code.visualstudio.com/docs/editor/accessibility#_terminal-accessibility' }; - verbositySettingKey: string = 'terminal'; + verbositySettingKey = AccessibilityVerbositySettingId.Terminal; constructor( private readonly _instance: Pick, From 85371faf398236fc00119d8eecc0c65fa9cff8e2 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 13:19:20 -0700 Subject: [PATCH 043/216] use hint for notifications --- .../notifications/notificationsToasts.ts | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 62d27bfc8ecae..aa936423b461c 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -25,6 +25,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IntervalCounter } from 'vs/base/common/async'; import { assertIsDefined } from 'vs/base/common/types'; import { NotificationsToastsVisibleContext } from 'vs/workbench/common/contextkeys'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; interface INotificationToast { readonly item: INotificationViewItem; @@ -83,7 +85,9 @@ export class NotificationsToasts extends Themable implements INotificationsToast @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IConfigurationService private readonly _configurationService: IConfigurationService ) { super(themeService); @@ -187,11 +191,24 @@ export class NotificationsToasts extends Themable implements INotificationsToast const notificationList = this.instantiationService.createInstance(NotificationsList, notificationToast, { verticalScrollMode: ScrollbarVisibility.Hidden, widgetAriaLabel: (() => { - if (!item.source) { - return localize('notificationAriaLabel', "{0}, notification", item.message.raw); + let accessibleViewHint: string | undefined; + const keybinding = this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); + if (this._configurationService.getValue('accessibility.verbosity.notification')) { + accessibleViewHint = keybinding ? localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); } - return localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", item.message.raw, item.source); + if (!item.source) { + if (accessibleViewHint) { + return localize('notificationAriaLabelViewHint', "{0}, notification {1}", item.message.raw, accessibleViewHint); + } else { + return localize('notificationAriaLabel', "{0}, notification", item.message.raw); + } + } + if (accessibleViewHint) { + return localize('notificationWithSourceAriaLabelViewHint', "{0}, source: {1}, notification {2}", item.message.raw, item.source, accessibleViewHint); + } else { + return localize('notificationWithSourceAriaLabel', "{0}, source: {1}, notification", item.message.raw, item.source); + } })() }); itemDisposables.add(notificationList); From 3ed9e5606d38eacd883b7912a3ff5cb56695242b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 14:13:58 -0700 Subject: [PATCH 044/216] clean up --- .../workbench/contrib/accessibility/browser/accessibleView.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index df10b0f0cae3f..fad255a9efb8b 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -245,7 +245,7 @@ class AccessibleView extends Disposable { const nextKeybinding = this._keybindingService.lookupKeybinding(AccessibleViewNextAction.id)?.getAriaLabel(); const previousKeybinding = this._keybindingService.lookupKeybinding(AccessibleViewNextAction.id)?.getAriaLabel(); if (this._configurationService.getValue(verbositySettingKey)) { - hint = (nextKeybinding && previousKeybinding) ? localize('chatAccessibleViewNextPreviousHint', "Focus the next {0} or previous {1} item without leaving the accessible view", nextKeybinding, previousKeybinding) : localize('chatAccessibleViewNextPreviousHintNoKb', "Focus the next or previous item without leaving the accessible view by configuring keybindings for Show Next / Previous in Accessible View"); + hint = (nextKeybinding && previousKeybinding) ? localize('chatAccessibleViewNextPreviousHint', "Show the next {0} or previous {1} item in the accessible view", nextKeybinding, previousKeybinding) : localize('chatAccessibleViewNextPreviousHintNoKb', "Show the next or previous item in the accessible view by configuring keybindings for Show Next / Previous in Accessible View"); } return hint; } @@ -284,5 +284,3 @@ export class AccessibleViewService extends Disposable implements IAccessibleView return hint; } } - - From fc411d58f2ee059866622393587c5a730693dbb7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 14:19:47 -0700 Subject: [PATCH 045/216] clean up --- .../contrib/chat/browser/chat.contribution.ts | 4 +-- src/vs/workbench/contrib/chat/browser/chat.ts | 3 +- .../contrib/chat/browser/chatWidget.ts | 35 ++++--------------- 3 files changed, 10 insertions(+), 32 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index d27847fd437f0..e47a319b53283 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -180,12 +180,12 @@ class ChatAccessibleViewContribution extends Disposable { }, next() { verifiedWidget.focus(focusedItem); - verifiedWidget.focusNext(focusedItem.id); + verifiedWidget.focusWithId(focusedItem.id, 'next'); renderAccessibleView(true, accessibleViewService, widgetService, codeEditorService); }, previous() { verifiedWidget.focus(focusedItem); - verifiedWidget.focusPrevious(focusedItem.id); + verifiedWidget.focusWithId(focusedItem.id, 'previous'); renderAccessibleView(true, accessibleViewService, widgetService, codeEditorService); }, options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index b454aaf079562..3c0244ad52a72 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -56,8 +56,7 @@ export interface IChatWidget { reveal(item: ChatTreeItem): void; focus(item: ChatTreeItem): void; - focusNext(id: string): void; - focusPrevious(id: string): void; + focusWithId(id: string, type: 'next' | 'previous'): void; getFocus(): ChatTreeItem | undefined; acceptInput(query?: string): void; focusLastMessage(): void; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 523a497438245..96f894641947b 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -168,44 +168,25 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.focus(); } - focusNext(id: string): void { + focusWithId(id: string, type: 'next' | 'previous'): void { const items = this.viewModel?.getItems(); if (!items) { return; } - const focusedElement = items?.find(i => i.id === id) as any as IChatResponseViewModel; - const responseItems = items?.filter(i => isResponseVM(i)); + const focusedElement = items.find(i => i.id === id); if (!focusedElement) { return; } - const currentlyFocusedIndex = responseItems?.indexOf(focusedElement); - if (currentlyFocusedIndex === undefined || !responseItems) { - return; - } - if (currentlyFocusedIndex === responseItems.length - 1) { - return; - } - this.focus(responseItems[currentlyFocusedIndex + 1], true); - } - - focusPrevious(id: string): void { - const items = this.viewModel?.getItems(); - if (!items) { - return; - } - const focusedElement = items.find(i => i.id === id) as any as IChatResponseViewModel; const responseItems = items.filter(i => isResponseVM(i)); - if (!focusedElement) { - return; - } const currentlyFocusedIndex = responseItems?.indexOf(focusedElement); - if (!currentlyFocusedIndex || !responseItems) { + if (currentlyFocusedIndex === undefined || !responseItems) { return; } - if (currentlyFocusedIndex - 1 < 0) { + const indexToFocus = type === 'next' ? currentlyFocusedIndex + 1 : currentlyFocusedIndex - 1; + if (indexToFocus < 0 || indexToFocus === responseItems.length - 1) { return; } - this.focus(responseItems[currentlyFocusedIndex - 1], true); + this.focus(responseItems[indexToFocus]); } private onDidChangeItems() { @@ -427,7 +408,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.tree.reveal(item); } - focus(item: ChatTreeItem, noDomFocus?: boolean): void { + focus(item: ChatTreeItem): void { const items = this.tree.getNode(null).children; const node = items.find(i => i.element?.id === item.id); if (!node) { @@ -435,9 +416,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } this.tree.setFocus([node.element]); - // if (!noDomFocus) { this.tree.domFocus(); - // } } async acceptInput(query?: string | IChatReplyFollowup): Promise { From 8180c273023f470af14de86020b6cc9dfa60e098 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 14:27:07 -0700 Subject: [PATCH 046/216] make it more concise --- .../contrib/chat/browser/chat.contribution.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index e47a319b53283..b57bb5ad970c8 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -135,15 +135,14 @@ class ChatAccessibleViewContribution extends Disposable { const accessibleViewService = accessor.get(IAccessibleViewService); const widgetService = accessor.get(IChatWidgetService); const codeEditorService = accessor.get(ICodeEditorService); - return renderAccessibleView(false, accessibleViewService, widgetService, codeEditorService); - function renderAccessibleView(ignoreChatInput: boolean, accessibleViewService: IAccessibleViewService, widgetService: IChatWidgetService, codeEditorService: ICodeEditorService): boolean { - + return renderAccessibleView(accessibleViewService, widgetService, codeEditorService, true); + function renderAccessibleView(accessibleViewService: IAccessibleViewService, widgetService: IChatWidgetService, codeEditorService: ICodeEditorService, initialRender?: boolean): boolean { let widget = widgetService.lastFocusedWidget; if (!widget) { return false; } - const chatInputFocused = !ignoreChatInput && !!(codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor()); + const chatInputFocused = initialRender && !!(codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor()); if (chatInputFocused) { widget.focusLastMessage(); @@ -179,14 +178,12 @@ class ChatAccessibleViewContribution extends Disposable { } }, next() { - verifiedWidget.focus(focusedItem); verifiedWidget.focusWithId(focusedItem.id, 'next'); - renderAccessibleView(true, accessibleViewService, widgetService, codeEditorService); + renderAccessibleView(accessibleViewService, widgetService, codeEditorService); }, previous() { - verifiedWidget.focus(focusedItem); verifiedWidget.focusWithId(focusedItem.id, 'previous'); - renderAccessibleView(true, accessibleViewService, widgetService, codeEditorService); + renderAccessibleView(accessibleViewService, widgetService, codeEditorService); }, options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } }); From 4707bf8e74241757e94e9248bd535b0e154165cb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 14:35:20 -0700 Subject: [PATCH 047/216] reveal --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index b57bb5ad970c8..530a0e8dbd5c7 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -174,6 +174,7 @@ class ChatAccessibleViewContribution extends Disposable { if (chatInputFocused) { verifiedWidget.focusInput(); } else { + verifiedWidget.reveal(focusedItem); verifiedWidget.focus(focusedItem); } }, From 588116e1ae05f80b9ee9fd19a27d83685c2f10d3 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 14:51:52 -0700 Subject: [PATCH 048/216] go with move focus --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 4 ++-- src/vs/workbench/contrib/chat/browser/chat.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 530a0e8dbd5c7..a148099eef1bb 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -179,11 +179,11 @@ class ChatAccessibleViewContribution extends Disposable { } }, next() { - verifiedWidget.focusWithId(focusedItem.id, 'next'); + verifiedWidget.moveFocus(focusedItem.id, 'next'); renderAccessibleView(accessibleViewService, widgetService, codeEditorService); }, previous() { - verifiedWidget.focusWithId(focusedItem.id, 'previous'); + verifiedWidget.moveFocus(focusedItem.id, 'previous'); renderAccessibleView(accessibleViewService, widgetService, codeEditorService); }, options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 3c0244ad52a72..33f5ab4a7d776 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -56,7 +56,7 @@ export interface IChatWidget { reveal(item: ChatTreeItem): void; focus(item: ChatTreeItem): void; - focusWithId(id: string, type: 'next' | 'previous'): void; + moveFocus(id: string, type: 'next' | 'previous'): void; getFocus(): ChatTreeItem | undefined; acceptInput(query?: string): void; focusLastMessage(): void; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 96f894641947b..d9ca280db969e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -168,7 +168,7 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.focus(); } - focusWithId(id: string, type: 'next' | 'previous'): void { + moveFocus(id: string, type: 'next' | 'previous'): void { const items = this.viewModel?.getItems(); if (!items) { return; From ef634eed103522fc44a9dd93387a4524f26d9208 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 14:53:08 -0700 Subject: [PATCH 049/216] remove ? --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index d9ca280db969e..fe39985676725 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -178,7 +178,7 @@ export class ChatWidget extends Disposable implements IChatWidget { return; } const responseItems = items.filter(i => isResponseVM(i)); - const currentlyFocusedIndex = responseItems?.indexOf(focusedElement); + const currentlyFocusedIndex = responseItems.indexOf(focusedElement); if (currentlyFocusedIndex === undefined || !responseItems) { return; } From 4c9028c392fa1ed04a82ecc0c80f90b8203c9440 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 14:55:51 -0700 Subject: [PATCH 050/216] trim before localizing --- src/vs/workbench/contrib/chat/browser/chatListRenderer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index ee93693be68a5..c9bf348036788 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -549,7 +549,7 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider token.type === 'code')?.length ?? 0; switch (codeBlockCount) { @@ -563,7 +563,6 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider Date: Wed, 19 Jul 2023 14:59:17 -0700 Subject: [PATCH 051/216] rename --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index fe39985676725..a00a5d7507ff4 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -173,16 +173,16 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!items) { return; } - const focusedElement = items.find(i => i.id === id); - if (!focusedElement) { + const targetElement = items.find(i => i.id === id); + if (!targetElement) { return; } const responseItems = items.filter(i => isResponseVM(i)); - const currentlyFocusedIndex = responseItems.indexOf(focusedElement); - if (currentlyFocusedIndex === undefined || !responseItems) { + const targetIndex = responseItems.indexOf(targetElement); + if (targetIndex === undefined || !responseItems) { return; } - const indexToFocus = type === 'next' ? currentlyFocusedIndex + 1 : currentlyFocusedIndex - 1; + const indexToFocus = type === 'next' ? targetIndex + 1 : targetIndex - 1; if (indexToFocus < 0 || indexToFocus === responseItems.length - 1) { return; } From d967ccfce4e4a9b9dad335e94e04960f46f03ddb Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 15:06:00 -0700 Subject: [PATCH 052/216] pass in item --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 4 ++-- src/vs/workbench/contrib/chat/browser/chat.ts | 2 +- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index a148099eef1bb..a9742eba96fa9 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -179,11 +179,11 @@ class ChatAccessibleViewContribution extends Disposable { } }, next() { - verifiedWidget.moveFocus(focusedItem.id, 'next'); + verifiedWidget.moveFocus(focusedItem, 'next'); renderAccessibleView(accessibleViewService, widgetService, codeEditorService); }, previous() { - verifiedWidget.moveFocus(focusedItem.id, 'previous'); + verifiedWidget.moveFocus(focusedItem, 'previous'); renderAccessibleView(accessibleViewService, widgetService, codeEditorService); }, options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index 33f5ab4a7d776..a2ad6867efa8a 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -56,7 +56,7 @@ export interface IChatWidget { reveal(item: ChatTreeItem): void; focus(item: ChatTreeItem): void; - moveFocus(id: string, type: 'next' | 'previous'): void; + moveFocus(item: ChatTreeItem, type: 'next' | 'previous'): void; getFocus(): ChatTreeItem | undefined; acceptInput(query?: string): void; focusLastMessage(): void; diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index a00a5d7507ff4..a38a40593432d 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -168,12 +168,12 @@ export class ChatWidget extends Disposable implements IChatWidget { this.inputPart.focus(); } - moveFocus(id: string, type: 'next' | 'previous'): void { + moveFocus(item: ChatTreeItem, type: 'next' | 'previous'): void { const items = this.viewModel?.getItems(); if (!items) { return; } - const targetElement = items.find(i => i.id === id); + const targetElement = items.find(i => i.id === item.id); if (!targetElement) { return; } From 499266dee9b99d3772ee69fce9b323583515b33d Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 15:07:02 -0700 Subject: [PATCH 053/216] don't mess w id --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index a38a40593432d..2f3957a4c283f 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -173,12 +173,8 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!items) { return; } - const targetElement = items.find(i => i.id === item.id); - if (!targetElement) { - return; - } const responseItems = items.filter(i => isResponseVM(i)); - const targetIndex = responseItems.indexOf(targetElement); + const targetIndex = responseItems.indexOf(item); if (targetIndex === undefined || !responseItems) { return; } From 45908d8979af59fbf4e1ba643e1cab850ffbfb8b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Wed, 19 Jul 2023 15:07:29 -0700 Subject: [PATCH 054/216] rm check --- src/vs/workbench/contrib/chat/browser/chatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 2f3957a4c283f..6a7768c2aa118 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -175,7 +175,7 @@ export class ChatWidget extends Disposable implements IChatWidget { } const responseItems = items.filter(i => isResponseVM(i)); const targetIndex = responseItems.indexOf(item); - if (targetIndex === undefined || !responseItems) { + if (targetIndex === undefined) { return; } const indexToFocus = type === 'next' ? targetIndex + 1 : targetIndex - 1; From daf9647eec0867d7bc32d935478fec4bd9de1f56 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 20 Jul 2023 00:25:14 +0200 Subject: [PATCH 055/216] Implements audio cues for diff editor (#188274) * Implements audio cues for diff editor * Fixes CI * Fixes import --- .../diffEditorWidget2/diffEditorWidget2.ts | 24 +++++++++++++++++++ .../widget/embeddedCodeEditorWidget.ts | 4 +++- .../browser/standaloneCodeEditor.ts | 5 +++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts index 339dc36a26304..5c18a7754f598 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts @@ -42,6 +42,8 @@ import { DiffEditorEditors } from './diffEditorEditors'; import { DiffEditorOptions } from './diffEditorOptions'; import { DiffEditorViewModel, DiffMapping, DiffState } from './diffEditorViewModel'; import { AccessibleDiffViewer } from 'vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer'; +import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; +import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { private readonly elements = h('div.monaco-diff-editor.side-by-side', { style: { position: 'relative', height: '100%' } }, [ @@ -83,6 +85,7 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { @IContextKeyService private readonly _parentContextKeyService: IContextKeyService, @IInstantiationService private readonly _parentInstantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, + @IAudioCueService private readonly _audioCueService: IAudioCueService, ) { super(); codeEditorService.willCreateDiffEditor(); @@ -235,6 +238,19 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { event.event.stopPropagation(); } })); + + this._register(Event.runAndSubscribe(this._editors.modified.onDidChangeCursorPosition, (e) => { + if (e?.reason === CursorChangeReason.Explicit) { + const diff = this._diffModel.get()?.diff.get()?.mappings.find(m => m.lineRangeMapping.modifiedRange.contains(e.position.lineNumber)); + if (diff?.lineRangeMapping.modifiedRange.isEmpty) { + this._audioCueService.playAudioCue(AudioCue.diffLineDeleted); + } else if (diff?.lineRangeMapping.originalRange.isEmpty) { + this._audioCueService.playAudioCue(AudioCue.diffLineInserted); + } else if (diff) { + this._audioCueService.playAudioCue(AudioCue.diffLineModified); + } + } + })); } public getContentHeight() { @@ -429,6 +445,14 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { diff = findLast(diffs, d => d.lineRangeMapping.modifiedRange.startLineNumber < curLineNumber) ?? diffs[diffs.length - 1]; } this._goTo(diff); + + if (diff.lineRangeMapping.modifiedRange.isEmpty) { + this._audioCueService.playAudioCue(AudioCue.diffLineDeleted); + } else if (diff.lineRangeMapping.originalRange.isEmpty) { + this._audioCueService.playAudioCue(AudioCue.diffLineInserted); + } else if (diff) { + this._audioCueService.playAudioCue(AudioCue.diffLineModified); + } } revealFirstDiff(): void { diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 06c449a33eaa0..dc5dd6c299cce 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -21,6 +21,7 @@ import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2'; +import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @@ -132,8 +133,9 @@ export class EmbeddedDiffEditorWidget2 extends DiffEditorWidget2 { @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, + @IAudioCueService audioCueService: IAudioCueService, ) { - super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService); + super(domElement, parentEditor.getRawOptions(), codeEditorWidgetOptions, contextKeyService, instantiationService, codeEditorService, audioCueService); this._parentEditor = parentEditor; this._overwriteOptions = options; diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 43e1f1eef10e8..d9a8d83cf09fa 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -38,6 +38,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2'; +import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; /** * Description of an action contribution @@ -572,7 +573,8 @@ export class StandaloneDiffEditor2 extends DiffEditorWidget2 implements IStandal @IConfigurationService configurationService: IConfigurationService, @IContextMenuService contextMenuService: IContextMenuService, @IEditorProgressService editorProgressService: IEditorProgressService, - @IClipboardService clipboardService: IClipboardService + @IClipboardService clipboardService: IClipboardService, + @IAudioCueService audioCueService: IAudioCueService, ) { const options = { ..._options }; updateConfigurationService(configurationService, options, true); @@ -591,6 +593,7 @@ export class StandaloneDiffEditor2 extends DiffEditorWidget2 implements IStandal contextKeyService, instantiationService, codeEditorService, + audioCueService, ); this._configurationService = configurationService; From ea2412a977b4e2489cc68d1ba137a56524ddc225 Mon Sep 17 00:00:00 2001 From: Sandeep Sen Date: Wed, 19 Jul 2023 16:45:26 -0700 Subject: [PATCH 056/216] Adding missing . on line 815 following Logan's feedback --- .../contrib/tags/electron-sandbox/workspaceTagsService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 04d93982b2a86..ca743c05c81e3 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -814,7 +814,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { if (firstRequireBlockFound && line !== '') { const packageName: string = line.split(' ')[0].trim(); if (GoModulesToLookFor.indexOf(packageName) > -1) { - tags['workspace.go.mod' + packageName] = true; + tags['workspace.go.mod.' + packageName] = true; } } } From f72b7b1aa8db627904442c7cbe11af7bfabf6523 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 19 Jul 2023 22:19:45 -0700 Subject: [PATCH 057/216] Update distro (#188336) update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 78579fcedf4d5..6f81d56536eac 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.81.0", - "distro": "183ff774d6e9be194f3ef3014b582d92a3b1ce9d", + "distro": "73eefb31f3f577c9082133b1befd33b4dff1fbb6", "author": { "name": "Microsoft Corporation" }, From 2cbf01b666657c07cde410e76e27d5d388333433 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Wed, 19 Jul 2023 23:02:39 -0700 Subject: [PATCH 058/216] Revert "Notebook UI heading tweaks (#188117)" (#188335) This reverts commit e0559220465961f30e1b92938baf43d2403b7924. --- .../notebook/index.ts | 65 ++++++------------- .../browser/markdownDocumentRenderer.ts | 2 - 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index f050f5a3162aa..d52d6b6b12a72 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -176,39 +176,42 @@ export const activate: ActivationFunction = (ctx) => { hr { border: 0; - height: 1px; - border-bottom: 1px solid; + height: 2px; + border-bottom: 2px solid; + } + + h2, h3, h4, h5, h6 { + font-weight: normal; } h1 { - font-size: 2em; - margin-top: 0; - padding-bottom: 0.3em; - border-bottom-width: 1px; - border-bottom-style: solid; + font-size: 2.3em; } h2 { - font-size: 1.5em; - padding-bottom: 0.3em; - border-bottom-width: 1px; - border-bottom-style: solid; + font-size: 2em; } h3 { - font-size: 1.25em; + font-size: 1.7em; + } + + h3 { + font-size: 1.5em; } h4 { - font-size: 1em; + font-size: 1.3em; } h5 { - font-size: 0.875em; + font-size: 1.2em; } - h6 { - font-size: 0.85em; + h1, + h2, + h3 { + font-weight: normal; } div { @@ -226,38 +229,12 @@ export const activate: ActivationFunction = (ctx) => { } /* Removes bottom margin when only one item exists in markdown cell */ - #preview > *:not(h1):not(h2):only-child, - #preview > *:not(h1):not(h2):last-child { + #preview > *:only-child, + #preview > *:last-child { margin-bottom: 0; padding-bottom: 0; } - h1, - h2, - h3, - h4, - h5, - h6 { - font-weight: 600; - margin-top: 24px; - margin-bottom: 16px; - line-height: 1.25; - } - - .vscode-light h1, - .vscode-light h2, - .vscode-light hr, - .vscode-light td { - border-color: rgba(0, 0, 0, 0.18); - } - - .vscode-dark h1, - .vscode-dark h2, - .vscode-dark hr, - .vscode-dark td { - border-color: rgba(255, 255, 255, 0.18); - } - /* makes all markdown cells consistent */ div { min-height: var(--notebook-markdown-min-height); diff --git a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts index 622adff44b562..0cf38581b03be 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownDocumentRenderer.ts @@ -132,14 +132,12 @@ pre code { } .vscode-light h1, -.vscode-light h2, .vscode-light hr, .vscode-light td { border-color: rgba(0, 0, 0, 0.18); } .vscode-dark h1, -.vscode-dark h2, .vscode-dark hr, .vscode-dark td { border-color: rgba(255, 255, 255, 0.18); From 16ca37cae949fda12e760894901e2e134b7d8d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Thu, 20 Jul 2023 11:52:57 +0200 Subject: [PATCH 059/216] skip group policy and explorer sparse package in CIBUILD runs (#188346) --- build/azure-pipelines/win32/product-build-win32.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index d733ec41efd45..bd0334b07ef13 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -136,7 +136,7 @@ steps: - template: ../common/install-builtin-extensions.yml - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), ne(parameters.VSCODE_QUALITY, 'oss')) }}: - powershell: node build\lib\policies displayName: Generate Group Policy definitions retryCountOnTaskFailure: 3 @@ -148,7 +148,7 @@ steps: displayName: Transpile - ${{ else }}: - - ${{ if eq(parameters.VSCODE_QUALITY, 'insider') }}: + - ${{ if and(ne(parameters.VSCODE_CIBUILD, true), eq(parameters.VSCODE_QUALITY, 'insider')) }}: - powershell: node build/win32/explorer-appx-fetcher .build/win32/appx displayName: Download Explorer Sparse Package From acf5d154249a1f5525dd960759732179e5cde0a7 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 20 Jul 2023 11:56:25 +0200 Subject: [PATCH 060/216] Expose tunnel factory API to non-resolvers (#188270) --- src/vs/platform/tunnel/common/tunnel.ts | 11 +++-- src/vs/platform/tunnel/node/tunnelService.ts | 10 ++--- .../api/browser/mainThreadTunnelService.ts | 7 ++- .../workbench/api/common/extHost.api.impl.ts | 4 ++ .../api/common/extHostTunnelService.ts | 31 ++++++++++++- .../contrib/remote/browser/remoteExplorer.ts | 4 +- .../common/extensionsApiProposals.ts | 1 + .../services/tunnel/browser/tunnelService.ts | 8 ++-- .../tunnel/electron-sandbox/tunnelService.ts | 10 ++--- .../vscode.proposed.tunnelFactory.d.ts | 44 +++++++++++++++++++ 10 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.tunnelFactory.d.ts diff --git a/src/vs/platform/tunnel/common/tunnel.ts b/src/vs/platform/tunnel/common/tunnel.ts index 8f46ec11b6093..7ce224a4b9858 100644 --- a/src/vs/platform/tunnel/common/tunnel.ts +++ b/src/vs/platform/tunnel/common/tunnel.ts @@ -64,6 +64,10 @@ export interface ITunnelProvider { forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise | undefined; } +export function isTunnelProvider(addressOrTunnelProvider: IAddressProvider | ITunnelProvider): addressOrTunnelProvider is ITunnelProvider { + return !!(addressOrTunnelProvider as ITunnelProvider).forwardPort; +} + export enum ProvidedOnAutoForward { Notify = 1, OpenBrowser = 2, @@ -315,7 +319,8 @@ export abstract class AbstractTunnelService implements ITunnelService { openTunnel(addressProvider: IAddressProvider | undefined, remoteHost: string | undefined, remotePort: number, localHost?: string, localPort?: number, elevateIfNeeded: boolean = false, privacy?: string, protocol?: string): Promise | undefined { this.logService.trace(`ForwardedPorts: (TunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`); - if (!addressProvider) { + const addressOrTunnelProvider = this._tunnelProvider ?? addressProvider; + if (!addressOrTunnelProvider) { return undefined; } @@ -332,7 +337,7 @@ export abstract class AbstractTunnelService implements ITunnelService { return; } - const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded, privacy, protocol); + const resolvedTunnel = this.retainOrCreateTunnel(addressOrTunnelProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded, privacy, protocol); if (!resolvedTunnel) { this.logService.trace(`ForwardedPorts: (TunnelService) Tunnel was not created.`); return resolvedTunnel; @@ -454,7 +459,7 @@ export abstract class AbstractTunnelService implements ITunnelService { public abstract isPortPrivileged(port: number): boolean; - protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined; + protected abstract retainOrCreateTunnel(addressProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined; protected createWithProvider(tunnelProvider: ITunnelProvider, remoteHost: string, remotePort: number, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel with provider ${remoteHost}:${remotePort} on local port ${localPort}.`); diff --git a/src/vs/platform/tunnel/node/tunnelService.ts b/src/vs/platform/tunnel/node/tunnelService.ts index 31f153b651a9d..8493fbc2fdfd8 100644 --- a/src/vs/platform/tunnel/node/tunnelService.ts +++ b/src/vs/platform/tunnel/node/tunnelService.ts @@ -14,7 +14,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ILogService } from 'vs/platform/log/common/log'; import { IProductService } from 'vs/platform/product/common/productService'; import { connectRemoteAgentTunnel, IAddressProvider, IConnectionOptions } from 'vs/platform/remote/common/remoteAgentConnection'; -import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as ISharedTunnelsService, isLocalhost, isPortPrivileged, ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel'; +import { AbstractTunnelService, isAllInterfaces, ISharedTunnelsService as ISharedTunnelsService, isLocalhost, isPortPrivileged, isTunnelProvider, ITunnelProvider, ITunnelService, RemoteTunnel, TunnelPrivacyId } from 'vs/platform/tunnel/common/tunnel'; import { ISignService } from 'vs/platform/sign/common/sign'; import { OS } from 'vs/base/common/platform'; import { IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; @@ -168,21 +168,21 @@ export class BaseTunnelService extends AbstractTunnelService { return isPortPrivileged(port, this.defaultTunnelHost, OS, os.release()); } - protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; return existing.value; } - if (this._tunnelProvider) { - return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); + if (isTunnelProvider(addressOrTunnelProvider)) { + return this.createWithProvider(addressOrTunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); } else { this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`); const options: IConnectionOptions = { commit: this.productService.commit, quality: this.productService.quality, - addressProvider, + addressProvider: addressOrTunnelProvider, remoteSocketFactoryService: this.remoteSocketFactoryService, signService: this.signService, logService: this.logService, diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index b58a0accfffe2..f2633afa04525 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -18,6 +18,8 @@ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteA import { CancellationToken } from 'vs/base/common/cancellation'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { forwardedPortsViewEnabled } from 'vs/workbench/contrib/remote/browser/tunnelView'; @extHostNamedCustomer(MainContext.MainThreadTunnelService) export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider { @@ -32,7 +34,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun @INotificationService private readonly notificationService: INotificationService, @IConfigurationService private readonly configurationService: IConfigurationService, @ILogService private readonly logService: ILogService, - @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService); @@ -192,6 +195,8 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun if (features) { this.tunnelService.setTunnelFeatures(features); } + // At this point we clearly want the ports view/features since we have a tunnel factory + this.contextKeyService.createKey(forwardedPortsViewEnabled.key, true); } async $setCandidateFilter(): Promise { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 1e585c0d3b01f..653d447ef1374 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1088,6 +1088,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'portsAttributes'); return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider); }, + registerTunnelProvider: (tunnelProvider: vscode.TunnelProvider, information: vscode.TunnelInformation) => { + checkProposedApiEnabled(extension, 'tunnelFactory'); + return extHostTunnelService.registerTunnelProvider(tunnelProvider, information); + }, registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => { checkProposedApiEnabled(extension, 'timeline'); return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter); diff --git a/src/vs/workbench/api/common/extHostTunnelService.ts b/src/vs/workbench/api/common/extHostTunnelService.ts index 4df3fc4c6005f..fbebe3ba9ed6d 100644 --- a/src/vs/workbench/api/common/extHostTunnelService.ts +++ b/src/vs/workbench/api/common/extHostTunnelService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; @@ -55,6 +56,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape { onDidChangeTunnels: vscode.Event; setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise; registerPortsAttributesProvider(portSelector: PortAttributesSelector, provider: vscode.PortAttributesProvider): IDisposable; + registerTunnelProvider(provider: vscode.TunnelProvider, information: vscode.TunnelInformation): Promise; } export const IExtHostTunnelService = createDecorator('IExtHostTunnelService'); @@ -62,7 +64,7 @@ export const IExtHostTunnelService = createDecorator('IEx export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService { readonly _serviceBrand: undefined; protected readonly _proxy: MainThreadTunnelServiceShape; - private _forwardPortProvider: ((tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => Thenable | undefined) | undefined; + private _forwardPortProvider: ((tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions, token?: vscode.CancellationToken) => Thenable | undefined) | undefined; private _showCandidatePort: (host: string, port: number, detail: string) => Thenable = () => { return Promise.resolve(true); }; private _extensionTunnels: Map> = new Map(); private _onDidChangeTunnels: Emitter = new Emitter(); @@ -135,6 +137,27 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe async $registerCandidateFinder(_enable: boolean): Promise { } + registerTunnelProvider(provider: vscode.TunnelProvider, information: vscode.TunnelInformation): Promise { + if (this._forwardPortProvider) { + throw new Error('A tunnel provider has already been registered. Only the first tunnel provider to be registered will be used.'); + } + this._forwardPortProvider = async (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions) => { + const result = await provider.provideTunnel(tunnelOptions, tunnelCreationOptions, new CancellationTokenSource().token); + return result ?? undefined; + }; + + const tunnelFeatures = information.tunnelFeatures ? { + elevation: !!information.tunnelFeatures?.elevation, + privacyOptions: information.tunnelFeatures?.privacyOptions + } : undefined; + + this._proxy.$setTunnelProvider(tunnelFeatures); + return Promise.resolve(toDisposable(() => { + this._forwardPortProvider = undefined; + this._proxy.$setTunnelProvider(undefined); + })); + } + async setTunnelFactory(provider: vscode.RemoteAuthorityResolver | undefined): Promise { // Do not wait for any of the proxy promises here. // It will delay startup and there is nothing that needs to be waited for. @@ -201,11 +224,15 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe if (this._forwardPortProvider) { try { this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Getting tunnel from provider.'); - const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions); + const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions,); this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Got tunnel promise from provider.'); if (providedPort !== undefined) { const tunnel = await providedPort; this.logService.trace('ForwardedPorts: (ExtHostTunnelService) Successfully awaited tunnel from provider.'); + if (tunnel === undefined) { + this.logService.error('ForwardedPorts: (ExtHostTunnelService) Resolved tunnel is undefined'); + return undefined; + } if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) { this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map()); } diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index 66ac320105c1d..433c796498cbf 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -76,7 +76,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu const viewEnabled: boolean = !!forwardedPortsViewEnabled.getValue(this.contextKeyService); - if (this.environmentService.remoteAuthority && viewEnabled) { + if (viewEnabled) { const viewContainer = await this.getViewContainer(); const tunnelPanelDescriptor = new TunnelPanelDescriptor(new TunnelViewModel(this.remoteExplorerService, this.tunnelService), this.environmentService); const viewsRegistry = Registry.as(Extensions.ViewsRegistry); @@ -84,7 +84,7 @@ export class ForwardedPortsView extends Disposable implements IWorkbenchContribu this.remoteExplorerService.enablePortsFeatures(); viewsRegistry.registerViews([tunnelPanelDescriptor!], viewContainer); } - } else if (this.environmentService.remoteAuthority) { + } else { this.contextKeyListener = this.contextKeyService.onDidChangeContext(e => { if (e.affectsSome(new Set(forwardedPortsViewEnabled.keys()))) { this.enableForwardedPortsView(); diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index cac9a0dfd298f..1017371144275 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -93,6 +93,7 @@ export const allApiProposals = Object.freeze({ tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts', treeViewActiveItem: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts', treeViewReveal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts', + tunnelFactory: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts', tunnels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts', windowActivity: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.windowActivity.d.ts', workspaceTrust: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.workspaceTrust.d.ts' diff --git a/src/vs/workbench/services/tunnel/browser/tunnelService.ts b/src/vs/workbench/services/tunnel/browser/tunnelService.ts index ff97c93c22f82..74c874e0bfdc8 100644 --- a/src/vs/workbench/services/tunnel/browser/tunnelService.ts +++ b/src/vs/workbench/services/tunnel/browser/tunnelService.ts @@ -8,7 +8,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; -import { AbstractTunnelService, ITunnelService, RemoteTunnel } from 'vs/platform/tunnel/common/tunnel'; +import { AbstractTunnelService, ITunnelProvider, ITunnelService, RemoteTunnel, isTunnelProvider } from 'vs/platform/tunnel/common/tunnel'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class TunnelService extends AbstractTunnelService { @@ -24,15 +24,15 @@ export class TunnelService extends AbstractTunnelService { return false; } - protected retainOrCreateTunnel(_addressProvider: IAddressProvider, remoteHost: string, remotePort: number, _localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + protected retainOrCreateTunnel(tunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, _localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; return existing.value; } - if (this._tunnelProvider) { - return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); + if (isTunnelProvider(tunnelProvider)) { + return this.createWithProvider(tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); } return undefined; } diff --git a/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts b/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts index 4e581d2e485e7..edd218d3a9c99 100644 --- a/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts +++ b/src/vs/workbench/services/tunnel/electron-sandbox/tunnelService.ts @@ -7,7 +7,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { URI } from 'vs/base/common/uri'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ITunnelService, AbstractTunnelService, RemoteTunnel, TunnelPrivacyId, isPortPrivileged } from 'vs/platform/tunnel/common/tunnel'; +import { ITunnelService, AbstractTunnelService, RemoteTunnel, TunnelPrivacyId, isPortPrivileged, ITunnelProvider, isTunnelProvider } from 'vs/platform/tunnel/common/tunnel'; import { Disposable } from 'vs/base/common/lifecycle'; import { IAddressProvider } from 'vs/platform/remote/common/remoteAgentConnection'; import { ISharedProcessTunnelService } from 'vs/platform/remote/common/sharedProcessTunnelService'; @@ -79,19 +79,19 @@ export class TunnelService extends AbstractTunnelService { return isPortPrivileged(port, this.defaultTunnelHost, OS, this._nativeWorkbenchEnvironmentService.os.release); } - protected retainOrCreateTunnel(addressProvider: IAddressProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { + protected retainOrCreateTunnel(addressOrTunnelProvider: IAddressProvider | ITunnelProvider, remoteHost: string, remotePort: number, localHost: string, localPort: number | undefined, elevateIfNeeded: boolean, privacy?: string, protocol?: string): Promise | undefined { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; return existing.value; } - if (this._tunnelProvider) { - return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); + if (isTunnelProvider(addressOrTunnelProvider)) { + return this.createWithProvider(addressOrTunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); } else { this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`); - const tunnel = this._createSharedProcessTunnel(addressProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded); + const tunnel = this._createSharedProcessTunnel(addressOrTunnelProvider, remoteHost, remotePort, localHost, localPort, elevateIfNeeded); this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.'); this.addTunnelToMap(remoteHost, remotePort, tunnel); return tunnel; diff --git a/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts b/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts new file mode 100644 index 0000000000000..5a2192df3c8c7 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + /** + * Used as part of the ResolverResult if the extension has any candidate, + * published, or forwarded ports. + */ + export interface TunnelInformation { + /** + * Tunnels that are detected by the extension. The remotePort is used for display purposes. + * The localAddress should be the complete local address (ex. localhost:1234) for connecting to the port. Tunnels provided through + * environmentTunnels are read-only from the forwarded ports UI. + */ + environmentTunnels?: TunnelDescription[]; + + tunnelFeatures?: { + elevation: boolean; + /** + * One of the the options must have the ID "private". + */ + privacyOptions: TunnelPrivacy[]; + }; + } + + export interface TunnelProvider { + /** + * Provides port forwarding capabilities. If there is a resolver that already provids tunnels, then the resolver's provider will + * be used. If multiple providers are registered, then only the first will be used. + */ + provideTunnel(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions, token: CancellationToken): ProviderResult; + } + + export namespace workspace { + /** + * Registering a tunnel provider enables port forwarding. This will cause the Ports view to show. + * @param provider + */ + export function registerTunnelProvider(provider: TunnelProvider, information: TunnelInformation): Thenable; + } + +} From da7ab4918d30a6e606e32cccfd06c7eca16e8984 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 20 Jul 2023 12:26:56 +0200 Subject: [PATCH 061/216] Fixes #188284 - Adds diffEditor.switchSide command (#188291) --- .../diffEditorWidget2.contribution.ts | 32 ++++++++- .../diffEditorWidget2/diffEditorWidget2.ts | 67 +++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts index 11543c2c18db2..ff8040b83ec07 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts @@ -5,8 +5,12 @@ import { Codicon } from 'vs/base/common/codicons'; import { ThemeIcon } from 'vs/base/common/themables'; -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { findFocusedDiffEditor } from 'vs/editor/browser/widget/diffEditor.contribution'; +import { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2'; import { localize } from 'vs/nls'; +import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -88,3 +92,29 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { group: '1_diff', when: ContextKeyEqualsExpr.create('diffEditorVersion', 2) }); + +const diffEditorCategory: ILocalizedString = { + value: localize('diffEditor', 'Diff Editor'), + original: 'Diff Editor', +}; +export class SwitchSide extends EditorAction2 { + constructor() { + super({ + id: 'diffEditor.switchSide', + title: { value: localize('switchSide', "Switch Side"), original: 'Switch Side' }, + icon: Codicon.arrowSwap, + precondition: ContextKeyExpr.and(ContextKeyEqualsExpr.create('diffEditorVersion', 2), ContextKeyExpr.has('isInDiffEditor')), + f1: true, + category: diffEditorCategory, + }); + } + + runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]): void { + const diffEditor = findFocusedDiffEditor(accessor); + if (diffEditor instanceof DiffEditorWidget2) { + diffEditor.switchSide(); + } + } +} + +registerAction2(SwitchSide); diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts index 5c18a7754f598..b4c0d0f648d14 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts @@ -44,6 +44,8 @@ import { DiffEditorViewModel, DiffMapping, DiffState } from './diffEditorViewMod import { AccessibleDiffViewer } from 'vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { LengthObj } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; +import { Range } from 'vs/editor/common/core/range'; export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { private readonly elements = h('div.monaco-diff-editor.side-by-side', { style: { position: 'relative', height: '100%' } }, [ @@ -479,6 +481,71 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { if (!diffModel) { return; } await diffModel.waitForDiff(); } + + switchSide(): void { + const isModifiedFocus = this._editors.modified.hasWidgetFocus(); + const source = isModifiedFocus ? this._editors.modified : this._editors.original; + const destination = isModifiedFocus ? this._editors.original : this._editors.modified; + + const sourceSelection = source.getSelection(); + if (sourceSelection) { + const mappings = this._diffModel.get()?.diff.get()?.mappings.map(m => isModifiedFocus ? m.lineRangeMapping.flip() : m.lineRangeMapping); + if (mappings) { + const newRange1 = translatePosition(sourceSelection.getStartPosition(), mappings); + const newRange2 = translatePosition(sourceSelection.getEndPosition(), mappings); + const range = Range.plusRange(newRange1, newRange2); + destination.setSelection(range); + } + } + destination.focus(); + } +} + +function translatePosition(posInOriginal: Position, mappings: LineRangeMapping[]): Range { + const mapping = findLast(mappings, m => m.originalRange.startLineNumber <= posInOriginal.lineNumber); + if (!mapping) { + // No changes before the position + return Range.fromPositions(posInOriginal); + } + + if (mapping.originalRange.endLineNumberExclusive <= posInOriginal.lineNumber) { + const newLineNumber = posInOriginal.lineNumber - mapping.originalRange.endLineNumberExclusive + mapping.modifiedRange.endLineNumberExclusive; + return Range.fromPositions(new Position(newLineNumber, posInOriginal.column)); + } + + if (!mapping.innerChanges) { + // Only for legacy algorithm + return Range.fromPositions(new Position(mapping.modifiedRange.startLineNumber, 1)); + } + + const innerMapping = findLast(mapping.innerChanges, m => m.originalRange.getStartPosition().isBeforeOrEqual(posInOriginal)); + if (!innerMapping) { + const newLineNumber = posInOriginal.lineNumber - mapping.originalRange.startLineNumber + mapping.modifiedRange.startLineNumber; + return Range.fromPositions(new Position(newLineNumber, posInOriginal.column)); + } + + if (innerMapping.originalRange.containsPosition(posInOriginal)) { + return innerMapping.modifiedRange; + } else { + const l = lengthBetweenPositions(innerMapping.originalRange.getEndPosition(), posInOriginal); + return Range.fromPositions(addLength(innerMapping.modifiedRange.getEndPosition(), l)); + } +} + +function lengthBetweenPositions(position1: Position, position2: Position): LengthObj { + if (position1.lineNumber === position2.lineNumber) { + return new LengthObj(0, position2.column - position1.column); + } else { + return new LengthObj(position2.lineNumber - position1.lineNumber, position2.column - 1); + } +} + +function addLength(position: Position, length: LengthObj): Position { + if (length.lineCount === 0) { + return new Position(position.lineNumber, position.column + length.columnCount); + } else { + return new Position(position.lineNumber + length.lineCount, length.columnCount + 1); + } } function toLineChanges(state: DiffState): ILineChange[] { From e7c46979012b74ec2079e3f644a1f6c0a3b929ca Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 20 Jul 2023 12:50:08 +0200 Subject: [PATCH 062/216] adding some console logs to understand the hover placement problem --- src/vs/editor/contrib/hover/browser/contentHover.ts | 4 ++++ src/vs/editor/contrib/hover/browser/resizableContentWidget.ts | 3 +++ 2 files changed, 7 insertions(+) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 18e1b5f9d2376..8df02583acccd 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -726,6 +726,9 @@ export class ContentHoverWidget extends ResizableContentWidget { } public showAt(node: DocumentFragment, hoverData: ContentHoverVisibleData): void { + + console.log('inside of showAt'); + if (!this._editor || !this._editor.hasModel()) { return; } @@ -733,6 +736,7 @@ export class ContentHoverWidget extends ResizableContentWidget { const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); const widgetPosition = hoverData.showAtPosition; this._positionPreference = this._findPositionPreference(widgetHeight, widgetPosition) ?? ContentWidgetPositionPreference.ABOVE; + console.log('this._positionPreference : ', this._positionPreference); // See https://github.com/microsoft/vscode/issues/140339 // TODO: Doing a second layout of the hover after force rendering the editor diff --git a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts index 0243d9e88bf48..89f3f5e3fd8fa 100644 --- a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts +++ b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts @@ -86,8 +86,11 @@ export abstract class ResizableContentWidget extends Disposable implements ICont protected _findPositionPreference(widgetHeight: number, showAtPosition: IPosition): ContentWidgetPositionPreference | undefined { const maxHeightBelow = Math.min(this._availableVerticalSpaceBelow(showAtPosition) ?? Infinity, widgetHeight); const maxHeightAbove = Math.min(this._availableVerticalSpaceAbove(showAtPosition) ?? Infinity, widgetHeight); + console.log('maxHeightBelow : ', maxHeightBelow); + console.log('maxHeightAbove : ', maxHeightAbove); const maxHeight = Math.min(Math.max(maxHeightAbove, maxHeightBelow), widgetHeight); const height = Math.min(widgetHeight, maxHeight); + console.log('height : ', height); let renderingAbove: ContentWidgetPositionPreference; if (this._editor.getOption(EditorOption.hover).above) { renderingAbove = height <= maxHeightAbove ? ContentWidgetPositionPreference.ABOVE : ContentWidgetPositionPreference.BELOW; From b97e7b8ed0ca1aeae37001d69ccec4e105e63be7 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 20 Jul 2023 12:54:25 +0200 Subject: [PATCH 063/216] removing the console logs --- .../editor/browser/widget/diffEditorWidget.ts | 3 -- .../browser/inlineChatController.ts | 4 --- .../browser/inlineChatStrategies.ts | 1 - .../inlineChat/browser/inlineChatWidget.ts | 7 ---- .../test/browser/inlineChatController.test.ts | 33 ------------------- 5 files changed, 48 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 47afe24c19605..e21c1e9f3beb2 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -709,8 +709,6 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE } public override dispose(): void { - console.log('inside of the dispose method'); - this._codeEditorService.removeDiffEditor(this); if (this._beginUpdateDecorationsTimeout !== -1) { @@ -886,7 +884,6 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._onDidChangeModel.fire(); // Diff navigator - console.log('right before the final _register'); this._diffNavigator = this._register(this._instantiationService.createInstance(DiffNavigator, this, { alwaysRevealFirst: false, findResultLoop: this.getModifiedEditor().getOption(EditorOption.find).loop diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 3306e796ce37e..39436818fa9aa 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -176,7 +176,6 @@ export class InlineChatController implements IEditorContribution { private _currentRun?: Promise; async run(options: InlineChatRunOptions | undefined = {}): Promise { - console.log('inside of run'); this.finishExistingSession(); if (this._currentRun) { await this._currentRun; @@ -668,7 +667,6 @@ export class InlineChatController implements IEditorContribution { } private async [State.CANCEL]() { - console.log('inside of cancel function'); assertType(this._activeSession); assertType(this._strategy); this._sessionStore.clear(); @@ -773,7 +771,6 @@ export class InlineChatController implements IEditorContribution { } cancelSession() { - console.log('inside of the cancel session method'); let result: string | undefined; if (this._strategy && this._activeSession) { const changedText = this._activeSession.asChangedText(); @@ -788,7 +785,6 @@ export class InlineChatController implements IEditorContribution { } finishExistingSession(): void { - console.log('inside of finish existing session'); if (this._activeSession) { if (this._activeSession.editMode === EditMode.Preview) { this._log('finishing existing session, using CANCEL', this._activeSession.editMode); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index bbc2aabacb852..36e64ebf09af5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -126,7 +126,6 @@ export class PreviewStrategy extends EditModeStrategy { const edits = response.localEdits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)); this._widget.showEditsPreview(this._session.textModel0, edits, this._session.lastTextModelChanges); } else { - console.log('inside of render changes'); this._widget.hideEditsPreview(); } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index a7cc7801921c5..adb0b77635e8f 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -521,7 +521,6 @@ export class InlineChatWidget { this._elements.statusToolbar.classList.add('hidden'); this._elements.feedbackToolbar.classList.add('hidden'); this.hideCreatePreview(); - console.log('inside of reset'); this.hideEditsPreview(); this._onDidChangeHeight.fire(); } @@ -538,7 +537,6 @@ export class InlineChatWidget { showEditsPreview(textModelv0: ITextModel, edits: ISingleEditOperation[], changes: readonly LineRangeMapping[]) { if (changes.length === 0) { - console.log('inside of show edits preview'); this.hideEditsPreview(); return; } @@ -548,7 +546,6 @@ export class InlineChatWidget { const languageSelection: ILanguageSelection = { languageId: textModelv0.getLanguageId(), onDidChange: Event.None }; const modified = this._modelService.createModel(createTextBufferFactoryFromSnapshot(textModelv0.createSnapshot()), languageSelection, undefined, true); modified.applyEdits(edits, false); - console.log('inside of show edits preview'); this._previewDiffEditor.value.setModel({ original: textModelv0, modified }); // joined ranges @@ -580,10 +577,7 @@ export class InlineChatWidget { } hideEditsPreview() { - // Error happens because this is called after the diff editor widget is already disposed. - console.log('inside of hide edits preview'); this._elements.previewDiff.classList.add('hidden'); - // TODO: error is happening here this._previewDiffEditor.value.setModel(null); this._previewDiffModel.clear(); this._onDidChangeHeight.fire(); @@ -840,7 +834,6 @@ export class InlineChatZoneWidget extends ZoneWidget { } override hide(): void { - console.log('inside of hide'); this.container!.classList.remove('inside-selection'); this._ctxVisible.reset(); this._ctxCursorPosition.reset(); diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index 1ace231c30b32..f4ba0f5b182ac 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -141,39 +141,26 @@ suite('InteractiveChatController', function () { }); test('creation, not showing anything', function () { - console.log('*** at the begining of creation'); ctrl = instaService.createInstance(TestController, editor); assert.ok(ctrl); assert.strictEqual(ctrl.getWidgetPosition(), undefined); - console.log('*** at the end of creation'); }); test('run (show/hide)', async function () { - console.log('*** at the beginning of run (show/hide)'); - ctrl = instaService.createInstance(TestController, editor); - console.log('right before run'); const run = ctrl.run({ message: 'Hello', autoSend: true }); - console.log('right before waitFor INIT_SEQUENCE_AUTO_SEND'); await ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); assert.ok(ctrl.getWidgetPosition() !== undefined); - - console.log('right before cancel session'); ctrl.cancelSession(); - console.log('right before run'); await run; - console.log('ctrl.getWidgetPosition() : ', ctrl.getWidgetPosition()); assert.ok(ctrl.getWidgetPosition() === undefined); - console.log('*** at the end of run (show/hide)'); }); test('wholeRange expands to whole lines, editor selection default', async function () { - console.log('*** at the beginning of wholeRange, editor selection default'); - editor.setSelection(new Range(1, 1, 1, 3)); ctrl = instaService.createInstance(TestController, editor); @@ -198,14 +185,10 @@ suite('InteractiveChatController', function () { ctrl.cancelSession(); d.dispose(); - - console.log('*** at the end of wholeRange, editor selection default'); }); test('wholeRange expands to whole lines, session provided', async function () { - console.log('*** at the beginning of wholeRange, editor selection provided'); - editor.setSelection(new Range(1, 1, 1, 1)); ctrl = instaService.createInstance(TestController, editor); @@ -231,14 +214,9 @@ suite('InteractiveChatController', function () { ctrl.cancelSession(); d.dispose(); - - console.log('*** at the end of wholeRange, editor selection provided'); }); test('typing outside of wholeRange finishes session', async function () { - - console.log('*** at the beginning of typing outside'); - ctrl = instaService.createInstance(TestController, editor); ctrl.run({ message: 'Hello', autoSend: true }); @@ -252,14 +230,10 @@ suite('InteractiveChatController', function () { editor.trigger('test', 'type', { text: 'a' }); await ctrl.waitFor([State.ACCEPT]); - - console.log('*** at the end of typing outside'); }); test('\'whole range\' isn\'t updated for edits outside whole range #4346', async function () { - console.log('*** at the beginning of whole range isnt updated'); - editor.setSelection(new Range(3, 1, 3, 1)); const d = inlineChatService.addProvider({ @@ -296,14 +270,9 @@ suite('InteractiveChatController', function () { await ctrl.waitFor([State.MAKE_REQUEST, State.APPLY_RESPONSE, State.SHOW_RESPONSE, State.WAIT_FOR_INPUT]); assert.deepStrictEqual(session.wholeRange.value, new Range(1, 1, 4, 12)); - - console.log('*** at the end of whole range isnt updated'); }); test('Stuck inline chat widget #211', async function () { - - console.log('*** at the beginning of stuck inline chat'); - const d = inlineChatService.addProvider({ debugName: 'Unit Test', prepareInlineChatSession() { @@ -336,7 +305,5 @@ suite('InteractiveChatController', function () { await p; assert.strictEqual(ctrl.getWidgetPosition(), undefined); - - console.log('*** at the end of stuck inline chat'); }); }); From 0f23d3ef825fdde9c709d77bd85271ef0c51a219 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 20 Jul 2023 12:55:40 +0200 Subject: [PATCH 064/216] Fixes inline diff editor --- .../widget/diffEditorWidget2/lineAlignment.ts | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts b/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts index 9960732a03faf..4babd037b69e1 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts @@ -85,7 +85,9 @@ export class ViewZoneManager extends Disposable { const diff = diffModel?.diff.read(reader); if (!diffModel || !diff) { return null; } state.read(reader); - return computeRangeAlignment(this._editors.original, this._editors.modified, diff.mappings, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod); + const renderSideBySide = this._options.renderSideBySide.read(reader); + const innerHunkAlignment = renderSideBySide; + return computeRangeAlignment(this._editors.original, this._editors.modified, diff.mappings, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod, innerHunkAlignment); }); const alignmentsSyncedMovedText = derived('alignments', (reader) => { @@ -94,7 +96,7 @@ export class ViewZoneManager extends Disposable { state.read(reader); const mappings = syncedMovedText.changes.map(c => new DiffMapping(c)); // TODO dont include alignments outside syncedMovedText - return computeRangeAlignment(this._editors.original, this._editors.modified, mappings, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod); + return computeRangeAlignment(this._editors.original, this._editors.modified, mappings, alignmentViewZoneIdsOrig, alignmentViewZoneIdsMod, true); }); function createFakeLinesDiv(): HTMLElement { @@ -455,6 +457,7 @@ function computeRangeAlignment( diffs: readonly DiffMapping[], originalEditorAlignmentViewZones: ReadonlySet, modifiedEditorAlignmentViewZones: ReadonlySet, + innerHunkAlignment: boolean, ): ILineRangeAlignment[] { const originalLineHeightOverrides = new ArrayQueue(getAdditionalLineHeights(originalEditor, originalEditorAlignmentViewZones)); const modifiedLineHeightOverrides = new ArrayQueue(getAdditionalLineHeights(modifiedEditor, modifiedEditorAlignmentViewZones)); @@ -546,18 +549,21 @@ function computeRangeAlignment( modifiedRange, originalHeightInPx: originalRange.length * origLineHeight + originalAdditionalHeight, modifiedHeightInPx: modifiedRange.length * modLineHeight + modifiedAdditionalHeight, + diff: m.lineRangeMapping, }); lastOrigLineNumber = origLineNumberExclusive; lastModLineNumber = modLineNumberExclusive; } - for (const i of c.innerChanges || []) { - if (i.originalRange.startColumn > 1 && i.modifiedRange.startColumn > 1) { - // There is some unmodified text on this line - emitAlignment(i.originalRange.startLineNumber, i.modifiedRange.startLineNumber); + if (innerHunkAlignment) { + for (const i of c.innerChanges || []) { + if (i.originalRange.startColumn > 1 && i.modifiedRange.startColumn > 1) { + // There is some unmodified text on this line + emitAlignment(i.originalRange.startLineNumber, i.modifiedRange.startLineNumber); + } + emitAlignment(i.originalRange.endLineNumber, i.modifiedRange.endLineNumber); } - emitAlignment(i.originalRange.endLineNumber, i.modifiedRange.endLineNumber); } emitAlignment(c.originalRange.endLineNumberExclusive, c.modifiedRange.endLineNumberExclusive); From e8f9d7587b45626fa8bb5a71b749c713f46e93c4 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 20 Jul 2023 13:03:00 +0200 Subject: [PATCH 065/216] Adopt new Java grammar! (#188361) --- extensions/java/cgmanifest.json | 9 +++--- extensions/java/package.json | 2 +- extensions/java/syntaxes/java.tmLanguage.json | 32 +++++++++++++++++-- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/extensions/java/cgmanifest.json b/extensions/java/cgmanifest.json index a4db862ab7d50..d5e92ad349dab 100644 --- a/extensions/java/cgmanifest.json +++ b/extensions/java/cgmanifest.json @@ -4,13 +4,14 @@ "component": { "type": "git", "git": { - "name": "atom/language-java", - "repositoryUrl": "https://github.com/atom/language-java", - "commitHash": "29f977dc42a7e2568b39bb6fb34c4ef108eb59b3" + "name": "redhat-developer/vscode-java", + "repositoryUrl": "https://github.com/redhat-developer/vscode-java", + "commitHash": "7a770ab6750b4b09173d98de14eb9792e3432b36" } }, "license": "MIT", - "version": "0.32.1" + "description": "This grammar was derived from https://github.com/atom/language-java/blob/master/grammars/java.cson.", + "version": "1.21.0" } ], "version": 1 diff --git a/extensions/java/package.json b/extensions/java/package.json index d71aa1c146ccd..6788ddd213335 100644 --- a/extensions/java/package.json +++ b/extensions/java/package.json @@ -9,7 +9,7 @@ "vscode": "*" }, "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin atom/language-java grammars/java.cson ./syntaxes/java.tmLanguage.json" + "update-grammar": "node ../node_modules/vscode-grammar-updater/bin redhat-developer/vscode-java language-support/java/java.tmLanguage.json ./syntaxes/java.tmLanguage.json" }, "contributes": { "languages": [ diff --git a/extensions/java/syntaxes/java.tmLanguage.json b/extensions/java/syntaxes/java.tmLanguage.json index be70cbb27c372..6fa78eb890952 100644 --- a/extensions/java/syntaxes/java.tmLanguage.json +++ b/extensions/java/syntaxes/java.tmLanguage.json @@ -1,10 +1,10 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/atom/language-java/blob/master/grammars/java.cson", + "This file has been converted from https://github.com/redhat-developer/vscode-java/blob/master/language-support/java/java.tmLanguage.json", "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-java/commit/29f977dc42a7e2568b39bb6fb34c4ef108eb59b3", + "version": "https://github.com/redhat-developer/vscode-java/commit/7a770ab6750b4b09173d98de14eb9792e3432b36", "name": "Java", "scopeName": "source.java", "patterns": [ @@ -1571,6 +1571,31 @@ }, "strings": { "patterns": [ + { + "begin": "\"\"\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.java" + } + }, + "end": "\"\"\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.java" + } + }, + "name": "string.quoted.triple.java", + "patterns": [ + { + "match": "\\\\\"\"\"", + "name": "constant.character.escape.java" + }, + { + "match": "\\\\.", + "name": "constant.character.escape.java" + } + ] + }, { "begin": "\"", "beginCaptures": { @@ -1632,6 +1657,9 @@ { "match": "[a-zA-Z$_][\\.a-zA-Z0-9$_]*", "name": "storage.type.java" + }, + { + "include": "#comments" } ] }, From 0e1f8b11ed62eaf0fe5a6348a458b8eddb85e691 Mon Sep 17 00:00:00 2001 From: Johannes Date: Thu, 20 Jul 2023 13:03:22 +0200 Subject: [PATCH 066/216] fix tests, `waitFor` needs to be called before entering `run` because that now pushes/enters a state sync --- .../test/browser/inlineChatController.test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index f4ba0f5b182ac..d491c19523468 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -148,9 +148,9 @@ suite('InteractiveChatController', function () { test('run (show/hide)', async function () { ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); const run = ctrl.run({ message: 'Hello', autoSend: true }); - - await ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); + await p; assert.ok(ctrl.getWidgetPosition() !== undefined); ctrl.cancelSession(); @@ -218,9 +218,10 @@ suite('InteractiveChatController', function () { test('typing outside of wholeRange finishes session', async function () { ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); ctrl.run({ message: 'Hello', autoSend: true }); - await ctrl.waitFor(TestController.INIT_SEQUENCE_AUTO_SEND); + await p; const session = inlineChatSessionService.getSession(editor, editor.getModel()!.uri); assert.ok(session); @@ -257,9 +258,10 @@ suite('InteractiveChatController', function () { }); store.add(d); ctrl = instaService.createInstance(TestController, editor); + const p = ctrl.waitFor(TestController.INIT_SEQUENCE); ctrl.run({ message: 'Hello', autoSend: false }); - await ctrl.waitFor(TestController.INIT_SEQUENCE); + await p; const session = inlineChatSessionService.getSession(editor, editor.getModel()!.uri); assert.ok(session); @@ -298,12 +300,13 @@ suite('InteractiveChatController', function () { }); store.add(d); ctrl = instaService.createInstance(TestController, editor); - const p = ctrl.run({ message: 'Hello', autoSend: true }); + const p = ctrl.waitFor([...TestController.INIT_SEQUENCE, State.MAKE_REQUEST]); + const r = ctrl.run({ message: 'Hello', autoSend: true }); - await ctrl.waitFor([...TestController.INIT_SEQUENCE, State.MAKE_REQUEST]); + await p; ctrl.acceptSession(); - await p; + await r; assert.strictEqual(ctrl.getWidgetPosition(), undefined); }); }); From 17252034ec25525f992ded8f152b7671619983bb Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 20 Jul 2023 15:10:28 +0200 Subject: [PATCH 067/216] adding code in order to also send the interactive response accepted to the extension --- src/vs/workbench/api/common/extHostInlineChat.ts | 3 +++ src/vs/workbench/api/common/extHostTypes.ts | 3 ++- .../contrib/inlineChat/browser/inlineChatController.ts | 8 +++++--- src/vs/workbench/contrib/inlineChat/common/inlineChat.ts | 3 ++- src/vscode-dts/vscode.proposed.interactive.d.ts | 3 ++- 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 866af7a087d21..5d958e7d659b3 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -234,6 +234,9 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { case InlineChatResponseFeedbackKind.Undone: apiKind = extHostTypes.InteractiveEditorResponseFeedbackKind.Undone; break; + case InlineChatResponseFeedbackKind.Accepted: + apiKind = extHostTypes.InteractiveEditorResponseFeedbackKind.Accepted; + break; } entry.provider.handleInteractiveEditorResponseFeedback?.(sessionData.session, response, apiKind); diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 2647d41c5db3e..057aee98ad494 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -4092,7 +4092,8 @@ export enum InteractiveSessionCopyKind { export enum InteractiveEditorResponseFeedbackKind { Unhelpful = 0, Helpful = 1, - Undone = 2 + Undone = 2, + Accepted = 3 } //#endregion diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 9128753de797f..a598d18cdf3e1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -798,16 +798,18 @@ export class InlineChatController implements IEditorContribution { } acceptSession(): void { + if (this._activeSession?.asChangedText() && this._activeSession.lastExchange?.response instanceof EditResponse) { + this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Accepted); + } this._messages.fire(Message.ACCEPT_SESSION); } cancelSession() { let result: string | undefined; - if (this._strategy && this._activeSession) { + if (this._activeSession) { const changedText = this._activeSession.asChangedText(); - if (changedText && this._activeSession?.lastExchange?.response instanceof EditResponse) { + if (changedText && this._activeSession.lastExchange?.response instanceof EditResponse) { this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); - } result = changedText; } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 855ffa543011a..e236132bf18f8 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -91,7 +91,8 @@ export interface IInlineChatProgressItem { export const enum InlineChatResponseFeedbackKind { Unhelpful = 0, Helpful = 1, - Undone = 2 + Undone = 2, + Accepted = 3 } export interface IInlineChatSessionProvider { diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index b83e3ab1da151..a0cb74cb49107 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -53,7 +53,8 @@ declare module 'vscode' { export enum InteractiveEditorResponseFeedbackKind { Unhelpful = 0, Helpful = 1, - Undone = 2 + Undone = 2, + Accepted = 3 } export interface TextDocumentContext { From a6b766d8ad00530e50a362fbca92a3fee9bc5dae Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Thu, 20 Jul 2023 15:46:52 +0200 Subject: [PATCH 068/216] Fix an async test suite (#188380) --- .../html-language-features/server/src/test/folding.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/html-language-features/server/src/test/folding.test.ts b/extensions/html-language-features/server/src/test/folding.test.ts index 44aaea9026cf5..ec33f7a5198f0 100644 --- a/extensions/html-language-features/server/src/test/folding.test.ts +++ b/extensions/html-language-features/server/src/test/folding.test.ts @@ -37,7 +37,7 @@ function r(startLine: number, endLine: number, kind?: string): ExpectedIndentRan return { startLine, endLine, kind }; } -suite('HTML Folding', async () => { +suite('HTML Folding', () => { test('Embedded JavaScript', async () => { const input = [ From dfb783168f812b91e00e79dbc2614f326afe17c6 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Thu, 20 Jul 2023 11:32:34 +0200 Subject: [PATCH 069/216] Add http.proxyKerberosServicePrincipal setting (#187456) --- src/vs/platform/request/common/request.ts | 5 +++++ src/vs/workbench/api/node/proxyResolver.ts | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index 42168d7a59c2d..125b79f2556b0 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -148,6 +148,11 @@ function registerProxyConfigurations(scope: ConfigurationScope): void { description: localize('strictSSL', "Controls whether the proxy server certificate should be verified against the list of supplied CAs."), restricted: true }, + 'http.proxyKerberosServicePrincipal': { + type: 'string', + markdownDescription: localize('proxyKerberosServicePrincipal', "Overrides the principal service name for Kerberos authentication with the HTTP proxy. A default based on the proxy hostname is used when this is not set."), + restricted: true + }, 'http.proxyAuthorization': { type: ['null', 'string'], default: null, diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index d8a79683c1c03..bb679680d83a4 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -136,8 +136,7 @@ async function lookupProxyAuthorization( try { const kerberos = await import('kerberos'); const url = new URL(proxyURL); - // TODO: Add core user setting. - const spn = configProvider.getConfiguration('github.copilot')?.advanced?.kerberosServicePrincipal as string | undefined + const spn = configProvider.getConfiguration('http').get('proxyKerberosServicePrincipal') || (process.platform === 'win32' ? `HTTP/${url.hostname}` : `HTTP@${url.hostname}`); extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Kerberos authentication lookup', `proxyURL:${proxyURL}`, `spn:${spn}`); const client = await kerberos.initializeClient(spn); From 3b7d859f92488489c015547549699839b72aac9e Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 20 Jul 2023 16:41:44 +0200 Subject: [PATCH 070/216] fix https://github.com/microsoft/vscode-copilot/issues/794 (#188386) --- .../inlineChat/browser/inlineChatWidget.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 28f192ef209e4..385770db29b59 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -12,7 +12,7 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { EventType, Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; @@ -234,9 +234,24 @@ export class InlineChatWidget { // (1) inner cursor position (last/first line selected) const updateInnerCursorFirstLast = () => { - const { lineNumber } = this._inputEditor.getPosition(); - this._ctxInnerCursorFirst.set(lineNumber === 1); - this._ctxInnerCursorLast.set(lineNumber === this._inputModel.getLineCount()); + const selection = this._inputEditor.getSelection(); + let onFirst = false; + let onLast = false; + if (selection.isEmpty()) { + const selectionTop = this._inputEditor.getTopForPosition(selection.startLineNumber, selection.startColumn); + const fullRange = this._inputModel.getFullModelRange(); + const firstViewLineTop = this._inputEditor.getTopForPosition(fullRange.startLineNumber, fullRange.startColumn); + const lastViewLineTop = this._inputEditor.getTopForPosition(fullRange.endLineNumber, fullRange.endColumn); + + if (selectionTop === firstViewLineTop) { + onFirst = true; + } + if (selectionTop === lastViewLineTop) { + onLast = true; + } + } + this._ctxInnerCursorFirst.set(onFirst); + this._ctxInnerCursorLast.set(onLast); }; this._store.add(this._inputEditor.onDidChangeCursorPosition(updateInnerCursorFirstLast)); updateInnerCursorFirstLast(); From fdef134c6ed7bb534785fb0b810e9f43bb13ff07 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 20 Jul 2023 17:14:46 +0200 Subject: [PATCH 071/216] make sure auto reveal of inline input is more careful (#188390) fixes https://github.com/microsoft/vscode-copilot/issues/793 --- .../inlineChat/browser/inlineChatController.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 7d8c80fbe9642..ad021100a9ef2 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -308,10 +308,16 @@ export class InlineChatController implements IEditorContribution { this._zone.value.widget.preferredExpansionState = this._activeSession.lastExpansionState; this._zone.value.widget.value = this._activeSession.lastInput?.value ?? this._zone.value.widget.value; this._zone.value.widget.onDidChangeInput(_ => { - const pos = this._zone.value.position; - if (pos && this._zone.value.widget.hasFocus() && this._zone.value.widget.value) { - this._editor.revealPosition(pos, ScrollType.Smooth); + const start = this._zone.value.position; + if (!start || !this._zone.value.widget.hasFocus() || !this._zone.value.widget.value || !this._editor.hasModel()) { + return; + } + const nextLine = start.lineNumber + 1; + if (nextLine >= this._editor.getModel().getLineCount()) { + // last line isn't supported + return; } + this._editor.revealLine(nextLine, ScrollType.Smooth); }); this._showWidget(true, options.position); From b0c4753cd7fa23f9663fa5d3a2d665bb8a303ed2 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 20 Jul 2023 17:18:54 +0200 Subject: [PATCH 072/216] cleaning the code --- src/vs/editor/contrib/hover/browser/contentHover.ts | 13 +++++++------ .../contrib/hover/browser/resizableContentWidget.ts | 3 --- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 8df02583acccd..5a7d68dcd2dd7 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -471,6 +471,7 @@ export class ContentHoverWidget extends ResizableContentWidget { private static _lastDimensions: dom.Dimension = new dom.Dimension(0, 0); private _visibleData: ContentHoverVisibleData | undefined; + private _position: Position | undefined; private _positionPreference: ContentWidgetPositionPreference | undefined; private readonly _hover: HoverWidget = this._register(new HoverWidget()); @@ -726,17 +727,13 @@ export class ContentHoverWidget extends ResizableContentWidget { } public showAt(node: DocumentFragment, hoverData: ContentHoverVisibleData): void { - - console.log('inside of showAt'); - if (!this._editor || !this._editor.hasModel()) { return; } this._render(node, hoverData); const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); - const widgetPosition = hoverData.showAtPosition; - this._positionPreference = this._findPositionPreference(widgetHeight, widgetPosition) ?? ContentWidgetPositionPreference.ABOVE; - console.log('this._positionPreference : ', this._positionPreference); + this._position = hoverData.showAtPosition; + this._positionPreference = this._findPositionPreference(widgetHeight, this._position) ?? ContentWidgetPositionPreference.ABOVE; // See https://github.com/microsoft/vscode/issues/140339 // TODO: Doing a second layout of the hover after force rendering the editor @@ -795,6 +792,10 @@ export class ContentHoverWidget extends ResizableContentWidget { this._adjustContentsBottomPadding(); this._adjustHoverHeightForScrollbar(height); } + if (this._position) { + const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); + this._positionPreference = this._findPositionPreference(widgetHeight, this._position); + } this._layoutContentWidget(); } diff --git a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts index 89f3f5e3fd8fa..0243d9e88bf48 100644 --- a/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts +++ b/src/vs/editor/contrib/hover/browser/resizableContentWidget.ts @@ -86,11 +86,8 @@ export abstract class ResizableContentWidget extends Disposable implements ICont protected _findPositionPreference(widgetHeight: number, showAtPosition: IPosition): ContentWidgetPositionPreference | undefined { const maxHeightBelow = Math.min(this._availableVerticalSpaceBelow(showAtPosition) ?? Infinity, widgetHeight); const maxHeightAbove = Math.min(this._availableVerticalSpaceAbove(showAtPosition) ?? Infinity, widgetHeight); - console.log('maxHeightBelow : ', maxHeightBelow); - console.log('maxHeightAbove : ', maxHeightAbove); const maxHeight = Math.min(Math.max(maxHeightAbove, maxHeightBelow), widgetHeight); const height = Math.min(widgetHeight, maxHeight); - console.log('height : ', height); let renderingAbove: ContentWidgetPositionPreference; if (this._editor.getOption(EditorOption.hover).above) { renderingAbove = height <= maxHeightAbove ? ContentWidgetPositionPreference.ABOVE : ContentWidgetPositionPreference.BELOW; From 5b98372842563572a3e0f862d33cdf692e536ba2 Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 20 Jul 2023 17:20:45 +0200 Subject: [PATCH 073/216] using directly the show at position from visible data --- src/vs/editor/contrib/hover/browser/contentHover.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 5a7d68dcd2dd7..cc0a0acd6a115 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -471,7 +471,6 @@ export class ContentHoverWidget extends ResizableContentWidget { private static _lastDimensions: dom.Dimension = new dom.Dimension(0, 0); private _visibleData: ContentHoverVisibleData | undefined; - private _position: Position | undefined; private _positionPreference: ContentWidgetPositionPreference | undefined; private readonly _hover: HoverWidget = this._register(new HoverWidget()); @@ -732,8 +731,8 @@ export class ContentHoverWidget extends ResizableContentWidget { } this._render(node, hoverData); const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); - this._position = hoverData.showAtPosition; - this._positionPreference = this._findPositionPreference(widgetHeight, this._position) ?? ContentWidgetPositionPreference.ABOVE; + const widgetPosition = hoverData.showAtPosition; + this._positionPreference = this._findPositionPreference(widgetHeight, widgetPosition) ?? ContentWidgetPositionPreference.ABOVE; // See https://github.com/microsoft/vscode/issues/140339 // TODO: Doing a second layout of the hover after force rendering the editor @@ -792,9 +791,9 @@ export class ContentHoverWidget extends ResizableContentWidget { this._adjustContentsBottomPadding(); this._adjustHoverHeightForScrollbar(height); } - if (this._position) { + if (this._visibleData?.showAtPosition) { const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode); - this._positionPreference = this._findPositionPreference(widgetHeight, this._position); + this._positionPreference = this._findPositionPreference(widgetHeight, this._visibleData.showAtPosition); } this._layoutContentWidget(); } From e6cb8b7fd9dd6fc8ff4954cc8078129397eb1f44 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Thu, 20 Jul 2023 17:29:25 +0200 Subject: [PATCH 074/216] properly detect when all lines want to be hidden (#188392) --- .../inlineChat/browser/inlineChatLivePreviewWidget.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index a743c224dee84..1374da2009011 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -252,10 +252,13 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { return; } - let hiddenRanges = lineRanges.map(lineRangeAsRange); - if (LineRange.fromRange(hiddenRanges.reduce(Range.plusRange)).equals(LineRange.ofLength(1, editor.getModel().getLineCount()))) { - // TODO not every line can be hidden, keep the first line around + let hiddenRanges: Range[]; + const hiddenLinesCount = lineRanges.reduce((p, c) => p + c.length, 0); // assumes no overlap + if (hiddenLinesCount >= editor.getModel().getLineCount()) { + // TODO: not every line can be hidden, keep the first line around hiddenRanges = [editor.getModel().getFullModelRange().delta(1)]; + } else { + hiddenRanges = lineRanges.map(lineRangeAsRange); } editor.setHiddenAreas(hiddenRanges, InlineChatLivePreviewWidget._hideId); this._logService.debug(`[IE] diff HIDING ${hiddenRanges} for ${editor.getId()} with ${String(editor.getModel()?.uri)}`); From 6f21bd8a7c492f5ac5a2053306d83689f29eb9b2 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 20 Jul 2023 17:48:52 +0200 Subject: [PATCH 075/216] Fixes #188348 (#188372) --- .../inlineCompletions/browser/inlineCompletionsSource.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts index c844e63e8a17b..af311ba716a5c 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsSource.ts @@ -221,7 +221,13 @@ export class UpToDateInlineCompletions implements IDisposable { public dispose(): void { this._refCount--; if (this._refCount === 0) { - this.textModel.deltaDecorations(this._inlineCompletions.map(i => i.decorationId), []); + setTimeout(() => { + // To fix https://github.com/microsoft/vscode/issues/188348 + if (!this.textModel.isDisposed()) { + // This is just cleanup. It's ok if it happens with a delay. + this.textModel.deltaDecorations(this._inlineCompletions.map(i => i.decorationId), []); + } + }, 0); this.inlineCompletionProviderResult.dispose(); for (const i of this._prependedInlineCompletionItems) { i.source.removeRef(); From a7d18433c56d6183ec4ed85e145bb8ea29c8a95e Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Thu, 20 Jul 2023 17:58:54 +0200 Subject: [PATCH 076/216] adding the code --- .../inlineChat/browser/inlineChatController.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index a598d18cdf3e1..382bea3bed56e 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -727,6 +727,10 @@ export class InlineChatController implements IEditorContribution { } } + private static isEditOrMarkdownResponse(response: EditResponse | MarkdownResponse | EmptyResponse | ErrorResponse | undefined): response is EditResponse | MarkdownResponse { + return response instanceof EditResponse || response instanceof MarkdownResponse; + } + // ---- controller API acceptInput(): void { @@ -783,7 +787,7 @@ export class InlineChatController implements IEditorContribution { } feedbackLast(helpful: boolean) { - if (this._activeSession?.lastExchange?.response instanceof EditResponse || this._activeSession?.lastExchange?.response instanceof MarkdownResponse) { + if (this._activeSession?.lastExchange && InlineChatController.isEditOrMarkdownResponse(this._activeSession.lastExchange.response)) { const kind = helpful ? InlineChatResponseFeedbackKind.Helpful : InlineChatResponseFeedbackKind.Unhelpful; this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, kind); this._ctxLastFeedbackKind.set(helpful ? 'helpful' : 'unhelpful'); @@ -798,20 +802,16 @@ export class InlineChatController implements IEditorContribution { } acceptSession(): void { - if (this._activeSession?.asChangedText() && this._activeSession.lastExchange?.response instanceof EditResponse) { + if (this._activeSession?.lastExchange && InlineChatController.isEditOrMarkdownResponse(this._activeSession.lastExchange.response)) { this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Accepted); } this._messages.fire(Message.ACCEPT_SESSION); } cancelSession() { - let result: string | undefined; - if (this._activeSession) { - const changedText = this._activeSession.asChangedText(); - if (changedText && this._activeSession.lastExchange?.response instanceof EditResponse) { - this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); - } - result = changedText; + const result = this._activeSession?.asChangedText(); + if (this._activeSession?.lastExchange && InlineChatController.isEditOrMarkdownResponse(this._activeSession.lastExchange.response)) { + this._activeSession.provider.handleInlineChatResponseFeedback?.(this._activeSession.session, this._activeSession.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone); } this._messages.fire(Message.CANCEL_SESSION); return result; From e87dc37041fb300cf9f903d68bc7b71d9fa4bfcf Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Thu, 20 Jul 2023 17:41:53 +0200 Subject: [PATCH 077/216] Enable system certificates v2 by default (#185098) --- src/vs/workbench/api/node/proxyResolver.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index bb679680d83a4..280c66597e409 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -18,6 +18,8 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { LogLevel, createHttpPatch, createProxyResolver, createTlsPatch, ProxySupportSetting, ProxyAgentParams, createNetPatch } from '@vscode/proxy-agent'; +const systemCertificatesV2Default = true; + export function connectProxyResolver( extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, @@ -74,12 +76,12 @@ function createPatchedModules(params: ProxyAgentParams, resolveProxy: ReturnType function certSettingV1(configProvider: ExtHostConfigProvider) { const http = configProvider.getConfiguration('http'); - return !http.get('experimental.systemCertificatesV2') && !!http.get('systemCertificates'); + return !http.get('experimental.systemCertificatesV2', systemCertificatesV2Default) && !!http.get('systemCertificates'); } function certSettingV2(configProvider: ExtHostConfigProvider) { const http = configProvider.getConfiguration('http'); - return !!http.get('experimental.systemCertificatesV2') && !!http.get('systemCertificates'); + return !!http.get('experimental.systemCertificatesV2', systemCertificatesV2Default) && !!http.get('systemCertificates'); } const modulesCache = new Map(); From cb0f0efbccc4a4b58ccf4986c32aaeb2a6e40088 Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Thu, 20 Jul 2023 18:07:51 +0200 Subject: [PATCH 078/216] Remember when ticket was requested (#187456) --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++--- src/vs/workbench/api/node/proxyResolver.ts | 39 ++++++++++------------ yarn.lock | 8 ++--- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 6f81d56536eac..eced648db3bb6 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/policy-watcher": "^1.1.4", - "@vscode/proxy-agent": "^0.17.0", + "@vscode/proxy-agent": "^0.17.1", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.10", "@vscode/sqlite3": "5.1.6-vscode", diff --git a/remote/package.json b/remote/package.json index d5ccd114e2579..c68f4105b30f4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -7,7 +7,7 @@ "@microsoft/1ds-post-js": "^3.2.2", "@parcel/watcher": "2.1.0", "@vscode/iconv-lite-umd": "0.7.0", - "@vscode/proxy-agent": "^0.17.0", + "@vscode/proxy-agent": "^0.17.1", "@vscode/ripgrep": "^1.15.5", "@vscode/spdlog": "^0.13.10", "@vscode/vscode-languagedetection": "1.0.21", diff --git a/remote/yarn.lock b/remote/yarn.lock index 15557fa622ef2..0034d6a8a56bf 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -58,10 +58,10 @@ resolved "https://registry.yarnpkg.com/@vscode/iconv-lite-umd/-/iconv-lite-umd-0.7.0.tgz#d2f1e0664ee6036408f9743fee264ea0699b0e48" integrity sha512-bRRFxLfg5dtAyl5XyiVWz/ZBPahpOpPrNYnnHpOpUZvam4tKH35wdhP4Kj6PbM0+KdliOsPzbGWpkxcdpNB/sg== -"@vscode/proxy-agent@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.0.tgz#e60d43e2779c07c223d3bad9b7de8eedf7ca1294" - integrity sha512-p4gJ57KeWGw0CEG9R13dmsgmWmszoOQ836pf/PVbAf+ZRF27il3QcFvOhA10XE2QFHaOcRxuJnnIpUD1lSMvqQ== +"@vscode/proxy-agent@^0.17.1": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.1.tgz#00ea42fb3565c78c38bc99a73d4460db538aef4e" + integrity sha512-KWQ5y2uB6547Oudx2TMV28PdcdqNzI4J7TZzhZht1kNra8spqOzQJXw6gBdoh2mMFVpNiKgVhZ9YinWR0BZHiw== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index 280c66597e409..475367d27b597 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -32,7 +32,7 @@ export function connectProxyResolver( const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; const params: ProxyAgentParams = { resolveProxy: url => extHostWorkspace.resolveProxy(url), - lookupProxyAuthorization: lookupProxyAuthorization.bind(undefined, extHostLogService, configProvider, {}, {}), + lookupProxyAuthorization: lookupProxyAuthorization.bind(undefined, extHostLogService, configProvider, {}), getProxyURL: () => configProvider.getConfiguration('http').get('proxy'), getProxySupport: () => configProvider.getConfiguration('http').get('proxySupport') || 'off', getSystemCertificatesV1: () => certSettingV1(configProvider), @@ -121,9 +121,9 @@ async function lookupProxyAuthorization( extHostLogService: ILogService, configProvider: ExtHostConfigProvider, proxyAuthenticateCache: Record, - pendingLookups: Record>, proxyURL: string, - proxyAuthenticate?: string | string[] + proxyAuthenticate: string | string[] | undefined, + state: { kerberosRequested?: boolean } ): Promise { const cached = proxyAuthenticateCache[proxyURL]; if (proxyAuthenticate) { @@ -132,25 +132,20 @@ async function lookupProxyAuthorization( extHostLogService.trace('ProxyResolver#lookupProxyAuthorization callback', `proxyURL:${proxyURL}`, `proxyAuthenticate:${proxyAuthenticate}`, `proxyAuthenticateCache:${cached}`); const header = proxyAuthenticate || cached; const authenticate = Array.isArray(header) ? header : typeof header === 'string' ? [header] : []; - if (authenticate.some(a => /^(Negotiate|Kerberos)( |$)/i.test(a))) { - const lookupKey = `${proxyURL}:Negotiate`; - return pendingLookups[lookupKey] ??= (async () => { - try { - const kerberos = await import('kerberos'); - const url = new URL(proxyURL); - const spn = configProvider.getConfiguration('http').get('proxyKerberosServicePrincipal') - || (process.platform === 'win32' ? `HTTP/${url.hostname}` : `HTTP@${url.hostname}`); - extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Kerberos authentication lookup', `proxyURL:${proxyURL}`, `spn:${spn}`); - const client = await kerberos.initializeClient(spn); - const response = await client.step(''); - return 'Negotiate ' + response; - } catch (err) { - extHostLogService.error('ProxyResolver#lookupProxyAuthorization Kerberos authentication failed', err); - return undefined; - } finally { - delete pendingLookups[lookupKey]; - } - })(); + if (authenticate.some(a => /^(Negotiate|Kerberos)( |$)/i.test(a)) && !state.kerberosRequested) { + try { + state.kerberosRequested = true; + const kerberos = await import('kerberos'); + const url = new URL(proxyURL); + const spn = configProvider.getConfiguration('http').get('proxyKerberosServicePrincipal') + || (process.platform === 'win32' ? `HTTP/${url.hostname}` : `HTTP@${url.hostname}`); + extHostLogService.debug('ProxyResolver#lookupProxyAuthorization Kerberos authentication lookup', `proxyURL:${proxyURL}`, `spn:${spn}`); + const client = await kerberos.initializeClient(spn); + const response = await client.step(''); + return 'Negotiate ' + response; + } catch (err) { + extHostLogService.error('ProxyResolver#lookupProxyAuthorization Kerberos authentication failed', err); + } } return undefined; } diff --git a/yarn.lock b/yarn.lock index 974dcf393b48e..651bda6187cae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1309,10 +1309,10 @@ bindings "^1.5.0" node-addon-api "^6.0.0" -"@vscode/proxy-agent@^0.17.0": - version "0.17.0" - resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.0.tgz#e60d43e2779c07c223d3bad9b7de8eedf7ca1294" - integrity sha512-p4gJ57KeWGw0CEG9R13dmsgmWmszoOQ836pf/PVbAf+ZRF27il3QcFvOhA10XE2QFHaOcRxuJnnIpUD1lSMvqQ== +"@vscode/proxy-agent@^0.17.1": + version "0.17.1" + resolved "https://registry.yarnpkg.com/@vscode/proxy-agent/-/proxy-agent-0.17.1.tgz#00ea42fb3565c78c38bc99a73d4460db538aef4e" + integrity sha512-KWQ5y2uB6547Oudx2TMV28PdcdqNzI4J7TZzhZht1kNra8spqOzQJXw6gBdoh2mMFVpNiKgVhZ9YinWR0BZHiw== dependencies: "@tootallnate/once" "^3.0.0" agent-base "^7.0.1" From 2d62fea265b2a03ea58d45c8676964f30e053e9b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 10:16:03 -0700 Subject: [PATCH 079/216] fix #188316 --- .../contrib/accessibility/browser/accessibleView.ts | 5 ++--- .../accessibility/browser/terminalAccessibleWidget.ts | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 52e818673beb9..b8816e7b47edf 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -14,7 +14,6 @@ import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/wi import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { AccessibilityHelpNLS } from 'vs/editor/common/standaloneStrings'; -import { LinkDetector } from 'vs/editor/contrib/links/browser/links'; import { localize } from 'vs/nls'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -24,7 +23,7 @@ import { IInstantiationService, createDecorator } from 'vs/platform/instantiatio import { IOpenerService } from 'vs/platform/opener/common/opener'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; -import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; const enum DEFAULT { WIDTH = 800, @@ -87,7 +86,7 @@ class AccessibleView extends Disposable { this._editorContainer = document.createElement('div'); this._editorContainer.classList.add('accessible-view'); const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - contributions: [...EditorExtensionsRegistry.getEditorContributions(), ...EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID, 'editor.contrib.selectionAnchorController'])] + contributions: EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeActionController.ID) }; const editorOptions: IEditorConstructionOptions = { ...getSimpleEditorOptions(this._configurationService), diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts index b28e397f7d195..da07be8e62cb3 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminalAccessibleWidget.ts @@ -13,15 +13,14 @@ import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; -import { LinkDetector } from 'vs/editor/contrib/links/browser/links'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; -import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import type { Terminal } from 'xterm'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController'; const enum ClassName { Active = 'active', @@ -62,7 +61,7 @@ export abstract class TerminalAccessibleWidget extends DisposableStore { this._element.classList.add(ClassName.Widget); this._editorContainer = document.createElement('div'); const codeEditorWidgetOptions: ICodeEditorWidgetOptions = { - contributions: [...EditorExtensionsRegistry.getEditorContributions(), ...EditorExtensionsRegistry.getSomeEditorContributions([LinkDetector.ID, SelectionClipboardContributionID, 'editor.contrib.selectionAnchorController'])] + contributions: EditorExtensionsRegistry.getEditorContributions().filter(c => c.id !== CodeActionController.ID) }; const font = _xterm.getFont(); const editorOptions: IEditorConstructionOptions = { From cd520b8c38c2799a8cfc1f3007f18a336de3c2ac Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 10:39:46 -0700 Subject: [PATCH 080/216] fix #188101 --- .../browser/terminal.accessibility.contribution.ts | 2 +- .../links/browser/terminal.links.contribution.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index 9e364caf3da7e..2bd6fe3006404 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -111,7 +111,7 @@ registerTerminalAction({ precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), keybinding: [ { - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, weight: KeybindingWeight.WorkbenchContrib + 2, when: TerminalContextKeys.accessibleBufferFocus } diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts index dbd35c08605fd..077dfd1d72366 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution.ts @@ -6,6 +6,7 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -112,7 +113,7 @@ registerActiveInstanceAction({ keybinding: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyO, weight: KeybindingWeight.WorkbenchContrib + 1, - when: TerminalContextKeys.focus, + when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.accessibleBufferFocus) }, run: (activeInstance) => TerminalLinkContribution.get(activeInstance)?.showLinkQuickpick() }); From 2bd814e1ff753461a5947e3b2f5513307f4bf6c9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 11:04:32 -0700 Subject: [PATCH 081/216] fix wording --- .../workbench/contrib/accessibility/browser/accessibleView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index fad255a9efb8b..ea024b618df6a 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -279,7 +279,7 @@ export class AccessibleViewService extends Disposable implements IAccessibleView let hint = ''; const keybinding = this._keybindingService.lookupKeybinding(AccessibleViewAction.id)?.getAriaLabel(); if (this._configurationService.getValue(verbositySettingKey)) { - hint = keybinding ? localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); + hint = keybinding ? localize('chatAccessibleViewHint', "Inspect this in the accessible view with {0}", keybinding) : localize('chatAccessibleViewHintNoKb', "Inspect this in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); } return hint; } From 4e539b0a96da04d1c18c210e30f483e2cdf6b776 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 11:09:32 -0700 Subject: [PATCH 082/216] or undefined --- .../contrib/accessibility/browser/accessibleView.ts | 11 +++++++++-- .../contrib/chat/browser/chatListRenderer.ts | 8 ++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index ea024b618df6a..72a4bde4b235e 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -50,7 +50,11 @@ export interface IAccessibleViewService { show(provider: IAccessibleContentProvider): void; next(): void; previous(): void; - getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string; + /** + * If the setting is enabled, provides the open accessible view hint as a localized string. + * @param verbositySettingKey The setting key for the verbosity of the feature + */ + getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | undefined; } export const enum AccessibleViewType { @@ -275,7 +279,10 @@ export class AccessibleViewService extends Disposable implements IAccessibleView previous(): void { this._accessibleView?.previous(); } - getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string { + getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | undefined { + if (!this._configurationService.getValue(verbositySettingKey)) { + return; + } let hint = ''; const keybinding = this._keybindingService.lookupKeybinding(AccessibleViewAction.id)?.getAriaLabel(); if (this._configurationService.getValue(verbositySettingKey)) { diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index 341ac9ea4c4a5..439afe5cbe5e8 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -553,18 +553,18 @@ export class ChatAccessibilityProvider implements IListAccessibilityProvider token.type === 'code')?.length ?? 0; switch (codeBlockCount) { case 0: - label = localize('noCodeBlocks', "{0} {1}", element.response.value, accessibleViewHint); + label = accessibleViewHint ? localize('noCodeBlocksHint', "{0} {1}", element.response.value, accessibleViewHint) : localize('noCodeBlocks', "{0}", element.response.value); break; case 1: - label = localize('singleCodeBlock', "1 code block: {0} {1}", element.response.value, accessibleViewHint); + label = accessibleViewHint ? localize('singleCodeBlockHint', "1 code block: {0} {1}", element.response.value, accessibleViewHint) : localize('singleCodeBlock', "1 code block: {0}", element.response.value); break; default: - label = localize('multiCodeBlock', "{0} code blocks: {1}", codeBlockCount, element.response.value, accessibleViewHint); + label = accessibleViewHint ? localize('multiCodeBlockHint', "{0} code blocks: {1}", codeBlockCount, element.response.value, accessibleViewHint) : localize('multiCodeBlock', "{0} code blocks", codeBlockCount, element.response.value); break; } return label; From 9c1da8c59bb56f9b143c5658e94b6b40ec93e051 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 11:25:22 -0700 Subject: [PATCH 083/216] fix #188319 --- src/vs/editor/contrib/hover/browser/hover.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 2c1e2b2611e7e..950b69d78976b 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -17,7 +17,7 @@ import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/go import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { ContentHoverWidget, ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHover'; import { MarginHoverWidget } from 'vs/editor/contrib/hover/browser/marginHover'; -import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { AccessibilitySupport, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -31,6 +31,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResultKind } from 'vs/platform/keybinding/common/keybindingResolver'; import * as nls from 'vs/nls'; import 'vs/css!./hover'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { status } from 'vs/base/browser/ui/aria/aria'; // sticky hover widget which doesn't disappear on focus out and such const _sticky = false @@ -361,6 +363,9 @@ class ShowOrFocusHoverAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { + const configurationService = accessor.get(IConfigurationService); + const accessibilityService = accessor.get(IAccessibilityService); + const keybindingService = accessor.get(IKeybindingService); if (!editor.hasModel()) { return; } @@ -377,6 +382,11 @@ class ShowOrFocusHoverAction extends EditorAction { } else { controller.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Keyboard, focus); } + if (configurationService.getValue('accessibility.verbosity.hover') && accessibilityService.isScreenReaderOptimized()) { + const keybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); + const hint = keybinding ? nls.localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : nls.localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); + status(hint); + } } } From 4654233dd200c47d36e4ee4d69610a361f2467d4 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 11:26:20 -0700 Subject: [PATCH 084/216] tweak wording --- src/vs/editor/contrib/hover/browser/hover.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 950b69d78976b..43969c828b5af 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -384,7 +384,7 @@ class ShowOrFocusHoverAction extends EditorAction { } if (configurationService.getValue('accessibility.verbosity.hover') && accessibilityService.isScreenReaderOptimized()) { const keybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); - const hint = keybinding ? nls.localize('chatAccessibleViewHint', "Inspect the response in the accessible view with {0}", keybinding) : nls.localize('chatAccessibleViewHintNoKb', "Inspect the response in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); + const hint = keybinding ? nls.localize('chatAccessibleViewHint', "Inspect this in the accessible view with {0}", keybinding) : nls.localize('chatAccessibleViewHintNoKb', "Inspect this in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); status(hint); } } From a4b7956a2ef53a56dc0ce0e0eb61eebfe52b7f73 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 11:33:18 -0700 Subject: [PATCH 085/216] fix #186487 --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index b1aeb25637897..d4aef5a97c76c 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -34,11 +34,10 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane if (upHistoryKeybinding && downHistoryKeybinding) { content.push(localize('inlineChat.requestHistory', 'In the input box, use {0} and {1} to navigate your request history. Edit input and use enter or the submit button to run a new request.', upHistoryKeybinding, downHistoryKeybinding)); } - content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with /fix or /explain. Type / to discover more ready-made commands.")); - content.push(localize('inlineChat.fix', "When a request is prefixed with /fix, a response will indicate the problem with the current code. A diff editor will be rendered and can be reached by tabbing.")); + content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with a /. Type / to discover more ready-made commands.")); + content.push(localize('inlineChat.fix', "If a fix action is invoked, a response will indicate the problem with the current code. A diff editor will be rendered and can be reached by tabbing.")); const diffReviewKeybinding = keybindingService.lookupKeybinding('editor.action.diffReview.next')?.getAriaLabel(); content.push(diffReviewKeybinding ? localize('inlineChat.diff', "Once in the diff editor, enter review mode with ({0}). Use up and down arrows to navigate lines with the proposed changes.", diffReviewKeybinding) : localize('inlineChat.diffNoKb', "Tab again to enter the Diff editor with the changes and enter review mode with the Go to Next Difference Command. Use Up/DownArrow to navigate lines with the proposed changes.")); - content.push(localize('inlineChat.explain', "When a request is prefixed with /explain, a response will explain the code in the current selection and the chat view will be focused.")); content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); } content.push(localize('chat.audioCues', "Audio cues can be changed via settings with a prefix of audioCues.chat. By default, if a request takes more than 4 seconds, you will hear an audio cue indicating that progress is still occurring.")); From e42c15fa4c2c928910dba5ef2ab0fc93d79a1af5 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 11:35:11 -0700 Subject: [PATCH 086/216] tweak --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index d4aef5a97c76c..c9de55bc0706a 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -34,7 +34,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane if (upHistoryKeybinding && downHistoryKeybinding) { content.push(localize('inlineChat.requestHistory', 'In the input box, use {0} and {1} to navigate your request history. Edit input and use enter or the submit button to run a new request.', upHistoryKeybinding, downHistoryKeybinding)); } - content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with a /. Type / to discover more ready-made commands.")); + content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with a /. Type / to discover such ready-made commands.")); content.push(localize('inlineChat.fix', "If a fix action is invoked, a response will indicate the problem with the current code. A diff editor will be rendered and can be reached by tabbing.")); const diffReviewKeybinding = keybindingService.lookupKeybinding('editor.action.diffReview.next')?.getAriaLabel(); content.push(diffReviewKeybinding ? localize('inlineChat.diff', "Once in the diff editor, enter review mode with ({0}). Use up and down arrows to navigate lines with the proposed changes.", diffReviewKeybinding) : localize('inlineChat.diffNoKb', "Tab again to enter the Diff editor with the changes and enter review mode with the Go to Next Difference Command. Use Up/DownArrow to navigate lines with the proposed changes.")); From cc6ee0efd54189b8cc64667124ecb1a87f012555 Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 19 Jul 2023 11:23:24 -0700 Subject: [PATCH 087/216] put cell output content into a11y view --- .../contributedStatusBarItemController.ts | 2 +- .../executionStatusBarItemController.ts | 2 +- .../browser/contrib/find/findModel.ts | 4 +- .../contrib/find/notebookFindWidget.ts | 4 +- .../browser/contrib/navigation/arrow.ts | 2 +- .../browser/contrib/troubleshoot/layout.ts | 2 +- .../contrib/undoRedo/notebookUndoRedo.ts | 4 +- .../browser/controller/cellOperations.ts | 4 +- .../browser/controller/foldingController.ts | 8 +-- .../notebook/browser/notebook.contribution.ts | 71 ++++++++++++++++++- .../notebook/browser/notebookBrowser.ts | 6 +- .../notebook/browser/notebookEditorWidget.ts | 2 +- .../browser/view/cellParts/foldedCellHint.ts | 4 +- .../viewParts/notebookOverviewRuler.ts | 2 +- .../test/browser/testNotebookEditor.ts | 2 +- 15 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts index 33ec159850729..de1b477cf67b3 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/contributedStatusBarItemController.ts @@ -47,7 +47,7 @@ export class ContributedStatusBarItemController extends Disposable implements IN added: ICellViewModel[]; removed: { handle: number }[]; }): void { - const vm = this._notebookEditor._getViewModel(); + const vm = this._notebookEditor.getViewModel(); if (!vm) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts index 3aa72fc3bfb97..34a10466dd1cb 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellStatusBar/executionStatusBarItemController.ts @@ -58,7 +58,7 @@ export class NotebookStatusBarController extends Disposable { } private _updateVisibleCells(e: ICellVisibilityChangeEvent): void { - const vm = this._notebookEditor._getViewModel(); + const vm = this._notebookEditor.getViewModel(); if (!vm) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts index 5c58ffda1bc1f..da74cbb1a7646 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/findModel.ts @@ -125,7 +125,7 @@ export class FindModel extends Disposable { // we only update cell state if users are using the hybrid mode (both input and preview are enabled) const updateEditingState = () => { - const viewModel = this._notebookEditor._getViewModel() as NotebookViewModel | undefined; + const viewModel = this._notebookEditor.getViewModel() as NotebookViewModel | undefined; if (!viewModel) { return; } @@ -164,7 +164,7 @@ export class FindModel extends Disposable { if (e.isReplaceRevealed && !this._state.isReplaceRevealed) { // replace is hidden, we need to switch all markdown cells to preview mode - const viewModel = this._notebookEditor._getViewModel() as NotebookViewModel | undefined; + const viewModel = this._notebookEditor.getViewModel() as NotebookViewModel | undefined; if (!viewModel) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index a5536043a40f2..e0c09afffbcfd 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -187,7 +187,7 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi const replacePattern = this.replacePattern; const replaceString = replacePattern.buildReplaceString(match.matches, this._state.preserveCase); - const viewModel = this._notebookEditor._getViewModel(); + const viewModel = this._notebookEditor.getViewModel(); viewModel.replaceOne(cell, match.range, replaceString).then(() => { this._progressBar.stop(); }); @@ -215,7 +215,7 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi }); }); - const viewModel = this._notebookEditor._getViewModel(); + const viewModel = this._notebookEditor.getViewModel(); viewModel.replaceAll(this._findModel.findMatches, replaceStrings).then(() => { this._progressBar.stop(); }); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts index facce42cfcf31..6b28f787ee43c 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/navigation/arrow.ts @@ -431,7 +431,7 @@ registerAction2(class extends NotebookCellAction { function getPageSize(context: INotebookCellActionContext) { const editor = context.notebookEditor; - const layoutInfo = editor._getViewModel().layoutInfo; + const layoutInfo = editor.getViewModel().layoutInfo; const lineHeight = layoutInfo?.fontInfo.lineHeight || 17; return Math.max(1, Math.floor((layoutInfo?.height || 0) / lineHeight) - 2); } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts index ce2b9849c2ae7..6404536b1df22 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout.ts @@ -82,7 +82,7 @@ export class TroubleshootController extends Disposable implements INotebookEdito }); })); - const vm = this._notebookEditor._getViewModel(); + const vm = this._notebookEditor.getViewModel(); let items: INotebookDeltaCellStatusBarItems[] = []; if (this._enabled) { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts index 22678c6af01e7..4a74f442f947a 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/undoRedo/notebookUndoRedo.ts @@ -21,7 +21,7 @@ class NotebookUndoRedoContribution extends Disposable { const PRIORITY = 105; this._register(UndoCommand.addImplementation(PRIORITY, 'notebook-undo-redo', () => { const editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - const viewModel = editor?._getViewModel() as NotebookViewModel | undefined; + const viewModel = editor?.getViewModel() as NotebookViewModel | undefined; if (editor && editor.hasModel() && viewModel) { return viewModel.undo().then(cellResources => { if (cellResources?.length) { @@ -42,7 +42,7 @@ class NotebookUndoRedoContribution extends Disposable { this._register(RedoCommand.addImplementation(PRIORITY, 'notebook-undo-redo', () => { const editor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); - const viewModel = editor?._getViewModel() as NotebookViewModel | undefined; + const viewModel = editor?.getViewModel() as NotebookViewModel | undefined; if (editor && editor.hasModel() && viewModel) { return viewModel.redo().then(cellResources => { diff --git a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts index bc8ea045516d8..651e9a2983744 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/cellOperations.ts @@ -498,7 +498,7 @@ export async function joinNotebookCells(editor: IActiveNotebookEditor, range: IC export async function joinCellsWithSurrounds(bulkEditService: IBulkEditService, context: INotebookCellActionContext, direction: 'above' | 'below'): Promise { const editor = context.notebookEditor; const textModel = editor.textModel; - const viewModel = editor._getViewModel() as NotebookViewModel; + const viewModel = editor.getViewModel() as NotebookViewModel; let ret: { edits: ResourceEdit[]; cell: ICellViewModel; @@ -656,7 +656,7 @@ export function insertCell( initialText: string = '', ui: boolean = false ) { - const viewModel = editor._getViewModel() as NotebookViewModel; + const viewModel = editor.getViewModel() as NotebookViewModel; const activeKernel = editor.activeKernel; if (viewModel.options.isReadOnly) { return null; diff --git a/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts b/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts index 0af3a8546ce90..349d4e2ea9e34 100644 --- a/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts +++ b/src/vs/workbench/contrib/notebook/browser/controller/foldingController.ts @@ -49,7 +49,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont this._foldingModel = new FoldingModel(); this._localStore.add(this._foldingModel); - this._foldingModel.attachViewModel(this._notebookEditor._getViewModel()); + this._foldingModel.attachViewModel(this._notebookEditor.getViewModel()); this._localStore.add(this._foldingModel.onDidFoldingRegionChanged(() => { this._updateEditorFoldingRanges(); @@ -103,7 +103,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont return; } - const vm = this._notebookEditor._getViewModel() as NotebookViewModel; + const vm = this._notebookEditor.getViewModel() as NotebookViewModel; vm.updateFoldingRanges(this._foldingModel.regions); const hiddenRanges = vm.getHiddenRanges(); @@ -119,7 +119,7 @@ export class FoldingController extends Disposable implements INotebookEditorCont return; } - const viewModel = this._notebookEditor._getViewModel() as NotebookViewModel; + const viewModel = this._notebookEditor.getViewModel() as NotebookViewModel; const target = e.event.target as HTMLElement; if (target.classList.contains('codicon-notebook-collapsed') || target.classList.contains('codicon-notebook-expanded')) { @@ -243,7 +243,7 @@ registerAction2(class extends Action2 { controller.setFoldingStateDown(index, CellFoldingState.Collapsed, levels); } - const viewIndex = editor._getViewModel().getNearestVisibleCellIndexUpwards(index); + const viewIndex = editor.getViewModel().getNearestVisibleCellIndexUpwards(index); editor.focusElement(editor.cellAt(viewIndex)); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 72cb92908d6a9..c74f399bff9f3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -57,6 +57,7 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/browser/services/notebookRendererMessagingServiceImpl'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; // Editor Controller import 'vs/workbench/contrib/notebook/browser/controller/coreActions'; @@ -112,9 +113,10 @@ import { NotebookKernelHistoryService } from 'vs/workbench/contrib/notebook/brow import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/notebookLoggingService'; import { NotebookLoggingService } from 'vs/workbench/contrib/notebook/browser/services/notebookLoggingServiceImpl'; import product from 'vs/platform/product/common/product'; -import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; -import { NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; +import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; import { runAccessibilityHelpAction } from 'vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; /*--------------------------------------------------------------------------------------------- */ @@ -689,6 +691,70 @@ class NotebookAccessibilityHelpContribution extends Disposable { } } +class NotebookAccessibleViewContribution extends Disposable { + static ID: 'chatAccessibleViewContribution'; + constructor() { + super(); + this._register(AccessibleViewAction.addImplementation(100, 'notebook', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + + const activePane = accessor.get(IEditorService).activeEditorPane; + const notebookEditor = getNotebookEditorFromEditorPane(activePane); + const notebookViewModel = notebookEditor?.getViewModel(); + const selections = notebookViewModel?.getSelections(); + notebookViewModel?.getCellIndex; + const notebookDocument = notebookViewModel?.notebookDocument; + + if (!selections || !notebookDocument || !notebookEditor?.textModel) { + return false; + } + + const viewCell = notebookViewModel.viewCells[selections[0].start]; + let outputContent = ''; + const decoder = new TextDecoder(); + for (let i = 0; i < viewCell.outputsViewModels.length; i++) { + const outputViewModel = viewCell.outputsViewModels[i]; + const outputTextModel = viewCell.model.outputs[i]; + const [mimeTypes, pick] = outputViewModel.resolveMimeTypes(notebookEditor.textModel, undefined); + const mimeType = mimeTypes[pick].mimeType; + const pickedBuffer = outputTextModel.outputs.find(output => output.mime === mimeType)?.data.buffer; + + let text = `${mimeType}\n`; + if (!pickedBuffer || mimeType.startsWith('image')) { + const altBuffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image'))?.data.buffer; + if (altBuffer) { + text = decoder.decode(altBuffer); + } + } else { + text = decoder.decode(pickedBuffer); + } + + outputContent = outputContent.concat(`${text}\n`); + } + + if (!outputContent) { + return false; + } + + accessibleViewService.show({ + verbositySettingKey: 'notebook', + provideContent(): string { return outputContent; }, + onClose() { + notebookEditor?.setFocus(selections[0]); + activePane?.focus(); + }, + options: { + ariaLabel: nls.localize('NotebookCellOutputAccessibleView', "Notebook Cell Output Accessible View"), + language: 'plaintext', + type: AccessibleViewType.View + } + }); + return true; + }, NOTEBOOK_OUTPUT_FOCUSED)); + } +} + + const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); @@ -698,6 +764,7 @@ workbenchContributionsRegistry.registerWorkbenchContribution(NotebookEditorManag workbenchContributionsRegistry.registerWorkbenchContribution(NotebookLanguageSelectorScoreRefine, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(SimpleNotebookWorkingCopyEditorHandler, LifecyclePhase.Ready); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibilityHelpContribution, LifecyclePhase.Eventually); +workbenchContributionsRegistry.registerWorkbenchContribution(NotebookAccessibleViewContribution, LifecyclePhase.Eventually); registerSingleton(INotebookService, NotebookService, InstantiationType.Delayed); registerSingleton(INotebookEditorWorkerService, NotebookEditorWorkerServiceImpl, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 5166e31799152..fa9048616fcdd 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -476,7 +476,7 @@ export interface INotebookEditor { setFocus(focus: ICellRange): void; getId(): string; - _getViewModel(): INotebookViewModel | undefined; + getViewModel(): INotebookViewModel | undefined; hasModel(): this is IActiveNotebookEditor; dispose(): void; getDomNode(): HTMLElement; @@ -684,7 +684,7 @@ export interface INotebookEditor { } export interface IActiveNotebookEditor extends INotebookEditor { - _getViewModel(): INotebookViewModel; + getViewModel(): INotebookViewModel; textModel: NotebookTextModel; getFocus(): ICellRange; cellAt(index: number): ICellViewModel; @@ -730,7 +730,7 @@ export interface INotebookEditorDelegate extends INotebookEditor { } export interface IActiveNotebookEditorDelegate extends INotebookEditorDelegate { - _getViewModel(): INotebookViewModel; + getViewModel(): INotebookViewModel; textModel: NotebookTextModel; getFocus(): ICellRange; cellAt(index: number): ICellViewModel; diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 71f9504ed238a..bf2276f2bf9ac 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -442,7 +442,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD return this._uuid; } - _getViewModel(): NotebookViewModel | undefined { + getViewModel(): NotebookViewModel | undefined { return this.viewModel; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts index d5dff6359c7d2..2fe72e05af8b7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/foldedCellHint.ts @@ -33,8 +33,8 @@ export class FoldedCellHint extends CellContentPart { if (element.isInputCollapsed || element.getEditState() === CellEditState.Editing) { DOM.hide(this._container); } else if (element.foldingState === CellFoldingState.Collapsed) { - const idx = this._notebookEditor._getViewModel().getCellIndex(element); - const length = this._notebookEditor._getViewModel().getFoldedLength(idx); + const idx = this._notebookEditor.getViewModel().getCellIndex(element); + const length = this._notebookEditor.getViewModel().getFoldedLength(idx); DOM.reset(this._container, this.getHiddenCellsLabel(length), this.getHiddenCellHintButton(element)); DOM.show(this._container); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts index 498635a2fc67e..ce31818ac2798 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler.ts @@ -46,7 +46,7 @@ export class NotebookOverviewRuler extends Themable { } private _render(ctx: CanvasRenderingContext2D, width: number, height: number, scrollHeight: number, ratio: number) { - const viewModel = this.notebookEditor._getViewModel(); + const viewModel = this.notebookEditor.getViewModel(); const fontInfo = this.notebookEditor.getLayoutInfo().fontInfo; const laneWidth = width / this._lanes; diff --git a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts index fd3cf5fdcb975..2c3ffb72e73ae 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts @@ -221,7 +221,7 @@ function _createTestNotebookEditor(instantiationService: TestInstantiationServic override notebookOptions = notebookOptions; override onDidChangeModel: Event = new Emitter().event; override onDidChangeCellState: Event = new Emitter().event; - override _getViewModel(): NotebookViewModel { + override getViewModel(): NotebookViewModel { return viewModel; } override textModel = viewModel.notebookDocument; From eb7ed2b2aa8e6ba90ce655f6903b563ac7a2101d Mon Sep 17 00:00:00 2001 From: aamunger Date: Wed, 19 Jul 2023 11:41:51 -0700 Subject: [PATCH 088/216] label output indexes if more than one --- .../contrib/notebook/browser/notebook.contribution.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index c74f399bff9f3..e65e41fcfb847 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -728,8 +728,10 @@ class NotebookAccessibleViewContribution extends Disposable { } else { text = decoder.decode(pickedBuffer); } - - outputContent = outputContent.concat(`${text}\n`); + const index = viewCell.outputsViewModels.length > 1 + ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` + : ''; + outputContent = outputContent.concat(`${index}${text}\n`); } if (!outputContent) { From 82b4f2bf4999a3627dbce7a119aabf656e6bab6c Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 09:14:37 -0700 Subject: [PATCH 089/216] make error output legible --- .../contrib/notebook/browser/notebook.contribution.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index e65e41fcfb847..93541c74a44f0 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -731,6 +731,9 @@ class NotebookAccessibleViewContribution extends Disposable { const index = viewCell.outputsViewModels.length > 1 ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` : ''; + if (mimeType.endsWith('error')) { + text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replace('\\n', '\\\n'); + } outputContent = outputContent.concat(`${index}${text}\n`); } From c3261e574910a0e0de0cbd6ec5f11e8246c41f42 Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 09:27:59 -0700 Subject: [PATCH 090/216] fix newline replace --- .../workbench/contrib/notebook/browser/notebook.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 93541c74a44f0..97040a2d4c1c4 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -732,7 +732,7 @@ class NotebookAccessibleViewContribution extends Disposable { ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` : ''; if (mimeType.endsWith('error')) { - text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replace('\\n', '\\\n'); + text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); } outputContent = outputContent.concat(`${index}${text}\n`); } From db6c9a92559b868baf834c404fd25c1c7fbf84e1 Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 10:27:54 -0700 Subject: [PATCH 091/216] limit the amount of data --- .../notebook/browser/notebook.contribution.ts | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 97040a2d4c1c4..4f9f98e4d810e 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -717,23 +717,29 @@ class NotebookAccessibleViewContribution extends Disposable { const outputTextModel = viewCell.model.outputs[i]; const [mimeTypes, pick] = outputViewModel.resolveMimeTypes(notebookEditor.textModel, undefined); const mimeType = mimeTypes[pick].mimeType; - const pickedBuffer = outputTextModel.outputs.find(output => output.mime === mimeType)?.data.buffer; + let buffer = outputTextModel.outputs.find(output => output.mime === mimeType); - let text = `${mimeType}\n`; - if (!pickedBuffer || mimeType.startsWith('image')) { - const altBuffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image'))?.data.buffer; - if (altBuffer) { - text = decoder.decode(altBuffer); + if (!buffer || mimeType.startsWith('image')) { + buffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image')); + } + + let text = `${mimeType}`; // default in case we can't get the text value for some reason. + if (buffer) { + const charLimit = 100_000; + text = decoder.decode(buffer.data.slice(0, charLimit).buffer); + + if (buffer.data.byteLength > charLimit) { + text = text + '...(truncated)'; + } + + if (mimeType.endsWith('error')) { + text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); } - } else { - text = decoder.decode(pickedBuffer); } + const index = viewCell.outputsViewModels.length > 1 ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` : ''; - if (mimeType.endsWith('error')) { - text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); - } outputContent = outputContent.concat(`${index}${text}\n`); } From 587ce364552dd6b1e5c210e3feca942f109c7215 Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 11:59:15 -0700 Subject: [PATCH 092/216] move function to helper file --- .../notebook/browser/notebook.contribution.ts | 71 ++----------------- ...bilityHelp.ts => notebookAccessibility.ts} | 67 +++++++++++++++++ 2 files changed, 71 insertions(+), 67 deletions(-) rename src/vs/workbench/contrib/notebook/browser/{notebookAccessibilityHelp.ts => notebookAccessibility.ts} (62%) diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 4f9f98e4d810e..ae426f620be39 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -57,7 +57,6 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/browser/services/notebookRendererMessagingServiceImpl'; import { INotebookRendererMessagingService } from 'vs/workbench/contrib/notebook/common/notebookRendererMessagingService'; -import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; // Editor Controller import 'vs/workbench/contrib/notebook/browser/controller/coreActions'; @@ -115,8 +114,8 @@ import { NotebookLoggingService } from 'vs/workbench/contrib/notebook/browser/se import product from 'vs/platform/product/common/product'; import { AccessibilityHelpAction, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys'; -import { runAccessibilityHelpAction } from 'vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp'; -import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { runAccessibilityHelpAction, showAccessibleOutput } from 'vs/workbench/contrib/notebook/browser/notebookAccessibility'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; /*--------------------------------------------------------------------------------------------- */ @@ -697,75 +696,13 @@ class NotebookAccessibleViewContribution extends Disposable { super(); this._register(AccessibleViewAction.addImplementation(100, 'notebook', accessor => { const accessibleViewService = accessor.get(IAccessibleViewService); + const editorService = accessor.get(IEditorService); - const activePane = accessor.get(IEditorService).activeEditorPane; - const notebookEditor = getNotebookEditorFromEditorPane(activePane); - const notebookViewModel = notebookEditor?.getViewModel(); - const selections = notebookViewModel?.getSelections(); - notebookViewModel?.getCellIndex; - const notebookDocument = notebookViewModel?.notebookDocument; - - if (!selections || !notebookDocument || !notebookEditor?.textModel) { - return false; - } - - const viewCell = notebookViewModel.viewCells[selections[0].start]; - let outputContent = ''; - const decoder = new TextDecoder(); - for (let i = 0; i < viewCell.outputsViewModels.length; i++) { - const outputViewModel = viewCell.outputsViewModels[i]; - const outputTextModel = viewCell.model.outputs[i]; - const [mimeTypes, pick] = outputViewModel.resolveMimeTypes(notebookEditor.textModel, undefined); - const mimeType = mimeTypes[pick].mimeType; - let buffer = outputTextModel.outputs.find(output => output.mime === mimeType); - - if (!buffer || mimeType.startsWith('image')) { - buffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image')); - } - - let text = `${mimeType}`; // default in case we can't get the text value for some reason. - if (buffer) { - const charLimit = 100_000; - text = decoder.decode(buffer.data.slice(0, charLimit).buffer); - - if (buffer.data.byteLength > charLimit) { - text = text + '...(truncated)'; - } - - if (mimeType.endsWith('error')) { - text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); - } - } - - const index = viewCell.outputsViewModels.length > 1 - ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` - : ''; - outputContent = outputContent.concat(`${index}${text}\n`); - } - - if (!outputContent) { - return false; - } - - accessibleViewService.show({ - verbositySettingKey: 'notebook', - provideContent(): string { return outputContent; }, - onClose() { - notebookEditor?.setFocus(selections[0]); - activePane?.focus(); - }, - options: { - ariaLabel: nls.localize('NotebookCellOutputAccessibleView', "Notebook Cell Output Accessible View"), - language: 'plaintext', - type: AccessibleViewType.View - } - }); - return true; + return showAccessibleOutput(accessibleViewService, editorService); }, NOTEBOOK_OUTPUT_FOCUSED)); } } - const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchContributionsRegistry.registerWorkbenchContribution(NotebookContribution, LifecyclePhase.Starting); workbenchContributionsRegistry.registerWorkbenchContribution(CellContentProvider, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts similarity index 62% rename from src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts rename to src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts index c5671a2487701..6c90a297a6aba 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts @@ -10,6 +10,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { getNotebookEditorFromEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; export function getAccessibilityHelpText(accessor: ServicesAccessor): string { const keybindingService = accessor.get(IKeybindingService); @@ -55,3 +57,68 @@ export async function runAccessibilityHelpAction(accessor: ServicesAccessor, edi options: { type: AccessibleViewType.HelpMenu, ariaLabel: 'Notebook accessibility help' } }); } + +export function showAccessibleOutput(accessibleViewService: IAccessibleViewService, editorService: IEditorService) { + const activePane = editorService.activeEditorPane; + const notebookEditor = getNotebookEditorFromEditorPane(activePane); + const notebookViewModel = notebookEditor?.getViewModel(); + const selections = notebookViewModel?.getSelections(); + const notebookDocument = notebookViewModel?.notebookDocument; + + if (!selections || !notebookDocument || !notebookEditor?.textModel) { + return false; + } + + const viewCell = notebookViewModel.viewCells[selections[0].start]; + let outputContent = ''; + const decoder = new TextDecoder(); + for (let i = 0; i < viewCell.outputsViewModels.length; i++) { + const outputViewModel = viewCell.outputsViewModels[i]; + const outputTextModel = viewCell.model.outputs[i]; + const [mimeTypes, pick] = outputViewModel.resolveMimeTypes(notebookEditor.textModel, undefined); + const mimeType = mimeTypes[pick].mimeType; + let buffer = outputTextModel.outputs.find(output => output.mime === mimeType); + + if (!buffer || mimeType.startsWith('image')) { + buffer = outputTextModel.outputs.find(output => !output.mime.startsWith('image')); + } + + let text = `${mimeType}`; // default in case we can't get the text value for some reason. + if (buffer) { + const charLimit = 100_000; + text = decoder.decode(buffer.data.slice(0, charLimit).buffer); + + if (buffer.data.byteLength > charLimit) { + text = text + '...(truncated)'; + } + + if (mimeType.endsWith('error')) { + text = text.replace(/\\u001b\[[0-9;]*m/gi, '').replaceAll('\\n', '\n'); + } + } + + const index = viewCell.outputsViewModels.length > 1 + ? `Cell output ${i + 1} of ${viewCell.outputsViewModels.length}\n` + : ''; + outputContent = outputContent.concat(`${index}${text}\n`); + } + + if (!outputContent) { + return false; + } + + accessibleViewService.show({ + verbositySettingKey: AccessibilityVerbositySettingId.Notebook, + provideContent(): string { return outputContent; }, + onClose() { + notebookEditor?.setFocus(selections[0]); + activePane?.focus(); + }, + options: { + ariaLabel: localize('NotebookCellOutputAccessibleView', "Notebook Cell Output Accessible View"), + language: 'plaintext', + type: AccessibleViewType.View + } + }); + return true; +} From 5def5bc8ea2bf0c60168f2a738f23000d0db3bf6 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 20 Jul 2023 12:48:46 -0700 Subject: [PATCH 093/216] Move off native private fields for IW (#188411) Move off native private fields --- .../interactive/browser/interactiveEditor.ts | 423 +++++++++--------- .../browser/interactiveHistoryService.ts | 26 +- 2 files changed, 222 insertions(+), 227 deletions(-) diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index c97adbabfe2bd..965719d15a646 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* eslint-disable local/code-no-native-private */ - import 'vs/css!./media/interactive'; import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; @@ -83,40 +81,39 @@ export interface InteractiveEditorOptions extends ITextEditorOptions { } export class InteractiveEditor extends EditorPane { - #rootElement!: HTMLElement; - #styleElement!: HTMLStyleElement; - #notebookEditorContainer!: HTMLElement; - #notebookWidget: IBorrowValue = { value: undefined }; - #inputCellContainer!: HTMLElement; - #inputFocusIndicator!: HTMLElement; - #inputRunButtonContainer!: HTMLElement; - #inputEditorContainer!: HTMLElement; - #codeEditorWidget!: CodeEditorWidget; - // #inputLineCount = 1; - #notebookWidgetService: INotebookEditorService; - #instantiationService: IInstantiationService; - #languageService: ILanguageService; - #contextKeyService: IContextKeyService; - #configurationService: IConfigurationService; - #notebookKernelService: INotebookKernelService; - #keybindingService: IKeybindingService; - #menuService: IMenuService; - #contextMenuService: IContextMenuService; - #editorGroupService: IEditorGroupsService; - #notebookExecutionStateService: INotebookExecutionStateService; - #extensionService: IExtensionService; - #widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); - #lastLayoutDimensions?: { readonly dimension: DOM.Dimension; readonly position: DOM.IDomPosition }; - #editorOptions: IEditorOptions; - #notebookOptions: NotebookOptions; - #editorMemento: IEditorMemento; - #groupListener = this._register(new DisposableStore()); - #runbuttonToolbar: ToolBar | undefined; - - #onDidFocusWidget = this._register(new Emitter()); - override get onDidFocus(): Event { return this.#onDidFocusWidget.event; } - #onDidChangeSelection = this._register(new Emitter()); - readonly onDidChangeSelection = this.#onDidChangeSelection.event; + private _rootElement!: HTMLElement; + private _styleElement!: HTMLStyleElement; + private _notebookEditorContainer!: HTMLElement; + private _notebookWidget: IBorrowValue = { value: undefined }; + private _inputCellContainer!: HTMLElement; + private _inputFocusIndicator!: HTMLElement; + private _inputRunButtonContainer!: HTMLElement; + private _inputEditorContainer!: HTMLElement; + private _codeEditorWidget!: CodeEditorWidget; + private _notebookWidgetService: INotebookEditorService; + private _instantiationService: IInstantiationService; + private _languageService: ILanguageService; + private _contextKeyService: IContextKeyService; + private _configurationService: IConfigurationService; + private _notebookKernelService: INotebookKernelService; + private _keybindingService: IKeybindingService; + private _menuService: IMenuService; + private _contextMenuService: IContextMenuService; + private _editorGroupService: IEditorGroupsService; + private _notebookExecutionStateService: INotebookExecutionStateService; + private _extensionService: IExtensionService; + private _widgetDisposableStore: DisposableStore = this._register(new DisposableStore()); + private _lastLayoutDimensions?: { readonly dimension: DOM.Dimension; readonly position: DOM.IDomPosition }; + private _editorOptions: IEditorOptions; + private _notebookOptions: NotebookOptions; + private _editorMemento: IEditorMemento; + private _groupListener = this._register(new DisposableStore()); + private _runbuttonToolbar: ToolBar | undefined; + + private _onDidFocusWidget = this._register(new Emitter()); + override get onDidFocus(): Event { return this._onDidFocusWidget.event; } + private _onDidChangeSelection = this._register(new Emitter()); + readonly onDidChangeSelection = this._onDidChangeSelection.event; constructor( @ITelemetryService telemetryService: ITelemetryService, @@ -143,68 +140,68 @@ export class InteractiveEditor extends EditorPane { themeService, storageService ); - this.#instantiationService = instantiationService; - this.#notebookWidgetService = notebookWidgetService; - this.#contextKeyService = contextKeyService; - this.#configurationService = configurationService; - this.#notebookKernelService = notebookKernelService; - this.#languageService = languageService; - this.#keybindingService = keybindingService; - this.#menuService = menuService; - this.#contextMenuService = contextMenuService; - this.#editorGroupService = editorGroupService; - this.#notebookExecutionStateService = notebookExecutionStateService; - this.#extensionService = extensionService; - - this.#editorOptions = this.#computeEditorOptions(); - this._register(this.#configurationService.onDidChangeConfiguration(e => { + this._instantiationService = instantiationService; + this._notebookWidgetService = notebookWidgetService; + this._contextKeyService = contextKeyService; + this._configurationService = configurationService; + this._notebookKernelService = notebookKernelService; + this._languageService = languageService; + this._keybindingService = keybindingService; + this._menuService = menuService; + this._contextMenuService = contextMenuService; + this._editorGroupService = editorGroupService; + this._notebookExecutionStateService = notebookExecutionStateService; + this._extensionService = extensionService; + + this._editorOptions = this._computeEditorOptions(); + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('editor') || e.affectsConfiguration('notebook')) { - this.#editorOptions = this.#computeEditorOptions(); + this._editorOptions = this._computeEditorOptions(); } })); - this.#notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, dragAndDropEnabled: false }); - this.#editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); + this._notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, dragAndDropEnabled: false }); + this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); - this._register(this.#keybindingService.onDidUpdateKeybindings(this.#updateInputDecoration, this)); - this._register(this.#notebookExecutionStateService.onDidChangeExecution((e) => { - if (e.type === NotebookExecutionType.cell && isEqual(e.notebook, this.#notebookWidget.value?.viewModel?.notebookDocument.uri)) { - const cell = this.#notebookWidget.value?.getCellByHandle(e.cellHandle); + this._register(this._keybindingService.onDidUpdateKeybindings(this._updateInputDecoration, this)); + this._register(this._notebookExecutionStateService.onDidChangeExecution((e) => { + if (e.type === NotebookExecutionType.cell && isEqual(e.notebook, this._notebookWidget.value?.viewModel?.notebookDocument.uri)) { + const cell = this._notebookWidget.value?.getCellByHandle(e.cellHandle); if (cell && e.changed?.state) { - this.#scrollIfNecessary(cell); + this._scrollIfNecessary(cell); } } })); } - get #inputCellContainerHeight() { + private get inputCellContainerHeight() { return 19 + 2 + INPUT_CELL_VERTICAL_PADDING * 2 + INPUT_EDITOR_PADDING * 2; } - get #inputCellEditorHeight() { + private get inputCellEditorHeight() { return 19 + INPUT_EDITOR_PADDING * 2; } protected createEditor(parent: HTMLElement): void { - this.#rootElement = DOM.append(parent, DOM.$('.interactive-editor')); - this.#rootElement.style.position = 'relative'; - this.#notebookEditorContainer = DOM.append(this.#rootElement, DOM.$('.notebook-editor-container')); - this.#inputCellContainer = DOM.append(this.#rootElement, DOM.$('.input-cell-container')); - this.#inputCellContainer.style.position = 'absolute'; - this.#inputCellContainer.style.height = `${this.#inputCellContainerHeight}px`; - this.#inputFocusIndicator = DOM.append(this.#inputCellContainer, DOM.$('.input-focus-indicator')); - this.#inputRunButtonContainer = DOM.append(this.#inputCellContainer, DOM.$('.run-button-container')); - this.#setupRunButtonToolbar(this.#inputRunButtonContainer); - this.#inputEditorContainer = DOM.append(this.#inputCellContainer, DOM.$('.input-editor-container')); - this.#createLayoutStyles(); + this._rootElement = DOM.append(parent, DOM.$('.interactive-editor')); + this._rootElement.style.position = 'relative'; + this._notebookEditorContainer = DOM.append(this._rootElement, DOM.$('.notebook-editor-container')); + this._inputCellContainer = DOM.append(this._rootElement, DOM.$('.input-cell-container')); + this._inputCellContainer.style.position = 'absolute'; + this._inputCellContainer.style.height = `${this.inputCellContainerHeight}px`; + this._inputFocusIndicator = DOM.append(this._inputCellContainer, DOM.$('.input-focus-indicator')); + this._inputRunButtonContainer = DOM.append(this._inputCellContainer, DOM.$('.run-button-container')); + this._setupRunButtonToolbar(this._inputRunButtonContainer); + this._inputEditorContainer = DOM.append(this._inputCellContainer, DOM.$('.input-editor-container')); + this._createLayoutStyles(); } - #setupRunButtonToolbar(runButtonContainer: HTMLElement) { - const menu = this._register(this.#menuService.createMenu(MenuId.InteractiveInputExecute, this.#contextKeyService)); - this.#runbuttonToolbar = this._register(new ToolBar(runButtonContainer, this.#contextMenuService, { - getKeyBinding: action => this.#keybindingService.lookupKeybinding(action.id), + private _setupRunButtonToolbar(runButtonContainer: HTMLElement) { + const menu = this._register(this._menuService.createMenu(MenuId.InteractiveInputExecute, this._contextKeyService)); + this._runbuttonToolbar = this._register(new ToolBar(runButtonContainer, this._contextMenuService, { + getKeyBinding: action => this._keybindingService.lookupKeybinding(action.id), actionViewItemProvider: action => { - return createActionViewItem(this.#instantiationService, action); + return createActionViewItem(this._instantiationService, action); }, renderDropdownAsChildElement: true })); @@ -214,18 +211,18 @@ export class InteractiveEditor extends EditorPane { const result = { primary, secondary }; createAndFillInActionBarActions(menu, { shouldForwardArgs: true }, result); - this.#runbuttonToolbar.setActions([...primary, ...secondary]); + this._runbuttonToolbar.setActions([...primary, ...secondary]); } - #createLayoutStyles(): void { - this.#styleElement = DOM.createStyleSheet(this.#rootElement); + private _createLayoutStyles(): void { + this._styleElement = DOM.createStyleSheet(this._rootElement); const styleSheets: string[] = []; const { focusIndicator, codeCellLeftMargin, cellRunGutter - } = this.#notebookOptions.getLayoutConfiguration(); + } = this._notebookOptions.getLayoutConfiguration(); const leftMargin = codeCellLeftMargin + cellRunGutter; styleSheets.push(` @@ -269,16 +266,16 @@ export class InteractiveEditor extends EditorPane { } `); - this.#styleElement.textContent = styleSheets.join('\n'); + this._styleElement.textContent = styleSheets.join('\n'); } - #computeEditorOptions(): IEditorOptions { + private _computeEditorOptions(): IEditorOptions { let overrideIdentifier: string | undefined = undefined; - if (this.#codeEditorWidget) { - overrideIdentifier = this.#codeEditorWidget.getModel()?.getLanguageId(); + if (this._codeEditorWidget) { + overrideIdentifier = this._codeEditorWidget.getModel()?.getLanguageId(); } - const editorOptions = deepClone(this.#configurationService.getValue('editor', { overrideIdentifier })); - const editorOptionsOverride = getSimpleEditorOptions(this.#configurationService); + const editorOptions = deepClone(this._configurationService.getValue('editor', { overrideIdentifier })); + const editorOptionsOverride = getSimpleEditorOptions(this._configurationService); const computed = Object.freeze({ ...editorOptions, ...editorOptionsOverride, @@ -298,7 +295,7 @@ export class InteractiveEditor extends EditorPane { } protected override saveState(): void { - this.#saveEditorViewState(this.input); + this._saveEditorViewState(this.input); super.saveState(); } @@ -308,39 +305,39 @@ export class InteractiveEditor extends EditorPane { return undefined; } - this.#saveEditorViewState(input); - return this.#loadNotebookEditorViewState(input); + this._saveEditorViewState(input); + return this._loadNotebookEditorViewState(input); } - #saveEditorViewState(input: EditorInput | undefined): void { - if (this.group && this.#notebookWidget.value && input instanceof InteractiveEditorInput) { - if (this.#notebookWidget.value.isDisposed) { + private _saveEditorViewState(input: EditorInput | undefined): void { + if (this.group && this._notebookWidget.value && input instanceof InteractiveEditorInput) { + if (this._notebookWidget.value.isDisposed) { return; } - const state = this.#notebookWidget.value.getEditorViewState(); - const editorState = this.#codeEditorWidget.saveViewState(); - this.#editorMemento.saveEditorState(this.group, input.notebookEditorInput.resource, { + const state = this._notebookWidget.value.getEditorViewState(); + const editorState = this._codeEditorWidget.saveViewState(); + this._editorMemento.saveEditorState(this.group, input.notebookEditorInput.resource, { notebook: state, input: editorState }); } } - #loadNotebookEditorViewState(input: InteractiveEditorInput): InteractiveEditorViewState | undefined { + private _loadNotebookEditorViewState(input: InteractiveEditorInput): InteractiveEditorViewState | undefined { let result: InteractiveEditorViewState | undefined; if (this.group) { - result = this.#editorMemento.loadEditorState(this.group, input.notebookEditorInput.resource); + result = this._editorMemento.loadEditorState(this.group, input.notebookEditorInput.resource); } if (result) { return result; } // when we don't have a view state for the group/input-tuple then we try to use an existing // editor for the same resource. - for (const group of this.#editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { + for (const group of this._editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE)) { if (group.activeEditorPane !== this && group.activeEditorPane === this && group.activeEditor?.matches(input)) { - const notebook = this.#notebookWidget.value?.getEditorViewState(); - const input = this.#codeEditorWidget.saveViewState(); + const notebook = this._notebookWidget.value?.getEditorViewState(); + const input = this._codeEditorWidget.saveViewState(); return { notebook, input @@ -356,13 +353,13 @@ export class InteractiveEditor extends EditorPane { // there currently is a widget which we still own so // we need to hide it before getting a new widget - this.#notebookWidget.value?.onWillHide(); + this._notebookWidget.value?.onWillHide(); - this.#codeEditorWidget?.dispose(); + this._codeEditorWidget?.dispose(); - this.#widgetDisposableStore.clear(); + this._widgetDisposableStore.clear(); - this.#notebookWidget = >this.#instantiationService.invokeFunction(this.#notebookWidgetService.retrieveWidget, group, notebookInput, { + this._notebookWidget = >this._instantiationService.invokeFunction(this._notebookWidgetService.retrieveWidget, group, notebookInput, { isEmbedded: true, isReadOnly: true, contributions: NotebookEditorExtensionsRegistry.getSomeEditorContributions([ @@ -385,10 +382,10 @@ export class InteractiveEditor extends EditorPane { ModesHoverController.ID, MarkerController.ID ]), - options: this.#notebookOptions + options: this._notebookOptions }); - this.#codeEditorWidget = this.#instantiationService.createInstance(CodeEditorWidget, this.#inputEditorContainer, this.#editorOptions, { + this._codeEditorWidget = this._instantiationService.createInstance(CodeEditorWidget, this._inputEditorContainer, this._editorOptions, { ...{ isSimpleWidget: false, contributions: EditorExtensionsRegistry.getSomeEditorContributions([ @@ -405,109 +402,109 @@ export class InteractiveEditor extends EditorPane { } }); - if (this.#lastLayoutDimensions) { - this.#notebookEditorContainer.style.height = `${this.#lastLayoutDimensions.dimension.height - this.#inputCellContainerHeight}px`; - this.#notebookWidget.value!.layout(new DOM.Dimension(this.#lastLayoutDimensions.dimension.width, this.#lastLayoutDimensions.dimension.height - this.#inputCellContainerHeight), this.#notebookEditorContainer); + if (this._lastLayoutDimensions) { + this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`; + this._notebookWidget.value!.layout(new DOM.Dimension(this._lastLayoutDimensions.dimension.width, this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight), this._notebookEditorContainer); const { codeCellLeftMargin, cellRunGutter - } = this.#notebookOptions.getLayoutConfiguration(); + } = this._notebookOptions.getLayoutConfiguration(); const leftMargin = codeCellLeftMargin + cellRunGutter; - const maxHeight = Math.min(this.#lastLayoutDimensions.dimension.height / 2, this.#inputCellEditorHeight); - this.#codeEditorWidget.layout(this.#validateDimension(this.#lastLayoutDimensions.dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight)); - this.#inputFocusIndicator.style.height = `${this.#inputCellEditorHeight}px`; - this.#inputCellContainer.style.top = `${this.#lastLayoutDimensions.dimension.height - this.#inputCellContainerHeight}px`; - this.#inputCellContainer.style.width = `${this.#lastLayoutDimensions.dimension.width}px`; + const maxHeight = Math.min(this._lastLayoutDimensions.dimension.height / 2, this.inputCellEditorHeight); + this._codeEditorWidget.layout(this._validateDimension(this._lastLayoutDimensions.dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight)); + this._inputFocusIndicator.style.height = `${this.inputCellEditorHeight}px`; + this._inputCellContainer.style.top = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`; + this._inputCellContainer.style.width = `${this._lastLayoutDimensions.dimension.width}px`; } await super.setInput(input, options, context, token); const model = await input.resolve(); - if (this.#runbuttonToolbar) { - this.#runbuttonToolbar.context = input.resource; + if (this._runbuttonToolbar) { + this._runbuttonToolbar.context = input.resource; } if (model === null) { throw new Error('The Interactive Window model could not be resolved'); } - this.#notebookWidget.value?.setParentContextKeyService(this.#contextKeyService); + this._notebookWidget.value?.setParentContextKeyService(this._contextKeyService); - const viewState = options?.viewState ?? this.#loadNotebookEditorViewState(input); - await this.#extensionService.whenInstalledExtensionsRegistered(); - await this.#notebookWidget.value!.setModel(model.notebook, viewState?.notebook); - model.notebook.setCellCollapseDefault(this.#notebookOptions.getCellCollapseDefault()); - this.#notebookWidget.value!.setOptions({ + const viewState = options?.viewState ?? this._loadNotebookEditorViewState(input); + await this._extensionService.whenInstalledExtensionsRegistered(); + await this._notebookWidget.value!.setModel(model.notebook, viewState?.notebook); + model.notebook.setCellCollapseDefault(this._notebookOptions.getCellCollapseDefault()); + this._notebookWidget.value!.setOptions({ isReadOnly: true }); - this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidResizeOutput((cvm) => { - this.#scrollIfNecessary(cvm); + this._widgetDisposableStore.add(this._notebookWidget.value!.onDidResizeOutput((cvm) => { + this._scrollIfNecessary(cvm); })); - this.#widgetDisposableStore.add(this.#notebookWidget.value!.onDidFocusWidget(() => this.#onDidFocusWidget.fire())); - this.#widgetDisposableStore.add(this.#notebookOptions.onDidChangeOptions(e => { + this._widgetDisposableStore.add(this._notebookWidget.value!.onDidFocusWidget(() => this._onDidFocusWidget.fire())); + this._widgetDisposableStore.add(this._notebookOptions.onDidChangeOptions(e => { if (e.compactView || e.focusIndicator) { // update the styling - this.#styleElement?.remove(); - this.#createLayoutStyles(); + this._styleElement?.remove(); + this._createLayoutStyles(); } - if (this.#lastLayoutDimensions && this.isVisible()) { - this.layout(this.#lastLayoutDimensions.dimension, this.#lastLayoutDimensions.position); + if (this._lastLayoutDimensions && this.isVisible()) { + this.layout(this._lastLayoutDimensions.dimension, this._lastLayoutDimensions.position); } if (e.interactiveWindowCollapseCodeCells) { - model.notebook.setCellCollapseDefault(this.#notebookOptions.getCellCollapseDefault()); + model.notebook.setCellCollapseDefault(this._notebookOptions.getCellCollapseDefault()); } })); - const languageId = this.#notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? input.language ?? PLAINTEXT_LANGUAGE_ID; + const languageId = this._notebookWidget.value?.activeKernel?.supportedLanguages[0] ?? input.language ?? PLAINTEXT_LANGUAGE_ID; const editorModel = await input.resolveInput(languageId); editorModel.setLanguage(languageId); - this.#codeEditorWidget.setModel(editorModel); + this._codeEditorWidget.setModel(editorModel); if (viewState?.input) { - this.#codeEditorWidget.restoreViewState(viewState.input); + this._codeEditorWidget.restoreViewState(viewState.input); } - this.#editorOptions = this.#computeEditorOptions(); - this.#codeEditorWidget.updateOptions(this.#editorOptions); + this._editorOptions = this._computeEditorOptions(); + this._codeEditorWidget.updateOptions(this._editorOptions); - this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidFocusEditorWidget(() => this.#onDidFocusWidget.fire())); - this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidContentSizeChange(e => { + this._widgetDisposableStore.add(this._codeEditorWidget.onDidFocusEditorWidget(() => this._onDidFocusWidget.fire())); + this._widgetDisposableStore.add(this._codeEditorWidget.onDidContentSizeChange(e => { if (!e.contentHeightChanged) { return; } - if (this.#lastLayoutDimensions) { - this.#layoutWidgets(this.#lastLayoutDimensions.dimension, this.#lastLayoutDimensions.position); + if (this._lastLayoutDimensions) { + this._layoutWidgets(this._lastLayoutDimensions.dimension, this._lastLayoutDimensions.position); } })); - this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidChangeCursorPosition(e => this.#onDidChangeSelection.fire({ reason: this.#toEditorPaneSelectionChangeReason(e) }))); - this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidChangeModelContent(() => this.#onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT }))); + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeCursorPosition(e => this._onDidChangeSelection.fire({ reason: this._toEditorPaneSelectionChangeReason(e) }))); + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => this._onDidChangeSelection.fire({ reason: EditorPaneSelectionChangeReason.EDIT }))); - this.#widgetDisposableStore.add(this.#notebookKernelService.onDidChangeNotebookAffinity(this.#syncWithKernel, this)); - this.#widgetDisposableStore.add(this.#notebookKernelService.onDidChangeSelectedNotebooks(this.#syncWithKernel, this)); + this._widgetDisposableStore.add(this._notebookKernelService.onDidChangeNotebookAffinity(this._syncWithKernel, this)); + this._widgetDisposableStore.add(this._notebookKernelService.onDidChangeSelectedNotebooks(this._syncWithKernel, this)); - this.#widgetDisposableStore.add(this.themeService.onDidColorThemeChange(() => { + this._widgetDisposableStore.add(this.themeService.onDidColorThemeChange(() => { if (this.isVisible()) { - this.#updateInputDecoration(); + this._updateInputDecoration(); } })); - this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidChangeModelContent(() => { + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeModelContent(() => { if (this.isVisible()) { - this.#updateInputDecoration(); + this._updateInputDecoration(); } })); - const cursorAtBoundaryContext = INTERACTIVE_INPUT_CURSOR_BOUNDARY.bindTo(this.#contextKeyService); + const cursorAtBoundaryContext = INTERACTIVE_INPUT_CURSOR_BOUNDARY.bindTo(this._contextKeyService); if (input.resource && input.historyService.has(input.resource)) { cursorAtBoundaryContext.set('top'); } else { cursorAtBoundaryContext.set('none'); } - this.#widgetDisposableStore.add(this.#codeEditorWidget.onDidChangeCursorPosition(({ position }) => { - const viewModel = this.#codeEditorWidget._getViewModel()!; + this._widgetDisposableStore.add(this._codeEditorWidget.onDidChangeCursorPosition(({ position }) => { + const viewModel = this._codeEditorWidget._getViewModel()!; const lastLineNumber = viewModel.getLineCount(); const lastLineCol = viewModel.getLineContent(lastLineNumber).length + 1; const viewPosition = viewModel.coordinatesConverter.convertModelPositionToViewPosition(position); @@ -529,22 +526,22 @@ export class InteractiveEditor extends EditorPane { } })); - this.#widgetDisposableStore.add(editorModel.onDidChangeContent(() => { + this._widgetDisposableStore.add(editorModel.onDidChangeContent(() => { const value = editorModel!.getValue(); if (this.input?.resource && value !== '') { (this.input as InteractiveEditorInput).historyService.replaceLast(this.input.resource, value); } })); - this.#syncWithKernel(); + this._syncWithKernel(); } override setOptions(options: INotebookEditorOptions | undefined): void { - this.#notebookWidget.value?.setOptions(options); + this._notebookWidget.value?.setOptions(options); super.setOptions(options); } - #toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason { + private _toEditorPaneSelectionChangeReason(e: ICursorPositionChangedEvent): EditorPaneSelectionChangeReason { switch (e.source) { case TextEditorSelectionSource.PROGRAMMATIC: return EditorPaneSelectionChangeReason.PROGRAMMATIC; case TextEditorSelectionSource.NAVIGATION: return EditorPaneSelectionChangeReason.NAVIGATION; @@ -553,31 +550,31 @@ export class InteractiveEditor extends EditorPane { } } - #cellAtBottom(cell: ICellViewModel): boolean { - const visibleRanges = this.#notebookWidget.value?.visibleRanges || []; - const cellIndex = this.#notebookWidget.value?.getCellIndex(cell); + private _cellAtBottom(cell: ICellViewModel): boolean { + const visibleRanges = this._notebookWidget.value?.visibleRanges || []; + const cellIndex = this._notebookWidget.value?.getCellIndex(cell); if (cellIndex === Math.max(...visibleRanges.map(range => range.end - 1))) { return true; } return false; } - #scrollIfNecessary(cvm: ICellViewModel) { - const index = this.#notebookWidget.value!.getCellIndex(cvm); - if (index === this.#notebookWidget.value!.getLength() - 1) { + private _scrollIfNecessary(cvm: ICellViewModel) { + const index = this._notebookWidget.value!.getCellIndex(cvm); + if (index === this._notebookWidget.value!.getLength() - 1) { // If we're already at the bottom or auto scroll is enabled, scroll to the bottom - if (this.#configurationService.getValue(InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell) || this.#cellAtBottom(cvm)) { - this.#notebookWidget.value!.scrollToBottom(); + if (this._configurationService.getValue(InteractiveWindowSetting.interactiveWindowAlwaysScrollOnNewCell) || this._cellAtBottom(cvm)) { + this._notebookWidget.value!.scrollToBottom(); } } } - #syncWithKernel() { - const notebook = this.#notebookWidget.value?.textModel; - const textModel = this.#codeEditorWidget.getModel(); + private _syncWithKernel() { + const notebook = this._notebookWidget.value?.textModel; + const textModel = this._codeEditorWidget.getModel(); if (notebook && textModel) { - const info = this.#notebookKernelService.getMatchingKernel(notebook); + const info = this._notebookKernelService.getMatchingKernel(notebook); const selectedOrSuggested = info.selected ?? (info.suggestions.length === 1 ? info.suggestions[0] : undefined) ?? (info.all.length === 1 ? info.all[0] : undefined); @@ -586,75 +583,75 @@ export class InteractiveEditor extends EditorPane { const language = selectedOrSuggested.supportedLanguages[0]; // All kernels will initially list plaintext as the supported language before they properly initialized. if (language && language !== 'plaintext') { - const newMode = this.#languageService.createById(language).languageId; + const newMode = this._languageService.createById(language).languageId; textModel.setLanguage(newMode); } - NOTEBOOK_KERNEL.bindTo(this.#contextKeyService).set(selectedOrSuggested.id); + NOTEBOOK_KERNEL.bindTo(this._contextKeyService).set(selectedOrSuggested.id); } } - this.#updateInputDecoration(); + this._updateInputDecoration(); } layout(dimension: DOM.Dimension, position: DOM.IDomPosition): void { - this.#rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600); - this.#rootElement.classList.toggle('narrow-width', dimension.width < 600); - const editorHeightChanged = dimension.height !== this.#lastLayoutDimensions?.dimension.height; - this.#lastLayoutDimensions = { dimension, position }; + this._rootElement.classList.toggle('mid-width', dimension.width < 1000 && dimension.width >= 600); + this._rootElement.classList.toggle('narrow-width', dimension.width < 600); + const editorHeightChanged = dimension.height !== this._lastLayoutDimensions?.dimension.height; + this._lastLayoutDimensions = { dimension, position }; - if (!this.#notebookWidget.value) { + if (!this._notebookWidget.value) { return; } - if (editorHeightChanged && this.#codeEditorWidget) { - SuggestController.get(this.#codeEditorWidget)?.cancelSuggestWidget(); + if (editorHeightChanged && this._codeEditorWidget) { + SuggestController.get(this._codeEditorWidget)?.cancelSuggestWidget(); } - this.#notebookEditorContainer.style.height = `${this.#lastLayoutDimensions.dimension.height - this.#inputCellContainerHeight}px`; - this.#layoutWidgets(dimension, position); + this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`; + this._layoutWidgets(dimension, position); } - #layoutWidgets(dimension: DOM.Dimension, position: DOM.IDomPosition) { - const contentHeight = this.#codeEditorWidget.hasModel() ? this.#codeEditorWidget.getContentHeight() : this.#inputCellEditorHeight; + private _layoutWidgets(dimension: DOM.Dimension, position: DOM.IDomPosition) { + const contentHeight = this._codeEditorWidget.hasModel() ? this._codeEditorWidget.getContentHeight() : this.inputCellEditorHeight; const maxHeight = Math.min(dimension.height / 2, contentHeight); const { codeCellLeftMargin, cellRunGutter - } = this.#notebookOptions.getLayoutConfiguration(); + } = this._notebookOptions.getLayoutConfiguration(); const leftMargin = codeCellLeftMargin + cellRunGutter; const inputCellContainerHeight = maxHeight + INPUT_CELL_VERTICAL_PADDING * 2; - this.#notebookEditorContainer.style.height = `${dimension.height - inputCellContainerHeight}px`; + this._notebookEditorContainer.style.height = `${dimension.height - inputCellContainerHeight}px`; - this.#notebookWidget.value!.layout(dimension.with(dimension.width, dimension.height - inputCellContainerHeight), this.#notebookEditorContainer, position); - this.#codeEditorWidget.layout(this.#validateDimension(dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight)); - this.#inputFocusIndicator.style.height = `${contentHeight}px`; - this.#inputCellContainer.style.top = `${dimension.height - inputCellContainerHeight}px`; - this.#inputCellContainer.style.width = `${dimension.width}px`; + this._notebookWidget.value!.layout(dimension.with(dimension.width, dimension.height - inputCellContainerHeight), this._notebookEditorContainer, position); + this._codeEditorWidget.layout(this._validateDimension(dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight)); + this._inputFocusIndicator.style.height = `${contentHeight}px`; + this._inputCellContainer.style.top = `${dimension.height - inputCellContainerHeight}px`; + this._inputCellContainer.style.width = `${dimension.width}px`; } - #validateDimension(width: number, height: number) { + private _validateDimension(width: number, height: number) { return new DOM.Dimension(Math.max(0, width), Math.max(0, height)); } - #updateInputDecoration(): void { - if (!this.#codeEditorWidget) { + private _updateInputDecoration(): void { + if (!this._codeEditorWidget) { return; } - if (!this.#codeEditorWidget.hasModel()) { + if (!this._codeEditorWidget.hasModel()) { return; } - const model = this.#codeEditorWidget.getModel(); + const model = this._codeEditorWidget.getModel(); const decorations: IDecorationOptions[] = []; if (model?.getValueLength() === 0) { const transparentForeground = resolveColorValue(editorForeground, this.themeService.getColorTheme())?.transparent(0.4); const languageId = model.getLanguageId(); - const keybinding = this.#keybindingService.lookupKeybinding('interactive.execute', this.#contextKeyService)?.getLabel(); + const keybinding = this._keybindingService.lookupKeybinding('interactive.execute', this._contextKeyService)?.getLabel(); const text = nls.localize('interactiveInputPlaceHolder', "Type '{0}' code here and press {1} to run", languageId, keybinding ?? 'ctrl+enter'); decorations.push({ range: { @@ -672,51 +669,51 @@ export class InteractiveEditor extends EditorPane { }); } - this.#codeEditorWidget.setDecorationsByType('interactive-decoration', DECORATION_KEY, decorations); + this._codeEditorWidget.setDecorationsByType('interactive-decoration', DECORATION_KEY, decorations); } override focus() { - this.#notebookWidget.value?.onShow(); - this.#codeEditorWidget.focus(); + this._notebookWidget.value?.onShow(); + this._codeEditorWidget.focus(); } focusHistory() { - this.#notebookWidget.value!.focus(); + this._notebookWidget.value!.focus(); } protected override setEditorVisible(visible: boolean, group: IEditorGroup | undefined): void { super.setEditorVisible(visible, group); if (group) { - this.#groupListener.clear(); - this.#groupListener.add(group.onWillCloseEditor(e => this.#saveEditorViewState(e.editor))); + this._groupListener.clear(); + this._groupListener.add(group.onWillCloseEditor(e => this._saveEditorViewState(e.editor))); } if (!visible) { - this.#saveEditorViewState(this.input); - if (this.input && this.#notebookWidget.value) { - this.#notebookWidget.value.onWillHide(); + this._saveEditorViewState(this.input); + if (this.input && this._notebookWidget.value) { + this._notebookWidget.value.onWillHide(); } } } override clearInput() { - if (this.#notebookWidget.value) { - this.#saveEditorViewState(this.input); - this.#notebookWidget.value.onWillHide(); + if (this._notebookWidget.value) { + this._saveEditorViewState(this.input); + this._notebookWidget.value.onWillHide(); } - this.#codeEditorWidget?.dispose(); + this._codeEditorWidget?.dispose(); - this.#notebookWidget = { value: undefined }; - this.#widgetDisposableStore.clear(); + this._notebookWidget = { value: undefined }; + this._widgetDisposableStore.clear(); super.clearInput(); } override getControl(): { notebookEditor: NotebookEditorWidget | undefined; codeEditor: CodeEditorWidget } { return { - notebookEditor: this.#notebookWidget.value, - codeEditor: this.#codeEditorWidget + notebookEditor: this._notebookWidget.value, + codeEditor: this._codeEditorWidget }; } } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts b/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts index f39479fa10ffc..cdfbf09592b69 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveHistoryService.ts @@ -3,8 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/* eslint-disable local/code-no-native-private */ - import { HistoryNavigator2 } from 'vs/base/common/history'; import { Disposable } from 'vs/base/common/lifecycle'; import { ResourceMap } from 'vs/base/common/map'; @@ -26,21 +24,21 @@ export interface IInteractiveHistoryService { export class InteractiveHistoryService extends Disposable implements IInteractiveHistoryService { declare readonly _serviceBrand: undefined; - #history: ResourceMap>; + _history: ResourceMap>; constructor() { super(); - this.#history = new ResourceMap>(); + this._history = new ResourceMap>(); } addToHistory(uri: URI, value: string): void { - if (!this.#history.has(uri)) { - this.#history.set(uri, new HistoryNavigator2([value], 50)); + if (!this._history.has(uri)) { + this._history.set(uri, new HistoryNavigator2([value], 50)); return; } - const history = this.#history.get(uri)!; + const history = this._history.get(uri)!; history.resetCursor(); if (history?.current() !== value) { @@ -48,22 +46,22 @@ export class InteractiveHistoryService extends Disposable implements IInteractiv } } getPreviousValue(uri: URI): string | null { - const history = this.#history.get(uri); + const history = this._history.get(uri); return history?.previous() ?? null; } getNextValue(uri: URI): string | null { - const history = this.#history.get(uri); + const history = this._history.get(uri); return history?.next() ?? null; } replaceLast(uri: URI, value: string) { - if (!this.#history.has(uri)) { - this.#history.set(uri, new HistoryNavigator2([value], 50)); + if (!this._history.has(uri)) { + this._history.set(uri, new HistoryNavigator2([value], 50)); return; } else { - const history = this.#history.get(uri); + const history = this._history.get(uri); if (history?.current() !== value) { history?.replaceLast(value); } @@ -72,11 +70,11 @@ export class InteractiveHistoryService extends Disposable implements IInteractiv } clearHistory(uri: URI) { - this.#history.delete(uri); + this._history.delete(uri); } has(uri: URI) { - return this.#history.has(uri) ? true : false; + return this._history.has(uri) ? true : false; } } From d205ce0ccbc59fd12316247e3968a12ae289248c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 13:24:48 -0700 Subject: [PATCH 094/216] use markdown as language for accessible view --- .../contrib/accessibility/browser/accessibleView.ts | 5 +++++ src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 137433f419890..235aed16963ad 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -64,6 +64,9 @@ export const enum AccessibleViewType { export interface IAccessibleViewOptions { ariaLabel: string; readMoreUrl?: string; + /** + * Defaults to markdown + */ language?: string; type: AccessibleViewType; } @@ -198,6 +201,8 @@ class AccessibleView extends Disposable { } if (provider.options.language) { model.setLanguage(provider.options.language); + } else { + model.setLanguage('markdown'); } container.appendChild(this._editorContainer); this._editorWidget.updateOptions({ ariaLabel: provider.next && provider.previous ? localize('accessibleViewAriaLabelWithNav', "{0} {1}", provider.options.ariaLabel, this._getNavigationAriaHint(provider.verbositySettingKey)) : localize('accessibleViewAriaLabel', "{0}", provider.options.ariaLabel) }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index a9742eba96fa9..0366560253afb 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -186,7 +186,7 @@ class ChatAccessibleViewContribution extends Disposable { verifiedWidget.moveFocus(focusedItem, 'previous'); renderAccessibleView(accessibleViewService, widgetService, codeEditorService); }, - options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), language: 'typescript', type: AccessibleViewType.View } + options: { ariaLabel: nls.localize('chatAccessibleView', "Chat Accessible View"), type: AccessibleViewType.View } }); return true; } From 594615537c25c2b81b6b63cbde9cb379cba6bdb7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 13:34:48 -0700 Subject: [PATCH 095/216] use language for hover, markdown for notebook output --- .../contrib/accessibility/browser/accessibility.contribution.ts | 1 + .../workbench/contrib/notebook/browser/notebookAccessibility.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts index 29976a108a529..c500500534f8d 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibility.contribution.ts @@ -117,6 +117,7 @@ class HoverAccessibleViewContribution extends Disposable { if (!editorHoverContent) { return false; } + this._options.language = editor?.getModel()?.getLanguageId() ?? undefined; accessibleViewService.show({ verbositySettingKey: AccessibilityVerbositySettingId.Hover, provideContent() { return editorHoverContent; }, diff --git a/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts b/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts index 6c90a297a6aba..8e94fe17ecd30 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookAccessibility.ts @@ -116,7 +116,6 @@ export function showAccessibleOutput(accessibleViewService: IAccessibleViewServi }, options: { ariaLabel: localize('NotebookCellOutputAccessibleView', "Notebook Cell Output Accessible View"), - language: 'plaintext', type: AccessibleViewType.View } }); From 5c06ca6274ec7edbeeaa1a29cffbcead85d0f36b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 13:36:32 -0700 Subject: [PATCH 096/216] consolidate --- .../contrib/accessibility/browser/accessibleView.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 235aed16963ad..4c918f1486bdd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -199,11 +199,7 @@ class AccessibleView extends Disposable { if (!domNode) { return; } - if (provider.options.language) { - model.setLanguage(provider.options.language); - } else { - model.setLanguage('markdown'); - } + model.setLanguage(provider.options.language ?? 'markdown'); container.appendChild(this._editorContainer); this._editorWidget.updateOptions({ ariaLabel: provider.next && provider.previous ? localize('accessibleViewAriaLabelWithNav', "{0} {1}", provider.options.ariaLabel, this._getNavigationAriaHint(provider.verbositySettingKey)) : localize('accessibleViewAriaLabel', "{0}", provider.options.ariaLabel) }); this._editorWidget.focus(); From 76a571684f73b84211f9dad9aa9fa5fcb9ab6da5 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 20 Jul 2023 22:53:59 +0200 Subject: [PATCH 097/216] Refactors async tokenization. Fixes #188351 --- src/vs/base/common/objects.ts | 7 +- src/vs/editor/common/languages.ts | 31 ++- src/vs/editor/common/tokenizationRegistry.ts | 2 - .../inspectEditorTokens.ts | 2 +- .../browser/themes.test.contribution.ts | 2 +- .../textMateWorkerTokenizerController.ts | 43 ++-- .../threadedBackgroundTokenizerFactory.ts} | 193 ++++++++---------- .../textMateTokenizationWorker.worker.ts} | 120 ++++++----- .../worker/textMateWorkerTokenizer.ts} | 104 +++++----- .../browser/textMateTokenizationFeature.ts | 2 +- .../textMateTokenizationFeatureImpl.ts | 45 ++-- 11 files changed, 268 insertions(+), 283 deletions(-) rename src/vs/workbench/services/textMate/browser/{workerHost => backgroundTokenization}/textMateWorkerTokenizerController.ts (87%) rename src/vs/workbench/services/textMate/browser/{workerHost/textMateWorkerHost.ts => backgroundTokenization/threadedBackgroundTokenizerFactory.ts} (68%) rename src/vs/workbench/services/textMate/browser/{worker/textMate.worker.ts => backgroundTokenization/worker/textMateTokenizationWorker.worker.ts} (50%) rename src/vs/workbench/services/textMate/browser/{worker/textMateWorkerModel.ts => backgroundTokenization/worker/textMateWorkerTokenizer.ts} (64%) diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 1633395798901..897a9fd824971 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -230,10 +230,9 @@ export function filter(obj: obj, predicate: (key: string, value: any) => boolean export function getAllPropertyNames(obj: object): string[] { let res: string[] = []; - let proto = Object.getPrototypeOf(obj); - while (Object.prototype !== proto) { - res = res.concat(Object.getOwnPropertyNames(proto)); - proto = Object.getPrototypeOf(proto); + while (Object.prototype !== obj) { + res = res.concat(Object.getOwnPropertyNames(obj)); + obj = Object.getPrototypeOf(obj); } return res; } diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 246d32bddd6d2..2e1b61be5d803 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -80,22 +80,6 @@ export class EncodedTokenizationResult { } } -/** - * @internal - */ -export interface IBackgroundTokenizer extends IDisposable { - /** - * Instructs the background tokenizer to set the tokens for the given range again. - * - * This might be necessary if the renderer overwrote those tokens with heuristically computed ones for some viewport, - * when the change does not even propagate to that viewport. - */ - requestTokens(startLineNumber: number, endLineNumberExclusive: number): void; - - reportMismatchingTokens?(lineNumber: number): void; -} - - /** * @internal */ @@ -118,6 +102,21 @@ export interface ITokenizationSupport { createBackgroundTokenizer?(textModel: model.ITextModel, store: IBackgroundTokenizationStore): IBackgroundTokenizer | undefined; } +/** + * @internal + */ +export interface IBackgroundTokenizer extends IDisposable { + /** + * Instructs the background tokenizer to set the tokens for the given range again. + * + * This might be necessary if the renderer overwrote those tokens with heuristically computed ones for some viewport, + * when the change does not even propagate to that viewport. + */ + requestTokens(startLineNumber: number, endLineNumberExclusive: number): void; + + reportMismatchingTokens?(lineNumber: number): void; +} + /** * @internal */ diff --git a/src/vs/editor/common/tokenizationRegistry.ts b/src/vs/editor/common/tokenizationRegistry.ts index 2d5ab7781a5a0..d9fb1bba82f2b 100644 --- a/src/vs/editor/common/tokenizationRegistry.ts +++ b/src/vs/editor/common/tokenizationRegistry.ts @@ -78,8 +78,6 @@ export class TokenizationRegistry implements ITokenizationRegistry { return this.get(languageId); } - - public isResolved(languageId: string): boolean { const tokenizationSupport = this.get(languageId); if (tokenizationSupport) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts index 18ced65b47d27..e4d481e7fe12c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectEditorTokens/inspectEditorTokens.ts @@ -236,7 +236,7 @@ class InspectEditorTokensWidget extends Disposable implements IContentWidget { } private _beginCompute(position: Position): void { - const grammar = this._textMateService.createGrammar(this._model.getLanguageId()); + const grammar = this._textMateService.createTokenizer(this._model.getLanguageId()); const semanticTokens = this._computeSemanticTokens(position); dom.clearNode(this._domNode); diff --git a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts index 6916d87256c88..ba09ebe897f81 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.test.contribution.ts @@ -219,7 +219,7 @@ class Snapper { public captureSyntaxTokens(fileName: string, content: string): Promise { const languageId = this.languageService.guessLanguageIdByFilepathOrFirstLine(URI.file(fileName)); - return this.textMateService.createGrammar(languageId!).then((grammar) => { + return this.textMateService.createTokenizer(languageId!).then((grammar) => { if (!grammar) { return []; } diff --git a/src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerTokenizerController.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts similarity index 87% rename from src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerTokenizerController.ts rename to src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts index 72fed613e4136..8c0d074883b01 100644 --- a/src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerTokenizerController.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController.ts @@ -16,12 +16,14 @@ import { IModelContentChange, IModelContentChangedEvent } from 'vs/editor/common import { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contiguousMultilineTokensBuilder'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ArrayEdit, MonotonousIndexTransformer, SingleArrayEdit } from 'vs/workbench/services/textMate/browser/arrayOperation'; -import { TextMateTokenizationWorker } from 'vs/workbench/services/textMate/browser/worker/textMate.worker'; -import type { StateDeltas } from 'vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost'; +import type { StateDeltas, TextMateTokenizationWorker } from 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'; import type { applyStateStackDiff, StateStack } from 'vscode-textmate'; export class TextMateWorkerTokenizerController extends Disposable { - private _pendingChanges: IModelContentChangedEvent[] = []; + private static _id = 0; + + public readonly controllerId = TextMateWorkerTokenizerController._id++; + private readonly _pendingChanges: IModelContentChangedEvent[] = []; /** * These states will eventually equal the worker states. @@ -47,13 +49,13 @@ export class TextMateWorkerTokenizerController extends Disposable { this._register(keepAlive(this._loggingEnabled)); this._register(this._model.onDidChangeContent((e) => { - if (this.shouldLog) { + if (this._shouldLog) { console.log('model change', { fileName: this._model.uri.fsPath.split('\\').pop(), changes: changesToString(e.changes), }); } - this._worker.acceptModelChanged(this._model.uri.toString(), e); + this._worker.acceptModelChanged(this.controllerId, e); this._pendingChanges.push(e); })); @@ -62,7 +64,7 @@ export class TextMateWorkerTokenizerController extends Disposable { const encodedLanguageId = this._languageIdCodec.encodeLanguageId(languageId); this._worker.acceptModelLanguageChanged( - this._model.uri.toString(), + this.controllerId, languageId, encodedLanguageId ); @@ -78,27 +80,33 @@ export class TextMateWorkerTokenizerController extends Disposable { languageId, encodedLanguageId, maxTokenizationLineLength: this._maxTokenizationLineLength.get(), + controllerId: this.controllerId, }); this._register(autorun('update maxTokenizationLineLength', reader => { const maxTokenizationLineLength = this._maxTokenizationLineLength.read(reader); - this._worker.acceptMaxTokenizationLineLength(this._model.uri.toString(), maxTokenizationLineLength); + this._worker.acceptMaxTokenizationLineLength(this.controllerId, maxTokenizationLineLength); })); } - get shouldLog() { - return this._loggingEnabled.get(); - } - public override dispose(): void { super.dispose(); - this._worker.acceptRemovedModel(this._model.uri.toString()); + this._worker.acceptRemovedModel(this.controllerId); + } + + public requestTokens(startLineNumber: number, endLineNumberExclusive: number): void { + this._worker.retokenize(this.controllerId, startLineNumber, endLineNumberExclusive); } /** * This method is called from the worker through the worker host. */ - public async setTokensAndStates(versionId: number, rawTokens: ArrayBuffer, stateDeltas: StateDeltas[]): Promise { + public async setTokensAndStates(controllerId: number, versionId: number, rawTokens: ArrayBuffer, stateDeltas: StateDeltas[]): Promise { + if (this.controllerId !== controllerId) { + // This event is for an outdated controller (the worker didn't receive the delete/create messages yet), ignore the event. + return; + } + // _states state, change{k}, ..., change{versionId}, state delta base & rawTokens, change{j}, ..., change{m}, current renderer state // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ // | past changes | future states @@ -107,7 +115,7 @@ export class TextMateWorkerTokenizerController extends Disposable { new Uint8Array(rawTokens) ); - if (this.shouldLog) { + if (this._shouldLog) { console.log('received background tokenization result', { fileName: this._model.uri.fsPath.split('\\').pop(), updatedTokenLines: tokens.map((t) => t.getLineRange()).join(' & '), @@ -115,7 +123,7 @@ export class TextMateWorkerTokenizerController extends Disposable { }); } - if (this.shouldLog) { + if (this._shouldLog) { const changes = this._pendingChanges.filter(c => c.versionId <= versionId).map(c => c.changes).map(c => changesToString(c)).join(' then '); console.log('Applying changes to local states', changes); } @@ -130,7 +138,7 @@ export class TextMateWorkerTokenizerController extends Disposable { } if (this._pendingChanges.length > 0) { - if (this.shouldLog) { + if (this._shouldLog) { const changes = this._pendingChanges.map(c => c.changes).map(c => changesToString(c)).join(' then '); console.log('Considering non-processed changes', changes); } @@ -205,6 +213,9 @@ export class TextMateWorkerTokenizerController extends Disposable { // First set states, then tokens, so that events fired from set tokens don't read invalid states this._backgroundTokenizationStore.setTokens(tokens); } + + private get _shouldLog() { return this._loggingEnabled.get(); } + } function fullLineArrayEditFromModelContentChange(c: IModelContentChange[]): ArrayEdit { diff --git a/src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts similarity index 68% rename from src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost.ts rename to src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts index 723152d3d6b7e..d3a89b5a1781b 100644 --- a/src/vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { BugIndicatingError } from 'vs/base/common/errors'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath } from 'vs/base/common/network'; import { IObservable } from 'vs/base/common/observable'; @@ -20,18 +19,18 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ICreateData, TextMateTokenizationWorker } from 'vs/workbench/services/textMate/browser/worker/textMate.worker'; -import { TextMateWorkerTokenizerController } from 'vs/workbench/services/textMate/browser/workerHost/textMateWorkerTokenizerController'; +import { ICreateData, ITextMateWorkerHost, StateDeltas, TextMateTokenizationWorker } from 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'; +import { TextMateWorkerTokenizerController } from 'vs/workbench/services/textMate/browser/backgroundTokenization/textMateWorkerTokenizerController'; import { IValidGrammarDefinition } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; -import type { IRawTheme, StackDiff } from 'vscode-textmate'; +import type { IRawTheme } from 'vscode-textmate'; -export class TextMateWorkerHost implements IDisposable { +export class ThreadedBackgroundTokenizerFactory implements IDisposable { private static _reportedMismatchingTokens = false; private _workerProxyPromise: Promise | null = null; private _worker: MonacoWebWorker | null = null; private _workerProxy: TextMateTokenizationWorker | null = null; - private readonly _workerTokenizerControllers = new Map(); + private readonly _workerTokenizerControllers = new Map(); private _currentTheme: IRawTheme | null = null; private _currentTokenColorMap: string[] | null = null; @@ -50,13 +49,64 @@ export class TextMateWorkerHost implements IDisposable { ) { } - public setGrammarDefinitions(grammarDefinitions: IValidGrammarDefinition[]): void { - this._grammarDefinitions = grammarDefinitions; - this._killWorker(); + public dispose(): void { + this._disposeWorker(); } - dispose(): void { - this._killWorker(); + // Will be recreated after worker is disposed (because tokenizer is re-registered when languages change) + public createBackgroundTokenizer(textModel: ITextModel, tokenStore: IBackgroundTokenizationStore, maxTokenizationLineLength: IObservable): IBackgroundTokenizer | undefined { + const shouldTokenizeAsync = this._configurationService.getValue('editor.experimental.asyncTokenization'); + // fallback to default sync background tokenizer + if (shouldTokenizeAsync !== true || textModel.isTooLargeForSyncing()) { return undefined; } + + const store = new DisposableStore(); + const controllerContainer = this._getWorkerProxy().then((workerProxy) => { + if (store.isDisposed || !workerProxy) { return undefined; } + + const controllerContainer = { controller: undefined as undefined | TextMateWorkerTokenizerController }; + store.add(keepAliveWhenAttached(textModel, () => { + const controller = new TextMateWorkerTokenizerController(textModel, workerProxy, this._languageService.languageIdCodec, tokenStore, this._configurationService, maxTokenizationLineLength); + controllerContainer.controller = controller; + this._workerTokenizerControllers.set(controller.controllerId, controller); + return toDisposable(() => { + controllerContainer.controller = undefined; + this._workerTokenizerControllers.delete(controller.controllerId); + controller.dispose(); + }); + })); + return controllerContainer; + }); + + return { + dispose() { + store.dispose(); + }, + requestTokens: async (startLineNumber, endLineNumberExclusive) => { + const controller = (await controllerContainer)?.controller; + if (controller) { + // If there is no controller, the model has been detached in the meantime + controller.requestTokens(startLineNumber, endLineNumberExclusive); + } + }, + reportMismatchingTokens: (lineNumber) => { + if (ThreadedBackgroundTokenizerFactory._reportedMismatchingTokens) { + return; + } + ThreadedBackgroundTokenizerFactory._reportedMismatchingTokens = true; + + this._notificationService.error({ + message: 'Async Tokenization Token Mismatch in line ' + lineNumber, + name: 'Async Tokenization Token Mismatch', + }); + + this._telemetryService.publicLog2<{}, { owner: 'hediet'; comment: 'Used to see if async tokenization is bug-free' }>('asyncTokenizationMismatchingTokens', {}); + }, + }; + } + + public setGrammarDefinitions(grammarDefinitions: IValidGrammarDefinition[]): void { + this._grammarDefinitions = grammarDefinitions; + this._disposeWorker(); } public acceptTheme(theme: IRawTheme, colorMap: string[]): void { @@ -67,14 +117,14 @@ export class TextMateWorkerHost implements IDisposable { } } - private getWorkerProxy(): Promise { + private _getWorkerProxy(): Promise { if (!this._workerProxyPromise) { - this._workerProxyPromise = this.createWorkerProxy(); + this._workerProxyPromise = this._createWorkerProxy(); } return this._workerProxyPromise; } - private async createWorkerProxy(): Promise { + private async _createWorkerProxy(): Promise { const textmateModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-textmate`; const textmateModuleLocationAsar: AppResourcePath = `${nodeModulesAsarPath}/vscode-textmate`; const onigurumaModuleLocation: AppResourcePath = `${nodeModulesPath}/vscode-oniguruma`; @@ -94,14 +144,30 @@ export class TextMateWorkerHost implements IDisposable { onigurumaMainUri: FileAccess.asBrowserUri(onigurumaMain).toString(true), onigurumaWASMUri: FileAccess.asBrowserUri(onigurumaWASM).toString(true), }; - const worker = createWebWorker(this._modelService, this._languageConfigurationService, { + const host: ITextMateWorkerHost = { + readFile: async (_resource: UriComponents): Promise => { + const resource = URI.revive(_resource); + return this._extensionResourceLoaderService.readExtensionResource(resource); + }, + setTokensAndStates: async (controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise => { + const controller = this._workerTokenizerControllers.get(controllerId); + // When a model detaches, it is removed synchronously from the map. + // However, the worker might still be sending tokens for that model, + // so we ignore the event when there is no controller. + if (controller) { + controller.setTokensAndStates(controllerId, versionId, tokens, lineEndStateDeltas); + } + }, + reportTokenizationTime: (timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void => { + this._reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); + } + }; + const worker = this._worker = createWebWorker(this._modelService, this._languageConfigurationService, { createData, label: 'textMateWorker', - moduleId: 'vs/workbench/services/textMate/browser/worker/textMate.worker', - host: this, + moduleId: 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker', + host, }); - - this._worker = worker; const proxy = await worker.getProxy(); if (this._worker !== worker) { @@ -115,7 +181,7 @@ export class TextMateWorkerHost implements IDisposable { return proxy; } - private _killWorker(): void { + private _disposeWorker(): void { for (const controller of this._workerTokenizerControllers.values()) { controller.dispose(); } @@ -128,93 +194,6 @@ export class TextMateWorkerHost implements IDisposable { this._workerProxy = null; this._workerProxyPromise = null; } - - // Will be recreated when worker is killed (because tokenizer is re-registered when languages change) - public createBackgroundTokenizer(textModel: ITextModel, tokenStore: IBackgroundTokenizationStore, maxTokenizationLineLength: IObservable): IBackgroundTokenizer | undefined { - if (this._workerTokenizerControllers.has(textModel.uri.toString())) { - throw new BugIndicatingError(); - } - - const shouldTokenizeAsync = this._configurationService.getValue('editor.experimental.asyncTokenization'); - if (shouldTokenizeAsync !== true) { - return undefined; - } - - if (textModel.isTooLargeForSyncing()) { - // fallback to default sync background tokenizer - return undefined; - } - - const store = new DisposableStore(); - this.getWorkerProxy().then((workerProxy) => { - if (store.isDisposed || !workerProxy) { - return; - } - - store.add(keepAliveWhenAttached(textModel, () => { - const controller = new TextMateWorkerTokenizerController(textModel, workerProxy, this._languageService.languageIdCodec, tokenStore, this._configurationService, maxTokenizationLineLength); - this._workerTokenizerControllers.set(textModel.uri.toString(), controller); - - return toDisposable(() => { - this._workerTokenizerControllers.delete(textModel.uri.toString()); - controller.dispose(); - }); - })); - }); - - return { - dispose() { - store.dispose(); - }, - requestTokens: (startLineNumber, endLineNumberExclusive) => { - this.getWorkerProxy().then((workerProxy) => { - workerProxy?.retokenize(textModel.uri.toString(), startLineNumber, endLineNumberExclusive); - }); - }, - reportMismatchingTokens: (lineNumber) => { - if (TextMateWorkerHost._reportedMismatchingTokens) { - return; - } - TextMateWorkerHost._reportedMismatchingTokens = true; - - this._notificationService.error({ - message: 'Async Tokenization Token Mismatch in line ' + lineNumber, - name: 'Async Tokenization Token Mismatch', - }); - - this._telemetryService.publicLog2<{}, { owner: 'hediet'; comment: 'Used to see if async tokenization is bug-free' }>('asyncTokenizationMismatchingTokens', {}); - }, - }; - } - - // #region called by the worker - - async readFile(_resource: UriComponents): Promise { - const resource = URI.revive(_resource); - return this._extensionResourceLoaderService.readExtensionResource(resource); - } - - async setTokensAndStates(_resource: UriComponents, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise { - const resource = URI.revive(_resource); - const controller = this._workerTokenizerControllers.get(resource.toString()); - if (controller) { - // When a model detaches, it is removed synchronously from the map. - // However, the worker might still be sending tokens for that model. - controller.setTokensAndStates(versionId, tokens, lineEndStateDeltas); - } - } - - public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void { - this._reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); - } - - // #endregion -} - -export interface StateDeltas { - startLineNumber: number; - // null means the state for that line did not change - stateDeltas: (StackDiff | null)[]; } function keepAliveWhenAttached(textModel: ITextModel, factory: () => IDisposable): IDisposable { diff --git a/src/vs/workbench/services/textMate/browser/worker/textMate.worker.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts similarity index 50% rename from src/vs/workbench/services/textMate/browser/worker/textMate.worker.ts rename to src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts index 275db92323383..3974d642732a9 100644 --- a/src/vs/workbench/services/textMate/browser/worker/textMate.worker.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker.ts @@ -7,11 +7,23 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { LanguageId } from 'vs/editor/common/encodedTokenAttributes'; import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel'; import { IWorkerContext } from 'vs/editor/common/services/editorSimpleWorker'; -import type { StateDeltas, TextMateWorkerHost } from 'vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost'; import { ICreateGrammarResult, TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; import { IValidEmbeddedLanguagesMap, IValidGrammarDefinition, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; -import type { IOnigLib, IRawTheme } from 'vscode-textmate'; -import { TextMateWorkerModel } from './textMateWorkerModel'; +import type { IOnigLib, IRawTheme, StackDiff } from 'vscode-textmate'; +import { TextMateWorkerTokenizer } from './textMateWorkerTokenizer'; + +/** + * Defines the worker entry point. Must be exported and named `create`. + */ +export function create(ctx: IWorkerContext, createData: ICreateData): TextMateTokenizationWorker { + return new TextMateTokenizationWorker(ctx, createData); +} + +export interface ITextMateWorkerHost { + readFile(_resource: UriComponents): Promise; + setTokensAndStates(controllerId: number, versionId: number, tokens: Uint8Array, lineEndStateDeltas: StateDeltas[]): Promise; + reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void; +} export interface ICreateData { grammarDefinitions: IValidGrammarDefinitionDTO[]; @@ -32,16 +44,24 @@ export interface IValidGrammarDefinitionDTO { sourceExtensionId?: string; } -export class TextMateTokenizationWorker { +export interface StateDeltas { + startLineNumber: number; + // null means the state for that line did not change + stateDeltas: (StackDiff | null)[]; +} - private readonly _host: TextMateWorkerHost; - private readonly _models: { [uri: string]: TextMateWorkerModel } = Object.create(null); +export class TextMateTokenizationWorker { + private readonly _host: ITextMateWorkerHost; + private readonly _models = new Map(); private readonly _grammarCache: Promise[] = []; private readonly _grammarFactory: Promise; - constructor(ctx: IWorkerContext, private readonly createData: ICreateData) { + constructor( + ctx: IWorkerContext, + private readonly _createData: ICreateData + ) { this._host = ctx.host; - const grammarDefinitions = createData.grammarDefinitions.map((def) => { + const grammarDefinitions = _createData.grammarDefinitions.map((def) => { return { location: URI.revive(def.location), language: def.language, @@ -58,10 +78,10 @@ export class TextMateTokenizationWorker { } private async _loadTMGrammarFactory(grammarDefinitions: IValidGrammarDefinition[]): Promise { - const uri = this.createData.textmateMainUri; + const uri = this._createData.textmateMainUri; const vscodeTextmate = await import(uri); - const vscodeOniguruma = await import(this.createData.onigurumaMainUri); - const response = await fetch(this.createData.onigurumaWASMUri); + const vscodeOniguruma = await import(this._createData.onigurumaMainUri); + const response = await fetch(this._createData.onigurumaWASMUri); // Using the response directly only works if the server sets the MIME type 'application/wasm'. // Otherwise, a TypeError is thrown when using the streaming compiler. @@ -81,30 +101,48 @@ export class TextMateTokenizationWorker { }, grammarDefinitions, vscodeTextmate, onigLib); } - // #region called by renderer + // These methods are called by the renderer public acceptNewModel(data: IRawModelData): void { const uri = URI.revive(data.uri); - const key = uri.toString(); - this._models[key] = new TextMateWorkerModel(uri, data.lines, data.EOL, data.versionId, this, data.languageId, data.encodedLanguageId, data.maxTokenizationLineLength); + const that = this; + this._models.set(data.controllerId, new TextMateWorkerTokenizer(uri, data.lines, data.EOL, data.versionId, { + async getOrCreateGrammar(languageId: string, encodedLanguageId: LanguageId): Promise { + const grammarFactory = await that._grammarFactory; + if (!grammarFactory) { + return Promise.resolve(null); + } + if (!that._grammarCache[encodedLanguageId]) { + that._grammarCache[encodedLanguageId] = grammarFactory.createGrammar(languageId, encodedLanguageId); + } + return that._grammarCache[encodedLanguageId]; + }, + setTokensAndStates(versionId: number, tokens: Uint8Array, stateDeltas: StateDeltas[]): void { + that._host.setTokensAndStates(data.controllerId, versionId, tokens, stateDeltas); + }, + reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void { + that._host.reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); + }, + }, data.languageId, data.encodedLanguageId, data.maxTokenizationLineLength)); } - public acceptModelChanged(strURL: string, e: IModelChangedEvent): void { - this._models[strURL].onEvents(e); + public acceptModelChanged(controllerId: number, e: IModelChangedEvent): void { + this._models.get(controllerId)!.onEvents(e); } - public retokenize(strURL: string, startLineNumber: number, endLineNumberExclusive: number): void { - this._models[strURL].retokenize(startLineNumber, endLineNumberExclusive); + public retokenize(controllerId: number, startLineNumber: number, endLineNumberExclusive: number): void { + this._models.get(controllerId)!.retokenize(startLineNumber, endLineNumberExclusive); } - public acceptModelLanguageChanged(strURL: string, newLanguageId: string, newEncodedLanguageId: LanguageId): void { - this._models[strURL].onLanguageId(newLanguageId, newEncodedLanguageId); + public acceptModelLanguageChanged(controllerId: number, newLanguageId: string, newEncodedLanguageId: LanguageId): void { + this._models.get(controllerId)!.onLanguageId(newLanguageId, newEncodedLanguageId); } - public acceptRemovedModel(strURL: string): void { - if (this._models[strURL]) { - this._models[strURL].dispose(); - delete this._models[strURL]; + public acceptRemovedModel(controllerId: number): void { + const model = this._models.get(controllerId); + if (model) { + model.dispose(); + this._models.delete(controllerId); } } @@ -113,34 +151,9 @@ export class TextMateTokenizationWorker { grammarFactory?.setTheme(theme, colorMap); } - public acceptMaxTokenizationLineLength(strURL: string, value: number): void { - this._models[strURL].acceptMaxTokenizationLineLength(value); + public acceptMaxTokenizationLineLength(controllerId: number, value: number): void { + this._models.get(controllerId)!.acceptMaxTokenizationLineLength(value); } - - // #endregion - - // #region called by worker model - - public async getOrCreateGrammar(languageId: string, encodedLanguageId: LanguageId): Promise { - const grammarFactory = await this._grammarFactory; - if (!grammarFactory) { - return Promise.resolve(null); - } - if (!this._grammarCache[encodedLanguageId]) { - this._grammarCache[encodedLanguageId] = grammarFactory.createGrammar(languageId, encodedLanguageId); - } - return this._grammarCache[encodedLanguageId]; - } - - public setTokensAndStates(resource: URI, versionId: number, tokens: Uint8Array, stateDeltas: StateDeltas[]): void { - this._host.setTokensAndStates(resource, versionId, tokens, stateDeltas); - } - - public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void { - this._host.reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, isRandomSample); - } - - // #endregion } export interface IRawModelData { @@ -151,8 +164,5 @@ export interface IRawModelData { languageId: string; encodedLanguageId: LanguageId; maxTokenizationLineLength: number; -} - -export function create(ctx: IWorkerContext, createData: ICreateData): TextMateTokenizationWorker { - return new TextMateTokenizationWorker(ctx, createData); + controllerId: number; } diff --git a/src/vs/workbench/services/textMate/browser/worker/textMateWorkerModel.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerTokenizer.ts similarity index 64% rename from src/vs/workbench/services/textMate/browser/worker/textMateWorkerModel.ts rename to src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerTokenizer.ts index c39793848673e..2fd2452087fa1 100644 --- a/src/vs/workbench/services/textMate/browser/worker/textMateWorkerModel.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerTokenizer.ts @@ -16,25 +16,29 @@ import { ContiguousMultilineTokensBuilder } from 'vs/editor/common/tokens/contig import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { TextMateTokenizationSupport } from 'vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport'; import { TokenizationSupportWithLineLimit } from 'vs/workbench/services/textMate/browser/tokenizationSupport/tokenizationSupportWithLineLimit'; -import { StateDeltas } from 'vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost'; import type { StackDiff, StateStack, diffStateStacksRefEq } from 'vscode-textmate'; -import { TextMateTokenizationWorker } from './textMate.worker'; +import { ICreateGrammarResult } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; +import { StateDeltas } from 'vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'; -export class TextMateWorkerModel extends MirrorTextModel { - private _tokenizationStateStore: TokenizerWithStateStore | null = null; +export interface TextMateModelTokenizerHost { + getOrCreateGrammar(languageId: string, encodedLanguageId: LanguageId): Promise; + setTokensAndStates(versionId: number, tokens: Uint8Array, stateDeltas: StateDeltas[]): void; + reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, isRandomSample: boolean): void; +} + +export class TextMateWorkerTokenizer extends MirrorTextModel { + private _tokenizerWithStateStore: TokenizerWithStateStore | null = null; private _isDisposed: boolean = false; - private readonly _maxTokenizationLineLength = observableValue( - '_maxTokenizationLineLength', - -1 - ); + private readonly _maxTokenizationLineLength = observableValue('_maxTokenizationLineLength', -1); private _diffStateStacksRefEqFn?: typeof diffStateStacksRefEq; + private readonly _tokenizeDebouncer = new RunOnceScheduler(() => this._tokenize(), 10); constructor( uri: URI, lines: string[], eol: string, versionId: number, - private readonly _worker: TextMateTokenizationWorker, + private readonly _host: TextMateModelTokenizerHost, private _languageId: string, private _encodedLanguageId: LanguageId, maxTokenizationLineLength: number, @@ -55,18 +59,11 @@ export class TextMateWorkerModel extends MirrorTextModel { this._resetTokenization(); } - private readonly tokenizeDebouncer = new RunOnceScheduler( - () => this._tokenize(), - 10 - ); - override onEvents(e: IModelChangedEvent): void { super.onEvents(e); - if (this._tokenizationStateStore) { - this._tokenizationStateStore.store.acceptChanges(e.changes); - } - this.tokenizeDebouncer.schedule(); + this._tokenizerWithStateStore?.store.acceptChanges(e.changes); + this._tokenizeDebouncer.schedule(); } public acceptMaxTokenizationLineLength(maxTokenizationLineLength: number): void { @@ -74,48 +71,44 @@ export class TextMateWorkerModel extends MirrorTextModel { } public retokenize(startLineNumber: number, endLineNumberExclusive: number) { - if (this._tokenizationStateStore) { - this._tokenizationStateStore.store.invalidateEndStateRange(new LineRange(startLineNumber, endLineNumberExclusive)); - this.tokenizeDebouncer.schedule(); + if (this._tokenizerWithStateStore) { + this._tokenizerWithStateStore.store.invalidateEndStateRange(new LineRange(startLineNumber, endLineNumberExclusive)); + this._tokenizeDebouncer.schedule(); } } - private _resetTokenization(): void { - this._tokenizationStateStore = null; + private async _resetTokenization() { + this._tokenizerWithStateStore = null; const languageId = this._languageId; const encodedLanguageId = this._encodedLanguageId; - this._worker.getOrCreateGrammar(languageId, encodedLanguageId).then((r) => { - if ( - this._isDisposed || - languageId !== this._languageId || - encodedLanguageId !== this._encodedLanguageId || - !r - ) { - return; - } - if (r.grammar) { - const tokenizationSupport = new TokenizationSupportWithLineLimit( - this._encodedLanguageId, - new TextMateTokenizationSupport(r.grammar, r.initialState, false, undefined, () => false, - (timeMs, lineLength, isRandomSample) => { - this._worker.reportTokenizationTime(timeMs, languageId, r.sourceExtensionId, lineLength, isRandomSample); - }, - false - ), - this._maxTokenizationLineLength - ); - this._tokenizationStateStore = new TokenizerWithStateStore(this._lines.length, tokenizationSupport); - } else { - this._tokenizationStateStore = null; - } - this._tokenize(); - }); + const r = await this._host.getOrCreateGrammar(languageId, encodedLanguageId); + + if (this._isDisposed || languageId !== this._languageId || encodedLanguageId !== this._encodedLanguageId || !r) { + return; + } + + if (r.grammar) { + const tokenizationSupport = new TokenizationSupportWithLineLimit( + this._encodedLanguageId, + new TextMateTokenizationSupport(r.grammar, r.initialState, false, undefined, () => false, + (timeMs, lineLength, isRandomSample) => { + this._host.reportTokenizationTime(timeMs, languageId, r.sourceExtensionId, lineLength, isRandomSample); + }, + false + ), + this._maxTokenizationLineLength + ); + this._tokenizerWithStateStore = new TokenizerWithStateStore(this._lines.length, tokenizationSupport); + } else { + this._tokenizerWithStateStore = null; + } + this._tokenize(); } private async _tokenize(): Promise { - if (this._isDisposed || !this._tokenizationStateStore) { + if (this._isDisposed || !this._tokenizerWithStateStore) { return; } @@ -132,7 +125,7 @@ export class TextMateWorkerModel extends MirrorTextModel { const stateDeltaBuilder = new StateDeltaBuilder(); while (true) { - const lineNumberToTokenize = this._tokenizationStateStore.store.getFirstInvalidEndStateLineNumber(); + const lineNumberToTokenize = this._tokenizerWithStateStore.store.getFirstInvalidEndStateLineNumber(); if (lineNumberToTokenize === null || tokenizedLines > 200) { break; } @@ -140,9 +133,9 @@ export class TextMateWorkerModel extends MirrorTextModel { tokenizedLines++; const text = this._lines[lineNumberToTokenize - 1]; - const lineStartState = this._tokenizationStateStore.getStartState(lineNumberToTokenize)!; - const r = this._tokenizationStateStore.tokenizationSupport.tokenizeEncoded(text, true, lineStartState); - if (this._tokenizationStateStore.store.setEndState(lineNumberToTokenize, r.endState as StateStack)) { + const lineStartState = this._tokenizerWithStateStore.getStartState(lineNumberToTokenize)!; + const r = this._tokenizerWithStateStore.tokenizationSupport.tokenizeEncoded(text, true, lineStartState); + if (this._tokenizerWithStateStore.store.setEndState(lineNumberToTokenize, r.endState as StateStack)) { const delta = this._diffStateStacksRefEqFn(lineStartState, r.endState as StateStack); stateDeltaBuilder.setState(lineNumberToTokenize, delta); } else { @@ -164,8 +157,7 @@ export class TextMateWorkerModel extends MirrorTextModel { } const stateDeltas = stateDeltaBuilder.getStateDeltas(); - this._worker.setTokensAndStates( - this._uri, + this._host.setTokensAndStates( this._versionId, tokenBuilder.serialize(), stateDeltas diff --git a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.ts b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.ts index 4b23b5693591e..34cc1cc5eadf4 100644 --- a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.ts +++ b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeature.ts @@ -11,7 +11,7 @@ export const ITextMateTokenizationService = createDecorator; + createTokenizer(languageId: string): Promise; startDebugMode(printFn: (str: string) => void, onStop: () => void): void; } diff --git a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts index dd19178ffe930..19e7a72bdcb96 100644 --- a/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts +++ b/src/vs/workbench/services/textMate/browser/textMateTokenizationFeatureImpl.ts @@ -32,7 +32,7 @@ import { ExtensionMessageCollector, IExtensionPointUser } from 'vs/workbench/ser import { ITextMateTokenizationService } from 'vs/workbench/services/textMate/browser/textMateTokenizationFeature'; import { TextMateTokenizationSupport } from 'vs/workbench/services/textMate/browser/tokenizationSupport/textMateTokenizationSupport'; import { TokenizationSupportWithLineLimit } from 'vs/workbench/services/textMate/browser/tokenizationSupport/tokenizationSupportWithLineLimit'; -import { TextMateWorkerHost } from 'vs/workbench/services/textMate/browser/workerHost/textMateWorkerHost'; +import { ThreadedBackgroundTokenizerFactory } from 'vs/workbench/services/textMate/browser/backgroundTokenization/threadedBackgroundTokenizerFactory'; import { TMGrammarFactory, missingTMGrammarErrorMessage } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; import { ITMSyntaxExtensionPoint, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { IValidEmbeddedLanguagesMap, IValidGrammarDefinition, IValidTokenTypeMap } from 'vs/workbench/services/textMate/common/TMScopeRegistry'; @@ -55,9 +55,9 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate private readonly _tokenizersRegistrations = new DisposableStore(); private _currentTheme: IRawTheme | null = null; private _currentTokenColorMap: string[] | null = null; - private readonly _workerHost = this._instantiationService.createInstance( - TextMateWorkerHost, - (timeMs, languageId, sourceExtensionId, lineLength, isRandomSample) => this.reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, true, isRandomSample) + private readonly _threadedBackgroundTokenizerFactory = this._instantiationService.createInstance( + ThreadedBackgroundTokenizerFactory, + (timeMs, languageId, sourceExtensionId, lineLength, isRandomSample) => this._reportTokenizationTime(timeMs, languageId, sourceExtensionId, lineLength, true, isRandomSample) ); constructor( @@ -77,7 +77,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate this._styleElement = dom.createStyleSheet(); this._styleElement.className = 'vscode-tokens-styles'; - grammarsExtPoint.setHandler((extensions) => this.handleGrammarsExtPoint(extensions)); + grammarsExtPoint.setHandler((extensions) => this._handleGrammarsExtPoint(extensions)); this._updateTheme(this._themeService.getColorTheme(), true); this._register(this._themeService.onDidColorThemeChange(() => { @@ -89,7 +89,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate }); } - private handleGrammarsExtPoint(extensions: readonly IExtensionPointUser[]): void { + private _handleGrammarsExtPoint(extensions: readonly IExtensionPointUser[]): void { this._grammarDefinitions = null; if (this._grammarFactory) { this._grammarFactory.dispose(); @@ -101,26 +101,26 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate for (const extension of extensions) { const grammars = extension.value; for (const grammar of grammars) { - const def = this.createValidGrammarDefinition(extension, grammar); - if (def) { - this._grammarDefinitions.push(def); - if (def.language) { - const lazyTokenizationSupport = new LazyTokenizationSupport(() => this.createTokenizationSupport(def.language!)); + const validatedGrammar = this._validateGrammarDefinition(extension, grammar); + if (validatedGrammar) { + this._grammarDefinitions.push(validatedGrammar); + if (validatedGrammar.language) { + const lazyTokenizationSupport = new LazyTokenizationSupport(() => this._createTokenizationSupport(validatedGrammar.language!)); this._tokenizersRegistrations.add(lazyTokenizationSupport); - this._tokenizersRegistrations.add(TokenizationRegistry.registerFactory(def.language, lazyTokenizationSupport)); + this._tokenizersRegistrations.add(TokenizationRegistry.registerFactory(validatedGrammar.language, lazyTokenizationSupport)); } } } } - this._workerHost.setGrammarDefinitions(this._grammarDefinitions); + this._threadedBackgroundTokenizerFactory.setGrammarDefinitions(this._grammarDefinitions); for (const createdMode of this._createdModes) { TokenizationRegistry.getOrCreate(createdMode); } } - private createValidGrammarDefinition(extension: IExtensionPointUser, grammar: ITMSyntaxExtensionPoint): IValidGrammarDefinition | null { + private _validateGrammarDefinition(extension: IExtensionPointUser, grammar: ITMSyntaxExtensionPoint): IValidGrammarDefinition | null { if (!validateGrammarExtensionPoint(extension.description.extensionLocation, grammar, extension.collector, this._languageService)) { return null; } @@ -162,10 +162,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate } } - let validLanguageId: string | null = null; - if (grammar.language && this._languageService.isRegisteredLanguageId(grammar.language)) { - validLanguageId = grammar.language; - } + const validLanguageId = grammar.language && this._languageService.isRegisteredLanguageId(grammar.language) ? grammar.language : null; function asStringArray(array: unknown, defaultValue: string[]): string[] { if (!Array.isArray(array)) { @@ -261,7 +258,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate return this._grammarFactory; } - private async createTokenizationSupport(languageId: string): Promise { + private async _createTokenizationSupport(languageId: string): Promise { if (!this._languageService.isRegisteredLanguageId(languageId)) { return null; } @@ -289,10 +286,10 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate r.grammar, r.initialState, r.containsEmbeddedLanguages, - (textModel, tokenStore) => this._workerHost.createBackgroundTokenizer(textModel, tokenStore, maxTokenizationLineLength), + (textModel, tokenStore) => this._threadedBackgroundTokenizerFactory.createBackgroundTokenizer(textModel, tokenStore, maxTokenizationLineLength), () => this._configurationService.getValue('editor.experimental.asyncTokenizationVerification'), (timeMs, lineLength, isRandomSample) => { - this.reportTokenizationTime(timeMs, languageId, r.sourceExtensionId, lineLength, false, isRandomSample); + this._reportTokenizationTime(timeMs, languageId, r.sourceExtensionId, lineLength, false, isRandomSample); }, true, ); @@ -329,11 +326,11 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate TokenizationRegistry.setColorMap(colorMap); if (this._currentTheme && this._currentTokenColorMap) { - this._workerHost.acceptTheme(this._currentTheme, this._currentTokenColorMap); + this._threadedBackgroundTokenizerFactory.acceptTheme(this._currentTheme, this._currentTokenColorMap); } } - public async createGrammar(languageId: string): Promise { + public async createTokenizer(languageId: string): Promise { if (!this._languageService.isRegisteredLanguageId(languageId)) { return null; } @@ -378,7 +375,7 @@ export class TextMateTokenizationFeature extends Disposable implements ITextMate } } - public reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, fromWorker: boolean, isRandomSample: boolean): void { + private _reportTokenizationTime(timeMs: number, languageId: string, sourceExtensionId: string | undefined, lineLength: number, fromWorker: boolean, isRandomSample: boolean): void { // 50 events per hour (one event has a low probability) if (TextMateTokenizationFeature.reportTokenizationTimeCounter > 50) { // Don't flood telemetry with too many events From 13e76637ba39d5bab6a50e9118934365ff3e5b28 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Thu, 20 Jul 2023 14:15:02 -0700 Subject: [PATCH 098/216] fix #188417 --- .../contrib/chat/browser/chatAccessibilityService.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index 87d9abc836c5a..84cadbfa36423 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -19,6 +19,7 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi private _responsePendingAudioCue: IDisposable | undefined; private _hasReceivedRequest: boolean = false; private _runOnceScheduler: RunOnceScheduler; + private _lastResponse: string | undefined; constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService) { super(); @@ -37,6 +38,10 @@ export class ChatAccessibilityService extends Disposable implements IChatAccessi const isPanelChat = typeof response !== 'string'; this._responsePendingAudioCue?.dispose(); this._runOnceScheduler?.cancel(); + if (this._lastResponse === response?.toString()) { + return; + } + this._lastResponse = response?.toString(); this._audioCueService.playAudioCue(AudioCue.chatResponseReceived, true); this._hasReceivedRequest = false; if (!response) { From b45ea5f5ffe5d7d3469cad1e177667b8f5ebb292 Mon Sep 17 00:00:00 2001 From: aamunger Date: Thu, 20 Jul 2023 14:17:37 -0700 Subject: [PATCH 099/216] bump version on metadata change --- .../notebook/browser/contrib/cellCommands/cellCommands.ts | 1 + .../notebook/common/model/notebookCellOutputTextModel.ts | 3 +++ src/vs/workbench/contrib/notebook/common/notebookCommon.ts | 1 + 3 files changed, 5 insertions(+) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts index 41d051edbb838..ed90ad3289482 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands.ts @@ -617,6 +617,7 @@ registerAction2(class ToggleCellOutputScrolling extends NotebookMultiCellAction const currentlyEnabled = cellMetadata['scrollable'] !== undefined ? cellMetadata['scrollable'] : globalScrollSetting; const shouldEnableScrolling = collapsed || !currentlyEnabled; cellMetadata['scrollable'] = shouldEnableScrolling; + viewModel.model.bumpVersion(); viewModel.resetRenderer(); } } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts index 74ed0aaf1583a..82d4fcd2a116b 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts @@ -118,5 +118,8 @@ export class NotebookCellOutputTextModel extends Disposable implements ICellOutp }; } + bumpVersion() { + this._versionId = this._versionId + 1; + } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d031650968a14..10e139ae0ebdd 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -218,6 +218,7 @@ export interface ICellOutput { replaceData(items: IOutputDto): void; appendData(items: IOutputItemDto[]): void; appendedSinceVersion(versionId: number, mime: string): VSBuffer | undefined; + bumpVersion(): void; } export interface CellInternalMetadataChangedEvent { From 17973aa241af8e1f77f76a27bc8e3532c175467a Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 20 Jul 2023 23:36:55 +0200 Subject: [PATCH 100/216] Fixes CI --- src/buildfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buildfile.js b/src/buildfile.js index 8917223094ad6..b91d19d597b0d 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -53,7 +53,7 @@ exports.workerProfileAnalysis = [createEditorWorkerModuleDescription('vs/platfor exports.workbenchDesktop = [ createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), - createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/worker/textMate.worker'), + createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/worker/textMateTokenizationWorker.worker'), createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), createModuleDescription('vs/platform/files/node/watcher/watcherMain'), createModuleDescription('vs/platform/terminal/node/ptyHostMain'), @@ -62,7 +62,7 @@ exports.workbenchDesktop = [ exports.workbenchWeb = [ createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), - createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/worker/textMate.worker'), + createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/worker/textMateTokenizationWorker.worker'), createModuleDescription('vs/code/browser/workbench/workbench', ['vs/workbench/workbench.web.main']) ]; From aa7bc8579ac2e20891173e54a25bfb634917f58e Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 20 Jul 2023 14:44:08 -0700 Subject: [PATCH 101/216] Support showing progress on InputBoxes (#188424) Fixes https://github.com/microsoft/vscode/issues/188080 --- src/vs/platform/quickinput/browser/quickInput.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/quickinput/browser/quickInput.ts b/src/vs/platform/quickinput/browser/quickInput.ts index c8158ad9b50d7..425a189da6c4b 100644 --- a/src/vs/platform/quickinput/browser/quickInput.ts +++ b/src/vs/platform/quickinput/browser/quickInput.ts @@ -1239,7 +1239,9 @@ class InputBox extends QuickInput implements IInputBox { const visibilities: Visibilities = { title: !!this.title || !!this.step || !!this.buttons.length, description: !!this.description || !!this.step, - inputBox: true, message: true + inputBox: true, + message: true, + progressBar: true }; this.ui.setVisibilities(visibilities); From 900874fdf2bd8147e6fe8648a535c05aaa2b53c8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 20 Jul 2023 23:57:38 +0200 Subject: [PATCH 102/216] Fixes CI --- src/buildfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/buildfile.js b/src/buildfile.js index b91d19d597b0d..f03de33fb3db5 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -53,7 +53,7 @@ exports.workerProfileAnalysis = [createEditorWorkerModuleDescription('vs/platfor exports.workbenchDesktop = [ createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), - createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/worker/textMateTokenizationWorker.worker'), + createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'), createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp'), createModuleDescription('vs/platform/files/node/watcher/watcherMain'), createModuleDescription('vs/platform/terminal/node/ptyHostMain'), @@ -62,7 +62,7 @@ exports.workbenchDesktop = [ exports.workbenchWeb = [ createEditorWorkerModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer'), - createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/worker/textMateTokenizationWorker.worker'), + createEditorWorkerModuleDescription('vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateTokenizationWorker.worker'), createModuleDescription('vs/code/browser/workbench/workbench', ['vs/workbench/workbench.web.main']) ]; From f5adc42f8a8e5a1985db40db374c304c0a054061 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 20 Jul 2023 23:58:09 +0200 Subject: [PATCH 103/216] Fixes #188350 --- src/vs/editor/common/model/textModelTokens.ts | 54 +++++++++++++------ .../common/model/tokenizationTextModelPart.ts | 10 ++-- .../worker/textMateWorkerTokenizer.ts | 19 ++++--- 3 files changed, 51 insertions(+), 32 deletions(-) diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index deb5fbf628f37..e308aa9f78ad8 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IdleDeadline, runWhenIdle } from 'vs/base/common/async'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { setTimeout0 } from 'vs/base/common/platform'; import { StopWatch } from 'vs/base/common/stopwatch'; import { countEOL } from 'vs/editor/common/core/eolCounter'; @@ -25,7 +25,7 @@ const enum Constants { } export class TokenizerWithStateStore { - private readonly initialState = this.tokenizationSupport.getInitialState(); + private readonly initialState = this.tokenizationSupport.getInitialState() as TState; public readonly store: TrackingTokenizationStateStore; @@ -37,10 +37,11 @@ export class TokenizerWithStateStore { } public getStartState(lineNumber: number): TState | null { - if (lineNumber === 1) { - return this.initialState as TState; - } - return this.store.getEndState(lineNumber - 1); + return this.store.getStartState(lineNumber, this.initialState); + } + + public getFirstInvalidLine(): { lineNumber: number; startState: TState } | null { + return this.store.getFirstInvalidLine(this.initialState); } } @@ -58,17 +59,16 @@ export class TokenizerWithStateStoreAndTextModel const languageId = this._textModel.getLanguageId(); while (true) { - const nextLineNumber = this.store.getFirstInvalidEndStateLineNumber(); - if (!nextLineNumber || nextLineNumber > lineNumber) { + const lineToTokenize = this.getFirstInvalidLine(); + if (!lineToTokenize || lineToTokenize.lineNumber > lineNumber) { break; } - const text = this._textModel.getLineContent(nextLineNumber); - const lineStartState = this.getStartState(nextLineNumber); + const text = this._textModel.getLineContent(lineToTokenize.lineNumber); - const r = safeTokenize(this._languageIdCodec, languageId, this.tokenizationSupport, text, true, lineStartState!); - builder.add(nextLineNumber, r.tokens); - this!.store.setEndState(nextLineNumber, r.endState as TState); + const r = safeTokenize(this._languageIdCodec, languageId, this.tokenizationSupport, text, true, lineToTokenize.startState); + builder.add(lineToTokenize.lineNumber, r.tokens); + this!.store.setEndState(lineToTokenize.lineNumber, r.endState as TState); } } @@ -217,12 +217,19 @@ export class TrackingTokenizationStateStore { } public setEndState(lineNumber: number, state: TState): boolean { + if (!state) { + throw new BugIndicatingError('Cannot set null/undefined state'); + } + if (lineNumber > 1 && !this.tokenizationStateStore.getEndState(lineNumber - 1)) { + throw new BugIndicatingError('Cannot set state before setting previous state'); + } + while (true) { const min = this._invalidEndStatesLineNumbers.min; - if (min !== null && min <= lineNumber) { - this._invalidEndStatesLineNumbers.removeMin(); - } else { + if (min === null || min > lineNumber) { break; + } else { + this._invalidEndStatesLineNumbers.removeMin(); } } @@ -263,6 +270,21 @@ export class TrackingTokenizationStateStore { public isTokenizationComplete(): boolean { return this._invalidEndStatesLineNumbers.min === null; } + + public getStartState(lineNumber: number, initialState: TState): TState | null { + if (lineNumber === 1) { + return initialState; + } + return this.getEndState(lineNumber - 1); + } + + public getFirstInvalidLine(initialState: TState): { lineNumber: number; startState: TState } | null { + const lineNumber = this.getFirstInvalidEndStateLineNumber(); + if (lineNumber === null) { + return null; + } + return { lineNumber, startState: this.getStartState(lineNumber, initialState)! }; + } } export class TokenizationStateStore { diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index c203f53ad7647..54476c26f2017 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -449,12 +449,10 @@ class GrammarTokens extends Disposable { this._onDidChangeBackgroundTokenizationState.fire(); }, setEndState: (lineNumber, state) => { - if (!state) { - throw new BugIndicatingError(); - } - const firstInvalidEndStateLineNumber = this._tokenizer?.store.getFirstInvalidEndStateLineNumber() ?? undefined; - if (firstInvalidEndStateLineNumber !== undefined && lineNumber >= firstInvalidEndStateLineNumber) { - // Don't accept states for definitely valid states + if (!this._tokenizer) { return; } + const firstInvalidEndStateLineNumber = this._tokenizer.store.getFirstInvalidEndStateLineNumber(); + // Don't accept states for definitely valid states, the renderer is ahead of the worker! + if (firstInvalidEndStateLineNumber !== null && lineNumber >= firstInvalidEndStateLineNumber) { this._tokenizer?.store.setEndState(lineNumber, state); } }, diff --git a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerTokenizer.ts b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerTokenizer.ts index 2fd2452087fa1..a7c586b2442fe 100644 --- a/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerTokenizer.ts +++ b/src/vs/workbench/services/textMate/browser/backgroundTokenization/worker/textMateWorkerTokenizer.ts @@ -125,25 +125,24 @@ export class TextMateWorkerTokenizer extends MirrorTextModel { const stateDeltaBuilder = new StateDeltaBuilder(); while (true) { - const lineNumberToTokenize = this._tokenizerWithStateStore.store.getFirstInvalidEndStateLineNumber(); - if (lineNumberToTokenize === null || tokenizedLines > 200) { + const lineToTokenize = this._tokenizerWithStateStore.getFirstInvalidLine(); + if (lineToTokenize === null || tokenizedLines > 200) { break; } tokenizedLines++; - const text = this._lines[lineNumberToTokenize - 1]; - const lineStartState = this._tokenizerWithStateStore.getStartState(lineNumberToTokenize)!; - const r = this._tokenizerWithStateStore.tokenizationSupport.tokenizeEncoded(text, true, lineStartState); - if (this._tokenizerWithStateStore.store.setEndState(lineNumberToTokenize, r.endState as StateStack)) { - const delta = this._diffStateStacksRefEqFn(lineStartState, r.endState as StateStack); - stateDeltaBuilder.setState(lineNumberToTokenize, delta); + const text = this._lines[lineToTokenize.lineNumber - 1]; + const r = this._tokenizerWithStateStore.tokenizationSupport.tokenizeEncoded(text, true, lineToTokenize.startState); + if (this._tokenizerWithStateStore.store.setEndState(lineToTokenize.lineNumber, r.endState as StateStack)) { + const delta = this._diffStateStacksRefEqFn(lineToTokenize.startState, r.endState as StateStack); + stateDeltaBuilder.setState(lineToTokenize.lineNumber, delta); } else { - stateDeltaBuilder.setState(lineNumberToTokenize, null); + stateDeltaBuilder.setState(lineToTokenize.lineNumber, null); } LineTokens.convertToEndOffset(r.tokens, text.length); - tokenBuilder.add(lineNumberToTokenize, r.tokens); + tokenBuilder.add(lineToTokenize.lineNumber, r.tokens); const deltaMs = new Date().getTime() - startTime; if (deltaMs > 20) { From 2ba9f17b3f4c9e30001431c57325f07dca0d4325 Mon Sep 17 00:00:00 2001 From: Meghan Kulkarni Date: Thu, 20 Jul 2023 15:16:40 -0700 Subject: [PATCH 104/216] making the markdown link paste feature smart (#188119) * making markdown link pasting feature smarter * Update settings description Co-authored-by: Joyce Er * made checkPaste more concise * won't paste md link in fenced code or math * updated the smart md link pasting * link validation and naming changes * resolving comments and tests * resolving comments & writing tests --------- Co-authored-by: Joyce Er --- .../package.nls.json | 6 +- .../src/commands/insertResource.ts | 4 +- .../languageFeatures/copyFiles/copyPaste.ts | 4 +- .../copyFiles/copyPasteLinks.ts | 24 +++- .../src/languageFeatures/copyFiles/shared.ts | 135 +++++++++++------- .../src/test/markdownLink.test.ts | 133 +++++++++++++++++ 6 files changed, 244 insertions(+), 62 deletions(-) create mode 100644 extensions/markdown-language-features/src/test/markdownLink.test.ts diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index 0468fbf1d7946..7336657621ee8 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -44,9 +44,9 @@ "configuration.copyIntoWorkspace.mediaFiles": "Try to copy external image and video files into the workspace.", "configuration.copyIntoWorkspace.never": "Do not copy external files into the workspace.", "configuration.markdown.editor.pasteUrlAsFormattedLink.enabled": "Controls how a Markdown link is created when a URL is pasted into the Markdown editor. Requires enabling `#editor.pasteAs.enabled#`.", - "configuration.pasteUrlAsFormattedLink.always": "Always create a Markdown link when a URL is pasted into the Markdown editor.", - "configuration.pasteUrlAsFormattedLink.smart": "Does not create a Markdown link within a link snippet or code bracket.", - "configuration.pasteUrlAsFormattedLink.never": "Never create a Markdown link when a URL is pasted into the Markdown editor.", + "configuration.pasteUrlAsFormattedLink.always": "Always creates a Markdown link when a URL is pasted into the Markdown editor.", + "configuration.pasteUrlAsFormattedLink.smart": "Smartly avoids creating a Markdown link in specific cases, such as within code brackets or inside an existing Markdown link.", + "configuration.pasteUrlAsFormattedLink.never": "Never creates a Markdown link when a URL is pasted into the Markdown editor.", "configuration.markdown.validate.enabled.description": "Enable all error reporting in Markdown files.", "configuration.markdown.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, for example: `[link][ref]`. Requires enabling `#markdown.validate.enabled#`.", "configuration.markdown.validate.fragmentLinks.enabled.description": "Validate fragment links to headers in the current Markdown file, for example: `[link](#header)`. Requires enabling `#markdown.validate.enabled#`.", diff --git a/extensions/markdown-language-features/src/commands/insertResource.ts b/extensions/markdown-language-features/src/commands/insertResource.ts index da50c83c991c5..8977d6ad39ed8 100644 --- a/extensions/markdown-language-features/src/commands/insertResource.ts +++ b/extensions/markdown-language-features/src/commands/insertResource.ts @@ -76,10 +76,10 @@ async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode await vscode.workspace.applyEdit(edit); } -function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0, smartPaste = false) { +function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0, smartPaste = false, isExternalLink = false) { const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => { const selectionText = activeEditor.document.getText(selection); - const snippet = createUriListSnippet(activeEditor.document, selectedFiles, title, placeholderValue, smartPaste, { + const snippet = createUriListSnippet(activeEditor.document, selectedFiles, title, placeholderValue, smartPaste, isExternalLink, { insertAsMedia, placeholderText: selectionText, placeholderStartIndex: (i + 1) * selectedFiles.length, diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts index 24e23758d2576..73839caeba261 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { Schemes } from '../../util/schemes'; -import { createEditForMediaFiles, getMarkdownLink, mediaMimes } from './shared'; +import { createEditForMediaFiles, createEditAddingLinksForUriList, mediaMimes } from './shared'; class PasteEditProvider implements vscode.DocumentPasteEditProvider { @@ -32,7 +32,7 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider { if (!urlList) { return; } - const pasteEdit = await getMarkdownLink(document, ranges, urlList, token); + const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, token, false); if (!pasteEdit) { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts index 90755d990fcac..61aa3a12067a6 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { getMarkdownLink } from './shared'; +import { externalUriSchemes, createEditAddingLinksForUriList } from './shared'; class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { readonly id = 'insertMarkdownLink'; @@ -19,20 +19,22 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { return; } - // Check if dataTransfer contains a URL const item = dataTransfer.get('text/plain'); - try { - new URL(await item?.value); - } catch (error) { + const urlList = await item?.asString(); + + if (urlList === undefined) { + return; + } + + if (!validateLink(urlList)) { return; } const uriEdit = new vscode.DocumentPasteEdit('', this.id, ''); - const urlList = await item?.asString(); if (!urlList) { return undefined; } - const pasteEdit = await getMarkdownLink(document, ranges, urlList, token); + const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, token, true); if (!pasteEdit) { return; } @@ -43,6 +45,14 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { } } +export function validateLink(urlList: string): boolean { + const url = urlList?.split(/\s+/); + if (url.length > 1 || !externalUriSchemes.includes(vscode.Uri.parse(url[0]).scheme)) { + return false; + } + return true; +} + export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) { return vscode.languages.registerDocumentPasteEditProvider(selector, new PasteLinkEditProvider(), { pasteMimeTypes: [ diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index f40fea3556fea..a6072fa5937d1 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -17,9 +17,11 @@ enum MediaKind { Audio, } -const externalUriSchemes = [ +export const externalUriSchemes = [ 'http', 'https', + 'mailto', + 'ftp', ]; export const mediaFileExtensions = new Map([ @@ -61,30 +63,53 @@ export const mediaMimes = new Set([ 'audio/x-wav', ]); -export async function getMarkdownLink(document: vscode.TextDocument, ranges: readonly vscode.Range[], urlList: string, token: vscode.CancellationToken): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string } | undefined> { +const smartPasteRegexes = [ + { regex: /\[.*\]\(.*\)/g, is_markdown_link: true }, // Is a Markdown Link + { regex: /!\[.*\]\(.*\)/g, is_markdown_link: true }, // Is a Markdown Image Link + { regex: /\[([^\]]*)\]\(([^)]*)\)/g, is_markdown_link: false }, // In a Markdown link + { regex: /^```[\s\S]*?```$/gm, is_markdown_link: false }, // In a fenced code block + { regex: /^\$\$[\s\S]*?\$\$$/gm, is_markdown_link: false }, // In a fenced math block + { regex: /`[^`]*`/g, is_markdown_link: false }, // In inline code + { regex: /\$[^$]*\$/g, is_markdown_link: false }, // In inline math +]; +export interface SmartPaste { + + /** + * `true` if the link is not being pasted within a markdown link, code, or math. + */ + pasteAsMarkdownLink: boolean; + + /** + * `true` if the link is being pasted over a markdown link. + */ + updateTitle: boolean; + +} + +export async function createEditAddingLinksForUriList(document: vscode.TextDocument, ranges: readonly vscode.Range[], urlList: string, token: vscode.CancellationToken, isExternalLink: boolean): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string } | undefined> { if (ranges.length === 0) { return; } const enabled = vscode.workspace.getConfiguration('markdown', document).get<'always' | 'smart' | 'never'>('editor.pasteUrlAsFormattedLink.enabled', 'always'); - const edits: vscode.SnippetTextEdit[] = []; let placeHolderValue: number = ranges.length; let label: string = ''; - let smartPaste: boolean = false; + let smartPaste = { pasteAsMarkdownLink: true, updateTitle: false }; + for (let i = 0; i < ranges.length; i++) { + + let title = document.getText(ranges[i]); if (enabled === 'smart') { - const inMarkdownLink = checkPaste(document, ranges, /\[([^\]]*)\]\(([^)]*)\)/g, i); - const inFencedCode = checkPaste(document, ranges, /^```[\s\S]*?```$/gm, i); - const inFencedMath = checkPaste(document, ranges, /^\$\$[\s\S]*?\$\$$/gm, i); - smartPaste = (inMarkdownLink || inFencedCode || inFencedMath); + smartPaste = checkSmartPaste(document.getText(), document.offsetAt(ranges[i].start), document.offsetAt(ranges[i].end)); + title = smartPaste.updateTitle ? '' : document.getText(ranges[i]); } - const snippet = await tryGetUriListSnippet(document, urlList, token, document.getText(ranges[i]), placeHolderValue, smartPaste); + const snippet = await tryGetUriListSnippet(document, urlList, token, title, placeHolderValue, smartPaste.pasteAsMarkdownLink, isExternalLink); if (!snippet) { return; } - smartPaste = false; + smartPaste.pasteAsMarkdownLink = true; placeHolderValue--; edits.push(new vscode.SnippetTextEdit(ranges[i], snippet.snippet)); label = snippet.label; @@ -96,20 +121,25 @@ export async function getMarkdownLink(document: vscode.TextDocument, ranges: rea return { additionalEdits, label }; } -function checkPaste(document: vscode.TextDocument, ranges: readonly vscode.Range[], regex: RegExp, index: number): boolean { - const rangeStartOffset = document.offsetAt(ranges[index].start); - const rangeEndOffset = document.offsetAt(ranges[index].end); - const matches = [...document.getText().matchAll(regex)]; - for (const match of matches) { - if (match.index !== undefined && rangeStartOffset > match.index && rangeEndOffset < match.index + match[0].length) { - return true; +export function checkSmartPaste(documentText: string, start: number, end: number): SmartPaste { + const SmartPaste: SmartPaste = { pasteAsMarkdownLink: true, updateTitle: false }; + for (const regex of smartPasteRegexes) { + const matches = [...documentText.matchAll(regex.regex)]; + for (const match of matches) { + if (match.index !== undefined) { + const useDefaultPaste = start > match.index && end < match.index + match[0].length; + SmartPaste.pasteAsMarkdownLink = !useDefaultPaste; + SmartPaste.updateTitle = regex.is_markdown_link && start === match.index && end === match.index + match[0].length; + if (!SmartPaste.pasteAsMarkdownLink || SmartPaste.updateTitle) { + return SmartPaste; + } + } } } - - return false; + return SmartPaste; } -export async function tryGetUriListSnippet(document: vscode.TextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0, smartPaste = false): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> { +export async function tryGetUriListSnippet(document: vscode.TextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> { if (token.isCancellationRequested) { return undefined; } @@ -123,7 +153,7 @@ export async function tryGetUriListSnippet(document: vscode.TextDocument, urlLis } } - return createUriListSnippet(document, uris, title, placeHolderValue, smartPaste); + return createUriListSnippet(document, uris, title, placeHolderValue, pasteAsMarkdownLink, isExternalLink); } interface UriListSnippetOptions { @@ -141,28 +171,48 @@ interface UriListSnippetOptions { readonly separator?: string; } +export function createLinkSnippet( + pasteAsMarkdownLink: boolean, + mdPath: string, + title: string, + uri: vscode.Uri, + placeholderValue: number, + isExternalLink: boolean, +): vscode.SnippetString { + const uriString = uri.toString(true); + const snippet = new vscode.SnippetString(); + if (pasteAsMarkdownLink) { + snippet.appendText('['); + snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue); + snippet.appendText(isExternalLink ? `](${uriString})` : `](${escapeMarkdownLinkPath(mdPath)})`); + } else { + snippet.appendText(isExternalLink ? uriString : escapeMarkdownLinkPath(mdPath)); + } + return snippet; +} + export function createUriListSnippet( document: vscode.TextDocument, uris: readonly vscode.Uri[], title = '', placeholderValue = 0, - smartPaste = false, + pasteAsMarkdownLink = true, + isExternalLink = false, options?: UriListSnippetOptions, ): { snippet: vscode.SnippetString; label: string } | undefined { if (!uris.length) { return; } - const dir = getDocumentDir(document); - - const snippet = new vscode.SnippetString(); + const documentDir = getDocumentDir(document); + let snippet = new vscode.SnippetString(); let insertedLinkCount = 0; let insertedImageCount = 0; let insertedAudioVideoCount = 0; uris.forEach((uri, i) => { - const mdPath = getMdPath(dir, uri); + const mdPath = getMdPath(documentDir, uri); const ext = URI.Utils.extname(uri).toLowerCase().replace('.', ''); const insertAsMedia = typeof options?.insertAsMedia === 'undefined' ? mediaFileExtensions.has(ext) : !!options.insertAsMedia; @@ -179,33 +229,22 @@ export function createUriListSnippet( snippet.appendText(`'); - } else { + } else if (insertAsMedia) { if (insertAsMedia) { insertedImageCount++; - snippet.appendText('!['); - const placeholderText = escapeBrackets(title) || options?.placeholderText || 'Alt text'; - const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : (placeholderValue === 0 ? undefined : placeholderValue); - snippet.appendPlaceholder(placeholderText, placeholderIndex); - snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); - } else { - insertedLinkCount++; - if (smartPaste) { - if (externalUriSchemes.includes(uri.scheme)) { - snippet.appendText(uri.toString(true)); - } else { - snippet.appendText(escapeMarkdownLinkPath(mdPath)); - } + if (pasteAsMarkdownLink) { + snippet.appendText('!['); + const placeholderText = escapeBrackets(title) || options?.placeholderText || 'Alt text'; + const placeholderIndex = typeof options?.placeholderStartIndex !== 'undefined' ? options?.placeholderStartIndex + i : (placeholderValue === 0 ? undefined : placeholderValue); + snippet.appendPlaceholder(placeholderText, placeholderIndex); + snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); } else { - snippet.appendText('['); - snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue); - if (externalUriSchemes.includes(uri.scheme)) { - const uriString = uri.toString(true); - snippet.appendText(`](${uriString})`); - } else { - snippet.appendText(`](${escapeMarkdownLinkPath(mdPath)})`); - } + snippet.appendText(escapeMarkdownLinkPath(mdPath)); } } + } else { + insertedLinkCount++; + snippet = createLinkSnippet(pasteAsMarkdownLink, mdPath, title, uri, placeholderValue, isExternalLink); } if (i < uris.length - 1 && uris.length > 1) { diff --git a/extensions/markdown-language-features/src/test/markdownLink.test.ts b/extensions/markdown-language-features/src/test/markdownLink.test.ts new file mode 100644 index 0000000000000..9999a61108fb6 --- /dev/null +++ b/extensions/markdown-language-features/src/test/markdownLink.test.ts @@ -0,0 +1,133 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; +import * as assert from 'assert'; +import 'mocha'; +import { checkSmartPaste, createLinkSnippet } from '../languageFeatures/copyFiles/shared'; +import { validateLink } from '../languageFeatures/copyFiles/copyPasteLinks'; + +suite('createEditAddingLinksForUriList', () => { + + // end to end test of checkSmartPaste & createLinkSnippet + // check multicursor (end to end) + + suite('validateLink', () => { + + test('Markdown pasting should occur for a valid link.', () => { + const isLink = validateLink('https://www.microsoft.com'); + assert.strictEqual(isLink, true); + }); + + test('Markdown pasting should not occur for a valid hostname and invalid protool.', () => { + const isLink = validateLink('invalid://www.microsoft.com'); + assert.strictEqual(isLink, false); + }); + + test('Markdown pasting should not occur for plain text.', () => { + const isLink = validateLink('hello world!'); + assert.strictEqual(isLink, false); + }); + + test('Markdown pasting should not occur for plain text including a colon.', () => { + const isLink = validateLink('hello: world!'); + assert.strictEqual(isLink, false); + }); + + test('Markdown pasting should not occur for plain text including a slashes.', () => { + const isLink = validateLink('hello//world!'); + assert.strictEqual(isLink, false); + }); + + test('Markdown pasting should not occur for a link followed by text.', () => { + const isLink = validateLink('https://www.microsoft.com hello world!'); + assert.strictEqual(isLink, false); + }); + + test('Markdown pasting should not occur for a link preceded or followed by spaces.', () => { + const isLink = validateLink(' https://www.microsoft.com '); + assert.strictEqual(isLink, false); + }); + + test('Markdown pasting should not occur for a link with an invalid scheme.', () => { + const isLink = validateLink('hello://www.microsoft.com'); + assert.strictEqual(isLink, false); + }); + + test('Markdown pasting should not occur for multiple links being pasted.', () => { + const isLink = validateLink('https://www.microsoft.com\r\nhttps://www.microsoft.com\r\nhttps://www.microsoft.com\r\nhttps://www.microsoft.com'); + assert.strictEqual(isLink, false); + }); + + }); + + suite('createLinkSnippet', () => { + test('Should not create Markdown link snippet when pasteAsMarkdownLink is false', () => { + const uri = vscode.Uri.parse('https://www.microsoft.com'); + const snippet = createLinkSnippet(false, 'https://www.microsoft.com', '', uri, 0, true); + assert.strictEqual(snippet?.value, 'https://www.microsoft.com/'); + }); + + test('Should create Markdown link snippet when pasteAsMarkdownLink is true', () => { + const uri = vscode.Uri.parse('https://www.microsoft.com'); + const snippet = createLinkSnippet(true, 'https://www.microsoft.com', '', uri, 0, true); + assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com/)'); + }); + + test('Should use an unencoded URI string in Markdown link when passing in an external browser link', () => { + const uri = vscode.Uri.parse('https://www.microsoft.com'); + const snippet = createLinkSnippet(true, 'https://www.microsoft.com', '', uri, 0, true); + assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com/)'); + }); + }); + + suite('pasteAsMarkdownLink', () => { + + test('Should evaluate pasteAsMarkdownLink as true for selected plain text', () => { + const smartPaste = checkSmartPaste("hello! world", 0, 5); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, true); + }); + + + test('Should evaluate updateTitle as true for pasting over a Markdown link', () => { + const smartPaste = checkSmartPaste("[a](bc)", 0, 7); + assert.strictEqual(smartPaste.updateTitle, true); + }); + + test('Should evaluate updateTitle as true for pasting over a Markdown image link', () => { + const smartPaste = checkSmartPaste("![a](bc)", 0, 8); + assert.strictEqual(smartPaste.updateTitle, true); + }); + + test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown link', () => { + const smartPaste = checkSmartPaste("[a](bcdef)", 4, 6); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); + }); + + test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown image link', () => { + const smartPaste = checkSmartPaste('![alt](https://)', 7, 15); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); + }); + + test('Should evaluate pasteAsMarkdownLink as false for pasting within a code block', () => { + const smartPaste = checkSmartPaste('```\r\n\r\n```', 5, 5); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); + }); + + test('Should evaluate pasteAsMarkdownLink as false for pasting within inline code', () => { + const smartPaste = checkSmartPaste('``', 1, 1); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); + }); + + test('Should evaluate pasteAsMarkdownLink as false for pasting within a math block', () => { + const smartPaste = checkSmartPaste('$$$\r\n\r\n$$$', 5, 5); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); + }); + + test('Should evaluate pasteAsMarkdownLink as false for pasting within inline math', () => { + const smartPaste = checkSmartPaste('$$', 1, 1); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); + }); + }); +}); From 28e972e496013881bf91702848d65c65687bd79d Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Thu, 20 Jul 2023 15:50:39 -0700 Subject: [PATCH 105/216] initial stickyScroll support for notebooks (#188323) * stickyscrool wip. dom node + filler static content * headers rendering. todo: too early, multiple headers * css file movement + outline rework * kernel css move * pop-in fixed // css cleaned // setting added * fix override * low hanging fixes * refactor to remove unnecessary layer * section transition logic cleanup + other misc fixes * whoops changed a little too much. todo: linesToRender thinking * finalized logic * nits * def false * Update zindex * Fix font size --------- Co-authored-by: Peng Lyu --- .../lib/stylelint/vscode-known-variables.json | 1 + .../interactive/browser/interactiveEditor.ts | 2 +- .../contrib/outline/notebookOutline.ts | 439 +++--------------- .../media/notebookEditorStickyScroll.css | 16 + .../notebookKernelActionViewItem.css | 0 .../outline => media}/notebookOutline.css | 0 .../notebook/browser/notebook.contribution.ts | 6 + .../notebook/browser/notebookEditorWidget.ts | 22 + .../notebook/browser/notebookOptions.ts | 13 +- .../notebook/browser/view/notebookCellList.ts | 20 + .../browser/view/notebookRenderingCommon.ts | 2 + .../viewModel/notebookOutlineProvider.ts | 397 ++++++++++++++++ .../viewParts/notebookEditorStickyScroll.ts | 306 ++++++++++++ .../notebookKernelQuickPickStrategy.ts | 1 - .../browser/viewParts/notebookKernelView.ts | 1 - .../contrib/notebook/common/notebookCommon.ts | 1 + .../browser/contrib/notebookOutline.test.ts | 3 +- 17 files changed, 839 insertions(+), 391 deletions(-) create mode 100644 src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css rename src/vs/workbench/contrib/notebook/browser/{viewParts => media}/notebookKernelActionViewItem.css (100%) rename src/vs/workbench/contrib/notebook/browser/{contrib/outline => media}/notebookOutline.css (100%) create mode 100644 src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts create mode 100644 src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index bfcddd7d1b006..1b606f2ad8565 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -755,6 +755,7 @@ "--z-index-notebook-progress-bar", "--z-index-notebook-scrollbar", "--z-index-run-button-container", + "--z-index-notebook-sticky-scroll", "--zoom-factor" ] } diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 965719d15a646..30fff709556ce 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -159,7 +159,7 @@ export class InteractiveEditor extends EditorPane { this._editorOptions = this._computeEditorOptions(); } })); - this._notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, dragAndDropEnabled: false }); + this._notebookOptions = new NotebookOptions(configurationService, notebookExecutionStateService, true, { cellToolbarInteraction: 'hover', globalToolbar: true, stickyScroll: false, dragAndDropEnabled: false }); this._editorMemento = this.getEditorMemento(editorGroupService, textResourceConfigurationService, INTERACTIVE_EDITOR_VIEW_STATE_PREFERENCE_KEY); codeEditorService.registerDecorationType('interactive-decoration', DECORATION_KEY, {}); diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index acd6f718da6d2..ea0f4250cf290 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -3,138 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./notebookOutline'; -import { Codicon } from 'vs/base/common/codicons'; -import { Emitter, Event } from 'vs/base/common/event'; -import { combinedDisposable, IDisposable, Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ThemeIcon } from 'vs/base/common/themables'; -import { CellRevealType, IActiveNotebookEditor, ICellViewModel, INotebookEditorOptions, INotebookEditorPane, INotebookViewCellsUpdateEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; -import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { IOutline, IOutlineComparator, IOutlineCreator, IOutlineListConfig, IOutlineService, IQuickPickDataSource, IQuickPickOutlineElement, OutlineChangeEvent, OutlineConfigCollapseItemsValues, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { IEditorPane } from 'vs/workbench/common/editor'; +import { localize } from 'vs/nls'; +import { IIconLabelValueOptions, IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; -import { createMatches, FuzzyScore } from 'vs/base/common/filters'; -import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; +import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { IdleValue } from 'vs/base/common/async'; +import { Emitter, Event } from 'vs/base/common/event'; +import { FuzzyScore, createMatches } from 'vs/base/common/filters'; +import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { URI } from 'vs/base/common/uri'; +import { getIconClassesForLanguageId } from 'vs/editor/common/services/getIconClasses'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { getIconClassesForLanguageId } from 'vs/editor/common/services/getIconClasses'; import { IWorkbenchDataTreeOptions } from 'vs/platform/list/browser/listService'; -import { localize } from 'vs/nls'; -import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { Registry } from 'vs/platform/registry/common/platform'; import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; -import { isEqual } from 'vs/base/common/resources'; -import { IdleValue } from 'vs/base/common/async'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; -import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { executingStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; -import { URI } from 'vs/base/common/uri'; -import { getMarkdownHeadersInCell } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; - -export interface IOutlineMarkerInfo { - readonly count: number; - readonly topSev: MarkerSeverity; -} - -export class OutlineEntry { - - private _children: OutlineEntry[] = []; - private _parent: OutlineEntry | undefined; - private _markerInfo: IOutlineMarkerInfo | undefined; - - get icon(): ThemeIcon { - return this.isExecuting && this.isPaused ? executingStateIcon : - this.isExecuting ? ThemeIcon.modify(executingStateIcon, 'spin') : - this.cell.cellKind === CellKind.Markup ? Codicon.markdown : Codicon.code; - } - - constructor( - readonly index: number, - readonly level: number, - readonly cell: ICellViewModel, - readonly label: string, - readonly isExecuting: boolean, - readonly isPaused: boolean - ) { } - - addChild(entry: OutlineEntry) { - this._children.push(entry); - entry._parent = this; - } - - get parent(): OutlineEntry | undefined { - return this._parent; - } - - get children(): Iterable { - return this._children; - } - - get markerInfo(): IOutlineMarkerInfo | undefined { - return this._markerInfo; - } - - updateMarkers(markerService: IMarkerService): void { - if (this.cell.cellKind === CellKind.Code) { - // a code cell can have marker - const marker = markerService.read({ resource: this.cell.uri, severities: MarkerSeverity.Error | MarkerSeverity.Warning }); - if (marker.length === 0) { - this._markerInfo = undefined; - } else { - const topSev = marker.find(a => a.severity === MarkerSeverity.Error)?.severity ?? MarkerSeverity.Warning; - this._markerInfo = { topSev, count: marker.length }; - } - } else { - // a markdown cell can inherit markers from its children - let topChild: MarkerSeverity | undefined; - for (const child of this.children) { - child.updateMarkers(markerService); - if (child.markerInfo) { - topChild = !topChild ? child.markerInfo.topSev : Math.max(child.markerInfo.topSev, topChild); - } - } - this._markerInfo = topChild && { topSev: topChild, count: 0 }; - } - } - - clearMarkers(): void { - this._markerInfo = undefined; - for (const child of this.children) { - child.clearMarkers(); - } - } - - find(cell: ICellViewModel, parents: OutlineEntry[]): OutlineEntry | undefined { - if (cell.id === this.cell.id) { - return this; - } - parents.push(this); - for (const child of this.children) { - const result = child.find(cell, parents); - if (result) { - return result; - } - } - parents.pop(); - return undefined; - } +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IEditorPane } from 'vs/workbench/common/editor'; +import { CellRevealType, INotebookEditorOptions, INotebookEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { NotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookEditor'; +import { NotebookCellOutlineProvider, OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IOutline, IOutlineComparator, IOutlineCreator, IOutlineListConfig, IOutlineService, IQuickPickDataSource, IQuickPickOutlineElement, OutlineChangeEvent, OutlineConfigCollapseItemsValues, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; - asFlatList(bucket: OutlineEntry[]): void { - bucket.push(this); - for (const child of this.children) { - child.asFlatList(bucket); - } - } -} class NotebookOutlineTemplate { @@ -294,73 +193,51 @@ export class NotebookCellOutline implements IOutline { readonly onDidChange: Event = this._onDidChange.event; - private _uri: URI | undefined; - private _entries: OutlineEntry[] = []; - private _activeEntry?: OutlineEntry; + get entries(): OutlineEntry[] { + return this._outlineProvider?.entries ?? []; + } + private readonly _entriesDisposables = new DisposableStore(); readonly config: IOutlineListConfig; + readonly outlineKind = 'notebookCells'; get activeElement(): OutlineEntry | undefined { - return this._activeEntry; + return this._outlineProvider?.activeElement; } + private _outlineProvider: NotebookCellOutlineProvider | undefined; + constructor( private readonly _editor: INotebookEditorPane, - private readonly _target: OutlineTarget, + _target: OutlineTarget, @IInstantiationService instantiationService: IInstantiationService, - @IThemeService themeService: IThemeService, @IEditorService private readonly _editorService: IEditorService, - @IMarkerService private readonly _markerService: IMarkerService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, + @IConfigurationService _configurationService: IConfigurationService, ) { - const selectionListener = new MutableDisposable(); - this._dispoables.add(selectionListener); const installSelectionListener = () => { const notebookEditor = _editor.getControl(); if (!notebookEditor?.hasModel()) { - selectionListener.clear(); + this._outlineProvider?.dispose(); + this._outlineProvider = undefined; } else { - selectionListener.value = combinedDisposable( - Event.debounce( - notebookEditor.onDidChangeSelection, - (last, _current) => last, - 200 - )(this._recomputeActive, this), - Event.debounce( - notebookEditor.onDidChangeViewCells, - (last, _current) => last ?? _current, - 200 - )(this._recomputeState, this) - ); + this._outlineProvider?.dispose(); + this._outlineProvider = instantiationService.createInstance(NotebookCellOutlineProvider, notebookEditor, _target); } }; this._dispoables.add(_editor.onDidChangeModel(() => { - this._recomputeState(); installSelectionListener(); })); - this._dispoables.add(_configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('notebook.outline.showCodeCells')) { - this._recomputeState(); - } - })); - - this._dispoables.add(themeService.onDidFileIconThemeChange(() => { - this._onDidChange.fire({}); - })); - this._dispoables.add(_notebookExecutionStateService.onDidChangeExecution(e => { - if (e.type === NotebookExecutionType.cell && !!this._editor.textModel && e.affectsNotebook(this._editor.textModel?.uri)) { - this._recomputeState(); - } - })); - this._recomputeState(); installSelectionListener(); + const treeDataSource: IDataSource = { getChildren: parent => parent instanceof NotebookCellOutline ? (this._outlineProvider?.entries ?? []) : parent.children }; + const delegate = new NotebookOutlineVirtualDelegate(); + const renderers = [instantiationService.createInstance(NotebookOutlineRenderer)]; + const comparator = new NotebookComparator(); const options: IWorkbenchDataTreeOptions = { collapseByDefault: _target === OutlineTarget.Breadcrumbs || (_target === OutlineTarget.OutlinePane && _configurationService.getValue(OutlineConfigKeys.collapseItems) === OutlineConfigCollapseItemsValues.Collapsed), @@ -371,16 +248,11 @@ export class NotebookCellOutline implements IOutline { keyboardNavigationLabelProvider: new NotebookNavigationLabelProvider() }; - const treeDataSource: IDataSource = { getChildren: parent => parent instanceof NotebookCellOutline ? this._entries : parent.children }; - const delegate = new NotebookOutlineVirtualDelegate(); - const renderers = [instantiationService.createInstance(NotebookOutlineRenderer)]; - const comparator = new NotebookComparator(); - this.config = { breadcrumbsDataSource: { getBreadcrumbElements: () => { const result: OutlineEntry[] = []; - let candidate = this._activeEntry; + let candidate = this.activeElement; while (candidate) { result.unshift(candidate); candidate = candidate.parent; @@ -388,7 +260,7 @@ export class NotebookCellOutline implements IOutline { return result; } }, - quickPickDataSource: instantiationService.createInstance(NotebookQuickPickProvider, () => this._entries), + quickPickDataSource: instantiationService.createInstance(NotebookQuickPickProvider, () => (this._outlineProvider?.entries ?? [])), treeDataSource, delegate, renderers, @@ -397,221 +269,12 @@ export class NotebookCellOutline implements IOutline { }; } - dispose(): void { - this._onDidChange.dispose(); - this._dispoables.dispose(); - this._entriesDisposables.dispose(); + get uri(): URI | undefined { + return this._outlineProvider?.uri; } - - private _recomputeState(): void { - this._entriesDisposables.clear(); - this._activeEntry = undefined; - this._entries.length = 0; - this._uri = undefined; - - const notebookEditorControl = this._editor.getControl(); - - if (!notebookEditorControl) { - return; - } - - if (!notebookEditorControl.hasModel()) { - return; - } - - this._uri = notebookEditorControl.textModel.uri; - - const notebookEditorWidget: IActiveNotebookEditor = notebookEditorControl; - - if (notebookEditorWidget.getLength() === 0) { - return; - } - - let includeCodeCells = true; - if (this._target === OutlineTarget.OutlinePane) { - includeCodeCells = this._configurationService.getValue('notebook.outline.showCodeCells'); - } else if (this._target === OutlineTarget.Breadcrumbs) { - includeCodeCells = this._configurationService.getValue('notebook.breadcrumbs.showCodeCells'); - } - - const focusedCellIndex = notebookEditorWidget.getFocus().start; - const focused = notebookEditorWidget.cellAt(focusedCellIndex)?.handle; - const entries: OutlineEntry[] = []; - - for (let i = 0; i < notebookEditorWidget.getLength(); i++) { - const cell = notebookEditorWidget.cellAt(i); - const isMarkdown = cell.cellKind === CellKind.Markup; - if (!isMarkdown && !includeCodeCells) { - continue; - } - - // cap the amount of characters that we look at and use the following logic - // - for MD prefer headings (each header is an entry) - // - otherwise use the first none-empty line of the cell (MD or code) - let content = this._getCellFirstNonEmptyLine(cell); - let hasHeader = false; - - if (isMarkdown) { - const fullContent = cell.getText().substring(0, 10_000); - for (const { depth, text } of getMarkdownHeadersInCell(fullContent)) { - hasHeader = true; - entries.push(new OutlineEntry(entries.length, depth, cell, text, false, false)); - } - - if (!hasHeader) { - // no markdown syntax headers, try to find html tags - const match = fullContent.match(/(.*)<\/h\1>/i); - if (match) { - hasHeader = true; - const level = parseInt(match[1]); - const text = match[2].trim(); - entries.push(new OutlineEntry(entries.length, level, cell, text, false, false)); - } - } - - if (!hasHeader) { - content = renderMarkdownAsPlaintext({ value: content }); - } - } - - if (!hasHeader) { - let preview = content.trim(); - if (preview.length === 0) { - // empty or just whitespace - preview = localize('empty', "empty cell"); - } - - const exeState = !isMarkdown && this._notebookExecutionStateService.getCellExecution(cell.uri); - entries.push(new OutlineEntry(entries.length, 7, cell, preview, !!exeState, exeState ? exeState.isPaused : false)); - } - - if (cell.handle === focused) { - this._activeEntry = entries[entries.length - 1]; - } - - // send an event whenever any of the cells change - this._entriesDisposables.add(cell.model.onDidChangeContent(() => { - this._recomputeState(); - this._onDidChange.fire({}); - })); - } - - // build a tree from the list of entries - if (entries.length > 0) { - const result: OutlineEntry[] = [entries[0]]; - const parentStack: OutlineEntry[] = [entries[0]]; - - for (let i = 1; i < entries.length; i++) { - const entry = entries[i]; - - while (true) { - const len = parentStack.length; - if (len === 0) { - // root node - result.push(entry); - parentStack.push(entry); - break; - - } else { - const parentCandidate = parentStack[len - 1]; - if (parentCandidate.level < entry.level) { - parentCandidate.addChild(entry); - parentStack.push(entry); - break; - } else { - parentStack.pop(); - } - } - } - } - this._entries = result; - } - - // feature: show markers with each cell - const markerServiceListener = new MutableDisposable(); - this._entriesDisposables.add(markerServiceListener); - const updateMarkerUpdater = () => { - if (notebookEditorWidget.isDisposed) { - return; - } - - const doUpdateMarker = (clear: boolean) => { - for (const entry of this._entries) { - if (clear) { - entry.clearMarkers(); - } else { - entry.updateMarkers(this._markerService); - } - } - }; - if (this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) { - markerServiceListener.value = this._markerService.onMarkerChanged(e => { - if (e.some(uri => notebookEditorWidget.getCellsInRange().some(cell => isEqual(cell.uri, uri)))) { - doUpdateMarker(false); - this._onDidChange.fire({}); - } - }); - doUpdateMarker(false); - } else { - markerServiceListener.clear(); - doUpdateMarker(true); - } - }; - updateMarkerUpdater(); - this._entriesDisposables.add(this._configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) { - updateMarkerUpdater(); - this._onDidChange.fire({}); - } - })); - - this._onDidChange.fire({}); - } - - private _recomputeActive(): void { - let newActive: OutlineEntry | undefined; - const notebookEditorWidget = this._editor.getControl(); - - if (notebookEditorWidget) { - if (notebookEditorWidget.hasModel() && notebookEditorWidget.getLength() > 0) { - const cell = notebookEditorWidget.cellAt(notebookEditorWidget.getFocus().start); - if (cell) { - for (const entry of this._entries) { - newActive = entry.find(cell, []); - if (newActive) { - break; - } - } - } - } - } - if (newActive !== this._activeEntry) { - this._activeEntry = newActive; - this._onDidChange.fire({ affectOnlyActiveElement: true }); - } - } - - private _getCellFirstNonEmptyLine(cell: ICellViewModel) { - const textBuffer = cell.textBuffer; - for (let i = 0; i < textBuffer.getLineCount(); i++) { - const firstNonWhitespace = textBuffer.getLineFirstNonWhitespaceColumn(i + 1); - const lineLength = textBuffer.getLineLength(i + 1); - if (firstNonWhitespace < lineLength) { - return textBuffer.getLineContent(i + 1); - } - } - - return cell.getText().substring(0, 10_000); - } - get isEmpty(): boolean { - return this._entries.length === 0; - } - - get uri() { - return this._uri; + return this._outlineProvider?.isEmpty ?? true; } - async reveal(entry: OutlineEntry, options: IEditorOptions, sideBySide: boolean): Promise { await this._editorService.openEditor({ resource: entry.cell.uri, @@ -646,9 +309,15 @@ export class NotebookCellOutline implements IOutline { } }); } + + dispose(): void { + this._onDidChange.dispose(); + this._dispoables.dispose(); + this._entriesDisposables.dispose(); + } } -class NotebookOutlineCreator implements IOutlineCreator { +export class NotebookOutlineCreator implements IOutlineCreator { readonly dispose: () => void; diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css new file mode 100644 index 0000000000000..a97c09ef13a31 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .notebookOverlay .notebook-sticky-scroll-container { + display: none; + position: absolute; + background-color: var(--vscode-notebook-editorBackground); + border-bottom: solid 1px var(--vscode-notebook-cellToolbarSeparator); + box-sizing: border-box; + z-index: var(--z-index-notebook-sticky-scroll); + width: 100%; + padding-left: 12px; + font-family: var(--notebook-cell-input-preview-font-family); +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.css b/src/vs/workbench/contrib/notebook/browser/media/notebookKernelActionViewItem.css similarity index 100% rename from src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelActionViewItem.css rename to src/vs/workbench/contrib/notebook/browser/media/notebookKernelActionViewItem.css diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.css b/src/vs/workbench/contrib/notebook/browser/media/notebookOutline.css similarity index 100% rename from src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.css rename to src/vs/workbench/contrib/notebook/browser/media/notebookOutline.css diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index ae426f620be39..e461088658249 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -862,6 +862,12 @@ configurationRegistry.registerConfiguration({ default: true, tags: ['notebookLayout'] }, + [NotebookSetting.stickyScroll]: { + description: nls.localize('notebook.stickyScroll.description', "Experimental. Control whether to render notebook Sticky Scroll headers in the notebook editor."), + type: 'boolean', + default: false, + tags: ['notebookLayout'] + }, [NotebookSetting.consolidatedOutputButton]: { description: nls.localize('notebook.consolidatedOutputButton.description', "Control whether outputs action should be rendered in the output toolbar."), type: 'boolean', diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index bf2276f2bf9ac..99ec30b3a35fa 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -12,6 +12,10 @@ import 'vs/css!./media/notebookToolbar'; import 'vs/css!./media/notebookDnd'; import 'vs/css!./media/notebookFolding'; import 'vs/css!./media/notebookCellOutput'; +import 'vs/css!./media/notebookEditorStickyScroll'; +import 'vs/css!./media/notebookKernelActionViewItem'; +import 'vs/css!./media/notebookOutline'; + import { PixelRatio } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -90,8 +94,11 @@ import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/no import { Schemas } from 'vs/base/common/network'; import { DropIntoEditorController } from 'vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController'; import { CopyPasteController } from 'vs/editor/contrib/dropOrPasteInto/browser/copyPasteController'; +import { NotebookEditorStickyScroll } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll'; +import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; const $ = DOM.$; @@ -171,6 +178,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD private _overlayContainer!: HTMLElement; private _notebookTopToolbarContainer!: HTMLElement; private _notebookTopToolbar!: NotebookEditorWorkbenchToolbar; + private _notebookStickyScrollContainer!: HTMLElement; private _notebookOverviewRulerContainer!: HTMLElement; private _notebookOverviewRuler!: NotebookOverviewRuler; private _body!: HTMLElement; @@ -261,6 +269,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD public readonly scopedContextKeyService: IContextKeyService; private readonly instantiationService: IInstantiationService; private readonly _notebookOptions: NotebookOptions; + public readonly _notebookOutline: NotebookCellOutlineProvider; private _currentProgress: IProgressRunner | undefined; @@ -315,6 +324,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._register(this.instantiationService.createInstance(NotebookEditorContextKeys, this)); + this._notebookOutline = this.instantiationService.createInstance(NotebookCellOutlineProvider, this, OutlineTarget.QuickPick); + this._register(notebookKernelService.onDidChangeSelectedNotebooks(e => { if (isEqual(e.notebook, this.viewModel?.uri)) { this._loadKernelPreloads(); @@ -565,6 +576,11 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._notebookTopToolbarContainer.classList.add('notebook-toolbar-container'); this._notebookTopToolbarContainer.style.display = 'none'; DOM.append(parent, this._notebookTopToolbarContainer); + + this._notebookStickyScrollContainer = document.createElement('div'); + this._notebookStickyScrollContainer.classList.add('notebook-sticky-scroll-container'); + DOM.append(parent, this._notebookStickyScrollContainer); + this._body = document.createElement('div'); DOM.append(parent, this._body); @@ -999,6 +1015,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD })); this._registerNotebookActionsToolbar(); + this._registerNotebookStickyScroll(); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AccessibilityVerbositySettingId.Notebook)) { @@ -1028,6 +1045,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD })); } + private _registerNotebookStickyScroll() { + this._register(this.instantiationService.createInstance(NotebookEditorStickyScroll, this._notebookStickyScrollContainer, this, this._notebookOutline, this._list)); + } + private _updateOutputRenderers() { if (!this.viewModel || !this._webview) { return; @@ -3089,6 +3110,7 @@ registerZIndex(ZIndex.Base, 28, 'notebook-cell-bottom-toolbar-container'); registerZIndex(ZIndex.Base, 29, 'notebook-run-button-container'); registerZIndex(ZIndex.Base, 29, 'notebook-input-collapse-condicon'); registerZIndex(ZIndex.Base, 30, 'notebook-cell-output-toolbar'); +registerZIndex(ZIndex.Base, 31, 'notebook-sticky-scroll'); registerZIndex(ZIndex.Sash, 1, 'notebook-cell-expand-part-button'); registerZIndex(ZIndex.Sash, 2, 'notebook-cell-toolbar'); registerZIndex(ZIndex.Sash, 3, 'notebook-cell-toolbar-dropdown-active'); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 2140b0286ae98..55f6fff83baa3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -61,6 +61,7 @@ export interface NotebookLayoutConfiguration { insertToolbarPosition: 'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'; insertToolbarAlignment: 'left' | 'center'; globalToolbar: boolean; + stickyScroll: boolean; consolidatedOutputButton: boolean; consolidatedRunButton: boolean; showFoldingControls: 'always' | 'never' | 'mouseover'; @@ -89,6 +90,7 @@ export interface NotebookOptionsChangeEvent { readonly insertToolbarPosition?: boolean; readonly insertToolbarAlignment?: boolean; readonly globalToolbar?: boolean; + readonly stickyScroll?: boolean; readonly showFoldingControls?: boolean; readonly consolidatedOutputButton?: boolean; readonly consolidatedRunButton?: boolean; @@ -134,11 +136,12 @@ export class NotebookOptions extends Disposable { private readonly configurationService: IConfigurationService, private readonly notebookExecutionStateService: INotebookExecutionStateService, private isReadonly: boolean, - private readonly overrides?: { cellToolbarInteraction: string; globalToolbar: boolean; dragAndDropEnabled: boolean } + private readonly overrides?: { cellToolbarInteraction: string; globalToolbar: boolean; stickyScroll: boolean; dragAndDropEnabled: boolean } ) { super(); const showCellStatusBar = this.configurationService.getValue(NotebookSetting.showCellStatusBar); const globalToolbar = overrides?.globalToolbar ?? this.configurationService.getValue(NotebookSetting.globalToolbar) ?? true; + const stickyScroll = overrides?.stickyScroll ?? this.configurationService.getValue(NotebookSetting.stickyScroll) ?? false; const consolidatedOutputButton = this.configurationService.getValue(NotebookSetting.consolidatedOutputButton) ?? true; const consolidatedRunButton = this.configurationService.getValue(NotebookSetting.consolidatedRunButton) ?? false; const dragAndDropEnabled = overrides?.dragAndDropEnabled ?? this.configurationService.getValue(NotebookSetting.dragAndDropEnabled) ?? true; @@ -213,6 +216,7 @@ export class NotebookOptions extends Disposable { collapsedIndicatorHeight: 28, showCellStatusBar, globalToolbar, + stickyScroll, consolidatedOutputButton, consolidatedRunButton, dragAndDropEnabled, @@ -335,6 +339,7 @@ export class NotebookOptions extends Disposable { const insertToolbarPosition = e.affectsConfiguration(NotebookSetting.insertToolbarLocation); const insertToolbarAlignment = e.affectsConfiguration(NotebookSetting.experimentalInsertToolbarAlignment); const globalToolbar = e.affectsConfiguration(NotebookSetting.globalToolbar); + const stickyScroll = e.affectsConfiguration(NotebookSetting.stickyScroll); const consolidatedOutputButton = e.affectsConfiguration(NotebookSetting.consolidatedOutputButton); const consolidatedRunButton = e.affectsConfiguration(NotebookSetting.consolidatedRunButton); const showFoldingControls = e.affectsConfiguration(NotebookSetting.showFoldingControls); @@ -359,6 +364,7 @@ export class NotebookOptions extends Disposable { && !insertToolbarPosition && !insertToolbarAlignment && !globalToolbar + && !stickyScroll && !consolidatedOutputButton && !consolidatedRunButton && !showFoldingControls @@ -414,6 +420,10 @@ export class NotebookOptions extends Disposable { configuration.globalToolbar = this.configurationService.getValue(NotebookSetting.globalToolbar) ?? true; } + if (stickyScroll && this.overrides?.stickyScroll === undefined) { + configuration.stickyScroll = this.configurationService.getValue(NotebookSetting.stickyScroll) ?? false; + } + if (consolidatedOutputButton) { configuration.consolidatedOutputButton = this.configurationService.getValue(NotebookSetting.consolidatedOutputButton) ?? true; } @@ -479,6 +489,7 @@ export class NotebookOptions extends Disposable { insertToolbarPosition, insertToolbarAlignment, globalToolbar, + stickyScroll, showFoldingControls, consolidatedOutputButton, consolidatedRunButton, diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 95e6b840346aa..80b657b5aad4f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -686,6 +686,26 @@ export class NotebookCellList extends WorkbenchList implements ID this.setSelection(indices); } + getCellViewScrollTop(cell: ICellViewModel) { + const index = this._getViewIndexUpperBound(cell); + if (index === undefined || index < 0 || index >= this.length) { + throw new ListError(this.listUser, `Invalid index ${index}`); + } + + return this.view.elementTop(index); + } + + getCellViewScrollBottom(cell: ICellViewModel) { + const index = this._getViewIndexUpperBound(cell); + if (index === undefined || index < 0 || index >= this.length) { + throw new ListError(this.listUser, `Invalid index ${index}`); + } + + const top = this.view.elementTop(index); + const height = this.view.elementHeight(index); + return top + height; + } + override setFocus(indexes: number[], browserEvent?: UIEvent, ignoreTextModelUpdate?: boolean): void { if (ignoreTextModelUpdate) { super.setFocus(indexes, browserEvent); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 0276ee9e10e6f..66926b44cf65e 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -54,6 +54,8 @@ export interface INotebookCellList { attachViewModel(viewModel: NotebookViewModel): void; attachWebview(element: HTMLElement): void; clear(): void; + getCellViewScrollTop(cell: ICellViewModel): number; + getCellViewScrollBottom(cell: ICellViewModel): number; getViewIndex(cell: ICellViewModel): number | undefined; getViewIndex2(modelIndex: number): number | undefined; getModelIndex(cell: CellViewModel): number | undefined; diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts new file mode 100644 index 0000000000000..a0791175d4247 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider.ts @@ -0,0 +1,397 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { renderMarkdownAsPlaintext } from 'vs/base/browser/markdownRenderer'; +import { Codicon } from 'vs/base/common/codicons'; +import { Emitter, Event } from 'vs/base/common/event'; +import { DisposableStore, MutableDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { isEqual } from 'vs/base/common/resources'; +import { ThemeIcon } from 'vs/base/common/themables'; +import { URI } from 'vs/base/common/uri'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IActiveNotebookEditor, ICellViewModel, INotebookEditor, INotebookViewCellsUpdateEvent } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { executingStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; +import { getMarkdownHeadersInCell } from 'vs/workbench/contrib/notebook/browser/viewModel/foldingModel'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { OutlineChangeEvent, OutlineConfigKeys, OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; + +export interface IOutlineMarkerInfo { + readonly count: number; + readonly topSev: MarkerSeverity; +} + +export class OutlineEntry { + private _children: OutlineEntry[] = []; + private _parent: OutlineEntry | undefined; + private _markerInfo: IOutlineMarkerInfo | undefined; + + get icon(): ThemeIcon { + return this.isExecuting && this.isPaused ? executingStateIcon : + this.isExecuting ? ThemeIcon.modify(executingStateIcon, 'spin') : + this.cell.cellKind === CellKind.Markup ? Codicon.markdown : Codicon.code; + } + + constructor( + readonly index: number, + readonly level: number, + readonly cell: ICellViewModel, + readonly label: string, + readonly isExecuting: boolean, + readonly isPaused: boolean + ) { } + + addChild(entry: OutlineEntry) { + this._children.push(entry); + entry._parent = this; + } + + get parent(): OutlineEntry | undefined { + return this._parent; + } + + get children(): Iterable { + return this._children; + } + + get markerInfo(): IOutlineMarkerInfo | undefined { + return this._markerInfo; + } + + updateMarkers(markerService: IMarkerService): void { + if (this.cell.cellKind === CellKind.Code) { + // a code cell can have marker + const marker = markerService.read({ resource: this.cell.uri, severities: MarkerSeverity.Error | MarkerSeverity.Warning }); + if (marker.length === 0) { + this._markerInfo = undefined; + } else { + const topSev = marker.find(a => a.severity === MarkerSeverity.Error)?.severity ?? MarkerSeverity.Warning; + this._markerInfo = { topSev, count: marker.length }; + } + } else { + // a markdown cell can inherit markers from its children + let topChild: MarkerSeverity | undefined; + for (const child of this.children) { + child.updateMarkers(markerService); + if (child.markerInfo) { + topChild = !topChild ? child.markerInfo.topSev : Math.max(child.markerInfo.topSev, topChild); + } + } + this._markerInfo = topChild && { topSev: topChild, count: 0 }; + } + } + + clearMarkers(): void { + this._markerInfo = undefined; + for (const child of this.children) { + child.clearMarkers(); + } + } + + find(cell: ICellViewModel, parents: OutlineEntry[]): OutlineEntry | undefined { + if (cell.id === this.cell.id) { + return this; + } + parents.push(this); + for (const child of this.children) { + const result = child.find(cell, parents); + if (result) { + return result; + } + } + parents.pop(); + return undefined; + } + + asFlatList(bucket: OutlineEntry[]): void { + bucket.push(this); + for (const child of this.children) { + child.asFlatList(bucket); + } + } +} + + +export class NotebookCellOutlineProvider { + private readonly _dispoables = new DisposableStore(); + private readonly _onDidChange = new Emitter(); + + readonly onDidChange: Event = this._onDidChange.event; + + private _uri: URI | undefined; + private _entries: OutlineEntry[] = []; + get entries(): OutlineEntry[] { + return this._entries; + } + + private _activeEntry?: OutlineEntry; + private readonly _entriesDisposables = new DisposableStore(); + + readonly outlineKind = 'notebookCells'; + + get activeElement(): OutlineEntry | undefined { + return this._activeEntry; + } + + constructor( + private readonly _editor: INotebookEditor, + private readonly _target: OutlineTarget, + @IThemeService themeService: IThemeService, + @IEditorService _editorService: IEditorService, + @IMarkerService private readonly _markerService: IMarkerService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService, + ) { + const selectionListener = new MutableDisposable(); + this._dispoables.add(selectionListener); + + selectionListener.value = combinedDisposable( + Event.debounce( + _editor.onDidChangeSelection, + (last, _current) => last, + 200 + )(this._recomputeActive, this), + Event.debounce( + _editor.onDidChangeViewCells, + (last, _current) => last ?? _current, + 200 + )(this._recomputeState, this) + ); + + this._dispoables.add(_configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('notebook.outline.showCodeCells')) { + this._recomputeState(); + } + })); + + this._dispoables.add(themeService.onDidFileIconThemeChange(() => { + this._onDidChange.fire({}); + })); + + this._dispoables.add(_notebookExecutionStateService.onDidChangeExecution(e => { + if (e.type === NotebookExecutionType.cell && !!this._editor.textModel && e.affectsNotebook(this._editor.textModel?.uri)) { + this._recomputeState(); + } + })); + + this._recomputeState(); + } + + dispose(): void { + // selectionListener.clear(); + this._dispoables.dispose(); + } + + init(): void { + this._recomputeState(); + } + + private _recomputeState(): void { + this._entriesDisposables.clear(); + this._activeEntry = undefined; + this._entries.length = 0; + this._uri = undefined; + + if (!this._editor.hasModel()) { + return; + } + + this._uri = this._editor.textModel.uri; + + const notebookEditorWidget: IActiveNotebookEditor = this._editor; + + if (notebookEditorWidget.getLength() === 0) { + return; + } + + let includeCodeCells = true; + if (this._target === OutlineTarget.OutlinePane) { + includeCodeCells = this._configurationService.getValue('notebook.outline.showCodeCells'); + } else if (this._target === OutlineTarget.Breadcrumbs) { + includeCodeCells = this._configurationService.getValue('notebook.breadcrumbs.showCodeCells'); + } + + const focusedCellIndex = notebookEditorWidget.getFocus().start; + const focused = notebookEditorWidget.cellAt(focusedCellIndex)?.handle; + const entries: OutlineEntry[] = []; + + for (let i = 0; i < notebookEditorWidget.getLength(); i++) { + const cell = notebookEditorWidget.cellAt(i); + const isMarkdown = cell.cellKind === CellKind.Markup; + if (!isMarkdown && !includeCodeCells) { + continue; + } + + // cap the amount of characters that we look at and use the following logic + // - for MD prefer headings (each header is an entry) + // - otherwise use the first none-empty line of the cell (MD or code) + let content = this._getCellFirstNonEmptyLine(cell); + let hasHeader = false; + + if (isMarkdown) { + const fullContent = cell.getText().substring(0, 10_000); + for (const { depth, text } of getMarkdownHeadersInCell(fullContent)) { + hasHeader = true; + entries.push(new OutlineEntry(entries.length, depth, cell, text, false, false)); + } + + if (!hasHeader) { + // no markdown syntax headers, try to find html tags + const match = fullContent.match(/(.*)<\/h\1>/i); + if (match) { + hasHeader = true; + const level = parseInt(match[1]); + const text = match[2].trim(); + entries.push(new OutlineEntry(entries.length, level, cell, text, false, false)); + } + } + + if (!hasHeader) { + content = renderMarkdownAsPlaintext({ value: content }); + } + } + + if (!hasHeader) { + let preview = content.trim(); + if (preview.length === 0) { + // empty or just whitespace + preview = localize('empty', "empty cell"); + } + + const exeState = !isMarkdown && this._notebookExecutionStateService.getCellExecution(cell.uri); + entries.push(new OutlineEntry(entries.length, 7, cell, preview, !!exeState, exeState ? exeState.isPaused : false)); + } + + if (cell.handle === focused) { + this._activeEntry = entries[entries.length - 1]; + } + + // send an event whenever any of the cells change + this._entriesDisposables.add(cell.model.onDidChangeContent(() => { + this._recomputeState(); + this._onDidChange.fire({}); + })); + } + + // build a tree from the list of entries + if (entries.length > 0) { + const result: OutlineEntry[] = [entries[0]]; + const parentStack: OutlineEntry[] = [entries[0]]; + + for (let i = 1; i < entries.length; i++) { + const entry = entries[i]; + + while (true) { + const len = parentStack.length; + if (len === 0) { + // root node + result.push(entry); + parentStack.push(entry); + break; + + } else { + const parentCandidate = parentStack[len - 1]; + if (parentCandidate.level < entry.level) { + parentCandidate.addChild(entry); + parentStack.push(entry); + break; + } else { + parentStack.pop(); + } + } + } + } + this._entries = result; + } + + // feature: show markers with each cell + const markerServiceListener = new MutableDisposable(); + this._entriesDisposables.add(markerServiceListener); + const updateMarkerUpdater = () => { + if (notebookEditorWidget.isDisposed) { + return; + } + + const doUpdateMarker = (clear: boolean) => { + for (const entry of this._entries) { + if (clear) { + entry.clearMarkers(); + } else { + entry.updateMarkers(this._markerService); + } + } + }; + if (this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) { + markerServiceListener.value = this._markerService.onMarkerChanged(e => { + if (e.some(uri => notebookEditorWidget.getCellsInRange().some(cell => isEqual(cell.uri, uri)))) { + doUpdateMarker(false); + this._onDidChange.fire({}); + } + }); + doUpdateMarker(false); + } else { + markerServiceListener.clear(); + doUpdateMarker(true); + } + }; + updateMarkerUpdater(); + this._entriesDisposables.add(this._configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) { + updateMarkerUpdater(); + this._onDidChange.fire({}); + } + })); + + this._onDidChange.fire({}); + } + + private _recomputeActive(): void { + let newActive: OutlineEntry | undefined; + const notebookEditorWidget = this._editor; + + if (notebookEditorWidget) {//TODO don't check for widget, only here if we do have + if (notebookEditorWidget.hasModel() && notebookEditorWidget.getLength() > 0) { + const cell = notebookEditorWidget.cellAt(notebookEditorWidget.getFocus().start); + if (cell) { + for (const entry of this._entries) { + newActive = entry.find(cell, []); + if (newActive) { + break; + } + } + } + } + } + if (newActive !== this._activeEntry) { + this._activeEntry = newActive; + this._onDidChange.fire({ affectOnlyActiveElement: true }); + } + } + + private _getCellFirstNonEmptyLine(cell: ICellViewModel) { + const textBuffer = cell.textBuffer; + for (let i = 0; i < textBuffer.getLineCount(); i++) { + const firstNonWhitespace = textBuffer.getLineFirstNonWhitespaceColumn(i + 1); + const lineLength = textBuffer.getLineLength(i + 1); + if (firstNonWhitespace < lineLength) { + return textBuffer.getLineContent(i + 1); + } + } + + return cell.getText().substring(0, 10_000); + } + + get isEmpty(): boolean { + return this._entries.length === 0; + } + + get uri() { + return this._uri; + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts new file mode 100644 index 0000000000000..37987fed8b624 --- /dev/null +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -0,0 +1,306 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; +import { NotebookCellOutlineProvider, OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; +import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + + +export class NotebookEditorStickyScroll extends Disposable { + private readonly _disposables = new DisposableStore(); + + constructor( + private readonly domNode: HTMLElement, + private readonly notebookEditor: INotebookEditor, + private readonly notebookOutline: NotebookCellOutlineProvider, + private readonly notebookCellList: INotebookCellList + ) { + super(); + + if (this.notebookEditor.notebookOptions.getLayoutConfiguration().stickyScroll) { + this.init(); + } + + this._register(this.notebookEditor.notebookOptions.onDidChangeOptions((e) => { + if (e.stickyScroll) { + this.updateConfig(); + } + if (e.globalToolbar) { + this.setTop(); + } + })); + } + + private updateConfig() { + if (this.notebookEditor.notebookOptions.getLayoutConfiguration().stickyScroll) { + this.init(); + } else { + this._disposables.clear(); + DOM.clearNode(this.domNode); + this.updateDisplay(); + } + } + + private setTop() { + if (this.notebookEditor.notebookOptions.getLayoutConfiguration().globalToolbar) { + this.domNode.style.top = '26px'; + } else { + this.domNode.style.top = '0px'; + } + } + + private init() { + this.notebookOutline.init(); + this.initializeContent(); + + this._disposables.add(this.notebookOutline.onDidChange(() => { + this.updateContent(); + })); + + this._disposables.add(this.notebookEditor.onDidAttachViewModel(() => { + this.notebookOutline.init(); + this.initializeContent(); + })); + + this._disposables.add(this.notebookEditor.onDidScroll(() => { + this.updateContent(); + })); + } + + private getVisibleOutlineEntry(visibleIndex: number): OutlineEntry | undefined { + let left = 0; + let right = this.notebookOutline.entries.length - 1; + let bucket = -1; + + while (left <= right) { + const mid = Math.floor((left + right) / 2); + if (this.notebookOutline.entries[mid].index < visibleIndex) { + bucket = mid; + left = mid + 1; + } else { + right = mid - 1; + } + } + + if (bucket !== -1) { + const rootEntry = this.notebookOutline.entries[bucket]; + const flatList: OutlineEntry[] = []; + rootEntry.asFlatList(flatList); + return flatList.find(entry => entry.index === visibleIndex); + } + return undefined; + } + + private initializeContent() { + + // find last code cell of section, store bottom scroll position in sectionBottom + const visibleRange = this.notebookEditor.visibleRanges[0]; + if (!visibleRange) { + return; + } + + DOM.clearNode(this.domNode); + const editorScrollTop = this.notebookEditor.scrollTop; + + let trackedEntry = undefined; + let sectionBottom = 0; + for (let i = visibleRange.start; i < visibleRange.end; i++) { + if (i === 0) { // don't show headers when you're viewing the top cell + return; + } + const cell = this.notebookEditor.cellAt(i); + if (!cell) { + return; + } + if (cell.cellKind === CellKind.Markup) { + continue; + } + + // if we are here, the cell is a code cell. + // check next cell, if markdown, that means this is the end of the section + const nextCell = this.notebookEditor.cellAt(i + 1); + if (nextCell) { + if (nextCell.cellKind === CellKind.Markup) { + // this is the end of the section + // store the bottom scroll position of this cell + sectionBottom = this.notebookCellList.getCellViewScrollBottom(cell); + // compute sticky scroll height + const entry = this.getVisibleOutlineEntry(i); + if (!entry) { + return; + } + // using 22 instead of stickyscrollheight, as we don't necessarily render each line. 22 starts rendering sticky when we have space for at least 1 of them + const newStickyHeight = this.computeStickyHeight(entry!); + if (editorScrollTop + newStickyHeight < sectionBottom) { + trackedEntry = entry; + break; + } else { + // if (editorScrollTop + stickyScrollHeight > sectionBottom), then continue to next section + continue; + } + } + } else { + // there is no next cell, so use the bottom of the editor as the sectionBottom, using scrolltop + height + sectionBottom = this.notebookEditor.scrollTop + this.notebookEditor.getLayoutInfo().scrollHeight; + trackedEntry = this.getVisibleOutlineEntry(i); + break; + } + } // cell loop close + + // ------------------------------------------------------------------------------------- + // we now know the cell which the sticky is determined by, and the sectionBottom value to determine how many sticky lines to render + // compute the space available for sticky lines, and render sticky lines + + const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); + this.renderStickyLines(trackedEntry?.parent, this.domNode, linesToRender); + this.updateDisplay(); + } + + + private updateContent() { + // find first code cell in visible range. this marks the start of the first section + // find the last code cell in the first section of the visible range, store the bottom scroll position in a const sectionBottom + // compute sticky scroll height, and check if editorScrolltop + stickyScrollHeight < sectionBottom // ? maybe use 22 instead of stickyscrollheight, as we don't necessarily render each line + // if that condition is true, break out of the loop with that cell as the tracked cell + // if that condition is false, continue to next cell + + DOM.clearNode(this.domNode); + const editorScrollTop = this.notebookEditor.scrollTop; + + // find last code cell of section, store bottom scroll position in sectionBottom + const visibleRange = this.notebookEditor.visibleRanges[0]; + if (!visibleRange) { + return; + } + + let trackedEntry = undefined; + let sectionBottom = 0; + for (let i = visibleRange.start; i < visibleRange.end; i++) { + if (i === 0) { // don't show headers when you're viewing the top cell + this.updateDisplay(); + return; + } + const cell = this.notebookEditor.cellAt(i); + if (!cell) { + return; + } + if (cell.cellKind === CellKind.Markup) { + continue; + } + + // if we are here, the cell is a code cell. + // check next cell, if markdown, that means this is the end of the section + const nextCell = this.notebookEditor.cellAt(i + 1); + if (nextCell) { + if (nextCell.cellKind === CellKind.Markup) { + // this is the end of the section + // store the bottom scroll position of this cell + sectionBottom = this.notebookCellList.getCellViewScrollBottom(cell); + // compute sticky scroll height + const entry = this.getVisibleOutlineEntry(i); + if (!entry) { + return; + } + // check if we can render this section of sticky + const currentSectionStickyHeight = this.computeStickyHeight(entry!); + if (editorScrollTop + currentSectionStickyHeight < sectionBottom) { + const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); + this.renderStickyLines(entry?.parent, this.domNode, linesToRender); + break; + } + + let nextSectionEntry = undefined; + for (let j = 1; j < visibleRange.end - i; j++) { + // find next code cell after this one + const cellCheck = this.notebookEditor.cellAt(i + j); + if (cellCheck && cellCheck.cellKind === CellKind.Code) { + nextSectionEntry = this.getVisibleOutlineEntry(i + j); + break; + } + } + const nextSectionStickyHeight = this.computeStickyHeight(nextSectionEntry!); + + // this block of logic cleans transitions between two sections that share a parent. + // if the current section and the next section share a parent, then we can render the next section's sticky lines to avoid pop-in between + if (entry?.parent?.parent === nextSectionEntry?.parent) { + const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22) + 1; + this.renderStickyLines(nextSectionEntry?.parent, this.domNode, linesToRender); + break; + } else if (Math.abs(currentSectionStickyHeight - nextSectionStickyHeight) > 22) { // only shrink sticky + const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); + this.renderStickyLines(entry?.parent, this.domNode, linesToRender); + break; + } + } + } else { + // there is no next cell, so use the bottom of the editor as the sectionBottom, using scrolltop + height + sectionBottom = this.notebookEditor.scrollTop + this.notebookEditor.getLayoutInfo().scrollHeight; + trackedEntry = this.getVisibleOutlineEntry(i); + const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); + this.renderStickyLines(trackedEntry?.parent, this.domNode, linesToRender); + break; + } + } // cell loop close + this.updateDisplay(); + } + + private updateDisplay() { + const hasChildren = this.domNode.hasChildNodes(); + if (!hasChildren) { + this.domNode.style.display = 'none'; + } else { + this.domNode.style.display = 'block'; + } + this.setTop(); + } + + private computeStickyHeight(entry: OutlineEntry) { + let height = 0; + while (entry.parent) { + height += 22; + entry = entry.parent; + } + return height; + } + + private renderStickyLines(entry: OutlineEntry | undefined, containerElement: HTMLElement, linesToRender: number) { + const partial = false; + if (!entry) { + return; + } + if (entry.parent) { + this.renderStickyLines(entry.parent, containerElement, linesToRender); + } + + const numStickyLines = containerElement.children.length; + if (numStickyLines >= linesToRender) { + return; + } + + DOM.append(containerElement, this.createStickyElement(entry, partial)); + } + + private createStickyElement(entry: OutlineEntry, partial: boolean) { + const stickyLine = document.createElement('div'); + stickyLine.classList.add('notebook-sticky-scroll-line'); + stickyLine.innerText = '#'.repeat(entry.level) + ' ' + entry.label; + + // todo: partial line rendering for animater + if (partial) { + // const partialHeight = Math.floor(remainder * 22); + // stickyLine.style.height = `${partialHeight}px`; + } + + return stickyLine; + } + + override dispose() { + this._disposables.dispose(); + super.dispose(); + } +} diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts index 473d46907b1d7..9a9ae9e0fdb57 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelQuickPickStrategy.ts @@ -12,7 +12,6 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { uppercaseFirstLetter } from 'vs/base/common/strings'; -import 'vs/css!./notebookKernelActionViewItem'; import { Command } from 'vs/editor/common/languages'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts index 7eb8d15c4d3a2..c530cacc2ebdd 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookKernelView.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./notebookKernelActionViewItem'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { Action, IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index d031650968a14..c3f6610d091d1 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -927,6 +927,7 @@ export const NotebookSetting = { focusIndicator: 'notebook.cellFocusIndicator', insertToolbarLocation: 'notebook.insertToolbarLocation', globalToolbar: 'notebook.globalToolbar', + stickyScroll: 'notebook.stickyScroll.enabled', undoRedoPerCell: 'notebook.undoRedoPerCell', consolidatedOutputButton: 'notebook.consolidatedOutputButton', showFoldingControls: 'notebook.showFoldingControls', diff --git a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts index afdd543fde967..323abe63dda52 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/contrib/notebookOutline.test.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import { setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; import { OutlineTarget } from 'vs/workbench/services/outline/browser/outline'; -import { NotebookCellOutline } from 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; import { IFileIconTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { mock } from 'vs/base/test/common/mock'; import { Event } from 'vs/base/common/event'; @@ -17,7 +16,7 @@ import { CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib import { IActiveNotebookEditor, INotebookEditorPane } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; - +import { NotebookCellOutline } from 'vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline'; suite('Notebook Outline', function () { From 696f9682f38055b0d82a076450a46b2ed5958cbe Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 20 Jul 2023 15:56:44 -0700 Subject: [PATCH 106/216] Introduce `chat.experimental.defaultMode` to decide default chat experience (#188429) --- .../quickQuestionActions/quickQuestionAction.ts | 14 ++++++++++++-- .../contrib/chat/browser/chat.contribution.ts | 7 +++++++ .../chat/browser/chatContributionServiceImpl.ts | 5 ++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts index 55d1b8faab9f7..6f40a200bd5d0 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Lazy } from 'vs/base/common/lazy'; import { localize } from 'vs/nls'; -import { Action2 } from 'vs/platform/actions/common/actions'; +import { Action2, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; @@ -25,6 +27,7 @@ export interface IQuickQuestionMode { run(accessor: ServicesAccessor, query: string): void; } +// TODO: This should be registered per chat-provider probably. export class AskQuickQuestionAction extends Action2 { private static readonly modeRegistry: Map> = new Map(); @@ -35,8 +38,9 @@ export class AskQuickQuestionAction extends Action2 { constructor() { super({ id: ASK_QUICK_QUESTION_ACTION_ID, - title: { value: localize('askQuickQuestion', "Ask Quick Question"), original: 'Ask Quick Question' }, + title: { value: localize('chat', "Chat"), original: 'Chat' }, precondition: CONTEXT_PROVIDER_EXISTS, + icon: Codicon.commentDiscussion, f1: false, category: CHAT_CATEGORY, keybinding: { @@ -46,6 +50,12 @@ export class AskQuickQuestionAction extends Action2 { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI } }, + menu: { + id: MenuId.LayoutControlMenu, + group: '0_workbench_toggles', + when: ContextKeyExpr.equals('config.chat.experimental.defaultMode', 'quickQuestion'), + order: 0 + } }); } diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 0366560253afb..81308b3f44b0b 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -78,6 +78,13 @@ configurationRegistry.registerConfiguration({ description: nls.localize('interactiveSession.editor.lineHeight', "Controls the line height in pixels in chat codeblocks. Use 0 to compute the line height from the font size."), default: 0 }, + 'chat.experimental.defaultMode': { + type: 'string', + tags: ['experimental'], + enum: ['chatView', 'quickQuestion'], + description: nls.localize('interactiveSession.defaultMode', "Controls the default mode of the chat experience."), + default: 'chatView' + }, 'chat.experimental.quickQuestion.mode': { type: 'string', tags: ['experimental'], diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index c4cc0badb4d8d..e9d90a2093d71 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -125,7 +125,10 @@ export class ChatContributionService implements IChatContributionService { canToggleVisibility: false, canMoveView: true, ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ providerId: providerDescriptor.id }]), - when: ContextKeyExpr.deserialize(providerDescriptor.when), + when: ContextKeyExpr.and( + ContextKeyExpr.deserialize(providerDescriptor.when), + ContextKeyExpr.equals('config.chat.experimental.defaultMode', 'chatView') + ) }]; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, viewContainer); From b949114b9fcff870a47163260b4cfe21855c9483 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Thu, 20 Jul 2023 20:01:12 -0700 Subject: [PATCH 107/216] Make InputOnTopChat the default (#188430) I think it's improved enough to be the new default. --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 81308b3f44b0b..eda5d692605f6 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -90,7 +90,7 @@ configurationRegistry.registerConfiguration({ tags: ['experimental'], enum: [QuickQuestionMode.SingleQuestion, QuickQuestionMode.InputOnTopChat, QuickQuestionMode.InputOnBottomChat], description: nls.localize('interactiveSession.quickQuestion.mode', "Controls the mode of quick question chat experience."), - default: QuickQuestionMode.SingleQuestion, + default: QuickQuestionMode.InputOnTopChat, } } }); From 00039b0fcf281275f6e313bacc830aca31e9bc92 Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 20 Jul 2023 21:02:56 -0700 Subject: [PATCH 108/216] refactor: linux pipeline to use containers only for yarn step (#188383) * ci: use container only in yarn step * chore: invalidate cache * chore: install missing deps for packaging * chore: remove duplicate package installations * fix: oss build * chore: separate deb and rpm package preparation * chore: mount out folder when packaging * ci: switch to official docker image --- build/.cachesalt | 2 +- build/azure-pipelines/linux/install.sh | 40 ++++ .../linux/product-build-linux-test.yml | 11 -- .../linux/product-build-linux.yml | 182 ++++++++++-------- build/azure-pipelines/product-build.yml | 18 -- build/gulpfile.vscode.linux.js | 6 +- build/npm/postinstall.js | 5 +- 7 files changed, 148 insertions(+), 116 deletions(-) create mode 100755 build/azure-pipelines/linux/install.sh diff --git a/build/.cachesalt b/build/.cachesalt index d63bdc3118947..26ad5de2bcab8 100644 --- a/build/.cachesalt +++ b/build/.cachesalt @@ -1 +1 @@ -2023-06-12T12:55:48.130Z +2023-07-20T13:31:34.746Z diff --git a/build/azure-pipelines/linux/install.sh b/build/azure-pipelines/linux/install.sh new file mode 100755 index 0000000000000..b8960fc5fd41d --- /dev/null +++ b/build/azure-pipelines/linux/install.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -e + +# To workaround the issue of yarn not respecting the registry value from .npmrc +yarn config set registry "$NPM_REGISTRY" + +if [ -z "$CC" ] || [ -z "$CXX" ]; then + # Download clang based on chromium revision used by vscode + curl -s https://raw.githubusercontent.com/chromium/chromium/108.0.5359.215/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux + + # Download libcxx headers and objects from upstream electron releases + DEBUG=libcxx-fetcher \ + VSCODE_LIBCXX_OBJECTS_DIR=$PWD/.build/libcxx-objects \ + VSCODE_LIBCXX_HEADERS_DIR=$PWD/.build/libcxx_headers \ + VSCODE_LIBCXXABI_HEADERS_DIR=$PWD/.build/libcxxabi_headers \ + VSCODE_ARCH="$npm_config_arch" \ + node build/linux/libcxx-fetcher.js + + # Set compiler toolchain + # Flags for the client build are based on + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/arm.gni + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/compiler/BUILD.gn + # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/c++/BUILD.gn + export CC=$PWD/.build/CR_Clang/bin/clang + export CXX=$PWD/.build/CR_Clang/bin/clang++ + export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr" + export LDFLAGS="-stdlib=libc++ -fuse-ld=lld -flto=thin -L$PWD/.build/libcxx-objects -lc++abi -Wl,--lto-O0" + export VSCODE_REMOTE_CC=$(which gcc) + export VSCODE_REMOTE_CXX=$(which g++) +fi + +for i in {1..5}; do # try 5 times + yarn --frozen-lockfile --check-files && break + if [ $i -eq 3 ]; then + echo "Yarn failed too many times" >&2 + exit 1 + fi + echo "Yarn failed $i, trying again..." +done diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index a80139473f6d5..504bc0b304ba7 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -14,17 +14,6 @@ steps: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright - - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - set -e - sudo apt-get update - sudo apt-get install -y libxkbfile-dev pkg-config libsecret-1-dev libkrb5-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 - displayName: Setup build environment - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | set -e diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index f366cf672f7ea..bf408683d9da9 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -41,15 +41,28 @@ steps: - script: tar -xzf $(Build.ArtifactStagingDirectory)/compilation.tar.gz displayName: Extract compilation output - - script: | - set -e - # Start X server - /etc/init.d/xvfb start - # Start dbus session - DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address) - echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT" - displayName: Setup system services - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64')) + - script: | + set -e + # Start X server + sudo apt-get update + sudo apt-get install -y pkg-config \ + libxss1 \ + dbus \ + xvfb \ + libgtk-3-0 \ + libgbm1 \ + libxkbfile-dev \ + libsecret-1-dev \ + libkrb5-dev + 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 + # Start dbus session + sudo mkdir -p /var/run/dbus + DBUS_LAUNCH_RESULT=$(sudo dbus-daemon --config-file=/usr/share/dbus-1/system.conf --print-address) + echo "##vso[task.setvariable variable=DBUS_SESSION_BUS_ADDRESS]$DBUS_LAUNCH_RESULT" + displayName: Setup system services - script: node build/setup-npm-registry.js $NPM_REGISTRY condition: and(succeeded(), ne(variables['NPM_REGISTRY'], 'none')) @@ -72,7 +85,10 @@ steps: - script: | set -e npm config set registry "$NPM_REGISTRY" --location=project - npm config set always-auth=true --location=project + # npm >v7 deprecated the `always-auth` config option, refs npm/cli@72a7eeb + # following is a workaround for yarn to send authorization header + # for GET requests to the registry. + echo "always-auth=true" >> .npmrc yarn config set registry "$NPM_REGISTRY" condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM & Yarn @@ -83,17 +99,6 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Authentication - # TODO@joaomoreno TODO@deepak1556 this should be part of the base image - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - - script: | - sudo apt-get update && sudo apt-get install -y ca-certificates curl gnupg - sudo mkdir -m 0755 -p /etc/apt/keyrings - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg - echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - sudo apt update && sudo apt install -y docker-ce-cli - displayName: Install Docker client - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - ${{ if and(ne(parameters.VSCODE_QUALITY, 'oss'), or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64'))) }}: - task: Docker@1 displayName: "Pull Docker image" @@ -105,74 +110,65 @@ steps: containerCommand: uname condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - ${{ if and(ne(parameters.VSCODE_QUALITY, 'oss'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: - - script: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - displayName: Register Docker QEMU - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), eq(variables['VSCODE_ARCH'], 'arm64')) - - - script: | - sudo apt-get update && sudo apt-get install -y libkrb5-dev - displayName: Install libkrb5-dev - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - - script: | - set -e + - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: + - script: | + set -e - for i in {1..5}; do # try 5 times - yarn --cwd build --frozen-lockfile --check-files && break - if [ $i -eq 3 ]; then - echo "Yarn failed too many times" >&2 - exit 1 - fi - echo "Yarn failed $i, trying again..." - done - - if [ -z "$CC" ] || [ -z "$CXX" ]; then - # Download clang based on chromium revision used by vscode - curl -s https://raw.githubusercontent.com/chromium/chromium/108.0.5359.215/tools/clang/scripts/update.py | python - --output-dir=$PWD/.build/CR_Clang --host-os=linux - # Download libcxx headers and objects from upstream electron releases - DEBUG=libcxx-fetcher \ - VSCODE_LIBCXX_OBJECTS_DIR=$PWD/.build/libcxx-objects \ - VSCODE_LIBCXX_HEADERS_DIR=$PWD/.build/libcxx_headers \ - VSCODE_LIBCXXABI_HEADERS_DIR=$PWD/.build/libcxxabi_headers \ - VSCODE_ARCH="$(NPM_ARCH)" \ - node build/linux/libcxx-fetcher.js - # Set compiler toolchain - # Flags for the client build are based on - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/arm.gni - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/compiler/BUILD.gn - # https://source.chromium.org/chromium/chromium/src/+/refs/tags/108.0.5359.215:build/config/c++/BUILD.gn - export CC=$PWD/.build/CR_Clang/bin/clang - export CXX=$PWD/.build/CR_Clang/bin/clang++ - export CXXFLAGS="-nostdinc++ -D__NO_INLINE__ -I$PWD/.build/libcxx_headers -isystem$PWD/.build/libcxx_headers/include -isystem$PWD/.build/libcxxabi_headers/include -fPIC -flto=thin -fsplit-lto-unit -D_LIBCPP_ABI_NAMESPACE=Cr" - export LDFLAGS="-stdlib=libc++ -fuse-ld=lld -flto=thin -L$PWD/.build/libcxx-objects -lc++abi -Wl,--lto-O0" - export VSCODE_REMOTE_CC=$(which gcc) - export VSCODE_REMOTE_CXX=$(which g++) - fi - - for i in {1..5}; do # try 5 times - yarn --frozen-lockfile --check-files && break - if [ $i -eq 3 ]; then - echo "Yarn failed too many times" >&2 - exit 1 - fi - echo "Yarn failed $i, trying again..." - done - env: - npm_config_arch: $(NPM_ARCH) - ELECTRON_SKIP_BINARY_DOWNLOAD: 1 - PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 - GITHUB_TOKEN: "$(github-distro-mixin-password)" - ${{ if and(ne(parameters.VSCODE_QUALITY, 'oss'), or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64'))) }}: - VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-$(VSCODE_ARCH) - displayName: Install dependencies - condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + for i in {1..5}; do # try 5 times + yarn --cwd build --frozen-lockfile --check-files && break + if [ $i -eq 3 ]; then + echo "Yarn failed too many times" >&2 + exit 1 + fi + echo "Yarn failed $i, trying again..." + done + + docker run -e GITHUB_TOKEN -e npm_config_arch -e NPM_REGISTRY \ + -e VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME -e VSCODE_HOST_MOUNT \ + -e ELECTRON_SKIP_BINARY_DOWNLOAD -e PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD \ + -v /mnt/vss/_work/1/s:/home/builduser/vscode -v /mnt/vss/_work/1/s/.build/.netrc:/home/builduser/.netrc \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -u 1000:1000 \ + -w /home/builduser/vscode vscodehub.azurecr.io/vscode-linux-build-agent:bionic-$(VSCODE_ARCH) \ + /bin/bash -c "./build/azure-pipelines/linux/install.sh" + + sudo chown -R $USER:$USER /mnt/vss/_work/1/s + env: + npm_config_arch: $(NPM_ARCH) + NPM_REGISTRY: "$(NPM_REGISTRY)" + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" + ${{ if or(eq(parameters.VSCODE_ARCH, 'x64'), eq(parameters.VSCODE_ARCH, 'arm64')) }}: + VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:centos7-devtoolset8-$(VSCODE_ARCH) + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: node build/azure-pipelines/distro/mixin-npm condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) displayName: Mixin distro node modules + - ${{ else }}: + - script: | + set -e + + for i in {1..5}; do # try 5 times + yarn --frozen-lockfile --check-files && break + if [ $i -eq 3 ]; then + echo "Yarn failed too many times" >&2 + exit 1 + fi + echo "Yarn failed $i, trying again..." + done + env: + npm_config_arch: $(NPM_ARCH) + ELECTRON_SKIP_BINARY_DOWNLOAD: 1 + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 + GITHUB_TOKEN: "$(github-distro-mixin-password)" + displayName: Install dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | set -e node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt @@ -253,12 +249,32 @@ steps: mv $(Build.ArtifactStagingDirectory)/cli/$APP_NAME $(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH)/bin/$CLI_APP_NAME displayName: Make CLI executable + - script: | + set -e + docker run -v /mnt/vss/_work/1/s:/home/builduser/vscode \ + -v /mnt/vss/_work/1/s/.build/.netrc:/home/builduser/.netrc \ + -v /mnt/vss/_work/1/VSCode-linux-$(VSCODE_ARCH):/home/builduser/VSCode-linux-$(VSCODE_ARCH) \ + -u 1000:1000 \ + -w /home/builduser/vscode vscodehub.azurecr.io/vscode-linux-build-agent:bionic-$(VSCODE_ARCH) \ + yarn gulp "vscode-linux-$(VSCODE_ARCH)-prepare-deb" + displayName: Prepare deb package + - script: | set -e yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-deb" echo "##vso[task.setvariable variable=DEB_PATH]$(ls .build/linux/deb/*/deb/*.deb)" displayName: Build deb package + - script: | + set -e + docker run -v /mnt/vss/_work/1/s:/home/builduser/vscode \ + -v /mnt/vss/_work/1/s/.build/.netrc:/home/builduser/.netrc \ + -v /mnt/vss/_work/1/VSCode-linux-$(VSCODE_ARCH):/home/builduser/VSCode-linux-$(VSCODE_ARCH) \ + -u 1000:1000 \ + -w /home/builduser/vscode vscodehub.azurecr.io/vscode-linux-build-agent:bionic-$(VSCODE_ARCH) \ + yarn gulp "vscode-linux-$(VSCODE_ARCH)-prepare-rpm" + displayName: Prepare rpm package + - script: | set -e yarn gulp "vscode-linux-$(VSCODE_ARCH)-build-rpm" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 1be2db3b5fa3c..9ab1d7ce5d3b3 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -143,18 +143,6 @@ name: "$(Date:yyyyMMdd).$(Rev:r) (${{ parameters.VSCODE_QUALITY }})" resources: containers: - - container: vscode-bionic-x64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-x64 - endpoint: VSCodeHub - options: --user 0:0 --cap-add SYS_ADMIN - - container: vscode-arm64 - image: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-arm64 - endpoint: VSCodeHub - options: --user 0:0 --cap-add SYS_ADMIN - - container: vscode-armhf - image: vscodehub.azurecr.io/vscode-linux-build-agent:bionic-armhf - endpoint: VSCodeHub - options: --user 0:0 --cap-add SYS_ADMIN - container: snapcraft image: vscodehub.azurecr.io/vscode-linux-build-agent:snapcraft-x64 endpoint: VSCodeHub @@ -382,7 +370,6 @@ stages: - ${{ if eq(variables['VSCODE_CIBUILD'], true) }}: - job: Linuxx64UnitTest displayName: Unit Tests - container: vscode-bionic-x64 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 @@ -398,7 +385,6 @@ stages: VSCODE_RUN_SMOKE_TESTS: false - job: Linuxx64IntegrationTest displayName: Integration Tests - container: vscode-bionic-x64 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 @@ -414,7 +400,6 @@ stages: VSCODE_RUN_SMOKE_TESTS: false - job: Linuxx64SmokeTest displayName: Smoke Tests - container: vscode-bionic-x64 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 @@ -431,7 +416,6 @@ stages: - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX, true)) }}: - job: Linuxx64 - container: vscode-bionic-x64 variables: VSCODE_ARCH: x64 NPM_ARCH: x64 @@ -458,7 +442,6 @@ stages: - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARMHF, true)) }}: - job: LinuxArmhf - container: vscode-armhf variables: VSCODE_ARCH: armhf NPM_ARCH: arm @@ -474,7 +457,6 @@ stages: - ${{ if and(eq(variables['VSCODE_CIBUILD'], false), eq(parameters.VSCODE_BUILD_LINUX_ARM64, true)) }}: - job: LinuxArm64 - container: vscode-arm64 variables: VSCODE_ARCH: arm64 NPM_ARCH: arm64 diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 90f75ccfabd7f..0d7d3c5b7f80c 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -297,12 +297,14 @@ const BUILD_TARGETS = [ BUILD_TARGETS.forEach(({ arch }) => { const debArch = getDebPackageArch(arch); const prepareDebTask = task.define(`vscode-linux-${arch}-prepare-deb`, task.series(util.rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch))); - const buildDebTask = task.define(`vscode-linux-${arch}-build-deb`, task.series(prepareDebTask, buildDebPackage(arch))); + gulp.task(prepareDebTask); + const buildDebTask = task.define(`vscode-linux-${arch}-build-deb`, buildDebPackage(arch)); gulp.task(buildDebTask); const rpmArch = getRpmPackageArch(arch); const prepareRpmTask = task.define(`vscode-linux-${arch}-prepare-rpm`, task.series(util.rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch))); - const buildRpmTask = task.define(`vscode-linux-${arch}-build-rpm`, task.series(prepareRpmTask, buildRpmPackage(arch))); + gulp.task(prepareRpmTask); + const buildRpmTask = task.define(`vscode-linux-${arch}-build-rpm`, buildRpmPackage(arch)); gulp.task(buildRpmTask); const prepareSnapTask = task.define(`vscode-linux-${arch}-prepare-snap`, task.series(util.rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch))); diff --git a/build/npm/postinstall.js b/build/npm/postinstall.js index d9280ffb1eb87..09df602a3bf5d 100644 --- a/build/npm/postinstall.js +++ b/build/npm/postinstall.js @@ -53,7 +53,10 @@ function yarnInstall(dir, opts) { console.log(`Installing dependencies in ${dir} inside container ${process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME']}...`); opts.cwd = root; - run('docker', ['run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `/mnt/vss/_work/1/s:/root/vscode`, '-v', `/mnt/vss/_work/1/s/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); + if (process.env['npm_config_arch'] === 'arm64') { + run('sudo', ['docker', 'run', '--rm', '--privileged', 'multiarch/qemu-user-static', '--reset', '-p', 'yes'], opts); + } + run('sudo', ['docker', 'run', '-e', 'GITHUB_TOKEN', '-e', 'npm_config_arch', '-v', `${process.env['VSCODE_HOST_MOUNT']}:/root/vscode`, '-v', `${process.env['VSCODE_HOST_MOUNT']}/.build/.netrc:/root/.netrc`, process.env['VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME'], 'yarn', '--cwd', dir, ...args], opts); run('sudo', ['chown', '-R', `${userinfo.uid}:${userinfo.gid}`, `${dir}/node_modules`], opts); } else { console.log(`Installing dependencies in ${dir}...`); From 2509b7f50472eac94b4f23671544571ba91a786e Mon Sep 17 00:00:00 2001 From: Robo Date: Thu, 20 Jul 2023 23:41:43 -0700 Subject: [PATCH 109/216] ci: fix alpine build (#188446) --- build/azure-pipelines/alpine/product-build-alpine.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index 1a7d33972686f..fd629627d1fe4 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -3,10 +3,6 @@ steps: inputs: versionSpec: "16.x" - - script: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - displayName: "Register Docker QEMU" - condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64')) - - template: ../distro/download-distro.yml - task: AzureKeyVault@1 @@ -87,6 +83,7 @@ steps: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" VSCODE_REMOTE_DEPENDENCIES_CONTAINER_NAME: vscodehub.azurecr.io/vscode-linux-build-agent:alpine-$(VSCODE_ARCH) + VSCODE_HOST_MOUNT: "/mnt/vss/_work/1/s" displayName: Install build dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) From dc00865687c22895f8cf8ad67c6e69804cc0e907 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 09:34:18 +0200 Subject: [PATCH 110/216] skip flaky test #188375 --- .../vscode-api-tests/src/singlefolder-tests/terminal.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts index 36237a54de7ba..915d85a12b8b5 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/terminal.test.ts @@ -387,7 +387,7 @@ import { assertNoRpc, poll } from '../utils'; }); suite('window.onDidWriteTerminalData', () => { - test('should listen to all future terminal data events', (done) => { + test.skip('should listen to all future terminal data events', (done) => { const openEvents: string[] = []; const dataEvents: { name: string; data: string }[] = []; const closeEvents: string[] = []; From 7628b550c051b4f8fa12ec503d10d9db02a2f178 Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 21 Jul 2023 00:40:53 -0700 Subject: [PATCH 111/216] ci: fix oss node_modules cache pipeline (#188448) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: fix oss node_modules cache pipeline * align all "Install build dependencies" steps --------- Co-authored-by: João Moreno --- build/azure-pipelines/alpine/product-build-alpine.yml | 5 ++--- build/azure-pipelines/oss/product-build-pr-cache-linux.yml | 4 ++++ build/azure-pipelines/web/product-build-web.yml | 5 ++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index fd629627d1fe4..cff116ceaece6 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -63,9 +63,8 @@ steps: displayName: "Pull image" condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - - script: | - sudo apt-get update && sudo apt-get install -y libkrb5-dev - displayName: Install libkrb5-dev + - script: sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install build dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - script: | diff --git a/build/azure-pipelines/oss/product-build-pr-cache-linux.yml b/build/azure-pipelines/oss/product-build-pr-cache-linux.yml index 97eba56abc3c7..97c5ddd78ffb2 100644 --- a/build/azure-pipelines/oss/product-build-pr-cache-linux.yml +++ b/build/azure-pipelines/oss/product-build-pr-cache-linux.yml @@ -39,6 +39,10 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Authentication + - script: sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install build dependencies + condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) + - script: | set -e for i in {1..5}; do # try 5 times diff --git a/build/azure-pipelines/web/product-build-web.yml b/build/azure-pipelines/web/product-build-web.yml index 8fcebf5b259af..95d1b6afefd03 100644 --- a/build/azure-pipelines/web/product-build-web.yml +++ b/build/azure-pipelines/web/product-build-web.yml @@ -53,9 +53,8 @@ steps: condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'), ne(variables['NPM_REGISTRY'], 'none')) displayName: Setup NPM Authentication - - script: | - sudo apt-get update && sudo apt-get install -y libkrb5-dev - displayName: Install libkrb5-dev + - script: sudo apt-get update && sudo apt-get install -y libkrb5-dev + displayName: Install build dependencies condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true')) - script: | From 4df4f694c35ec60bc523c321dd40bb6b7862bf1b Mon Sep 17 00:00:00 2001 From: Robo Date: Fri, 21 Jul 2023 00:51:34 -0700 Subject: [PATCH 112/216] ci: install qemu for alpine arm64 (#188455) --- build/azure-pipelines/alpine/product-build-alpine.yml | 1 + build/azure-pipelines/product-build.yml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/build/azure-pipelines/alpine/product-build-alpine.yml b/build/azure-pipelines/alpine/product-build-alpine.yml index cff116ceaece6..44af6a76985d2 100644 --- a/build/azure-pipelines/alpine/product-build-alpine.yml +++ b/build/azure-pipelines/alpine/product-build-alpine.yml @@ -78,6 +78,7 @@ steps: echo "Yarn failed $i, trying again..." done env: + npm_config_arch: $(NPM_ARCH) ELECTRON_SKIP_BINARY_DOWNLOAD: 1 PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1 GITHUB_TOKEN: "$(github-distro-mixin-password)" diff --git a/build/azure-pipelines/product-build.yml b/build/azure-pipelines/product-build.yml index 9ab1d7ce5d3b3..2d4ad8aa84a0e 100644 --- a/build/azure-pipelines/product-build.yml +++ b/build/azure-pipelines/product-build.yml @@ -481,6 +481,7 @@ stages: - job: LinuxAlpine variables: VSCODE_ARCH: x64 + NPM_ARCH: x64 steps: - template: alpine/product-build-alpine.yml @@ -489,6 +490,7 @@ stages: timeoutInMinutes: 120 variables: VSCODE_ARCH: arm64 + NPM_ARCH: arm64 steps: - template: alpine/product-build-alpine.yml From 5b48914b4ca10c4c70c4b06ae18cb074fb22dfd1 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 21 Jul 2023 10:16:36 +0200 Subject: [PATCH 113/216] fix https://github.com/microsoft/vscode-copilot-release/issues/283 (#188458) --- .../workbench/contrib/inlineChat/browser/inlineChatController.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 38e2a9bda1172..425347ef060da 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -630,6 +630,7 @@ export class InlineChatController implements IEditorContribution { } } this._ctxResponseTypes.set(responseTypes); + this._ctxDidEdit.set(this._activeSession.hasChangedText); if (response instanceof EmptyResponse) { // show status message From 3987f96f95c36326c9466115646ed03a36574a5e Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 10:44:15 +0200 Subject: [PATCH 114/216] fix #188463 --- .../contrib/extensions/browser/fileBasedRecommendations.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts index fed47c1958c02..996682a745537 100644 --- a/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts +++ b/src/vs/workbench/contrib/extensions/browser/fileBasedRecommendations.ts @@ -55,6 +55,7 @@ export class FileBasedRecommendations extends ExtensionRecommendations { private readonly recommendationsByPattern = new Map>(); private readonly fileBasedRecommendations = new Map(); private readonly fileBasedImportantRecommendations = new Set(); + private readonly processedFileExtensions: string[] = []; get recommendations(): ReadonlyArray { const recommendations: ExtensionRecommendation[] = []; @@ -156,7 +157,11 @@ export class FileBasedRecommendations extends ExtensionRecommendations { return; } - this.promptRecommendedExtensionForFileExtension(uri, extname(uri).toLowerCase()); + const fileExtension = extname(uri).toLowerCase(); + if (!this.processedFileExtensions.includes(fileExtension)) { + this.processedFileExtensions.push(fileExtension); + this.promptRecommendedExtensionForFileExtension(uri, fileExtension); + } } /** From fdac0768d11dcbdad04431d11de54b7b9151c4ee Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 21 Jul 2023 11:11:31 +0200 Subject: [PATCH 115/216] likely fix https://github.com/microsoft/vscode/issues/188208 (#188468) --- .../contrib/inlineChat/browser/inlineChatStrategies.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index c46e3387aa0d8..c3a08e55ce986 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -66,7 +66,9 @@ export class PreviewStrategy extends EditModeStrategy { this._ctxDocumentChanged = CTX_INLINE_CHAT_DOCUMENT_CHANGED.bindTo(contextKeyService); this._listener = Event.debounce(_session.textModelN.onDidChangeContent.bind(_session.textModelN), () => { }, 350)(_ => { - this._ctxDocumentChanged.set(!_session.textModelN.equalsTextBuffer(_session.textModel0.getTextBuffer())); + if (!_session.textModelN.isDisposed() && !_session.textModel0.isDisposed()) { + this._ctxDocumentChanged.set(_session.hasChangedText); + } }); } From c815a857ddb5c90c023dda8f4c8e397984b34365 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 11:27:07 +0200 Subject: [PATCH 116/216] Fix syncing extensions when new property is added (#188462) - make isApplicationScoped property optional - bump the version so that older clients wont remove isApplicationScoped - make the client compatible when new properties are added later --- .../userDataSync/common/extensionsMerge.ts | 22 ++++++++++--------- .../userDataSync/common/extensionsSync.ts | 5 +++-- .../test/common/extensionsMerge.test.ts | 6 +---- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/userDataSync/common/extensionsMerge.ts b/src/vs/platform/userDataSync/common/extensionsMerge.ts index 905a48be4e3d6..9ec07dceb5098 100644 --- a/src/vs/platform/userDataSync/common/extensionsMerge.ts +++ b/src/vs/platform/userDataSync/common/extensionsMerge.ts @@ -278,7 +278,7 @@ function areSame(fromExtension: ISyncExtension, toExtension: ISyncExtension, che return false; } - if (fromExtension.isApplicationScoped !== toExtension.isApplicationScoped) { + if (!!fromExtension.isApplicationScoped !== !!toExtension.isApplicationScoped) { /* extension application scope has changed */ return false; } @@ -395,30 +395,32 @@ function isSameExtensionState(a: IStringDictionary = {}, b: IStringDictiona // massage incoming extension - add optional properties function massageIncomingExtension(extension: ISyncExtension): ISyncExtension { - return { ...extension, ...{ disabled: !!extension.disabled, installed: !!extension.installed, isApplicationScoped: !!extension.isApplicationScoped } }; + return { ...extension, ...{ disabled: !!extension.disabled, installed: !!extension.installed } }; } // massage outgoing extension - remove optional properties function massageOutgoingExtension(extension: ISyncExtension, key: string): ISyncExtension { const massagedExtension: ISyncExtension = { + ...extension, identifier: { id: extension.identifier.id, uuid: key.startsWith('uuid:') ? key.substring('uuid:'.length) : undefined }, - version: extension.version, /* set following always so that to differentiate with older clients */ preRelease: !!extension.preRelease, pinned: !!extension.pinned, - isApplicationScoped: !!extension.isApplicationScoped, }; - if (extension.disabled) { - massagedExtension.disabled = true; + if (!extension.disabled) { + delete massagedExtension.disabled; } - if (extension.installed) { - massagedExtension.installed = true; + if (!extension.installed) { + delete massagedExtension.installed; } - if (extension.state) { - massagedExtension.state = extension.state; + if (!extension.state) { + delete massagedExtension.state; + } + if (!extension.isApplicationScoped) { + delete massagedExtension.isApplicationScoped; } return massagedExtension; } diff --git a/src/vs/platform/userDataSync/common/extensionsSync.ts b/src/vs/platform/userDataSync/common/extensionsSync.ts index aa22d90107dee..bd85da7b12c5e 100644 --- a/src/vs/platform/userDataSync/common/extensionsSync.ts +++ b/src/vs/platform/userDataSync/common/extensionsSync.ts @@ -101,7 +101,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse */ /* Version 4: Change settings from `sync.${setting}` to `settingsSync.{setting}` */ /* Version 5: Introduce extension state */ - protected readonly version: number = 5; + /* Version 6: Added isApplicationScoped property */ + protected readonly version: number = 6; private readonly previewResource: URI = this.extUri.joinPath(this.syncPreviewFolder, 'extensions.json'); private readonly baseResource: URI = this.previewResource.with({ scheme: USER_DATA_SYNC_SCHEME, authority: 'base' }); @@ -377,7 +378,7 @@ export class LocalExtensionsProvider { .map(extension => { const { identifier, isBuiltin, manifest, preRelease, pinned, isApplicationScoped } = extension; const syncExntesion: ILocalSyncExtension = { identifier, preRelease, version: manifest.version, pinned: !!pinned }; - if (!isApplicationScopedExtension(manifest)) { + if (isApplicationScoped && !isApplicationScopedExtension(manifest)) { syncExntesion.isApplicationScoped = isApplicationScoped; } if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) { diff --git a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts index 1b2663593ec51..34d05f3a3a6ce 100644 --- a/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts +++ b/src/vs/platform/userDataSync/test/common/extensionsMerge.test.ts @@ -1344,7 +1344,7 @@ suite('ExtensionsMerge', () => { assert.deepStrictEqual(actual.local.added, []); assert.deepStrictEqual(actual.local.removed, []); assert.deepStrictEqual(actual.local.updated, []); - assert.deepStrictEqual(actual.remote?.all, localExtensions); + assert.deepStrictEqual(actual.remote?.all, [anExpectedSyncExtension({ identifier: { id: 'a', uuid: 'a' } })]); }); test('sync merging when applicaiton scope is changed locally', () => { @@ -1392,7 +1392,6 @@ suite('ExtensionsMerge', () => { pinned: false, preRelease: false, installed: true, - isApplicationScoped: false, ...extension }; } @@ -1403,7 +1402,6 @@ suite('ExtensionsMerge', () => { version: '1.0.0', pinned: false, preRelease: false, - isApplicationScoped: false, ...extension }; } @@ -1415,7 +1413,6 @@ suite('ExtensionsMerge', () => { pinned: false, preRelease: false, installed: true, - isApplicationScoped: false, ...extension }; } @@ -1427,7 +1424,6 @@ suite('ExtensionsMerge', () => { pinned: false, preRelease: false, installed: true, - isApplicationScoped: false, ...extension }; } From 34645e40a970e148c403434e2c1879e2215dffd9 Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 21 Jul 2023 12:18:07 +0200 Subject: [PATCH 117/216] Improve message of performance error fixes https://github.com/microsoft/vscode-internalbacklog/issues/4472 --- src/vs/platform/profiling/common/profilingTelemetrySpec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/profiling/common/profilingTelemetrySpec.ts b/src/vs/platform/profiling/common/profilingTelemetrySpec.ts index 2fa784bbc0406..cd85a0d7385bc 100644 --- a/src/vs/platform/profiling/common/profilingTelemetrySpec.ts +++ b/src/vs/platform/profiling/common/profilingTelemetrySpec.ts @@ -67,11 +67,11 @@ class PerformanceError extends Error { readonly selfTime: number; constructor(data: SampleData) { - super('[PerfSampleError]'); + super(`PerfSampleError: by ${data.source} in ${data.sample.location}`); this.name = 'PerfSampleError'; this.selfTime = data.sample.selfTime; const trace = [data.sample.absLocation, ...data.sample.caller.map(c => c.absLocation)]; - this.stack = `${this.message} by ${data.source} in ${data.sample.location}\n\t at ${trace.join('\n\t at ')}`; + this.stack = `\n\t at ${trace.join('\n\t at ')}`; } } From eda33fc3e44ec06415e4ff4837a72ef97fde7089 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 21 Jul 2023 12:27:22 +0200 Subject: [PATCH 118/216] Fixes #184900. Enables advanced diff algorithm by default. (#188474) --- src/vs/editor/common/config/editorConfigurationSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/common/config/editorConfigurationSchema.ts b/src/vs/editor/common/config/editorConfigurationSchema.ts index e127ddc675edb..d12ad4189c757 100644 --- a/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -195,7 +195,7 @@ const editorConfiguration: IConfigurationNode = { 'diffEditor.diffAlgorithm': { type: 'string', enum: ['legacy', 'advanced'], - default: 'legacy', + default: 'advanced', markdownEnumDescriptions: [ nls.localize('diffAlgorithm.legacy', "Uses the legacy diffing algorithm."), nls.localize('diffAlgorithm.advanced', "Uses the advanced diffing algorithm."), From 76415ef0b1f60e0479bdfee173c1a4f97e785b52 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 21 Jul 2023 13:06:37 +0200 Subject: [PATCH 119/216] Fixes illegal value for lineNumber error (#188479) --- .../inlineCompletions/browser/inlineCompletionsController.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index e8699277b347f..b88014b9bbfca 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -179,9 +179,9 @@ export class InlineCompletionsController extends Disposable { if (state.completion.semanticId !== lastInlineCompletionId) { lastInlineCompletionId = state.completion.semanticId; + const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); this.audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { - const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); alert(state.ghostText.renderForScreenReader(lineText)); } }); From eeb718aee49ab5dd7c0eb6377444ed2e4ab6acfe Mon Sep 17 00:00:00 2001 From: Johannes Date: Fri, 21 Jul 2023 14:35:41 +0200 Subject: [PATCH 120/216] fix https://github.com/microsoft/vscode/issues/188317 --- .../contrib/inlineChat/browser/inlineChatActions.ts | 6 +++--- .../contrib/inlineChat/browser/inlineChatWidget.ts | 10 ++++++++-- .../workbench/contrib/inlineChat/common/inlineChat.ts | 2 ++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts index 341a38fb18111..fa9f70e5179c5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatActions.ts @@ -10,7 +10,7 @@ import { EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget2 } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, InlineChatResponseType, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChateResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, MENU_INLINE_CHAT_WIDGET_TOGGLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_LAST_RESPONSE_TYPE, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_ACCEPT_CHANGES, ACTION_REGENERATE_RESPONSE, InlineChatResponseType, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChateResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { localize } from 'vs/nls'; import { IAction2Options, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -279,7 +279,7 @@ export class PreviousFromHistory extends AbstractInlineChatAction { super({ id: 'inlineChat.previousFromHistory', title: localize('previousFromHistory', 'Previous From History'), - precondition: CTX_INLINE_CHAT_FOCUSED, + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_START), keybinding: { weight: KeybindingWeight.EditorCore + 10, // win against core_command primary: KeyMod.CtrlCmd | KeyCode.UpArrow, @@ -298,7 +298,7 @@ export class NextFromHistory extends AbstractInlineChatAction { super({ id: 'inlineChat.nextFromHistory', title: localize('nextFromHistory', 'Next From History'), - precondition: CTX_INLINE_CHAT_FOCUSED, + precondition: ContextKeyExpr.and(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_INNER_CURSOR_END), keybinding: { weight: KeybindingWeight.EditorCore + 10, // win against core_command primary: KeyMod.CtrlCmd | KeyCode.DownArrow, diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 385770db29b59..78a6e72367545 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -12,7 +12,7 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { EventType, Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; @@ -160,6 +160,8 @@ export class InlineChatWidget { private readonly _ctxMessageCropState: IContextKey<'cropped' | 'not_cropped' | 'expanded'>; private readonly _ctxInnerCursorFirst: IContextKey; private readonly _ctxInnerCursorLast: IContextKey; + private readonly _ctxInnerCursorStart: IContextKey; + private readonly _ctxInnerCursorEnd: IContextKey; private readonly _ctxInputEditorFocused: IContextKey; private readonly _progressBar: ProgressBar; @@ -230,16 +232,18 @@ export class InlineChatWidget { this._ctxInnerCursorFirst = CTX_INLINE_CHAT_INNER_CURSOR_FIRST.bindTo(this._contextKeyService); this._ctxInnerCursorLast = CTX_INLINE_CHAT_INNER_CURSOR_LAST.bindTo(this._contextKeyService); + this._ctxInnerCursorStart = CTX_INLINE_CHAT_INNER_CURSOR_START.bindTo(this._contextKeyService); + this._ctxInnerCursorEnd = CTX_INLINE_CHAT_INNER_CURSOR_END.bindTo(this._contextKeyService); this._ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(this._contextKeyService); // (1) inner cursor position (last/first line selected) const updateInnerCursorFirstLast = () => { const selection = this._inputEditor.getSelection(); + const fullRange = this._inputModel.getFullModelRange(); let onFirst = false; let onLast = false; if (selection.isEmpty()) { const selectionTop = this._inputEditor.getTopForPosition(selection.startLineNumber, selection.startColumn); - const fullRange = this._inputModel.getFullModelRange(); const firstViewLineTop = this._inputEditor.getTopForPosition(fullRange.startLineNumber, fullRange.startColumn); const lastViewLineTop = this._inputEditor.getTopForPosition(fullRange.endLineNumber, fullRange.endColumn); @@ -252,6 +256,8 @@ export class InlineChatWidget { } this._ctxInnerCursorFirst.set(onFirst); this._ctxInnerCursorLast.set(onLast); + this._ctxInnerCursorStart.set(fullRange.getStartPosition().equals(selection.getStartPosition())); + this._ctxInnerCursorEnd.set(fullRange.getEndPosition().equals(selection.getEndPosition())); }; this._store.add(this._inputEditor.onDidChangeCursorPosition(updateInnerCursorFirstLast)); updateInnerCursorFirstLast(); diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index e236132bf18f8..f11624e761e06 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -124,6 +124,8 @@ export const CTX_INLINE_CHAT_FOCUSED = new RawContextKey('inlineChatFoc export const CTX_INLINE_CHAT_EMPTY = new RawContextKey('inlineChatEmpty', false, localize('inlineChatEmpty', "Whether the interactive editor input is empty")); export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey('inlineChatInnerCursorFirst', false, localize('inlineChatInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line")); export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line")); +export const CTX_INLINE_CHAT_INNER_CURSOR_START = new RawContextKey('inlineChatInnerCursorStart', false, localize('inlineChatInnerCursorStart', "Whether the cursor of the iteractive editor input is on the start of the input")); +export const CTX_INLINE_CHAT_INNER_CURSOR_END = new RawContextKey('inlineChatInnerCursorEnd', false, localize('inlineChatInnerCursorEnd', "Whether the cursor of the iteractive editor input is on the end of the input")); export const CTX_INLINE_CHAT_MESSAGE_CROP_STATE = new RawContextKey<'cropped' | 'not_cropped' | 'expanded'>('inlineChatMarkdownMessageCropState', 'not_cropped', localize('inlineChatMarkdownMessageCropState', "Whether the interactive editor message is cropped, not cropped or expanded")); export const CTX_INLINE_CHAT_OUTER_CURSOR_POSITION = new RawContextKey<'above' | 'below' | ''>('inlineChatOuterCursorPosition', '', localize('inlineChatOuterCursorPosition', "Whether the cursor of the outer editor is above or below the interactive editor input")); export const CTX_INLINE_CHAT_HAS_ACTIVE_REQUEST = new RawContextKey('inlineChatHasActiveRequest', false, localize('inlineChatHasActiveRequest', "Whether interactive editor has an active request")); From 8d808bc39574de907a7a2014738218ef4fd69ffd Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 21 Jul 2023 15:02:39 +0200 Subject: [PATCH 121/216] Fixes #183228 (#188489) --- src/vs/editor/browser/editorBrowser.ts | 4 +- .../browser/widget/diffEditor.contribution.ts | 42 ++++++++++++------- .../editor/browser/widget/diffEditorWidget.ts | 4 +- .../diffEditorWidget2/accessibleDiffViewer.ts | 12 +++--- .../diffEditorWidget2/diffEditorWidget2.ts | 9 +++- src/vs/editor/common/editorContextKeys.ts | 1 + src/vs/monaco.d.ts | 4 +- .../browser/audioCues.contribution.ts | 6 +-- .../browser/actions/chatAccessibilityHelp.ts | 3 +- .../codeEditor/browser/diffEditorHelper.ts | 6 +-- 10 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 4d6c57410be56..d037337887ccc 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -1263,9 +1263,9 @@ export interface IDiffEditor extends editorCommon.IEditor { */ revealFirstDiff(): unknown; - diffReviewNext(): void; + accessibleDiffViewerNext(): void; - diffReviewPrev(): void; + accessibleDiffViewerPrev(): void; } /** diff --git a/src/vs/editor/browser/widget/diffEditor.contribution.ts b/src/vs/editor/browser/widget/diffEditor.contribution.ts index 2deb1fbc1338f..c7f1f9d5398c0 100644 --- a/src/vs/editor/browser/widget/diffEditor.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditor.contribution.ts @@ -7,25 +7,26 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction2, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { localize } from 'vs/nls'; import { ILocalizedString } from 'vs/platform/action/common/action'; -import { registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; - const accessibleDiffViewerCategory: ILocalizedString = { value: localize('accessibleDiffViewer', 'Accessible Diff Viewer'), original: 'Accessible Diff Viewer', }; -export class DiffReviewNext extends EditorAction2 { - public static id = 'editor.action.diffReview.next'; +export class AccessibleDiffViewerNext extends EditorAction2 { + public static id = 'editor.action.accessibleDiffViewer.next'; constructor() { super({ - id: DiffReviewNext.id, - title: { value: localize('editor.action.diffReview.next', "Go to Next Difference"), original: 'Go to Next Difference' }, + id: AccessibleDiffViewerNext.id, + title: { value: localize('editor.action.accessibleDiffViewer.next', "Go to Next Difference"), original: 'Go to Next Difference' }, category: accessibleDiffViewerCategory, precondition: ContextKeyExpr.has('isInDiffEditor'), keybinding: { @@ -38,17 +39,27 @@ export class DiffReviewNext extends EditorAction2 { public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { const diffEditor = findFocusedDiffEditor(accessor); - diffEditor?.diffReviewNext(); + diffEditor?.accessibleDiffViewerNext(); } } -export class DiffReviewPrev extends EditorAction2 { - public static id = 'editor.action.diffReview.prev'; +MenuRegistry.appendMenuItem(MenuId.EditorTitle, { + command: { + id: AccessibleDiffViewerNext.id, + title: localize('Open Accessible Diff Viewer', "Open Accessible Diff Viewer"), + }, + order: 10, + group: '2_diff', + when: EditorContextKeys.accessibleDiffViewerVisible.negate(), +}); + +export class AccessibleDiffViewerPrev extends EditorAction2 { + public static id = 'editor.action.accessibleDiffViewer.prev'; constructor() { super({ - id: DiffReviewPrev.id, - title: { value: localize('editor.action.diffReview.prev', "Go to Previous Difference"), original: 'Go to Previous Difference' }, + id: AccessibleDiffViewerPrev.id, + title: { value: localize('editor.action.accessibleDiffViewer.prev', "Go to Previous Difference"), original: 'Go to Previous Difference' }, category: accessibleDiffViewerCategory, precondition: ContextKeyExpr.has('isInDiffEditor'), keybinding: { @@ -61,7 +72,7 @@ export class DiffReviewPrev extends EditorAction2 { public override runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void { const diffEditor = findFocusedDiffEditor(accessor); - diffEditor?.diffReviewPrev(); + diffEditor?.accessibleDiffViewerPrev(); } } @@ -82,5 +93,8 @@ export function findFocusedDiffEditor(accessor: ServicesAccessor): IDiffEditor | return null; } -registerAction2(DiffReviewNext); -registerAction2(DiffReviewPrev); +CommandsRegistry.registerCommandAlias('editor.action.diffReview.next', AccessibleDiffViewerNext.id); +registerAction2(AccessibleDiffViewerNext); + +CommandsRegistry.registerCommandAlias('editor.action.diffReview.prev', AccessibleDiffViewerPrev.id); +registerAction2(AccessibleDiffViewerPrev); diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 5166c3fe56158..a8ac9eb70b580 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -445,11 +445,11 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE return dom.isAncestor(document.activeElement, this._domElement); } - public diffReviewNext(): void { + public accessibleDiffViewerNext(): void { this._reviewPane.next(); } - public diffReviewPrev(): void { + public accessibleDiffViewerPrev(): void { this._reviewPane.prev(); } diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts index cc0f3a5a7a04c..bb5aaca0e60c6 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/accessibleDiffViewer.ts @@ -35,9 +35,9 @@ import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioC import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; -const diffReviewInsertIcon = registerIcon('diff-review-insert', Codicon.add, localize('diffReviewInsertIcon', 'Icon for \'Insert\' in diff review.')); -const diffReviewRemoveIcon = registerIcon('diff-review-remove', Codicon.remove, localize('diffReviewRemoveIcon', 'Icon for \'Remove\' in diff review.')); -const diffReviewCloseIcon = registerIcon('diff-review-close', Codicon.close, localize('diffReviewCloseIcon', 'Icon for \'Close\' in diff review.')); +const accessibleDiffViewerInsertIcon = registerIcon('diff-review-insert', Codicon.add, localize('accessibleDiffViewerInsertIcon', 'Icon for \'Insert\' in accessible diff viewer.')); +const accessibleDiffViewerRemoveIcon = registerIcon('diff-review-remove', Codicon.remove, localize('accessibleDiffViewerRemoveIcon', 'Icon for \'Remove\' in accessible diff viewer.')); +const accessibleDiffViewerCloseIcon = registerIcon('diff-review-close', Codicon.close, localize('accessibleDiffViewerCloseIcon', 'Icon for \'Close\' in accessible diff viewer.')); export class AccessibleDiffViewer extends Disposable { constructor( @@ -345,7 +345,7 @@ class View extends Disposable { this._actionBar.push(new Action( 'diffreview.close', localize('label.close', "Close"), - 'close-diff-review ' + ThemeIcon.asClassName(diffReviewCloseIcon), + 'close-diff-review ' + ThemeIcon.asClassName(accessibleDiffViewerCloseIcon), true, async () => _model.close() ), { label: false, icon: true }); @@ -526,12 +526,12 @@ class View extends Disposable { case LineType.Added: rowClassName = 'diff-review-row line-insert'; lineNumbersExtraClassName = ' char-insert'; - spacerIcon = diffReviewInsertIcon; + spacerIcon = accessibleDiffViewerInsertIcon; break; case LineType.Deleted: rowClassName = 'diff-review-row line-delete'; lineNumbersExtraClassName = ' char-delete'; - spacerIcon = diffReviewRemoveIcon; + spacerIcon = accessibleDiffViewerRemoveIcon; break; } diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts index b4c0d0f648d14..0a9671f772c76 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts @@ -103,6 +103,11 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { isEmbeddedDiffEditorKey.set(this._options.isInEmbeddedEditor.read(reader)); })); + const accessibleDiffViewerVisibleContextKeyValue = EditorContextKeys.accessibleDiffViewerVisible.bindTo(this._contextKeyService); + this._register(autorun('update accessibleDiffViewerVisible context key', reader => { + accessibleDiffViewerVisibleContextKeyValue.set(this._accessibleDiffViewerVisible.read(reader)); + })); + this._domElement.appendChild(this.elements.root); this._rootSizeObserver = this._register(new ObservableElementSizeObserver(this.elements.root, options.dimension)); @@ -472,9 +477,9 @@ export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { }); } - diffReviewNext(): void { this._accessibleDiffViewer.next(); } + accessibleDiffViewerNext(): void { this._accessibleDiffViewer.next(); } - diffReviewPrev(): void { this._accessibleDiffViewer.prev(); } + accessibleDiffViewerPrev(): void { this._accessibleDiffViewer.prev(); } async waitForDiff(): Promise { const diffModel = this._diffModel.get(); diff --git a/src/vs/editor/common/editorContextKeys.ts b/src/vs/editor/common/editorContextKeys.ts index 899ad35781f3e..56bd035e9d047 100644 --- a/src/vs/editor/common/editorContextKeys.ts +++ b/src/vs/editor/common/editorContextKeys.ts @@ -27,6 +27,7 @@ export namespace EditorContextKeys { export const readOnly = new RawContextKey('editorReadonly', false, nls.localize('editorReadonly', "Whether the editor is read-only")); export const inDiffEditor = new RawContextKey('inDiffEditor', false, nls.localize('inDiffEditor', "Whether the context is a diff editor")); export const isEmbeddedDiffEditor = new RawContextKey('isEmbeddedDiffEditor', false, nls.localize('isEmbeddedDiffEditor', "Whether the context is an embedded diff editor")); + export const accessibleDiffViewerVisible = new RawContextKey('accessibleDiffViewerVisible', false, nls.localize('accessibleDiffViewerVisible', "Whether the accessible diff viewer is visible")); export const columnSelection = new RawContextKey('editorColumnSelection', false, nls.localize('editorColumnSelection', "Whether `editor.columnSelection` is enabled")); export const writable = readOnly.toNegated(); export const hasNonEmptySelection = new RawContextKey('editorHasSelection', false, nls.localize('editorHasSelection', "Whether the editor has text selected")); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 5011ecde3ccfc..8d88aa92a7fdd 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -6133,8 +6133,8 @@ declare namespace monaco.editor { * Update the editor's options after the editor has been created. */ updateOptions(newOptions: IDiffEditorOptions): void; - diffReviewNext(): void; - diffReviewPrev(): void; + accessibleDiffViewerNext(): void; + accessibleDiffViewerPrev(): void; } export class FontInfo extends BareFontInfo { diff --git a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts index 6de43d8e9a458..f34f32e9be253 100644 --- a/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts +++ b/src/vs/workbench/contrib/audioCues/browser/audioCues.contribution.ts @@ -98,15 +98,15 @@ Registry.as(ConfigurationExtensions.Configuration).regis ...audioCueFeatureBase, }, 'audioCues.diffLineInserted': { - 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in diff review mode or to the next/previous change"), + 'description': localize('audioCues.diffLineInserted', "Plays a sound when the focus moves to an inserted line in accessible diff viewer mode or to the next/previous change"), ...audioCueFeatureBase, }, 'audioCues.diffLineDeleted': { - 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in diff review mode or to the next/previous change"), + 'description': localize('audioCues.diffLineDeleted', "Plays a sound when the focus moves to a deleted line in accessible diff viewer mode or to the next/previous change"), ...audioCueFeatureBase, }, 'audioCues.diffLineModified': { - 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in diff review mode or to the next/previous change"), + 'description': localize('audioCues.diffLineModified', "Plays a sound when the focus moves to a modified line in accessible diff viewer mode or to the next/previous change"), ...audioCueFeatureBase, }, 'audioCues.notebookCellCompleted': { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index c9de55bc0706a..04647bb8edf24 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -13,6 +13,7 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { AccessibleDiffViewerNext } from 'vs/editor/browser/widget/diffEditor.contribution'; export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string { const keybindingService = accessor.get(IKeybindingService); @@ -36,7 +37,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane } content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with a /. Type / to discover such ready-made commands.")); content.push(localize('inlineChat.fix', "If a fix action is invoked, a response will indicate the problem with the current code. A diff editor will be rendered and can be reached by tabbing.")); - const diffReviewKeybinding = keybindingService.lookupKeybinding('editor.action.diffReview.next')?.getAriaLabel(); + const diffReviewKeybinding = keybindingService.lookupKeybinding(AccessibleDiffViewerNext.id)?.getAriaLabel(); content.push(diffReviewKeybinding ? localize('inlineChat.diff', "Once in the diff editor, enter review mode with ({0}). Use up and down arrows to navigate lines with the proposed changes.", diffReviewKeybinding) : localize('inlineChat.diffNoKb', "Tab again to enter the Diff editor with the changes and enter review mode with the Go to Next Difference Command. Use Up/DownArrow to navigate lines with the proposed changes.")); content.push(localize('inlineChat.toolbar', "Use tab to reach conditional parts like commands, status, message responses and more.")); } diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index c759bc6f8b535..f0da57c7cc944 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -7,7 +7,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { registerDiffEditorContribution } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { DiffReviewNext, DiffReviewPrev } from 'vs/editor/browser/widget/diffEditor.contribution'; +import { AccessibleDiffViewerNext, AccessibleDiffViewerPrev } from 'vs/editor/browser/widget/diffEditor.contribution'; import { DiffEditorWidget2 } from 'vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2'; import { EmbeddedDiffEditorWidget, EmbeddedDiffEditorWidget2 } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IDiffEditorContribution } from 'vs/editor/common/editorCommon'; @@ -86,8 +86,8 @@ function createScreenReaderHelp(): IDisposable { const codeEditorService = accessor.get(ICodeEditorService); const keybindingService = accessor.get(IKeybindingService); - const next = keybindingService.lookupKeybinding(DiffReviewNext.id)?.getAriaLabel(); - const previous = keybindingService.lookupKeybinding(DiffReviewPrev.id)?.getAriaLabel(); + const next = keybindingService.lookupKeybinding(AccessibleDiffViewerNext.id)?.getAriaLabel(); + const previous = keybindingService.lookupKeybinding(AccessibleDiffViewerPrev.id)?.getAriaLabel(); if (!(editorService.activeTextEditorControl instanceof DiffEditorWidget2)) { return; From ab8498ab2a4bb8772034fad14a48b0a41b635caa Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 21 Jul 2023 15:07:43 +0200 Subject: [PATCH 122/216] Fixes https://github.com/microsoft/vscode/issues/182782 (#188478) --- .../common/diff/standardLinesDiffComputer.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/vs/editor/common/diff/standardLinesDiffComputer.ts b/src/vs/editor/common/diff/standardLinesDiffComputer.ts index 942308e0f0916..046e8ce0ca6af 100644 --- a/src/vs/editor/common/diff/standardLinesDiffComputer.ts +++ b/src/vs/editor/common/diff/standardLinesDiffComputer.ts @@ -169,6 +169,35 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer { } } + // Make sure all ranges are valid + assertFn(() => { + function validatePosition(pos: Position, lines: string[]): boolean { + if (pos.lineNumber < 1 || pos.lineNumber > lines.length) { return false; } + const line = lines[pos.lineNumber - 1]; + if (pos.column < 1 || pos.column > line.length + 1) { return false; } + return true; + } + + function validateRange(range: LineRange, lines: string[]): boolean { + if (range.startLineNumber < 1 || range.startLineNumber > lines.length + 1) { return false; } + if (range.endLineNumberExclusive < 1 || range.endLineNumberExclusive > lines.length + 1) { return false; } + return true; + } + + for (const c of changes) { + if (!c.innerChanges) { return false; } + for (const ic of c.innerChanges) { + const valid = validatePosition(ic.modifiedRange.getStartPosition(), modifiedLines) && validatePosition(ic.modifiedRange.getEndPosition(), modifiedLines) && + validatePosition(ic.originalRange.getStartPosition(), originalLines) && validatePosition(ic.originalRange.getEndPosition(), originalLines); + if (!valid) { return false; } + } + if (!validateRange(c.modifiedRange, modifiedLines) || !validateRange(c.originalRange, originalLines)) { + return false; + } + } + return true; + }); + return new LinesDiff(changes, moves, hitTimeout); } From 501a0190687e5bd799ff6844d9563b45c0e3ed53 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 21 Jul 2023 15:54:23 +0200 Subject: [PATCH 123/216] Fixes #188388 (#188495) --- .../browser/inlineCompletionContextKeys.ts | 11 ++++++----- .../browser/inlineCompletionsController.ts | 6 +++--- .../browser/inlineCompletionsModel.ts | 16 ++++++++-------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts index b027df1b39cd3..30556a12cc6c6 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionContextKeys.ts @@ -30,12 +30,13 @@ export class InlineCompletionContextKeys extends Disposable { this._register(autorun('update context key: inlineCompletionVisible, suppressSuggestions', (reader) => { const model = this.model.read(reader); - const suggestion = model?.selectedInlineCompletion.read(reader); - const ghostText = model?.ghostText.read(reader); - this.inlineCompletionVisible.set(ghostText !== undefined && !ghostText.isEmpty()); + const state = model?.state.read(reader); + + const isInlineCompletionVisible = !!state?.inlineCompletion && state?.ghostText !== undefined && !state?.ghostText.isEmpty(); + this.inlineCompletionVisible.set(isInlineCompletionVisible); - if (ghostText && suggestion) { - this.suppressSuggestions.set(suggestion.inlineCompletion.source.inlineCompletions.suppressSuggestions); + if (state?.ghostText && state?.inlineCompletion) { + this.suppressSuggestions.set(state.inlineCompletion.inlineCompletion.source.inlineCompletions.suppressSuggestions); } })); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index b88014b9bbfca..d764b7b0d751b 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -172,13 +172,13 @@ export class InlineCompletionsController extends Disposable { this._register(autorun('play audio cue & read suggestion', reader => { const model = this.model.read(reader); const state = model?.state.read(reader); - if (!model || !state || !state.completion) { + if (!model || !state || !state.inlineCompletion) { lastInlineCompletionId = undefined; return; } - if (state.completion.semanticId !== lastInlineCompletionId) { - lastInlineCompletionId = state.completion.semanticId; + if (state.inlineCompletion.semanticId !== lastInlineCompletionId) { + lastInlineCompletionId = state.inlineCompletion.semanticId; const lineText = model.textModel.getLineContent(state.ghostText.lineNumber); this.audioCueService.playAudioCue(AudioCue.inlineSuggestion).then(() => { if (this.editor.getOption(EditorOption.screenReaderAnnounceInlineSuggestion)) { diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 05d6de2fed1f9..58e38cd7a51d1 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -65,7 +65,7 @@ export class InlineCompletionsModel extends Disposable { let lastItem: InlineCompletionWithUpdatedRange | undefined = undefined; this._register(autorun('call handleItemDidShow', reader => { const item = this.state.read(reader); - const completion = item?.completion; + const completion = item?.inlineCompletion; if (completion?.semanticId !== lastItem?.semanticId) { lastItem = completion; if (completion) { @@ -191,7 +191,7 @@ export class InlineCompletionsModel extends Disposable { public readonly state = derived<{ suggestItem: SuggestItemInfo | undefined; - completion: InlineCompletionWithUpdatedRange | undefined; + inlineCompletion: InlineCompletionWithUpdatedRange | undefined; ghostText: GhostTextOrReplacement; } | undefined>('ghostTextAndCompletion', (reader) => { const model = this.textModel; @@ -225,7 +225,7 @@ export class InlineCompletionsModel extends Disposable { // Show an invisible ghost text to reserve space const ghostText = newGhostText ?? new GhostText(edit.range.endLineNumber, []); - return { ghostText, completion: augmentedCompletion?.completion, suggestItem }; + return { ghostText, inlineCompletion: augmentedCompletion?.completion, suggestItem }; } else { if (!this._isActive.read(reader)) { return undefined; } const item = this.selectedInlineCompletion.read(reader); @@ -235,7 +235,7 @@ export class InlineCompletionsModel extends Disposable { const mode = this._inlineSuggestMode.read(reader); const cursor = this.cursorPosition.read(reader); const ghostText = replacement.computeGhostText(model, mode, cursor); - return ghostText ? { ghostText, completion: item, suggestItem: undefined } : undefined; + return ghostText ? { ghostText, inlineCompletion: item, suggestItem: undefined } : undefined; } }); @@ -271,10 +271,10 @@ export class InlineCompletionsModel extends Disposable { } const state = this.state.get(); - if (!state || state.ghostText.isEmpty() || !state.completion) { + if (!state || state.ghostText.isEmpty() || !state.inlineCompletion) { return; } - const completion = state.completion.toInlineCompletion(undefined); + const completion = state.inlineCompletion.toInlineCompletion(undefined); editor.pushUndoStop(); if (completion.snippetInfo) { @@ -363,11 +363,11 @@ export class InlineCompletionsModel extends Disposable { } const state = this.state.get(); - if (!state || state.ghostText.isEmpty() || !state.completion) { + if (!state || state.ghostText.isEmpty() || !state.inlineCompletion) { return; } const ghostText = state.ghostText; - const completion = state.completion.toInlineCompletion(undefined); + const completion = state.inlineCompletion.toInlineCompletion(undefined); if (completion.snippetInfo || completion.filterText !== completion.insertText) { // not in WYSIWYG mode, partial commit might change completion, thus it is not supported From 2077a4c39738ecbb96247b240966802675e6267a Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 16:24:32 +0200 Subject: [PATCH 124/216] Show builtin profile templates in import flow (#188498) * Show builtin profile templates in import flow * feedback --- .../browser/userDataProfile.ts | 55 +++++++++++++++---- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 429f95af11e32..19973607ef42f 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -15,7 +15,7 @@ import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, UseDef import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, PROFILES_CATEGORY, PROFILE_FILTER, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ProfilesMenu, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TITLE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { URI } from 'vs/base/common/uri'; @@ -42,6 +42,8 @@ interface IProfileTemplateInfo { readonly url: string; } +type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo; + export class UserDataProfilesWorkbenchContribution extends Disposable implements IWorkbenchContribution { private readonly currentProfileContext: IContextKey; @@ -308,6 +310,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements private registerImportProfileAction(): IDisposable { const disposables = new DisposableStore(); const id = 'workbench.profiles.actions.importProfile'; + const that = this; disposables.add(registerAction2(class ImportProfileAction extends Action2 { constructor() { super({ @@ -340,21 +343,41 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements const disposables = new DisposableStore(); const quickPick = disposables.add(quickInputService.createQuickPick()); + const profileTemplateQuickPickItems = await that.getProfileTemplatesQuickPickItems(); + const updateQuickPickItems = (value?: string) => { - const selectFromFileItem: IQuickPickItem = { label: localize('import from file', "Create from profile template file") }; - quickPick.items = value ? [{ label: localize('import from url', "Create from profile template URL"), description: quickPick.value }, selectFromFileItem] : [selectFromFileItem]; + const quickPickItems: (IQuickPickItem | IQuickPickSeparator)[] = []; + if (value) { + quickPickItems.push({ label: quickPick.value, description: localize('import from url', "Import from URL") }); + } + quickPickItems.push({ label: localize('import from file', "Select File...") }); + if (profileTemplateQuickPickItems.length) { + quickPickItems.push({ + type: 'separator', + label: localize('templates', "Profile Templates") + }, ...profileTemplateQuickPickItems); + } + quickPick.items = quickPickItems; }; - quickPick.title = localize('import profile quick pick title', "Create Profile from Profile Template..."); - quickPick.placeholder = localize('import profile placeholder', "Provide profile template URL or select profile template file"); + + quickPick.title = localize('import profile quick pick title', "Import from Profile Template..."); + quickPick.placeholder = localize('import profile placeholder', "Provide Profile Template URL"); quickPick.ignoreFocusOut = true; disposables.add(quickPick.onDidChangeValue(updateQuickPickItems)); updateQuickPickItems(); quickPick.matchOnLabel = false; quickPick.matchOnDescription = false; disposables.add(quickPick.onDidAccept(async () => { + quickPick.hide(); + const selectedItem = quickPick.selectedItems[0]; + if (!selectedItem) { + return; + } try { - quickPick.hide(); - const profile = quickPick.selectedItems[0].description ? URI.parse(quickPick.value) : await this.getProfileUriFromFileSystem(fileDialogService); + if ((selectedItem).url) { + return await that.saveProfile(undefined, (selectedItem).url); + } + const profile = selectedItem.label === quickPick.value ? URI.parse(quickPick.value) : await this.getProfileUriFromFileSystem(fileDialogService); if (profile) { await userDataProfileImportExportService.importProfile(profile); } @@ -541,7 +564,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements selectBox.render(append(domNode, $('.profile-type-select-container'))); quickPick.widget = domNode; - const updateOptions = () => { + const updateQuickpickInfo = () => { const option = profileOptions[findOptionIndex()]; for (const resource of resources) { resource.picked = option.source && !isString(option.source) ? !option.source?.useDefaultFlags?.[resource.id] : true; @@ -549,10 +572,10 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements update(); }; - updateOptions(); + updateQuickpickInfo(); disposables.add(selectBox.onDidSelect(({ index }) => { source = profileOptions[index].source; - updateOptions(); + updateQuickpickInfo(); })); } @@ -701,6 +724,18 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements })); } + private async getProfileTemplatesQuickPickItems(): Promise { + const quickPickItems: IProfileTemplateQuickPickItem[] = []; + const profileTemplates = await this.getProfileTemplatesFromProduct(); + for (const template of profileTemplates) { + quickPickItems.push({ + label: template.name, + ...template + }); + } + return quickPickItems; + } + private async getProfileTemplatesFromProduct(): Promise { if (this.productService.profileTemplatesUrl) { try { From 21ae90ffce36e26ad0b001c1f84dbd6db3919493 Mon Sep 17 00:00:00 2001 From: Aaron Munger Date: Fri, 21 Jul 2023 08:04:46 -0700 Subject: [PATCH 125/216] Revert "treat soon-to-be-autosaved as saving" --- .../contrib/customEditor/browser/customEditorInput.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 3f40d26457497..964a6a0443ba7 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -22,7 +22,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ICustomEditorModel, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor'; import { IOverlayWebview, IWebviewService } from 'vs/workbench/contrib/webview/browser/webview'; import { IWebviewWorkbenchService, LazilyResolvedWebviewEditorInput } from 'vs/workbench/contrib/webviewPanel/browser/webviewWorkbenchService'; -import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService'; interface CustomEditorInputInitInfo { @@ -302,14 +302,6 @@ export class CustomEditorInput extends LazilyResolvedWebviewEditorInput { return (await this.rename(groupId, target))?.editor; } - override isSaving(): boolean { - if (this.isDirty() && !this.hasCapability(EditorInputCapabilities.Untitled) && this.filesConfigurationService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY) { - return true; // will be saved soon - } - - return super.isSaving(); - } - public override async revert(group: GroupIdentifier, options?: IRevertOptions): Promise { if (this._modelRef) { return this._modelRef.object.revert(options); From ed1de9e0e94dd4baf2657131d9826565229c2950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Fri, 21 Jul 2023 17:08:05 +0200 Subject: [PATCH 126/216] fixes #187885 (#188502) --- build/win32/Cargo.lock | 2 +- build/win32/Cargo.toml | 2 +- build/win32/inno_updater.exe | Bin 459776 -> 464896 bytes 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/win32/Cargo.lock b/build/win32/Cargo.lock index f83ae22dfc260..fb5217556906a 100644 --- a/build/win32/Cargo.lock +++ b/build/win32/Cargo.lock @@ -109,7 +109,7 @@ dependencies = [ [[package]] name = "inno_updater" -version = "0.10.0" +version = "0.10.1" dependencies = [ "byteorder", "crc", diff --git a/build/win32/Cargo.toml b/build/win32/Cargo.toml index faf7e7fe6d1a1..cf3cc9de80be5 100644 --- a/build/win32/Cargo.toml +++ b/build/win32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "inno_updater" -version = "0.10.0" +version = "0.10.1" authors = ["Microsoft "] build = "build.rs" diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index 941ebfe40814570787700b67b778d399a6b30c92..fa2fd26a466dedee7755c873a88cdb7168c38737 100644 GIT binary patch literal 464896 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 z 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νDc0~ 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 z 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 dfd4aKNDE0aFS_y5EW{2Q&4>}>!5 literal 459776 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
b;<@%^Zej$lYB^T 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 Date: Fri, 21 Jul 2023 17:30:45 +0200 Subject: [PATCH 127/216] Revert "Skip failing integration tests (#187424)" (#188459) This reverts commit 00db67e44079d78850922701c0e63e3053d48640. --- extensions/emmet/src/test/abbreviationAction.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts index beb3bfa1ef3e6..17ccacfc94a52 100644 --- a/extensions/emmet/src/test/abbreviationAction.test.ts +++ b/extensions/emmet/src/test/abbreviationAction.test.ts @@ -410,7 +410,7 @@ suite('Tests for Expand Abbreviations (HTML)', () => { // }); // }); - test.skip('No expanding when html is excluded in the settings in completion list', async () => { + test('No expanding when html is excluded in the settings in completion list', async () => { const oldConfig = workspace.getConfiguration('emmet').inspect('excludeLanguages')?.globalValue; await workspace.getConfiguration('emmet').update('excludeLanguages', ['html'], ConfigurationTarget.Global); await testHtmlCompletionProvider(new Selection(9, 6, 9, 6), '', '', true); @@ -469,7 +469,7 @@ suite('Tests for jsx, xml and xsl', () => { }); }); - test.skip('Expand abbreviation with no self closing tags for html', () => { + test('Expand abbreviation with no self closing tags for html', () => { return withRandomFileEditor('img', 'html', async (editor, _doc) => { editor.selection = new Selection(0, 6, 0, 6); await expandEmmetAbbreviation({ language: 'html' }); From 698b8adae8c61d61dd3323050bd2a8d922ac9abb Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 21 Jul 2023 17:37:33 +0200 Subject: [PATCH 128/216] Fixes #187939 (#188504) --- .../browser/widget/diffEditorWidget2/lineAlignment.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts b/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts index 4babd037b69e1..a3c29b25b4ebc 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/lineAlignment.ts @@ -559,10 +559,13 @@ function computeRangeAlignment( if (innerHunkAlignment) { for (const i of c.innerChanges || []) { if (i.originalRange.startColumn > 1 && i.modifiedRange.startColumn > 1) { - // There is some unmodified text on this line + // There is some unmodified text on this line before the diff emitAlignment(i.originalRange.startLineNumber, i.modifiedRange.startLineNumber); } - emitAlignment(i.originalRange.endLineNumber, i.modifiedRange.endLineNumber); + if (i.originalRange.endColumn < originalEditor.getModel()!.getLineMaxColumn(i.originalRange.endLineNumber)) { + // // There is some unmodified text on this line after the diff + emitAlignment(i.originalRange.endLineNumber, i.modifiedRange.endLineNumber); + } } } From a9c39b8e79b324598fa14b230a769657b174c8dd Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 08:43:04 -0700 Subject: [PATCH 129/216] fix #188442 --- .../contrib/codeEditor/browser/accessibility/accessibility.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 1b57154caed3f..d10802456cbab 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -28,7 +28,7 @@ class ToggleScreenReaderMode extends Action2 { when: accessibilityHelpIsShown }, { - primary: KeyMod.Alt | KeyCode.F3, + primary: KeyMod.Alt | KeyCode.F1 | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib + 10, }] }); From 34db0b6838c5d5ea247a0ee3ab8a89954b08e108 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 17:58:35 +0200 Subject: [PATCH 130/216] Integrate create profile with import flow (#188510) --- .../media/userDataProfileCreateWidget.css | 28 -- .../browser/userDataProfile.ts | 238 +---------------- .../browser/media/userDataProfileView.css | 46 +++- .../userDataProfileImportExportService.ts | 245 +++++++++++++++++- .../browser/userDataProfileManagement.ts | 25 +- .../userDataProfile/common/userDataProfile.ts | 8 + 6 files changed, 307 insertions(+), 283 deletions(-) delete mode 100644 src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css diff --git a/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css b/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css deleted file mode 100644 index 899cf1a074a4c..0000000000000 --- a/src/vs/workbench/contrib/userDataProfile/browser/media/userDataProfileCreateWidget.css +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.profile-type-widget { - display: flex; - margin: 0px 6px 8px 11px; - align-items: center; - justify-content: space-between; - font-size: 12px; -} - -.profile-type-widget>.profile-type-select-container { - overflow: hidden; - padding-left: 10px; - flex: 1; - display: flex; - align-items: center; - justify-content: center; -} - -.profile-type-widget>.profile-type-select-container>.monaco-select-box { - cursor: pointer; - line-height: 17px; - padding: 2px 23px 2px 8px; - border-radius: 2px; -} diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 19973607ef42f..60052d90c5566 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -3,18 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/userDataProfileCreateWidget'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; -import { Event } from 'vs/base/common/event'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, IMenuService, ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IUserDataProfile, IUserDataProfilesService, ProfileResourceType, UseDefaultProfileFlags } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, PROFILES_CATEGORY, PROFILE_FILTER, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ProfilesMenu, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TITLE } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { CURRENT_PROFILE_CONTEXT, HAS_PROFILES_CONTEXT, IS_CURRENT_PROFILE_TRANSIENT_CONTEXT, IS_PROFILE_IMPORT_IN_PROGRESS_CONTEXT, IUserDataProfileImportExportService, IUserDataProfileManagementService, IUserDataProfileService, PROFILES_CATEGORY, PROFILE_FILTER, IS_PROFILE_EXPORT_IN_PROGRESS_CONTEXT, ProfilesMenu, PROFILES_ENABLEMENT_CONTEXT, PROFILES_TITLE, IProfileTemplateInfo } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; @@ -25,22 +23,6 @@ import { IWorkspaceTagsService } from 'vs/workbench/contrib/tags/common/workspac import { getErrorMessage } from 'vs/base/common/errors'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { IProductService } from 'vs/platform/product/common/productService'; -import { IRequestService, asJson } from 'vs/platform/request/common/request'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { ILogService } from 'vs/platform/log/common/log'; -import Severity from 'vs/base/common/severity'; -import { $, append } from 'vs/base/browser/dom'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; -import { isString } from 'vs/base/common/types'; - -interface IProfileTemplateInfo { - readonly name: string; - readonly url: string; -} type IProfileTemplateQuickPickItem = IQuickPickItem & IProfileTemplateInfo; @@ -59,14 +41,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @IWorkspaceTagsService private readonly workspaceTagsService: IWorkspaceTagsService, @IContextKeyService contextKeyService: IContextKeyService, - @IQuickInputService private readonly quickInputService: IQuickInputService, - @INotificationService private readonly notificationService: INotificationService, @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IProductService private readonly productService: IProductService, - @IRequestService private readonly requestService: IRequestService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IContextViewService private readonly contextViewService: IContextViewService, - @ILogService private readonly logService: ILogService, ) { super(); @@ -228,7 +203,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements }); } run() { - return that.saveProfile(that.userDataProfileService.currentProfile); + return that.userDataProfileImportExportService.editProfile(that.userDataProfileService.currentProfile); } }); } @@ -375,7 +350,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } try { if ((selectedItem).url) { - return await that.saveProfile(undefined, (selectedItem).url); + return await that.userDataProfileImportExportService.createProfile(URI.parse((selectedItem).url)); } const profile = selectedItem.label === quickPick.value ? URI.parse(quickPick.value) : await this.getProfileUriFromFileSystem(fileDialogService); if (profile) { @@ -433,194 +408,11 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } run(accessor: ServicesAccessor) { - return that.saveProfile(undefined, that.userDataProfileService.currentProfile); + return that.userDataProfileImportExportService.createProfile(that.userDataProfileService.currentProfile); } })); } - private async saveProfile(profile: IUserDataProfile): Promise; - private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | string): Promise; - private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | string): Promise { - - type CreateProfileInfoClassification = { - owner: 'sandy081'; - comment: 'Report when profile is about to be created'; - }; - this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.startCreate'); - - const disposables = new DisposableStore(); - const title = profile ? localize('save profile', "Edit {0} Profile...", profile.name) : localize('create new profle', "Create New Profile..."); - - const settings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: !profile?.useDefaultFlags?.settings }; - const keybindings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: !profile?.useDefaultFlags?.keybindings }; - const snippets: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: !profile?.useDefaultFlags?.snippets }; - const tasks: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: !profile?.useDefaultFlags?.tasks }; - const extensions: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: !profile?.useDefaultFlags?.extensions }; - const resources = [settings, keybindings, snippets, tasks, extensions]; - - const quickPick = this.quickInputService.createQuickPick(); - quickPick.title = title; - quickPick.placeholder = localize('name placeholder', "Profile name"); - quickPick.value = profile?.name ?? ''; - quickPick.canSelectMany = true; - quickPick.matchOnDescription = false; - quickPick.matchOnDetail = false; - quickPick.matchOnLabel = false; - quickPick.sortByLabel = false; - quickPick.hideCountBadge = true; - quickPick.ok = false; - quickPick.customButton = true; - quickPick.hideCheckAll = true; - quickPick.ignoreFocusOut = true; - quickPick.customLabel = profile ? localize('save', "Save") : localize('create', "Create"); - quickPick.description = localize('customise the profile', "Choose what to configure in your Profile:"); - quickPick.items = [...resources]; - - const update = () => { - quickPick.items = resources; - quickPick.selectedItems = resources.filter(item => item.picked); - }; - update(); - - const validate = () => { - if (!profile && this.userDataProfilesService.profiles.some(p => p.name === quickPick.value)) { - quickPick.validationMessage = localize('profileExists', "Profile with name {0} already exists.", quickPick.value); - quickPick.severity = Severity.Warning; - return; - } - if (resources.every(resource => !resource.picked)) { - quickPick.validationMessage = localize('invalid configurations', "The profile should contain at least one configuration."); - quickPick.severity = Severity.Warning; - return; - } - quickPick.severity = Severity.Ignore; - quickPick.validationMessage = undefined; - }; - - disposables.add(quickPick.onDidChangeSelection(items => { - let needUpdate = false; - for (const resource of resources) { - resource.picked = items.includes(resource); - const description = resource.picked ? undefined : localize('use default profile', "Using Default Profile"); - if (resource.description !== description) { - resource.description = description; - needUpdate = true; - } - } - if (needUpdate) { - update(); - } - validate(); - })); - - disposables.add(quickPick.onDidChangeValue(validate)); - - let result: { name: string; items: ReadonlyArray } | undefined; - disposables.add(Event.any(quickPick.onDidCustom, quickPick.onDidAccept)(() => { - if (!quickPick.value) { - quickPick.validationMessage = localize('name required', "Provide a name for the new profile"); - quickPick.severity = Severity.Error; - } - if (quickPick.validationMessage) { - return; - } - result = { name: quickPick.value, items: quickPick.selectedItems }; - quickPick.hide(); - quickPick.severity = Severity.Ignore; - quickPick.validationMessage = undefined; - })); - - if (!profile) { - const domNode = $('.profile-type-widget'); - append(domNode, $('.profile-type-create-label', undefined, localize('create from', "Copy from:"))); - const separator = { text: '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true }; - const profileOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | string })[] = []; - profileOptions.push({ text: localize('empty profile', "None") }); - const templates = await this.getProfileTemplatesFromProduct(); - if (templates.length) { - profileOptions.push({ ...separator, decoratorRight: localize('from templates', "Profile Templates") }); - for (const template of templates) { - profileOptions.push({ text: template.name, id: template.url, source: template.url }); - } - } - profileOptions.push({ ...separator, decoratorRight: localize('from existing profiles', "Existing Profiles") }); - for (const profile of this.userDataProfilesService.profiles) { - profileOptions.push({ text: profile.name, id: profile.id, source: profile }); - } - - const findOptionIndex = () => { - const index = profileOptions.findIndex(option => { - if (isString(source)) { - return option.id === source; - } else if (source) { - return option.id === source.id; - } - return false; - }); - return index > -1 ? index : 0; - }; - - const selectBox = disposables.add(this.instantiationService.createInstance(SelectBox, profileOptions, findOptionIndex(), this.contextViewService, defaultSelectBoxStyles, { useCustomDrawn: true })); - selectBox.render(append(domNode, $('.profile-type-select-container'))); - quickPick.widget = domNode; - - const updateQuickpickInfo = () => { - const option = profileOptions[findOptionIndex()]; - for (const resource of resources) { - resource.picked = option.source && !isString(option.source) ? !option.source?.useDefaultFlags?.[resource.id] : true; - } - update(); - }; - - updateQuickpickInfo(); - disposables.add(selectBox.onDidSelect(({ index }) => { - source = profileOptions[index].source; - updateQuickpickInfo(); - })); - } - - quickPick.show(); - - await new Promise((c, e) => { - disposables.add(quickPick.onDidHide(() => { - disposables.dispose(); - c(); - })); - }); - - if (!result) { - this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.cancelCreate'); - return; - } - - this.telemetryService.publicLog2<{}, CreateProfileInfoClassification>('userDataProfile.successCreate'); - - try { - const useDefaultFlags: UseDefaultProfileFlags | undefined = result.items.length === resources.length - ? undefined - : { - settings: !result.items.includes(settings), - keybindings: !result.items.includes(keybindings), - snippets: !result.items.includes(snippets), - tasks: !result.items.includes(tasks), - extensions: !result.items.includes(extensions) - }; - if (profile) { - await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags }); - } else { - if (isString(source)) { - await this.userDataProfileImportExportService.importProfile(URI.parse(source), { mode: 'apply', name: result.name, useDefaultFlags }); - } else if (source) { - await this.userDataProfileImportExportService.createFromProfile(source, result.name, { useDefaultFlags }); - } else { - await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags }); - } - } - } catch (error) { - this.notificationService.error(error); - } - } - private registerCreateProfileAction(): void { const that = this; this._register(registerAction2(class CreateProfileAction extends Action2 { @@ -646,7 +438,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements } async run(accessor: ServicesAccessor) { - return that.saveProfile(); + return that.userDataProfileImportExportService.createProfile(); } })); } @@ -726,7 +518,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements private async getProfileTemplatesQuickPickItems(): Promise { const quickPickItems: IProfileTemplateQuickPickItem[] = []; - const profileTemplates = await this.getProfileTemplatesFromProduct(); + const profileTemplates = await this.userDataProfileManagementService.getBuiltinProfileTemplates(); for (const template of profileTemplates) { quickPickItems.push({ label: template.name, @@ -736,22 +528,6 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements return quickPickItems; } - private async getProfileTemplatesFromProduct(): Promise { - if (this.productService.profileTemplatesUrl) { - try { - const context = await this.requestService.request({ type: 'GET', url: this.productService.profileTemplatesUrl }, CancellationToken.None); - if (context.res.statusCode === 200) { - return (await asJson(context)) || []; - } else { - this.logService.error('Could not get profile templates.', context.res.statusCode); - } - } catch (error) { - this.logService.error(error); - } - } - return []; - } - private async reportWorkspaceProfileInfo(): Promise { await this.lifecycleService.when(LifecyclePhase.Eventually); const workspaceId = await this.workspaceTagsService.getTelemetryWorkspaceId(this.workspaceContextService.getWorkspace(), this.workspaceContextService.getWorkbenchState()); diff --git a/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css b/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css index efc8cd8c16b3b..aa12a2746204c 100644 --- a/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css +++ b/src/vs/workbench/services/userDataProfile/browser/media/userDataProfileView.css @@ -7,43 +7,43 @@ display: inherit; } -.monaco-workbench .pane > .pane-body > .profile-view-message-container { +.monaco-workbench .pane>.pane-body>.profile-view-message-container { display: flex; padding: 13px 20px 0px 20px; box-sizing: border-box; } -.monaco-workbench .pane > .pane-body > .profile-view-message-container p { +.monaco-workbench .pane>.pane-body>.profile-view-message-container p { margin-block-start: 0em; margin-block-end: 0em; } -.monaco-workbench .pane > .pane-body > .profile-view-message-container a { +.monaco-workbench .pane>.pane-body>.profile-view-message-container a { color: var(--vscode-textLink-foreground) } -.monaco-workbench .pane > .pane-body > .profile-view-message-container a:hover { +.monaco-workbench .pane>.pane-body>.profile-view-message-container a:hover { text-decoration: underline; color: var(--vscode-textLink-activeForeground) } -.monaco-workbench .pane > .pane-body > .profile-view-message-container a:active { +.monaco-workbench .pane>.pane-body>.profile-view-message-container a:active { color: var(--vscode-textLink-activeForeground) } -.monaco-workbench .pane > .pane-body > .profile-view-message-container.hide { +.monaco-workbench .pane>.pane-body>.profile-view-message-container.hide { display: none; } -.monaco-workbench .pane > .pane-body > .profile-view-buttons-container { +.monaco-workbench .pane>.pane-body>.profile-view-buttons-container { display: flex; flex-direction: column; padding: 13px 20px; box-sizing: border-box; } -.monaco-workbench .pane > .pane-body > .profile-view-buttons-container > .monaco-button, -.monaco-workbench .pane > .pane-body > .profile-view-buttons-container > .monaco-button-dropdown { +.monaco-workbench .pane>.pane-body>.profile-view-buttons-container>.monaco-button, +.monaco-workbench .pane>.pane-body>.profile-view-buttons-container>.monaco-button-dropdown { margin-block-start: 13px; margin-inline-start: 0px; margin-inline-end: 0px; @@ -52,12 +52,36 @@ margin-right: auto; } -.monaco-workbench .pane > .pane-body > .profile-view-buttons-container > .monaco-button-dropdown { +.monaco-workbench .pane>.pane-body>.profile-view-buttons-container>.monaco-button-dropdown { width: 100%; } -.monaco-workbench .pane > .pane-body > .profile-view-buttons-container > .monaco-button-dropdown > .monaco-dropdown-button { +.monaco-workbench .pane>.pane-body>.profile-view-buttons-container>.monaco-button-dropdown>.monaco-dropdown-button { display: flex; align-items: center; padding: 0 4px; } + +.profile-type-widget { + display: flex; + margin: 0px 6px 8px 11px; + align-items: center; + justify-content: space-between; + font-size: 12px; +} + +.profile-type-widget>.profile-type-select-container { + overflow: hidden; + padding-left: 10px; + flex: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.profile-type-widget>.profile-type-select-container>.monaco-select-box { + cursor: pointer; + line-height: 17px; + padding: 2px 23px 2px 8px; + border-radius: 2px; +} diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 1215f0c55dd91..0bf06dbf851df 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -18,7 +18,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IFileService } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { Extensions, ITreeItem, ITreeViewDataProvider, ITreeViewDescriptor, IViewContainersRegistry, IViewDescriptorService, IViewsRegistry, IViewsService, TreeItemCollapsibleState, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views'; -import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, ProfileResourceType, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, ProfileResourceType, UseDefaultProfileFlags, isUserDataProfile, toUserDataProfile } from 'vs/platform/userDataProfile/common/userDataProfile'; import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -35,19 +35,19 @@ import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFil import { Button } from 'vs/base/browser/ui/button/button'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { defaultButtonStyles, defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; import { generateUuid } from 'vs/base/common/uuid'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorsOrder } from 'vs/workbench/common/editor'; import { getErrorMessage, onUnexpectedError } from 'vs/base/common/errors'; import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IQuickInputService, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, QuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { VSBuffer } from 'vs/base/common/buffer'; import { joinPath } from 'vs/base/common/resources'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; @@ -70,6 +70,7 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { MarkdownString } from 'vs/base/common/htmlContent'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { showWindowLogActionId } from 'vs/workbench/services/log/common/logConstants'; +import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; interface IUserDataProfileTemplate { readonly name: string; @@ -129,6 +130,8 @@ export class UserDataProfileImportExportService extends Disposable implements IU @IURLService urlService: IURLService, @IProductService private readonly productService: IProductService, @IUriIdentityService private readonly uriIdentityService: IUriIdentityService, + @ITelemetryService private readonly telemetryService: ITelemetryService, + @IContextViewService private readonly contextViewService: IContextViewService, @ILogService private readonly logService: ILogService, ) { super(); @@ -220,6 +223,215 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } + createProfile(from?: IUserDataProfile | URI): Promise { + return this.saveProfile(undefined, from); + } + + editProfile(profile: IUserDataProfile): Promise { + return this.saveProfile(profile); + } + + private saveProfile(profile: IUserDataProfile): Promise; + private saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | URI | IUserDataProfileTemplate): Promise; + private async saveProfile(profile?: IUserDataProfile, source?: IUserDataProfile | URI | Mutable): Promise { + + type SaveProfileInfoClassification = { + owner: 'sandy081'; + comment: 'Report when profile is about to be saved'; + }; + + if (profile) { + this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.startEdit'); + } else { + this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.startCreate'); + } + + const disposables = new DisposableStore(); + const title = profile ? localize('save profile', "Edit {0} Profile...", profile.name) : localize('create new profle', "Create New Profile..."); + + const settings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Settings, label: localize('settings', "Settings"), picked: !profile?.useDefaultFlags?.settings }; + const keybindings: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Keybindings, label: localize('keybindings', "Keyboard Shortcuts"), picked: !profile?.useDefaultFlags?.keybindings }; + const snippets: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Snippets, label: localize('snippets', "User Snippets"), picked: !profile?.useDefaultFlags?.snippets }; + const tasks: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Tasks, label: localize('tasks', "User Tasks"), picked: !profile?.useDefaultFlags?.tasks }; + const extensions: IQuickPickItem & { id: ProfileResourceType } = { id: ProfileResourceType.Extensions, label: localize('extensions', "Extensions"), picked: !profile?.useDefaultFlags?.extensions }; + const resources = [settings, keybindings, snippets, tasks, extensions]; + + const quickPick = this.quickInputService.createQuickPick(); + quickPick.title = title; + quickPick.placeholder = localize('name placeholder', "Profile name"); + quickPick.value = profile?.name ?? (isUserDataProfileTemplate(source) ? this.generateProfileName(source.name) : ''); + quickPick.canSelectMany = true; + quickPick.matchOnDescription = false; + quickPick.matchOnDetail = false; + quickPick.matchOnLabel = false; + quickPick.sortByLabel = false; + quickPick.hideCountBadge = true; + quickPick.ok = false; + quickPick.customButton = true; + quickPick.hideCheckAll = true; + quickPick.ignoreFocusOut = true; + quickPick.customLabel = profile ? localize('save', "Save") : localize('create', "Create"); + quickPick.description = localize('customise the profile', "Choose what to configure in your Profile:"); + quickPick.items = [...resources]; + + const update = () => { + quickPick.items = resources; + quickPick.selectedItems = resources.filter(item => item.picked); + }; + update(); + + const validate = () => { + if (!profile && this.userDataProfilesService.profiles.some(p => p.name === quickPick.value)) { + quickPick.validationMessage = localize('profileExists', "Profile with name {0} already exists.", quickPick.value); + quickPick.severity = Severity.Warning; + return; + } + if (resources.every(resource => !resource.picked)) { + quickPick.validationMessage = localize('invalid configurations', "The profile should contain at least one configuration."); + quickPick.severity = Severity.Warning; + return; + } + quickPick.severity = Severity.Ignore; + quickPick.validationMessage = undefined; + }; + + disposables.add(quickPick.onDidChangeSelection(items => { + let needUpdate = false; + for (const resource of resources) { + resource.picked = items.includes(resource); + const description = resource.picked ? undefined : localize('use default profile', "Using Default Profile"); + if (resource.description !== description) { + resource.description = description; + needUpdate = true; + } + } + if (needUpdate) { + update(); + } + validate(); + })); + + disposables.add(quickPick.onDidChangeValue(validate)); + + let result: { name: string; items: ReadonlyArray } | undefined; + disposables.add(Event.any(quickPick.onDidCustom, quickPick.onDidAccept)(() => { + if (!quickPick.value) { + quickPick.validationMessage = localize('name required', "Provide a name for the new profile"); + quickPick.severity = Severity.Error; + } + if (quickPick.validationMessage) { + return; + } + result = { name: quickPick.value, items: quickPick.selectedItems }; + quickPick.hide(); + quickPick.severity = Severity.Ignore; + quickPick.validationMessage = undefined; + })); + + if (!profile && !isUserDataProfileTemplate(source)) { + const domNode = DOM.$('.profile-type-widget'); + DOM.append(domNode, DOM.$('.profile-type-create-label', undefined, localize('create from', "Copy from:"))); + const separator = { text: '\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500', isDisabled: true }; + const profileOptions: (ISelectOptionItem & { id?: string; source?: IUserDataProfile | URI })[] = []; + profileOptions.push({ text: localize('empty profile', "None") }); + const templates = await this.userDataProfileManagementService.getBuiltinProfileTemplates(); + if (templates.length) { + profileOptions.push({ ...separator, decoratorRight: localize('from templates', "Profile Templates") }); + for (const template of templates) { + profileOptions.push({ text: template.name, id: template.url, source: URI.parse(template.url) }); + } + } + profileOptions.push({ ...separator, decoratorRight: localize('from existing profiles', "Existing Profiles") }); + for (const profile of this.userDataProfilesService.profiles) { + profileOptions.push({ text: profile.name, id: profile.id, source: profile }); + } + + const findOptionIndex = () => { + const index = profileOptions.findIndex(option => { + if (source instanceof URI) { + return option.source instanceof URI && this.uriIdentityService.extUri.isEqual(option.source, source); + } else if (isUserDataProfile(source)) { + return option.id === source.id; + } + return false; + }); + return index > -1 ? index : 0; + }; + + const initialIndex = findOptionIndex(); + const selectBox = disposables.add(this.instantiationService.createInstance(SelectBox, profileOptions, initialIndex, this.contextViewService, defaultSelectBoxStyles, { useCustomDrawn: true })); + selectBox.render(DOM.append(domNode, DOM.$('.profile-type-select-container'))); + quickPick.widget = domNode; + + if (profileOptions[initialIndex].source) { + quickPick.value = this.generateProfileName(profileOptions[initialIndex].text); + } + + const updateOptions = () => { + const option = profileOptions[findOptionIndex()]; + for (const resource of resources) { + resource.picked = option.source && !(option.source instanceof URI) ? !option.source?.useDefaultFlags?.[resource.id] : true; + } + update(); + }; + + updateOptions(); + disposables.add(selectBox.onDidSelect(({ index }) => { + source = profileOptions[index].source; + updateOptions(); + })); + } + + quickPick.show(); + + await new Promise((c, e) => { + disposables.add(quickPick.onDidHide(() => { + disposables.dispose(); + c(); + })); + }); + + if (!result) { + if (profile) { + this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.cancelEdit'); + } else { + this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.cancelCreate'); + } + return; + } + + try { + const useDefaultFlags: UseDefaultProfileFlags | undefined = result.items.length === resources.length + ? undefined + : { + settings: !result.items.includes(settings), + keybindings: !result.items.includes(keybindings), + snippets: !result.items.includes(snippets), + tasks: !result.items.includes(tasks), + extensions: !result.items.includes(extensions) + }; + if (profile) { + await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags }); + } else { + if (source instanceof URI) { + this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.createFromTemplate'); + await this.importProfile(source, { mode: 'apply', name: result.name, useDefaultFlags }); + } else if (isUserDataProfileTemplate(source)) { + source.name = result.name; + await this.createAndSwitch(source, false, true, { useDefaultFlags }, localize('create profile', "Create Profile")); + } else if (source) { + this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.createFromProfile'); + await this.createFromProfile(source, result.name, { useDefaultFlags }); + } else { + this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.createEmptyProfile'); + await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags }); + } + } + } catch (error) { + this.notificationService.error(error); + } + } + async showProfileContents(): Promise { const view = this.viewsService.getViewWithId(EXPORT_PROFILE_PREVIEW_VIEW); if (view) { @@ -269,7 +481,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU sticky: true, }, async progress => { const reportProgress = (message: string) => progress.report({ message: localize('troubleshoot profile progress', "Setting up Troubleshoot Profile: {0}", message) }); - const profile = await this.createProfile(profileTemplate, true, false, { useDefaultFlags: this.userDataProfileService.currentProfile.useDefaultFlags }, reportProgress); + const profile = await this.doCreateProfile(profileTemplate, true, false, { useDefaultFlags: this.userDataProfileService.currentProfile.useDefaultFlags }, reportProgress); if (profile) { reportProgress(localize('progress extensions', "Applying Extensions...")); await this.instantiationService.createInstance(ExtensionsResource).copy(this.userDataProfileService.currentProfile, profile, true); @@ -380,7 +592,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU } const barrier = new Barrier(); - const importAction = this.getCreateAction(barrier, userDataProfileImportState, options); + const importAction = this.getCreateAction(barrier, userDataProfileImportState); const primaryAction = isWeb ? new Action('importInDesktop', localize('import in desktop', "Create Profile in {0}", this.productService.nameLong), undefined, true, async () => this.openerService.open(uri, { openExternal: true })) : importAction; @@ -447,8 +659,9 @@ export class UserDataProfileImportExportService extends Disposable implements IU await this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); } else { const barrier = new Barrier(); - const importAction = this.getCreateAction(barrier, userDataProfileImportState, options); - await this.showProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW, profileTemplate.name, importAction, new BarrierAction(barrier, new Action('cancel', localize('cancel', "Cancel")), this.notificationService), false, userDataProfileImportState); + const cancelAction = new BarrierAction(barrier, new Action('cancel', localize('cancel', "Cancel")), this.notificationService); + const importAction = this.getCreateAction(barrier, userDataProfileImportState, cancelAction); + await this.showProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW, profileTemplate.name, importAction, cancelAction, false, userDataProfileImportState); await barrier.wait(); await this.hideProfilePreviewView(IMPORT_PROFILE_PREVIEW_VIEW); } @@ -457,11 +670,14 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - private getCreateAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState, options: IUserDataProfileOptions | undefined): IAction { + private getCreateAction(barrier: Barrier, userDataProfileImportState: UserDataProfileImportState, cancelAction?: IAction): IAction { const importAction = new BarrierAction(barrier, new Action('title', localize('import', "Create Profile"), undefined, true, async () => { importAction.enabled = false; + if (cancelAction) { + cancelAction.enabled = false; + } const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); - return this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); + return this.saveProfile(undefined, profileTemplate); }), this.notificationService); return importAction; } @@ -475,7 +691,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU title = `${title} (${profileTemplate.name})`; progress.report({ message: title }); const reportProgress = (message: string) => progress.report({ message: `${title}: ${message}` }); - const profile = await this.createProfile(profileTemplate, temporaryProfile, extensions, options, reportProgress); + const profile = await this.doCreateProfile(profileTemplate, temporaryProfile, extensions, options, reportProgress); if (profile) { reportProgress(localize('switching profile', "Switching Profile...")); await this.userDataProfileManagementService.switchProfile(profile); @@ -484,7 +700,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU }); } - private async createProfile(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, options: IUserDataProfileOptions | undefined, progress: (message: string) => void): Promise { + private async doCreateProfile(profileTemplate: IUserDataProfileTemplate, temporaryProfile: boolean, extensions: boolean, options: IUserDataProfileOptions | undefined, progress: (message: string) => void): Promise { const profile = await this.getProfileToImport(profileTemplate, temporaryProfile, options); if (!profile) { return undefined; @@ -633,6 +849,11 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } + private generateProfileName(profileName: string): string { + const existingProfile = this.userDataProfilesService.profiles.find(p => p.name === profileName); + return existingProfile ? `${profileName} ${this.getProfileNameIndex(profileName)}` : profileName; + } + private getProfileNameIndex(name: string): number { const nameRegEx = new RegExp(`${escapeRegExpCharacters(name)}\\s(\\d+)`); let nameIndex = 0; diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts index 9468c15aee2d9..33757af0467cb 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileManagement.ts @@ -3,18 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IProductService } from 'vs/platform/product/common/productService'; +import { IRequestService, asJson } from 'vs/platform/request/common/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { DidChangeProfilesEvent, IUserDataProfile, IUserDataProfileOptions, IUserDataProfilesService, IUserDataProfileUpdateOptions } from 'vs/platform/userDataProfile/common/userDataProfile'; import { IWorkspaceContextService, toWorkspaceIdentifier } from 'vs/platform/workspace/common/workspace'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IHostService } from 'vs/workbench/services/host/browser/host'; -import { DidChangeUserDataProfileEvent, IUserDataProfileManagementService, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { DidChangeUserDataProfileEvent, IProfileTemplateInfo, IUserDataProfileManagementService, IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; export type ProfileManagementActionExecutedClassification = { owner: 'sandy081'; @@ -38,6 +42,9 @@ export class UserDataProfileManagementService extends Disposable implements IUse @IExtensionService private readonly extensionService: IExtensionService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IProductService private readonly productService: IProductService, + @IRequestService private readonly requestService: IRequestService, + @ILogService private readonly logService: ILogService, ) { super(); this._register(userDataProfilesService.onDidChangeProfiles(e => this.onDidChangeProfiles(e))); @@ -120,6 +127,22 @@ export class UserDataProfileManagementService extends Disposable implements IUse this.telemetryService.publicLog2('profileManagementActionExecuted', { id: 'switchProfile' }); } + async getBuiltinProfileTemplates(): Promise { + if (this.productService.profileTemplatesUrl) { + try { + const context = await this.requestService.request({ type: 'GET', url: this.productService.profileTemplatesUrl }, CancellationToken.None); + if (context.res.statusCode === 200) { + return (await asJson(context)) || []; + } else { + this.logService.error('Could not get profile templates.', context.res.statusCode); + } + } catch (error) { + this.logService.error(error); + } + } + return []; + } + private async changeCurrentProfile(profile: IUserDataProfile, reloadMessage?: string): Promise { const isRemoteWindow = !!this.environmentService.remoteAuthority; diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index d68ed2e329e8e..b66d15b26d304 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -33,6 +33,11 @@ export interface IUserDataProfileService { getShortName(profile: IUserDataProfile): string; } +export interface IProfileTemplateInfo { + readonly name: string; + readonly url: string; +} + export const IUserDataProfileManagementService = createDecorator('IUserDataProfileManagementService'); export interface IUserDataProfileManagementService { readonly _serviceBrand: undefined; @@ -42,6 +47,7 @@ export interface IUserDataProfileManagementService { removeProfile(profile: IUserDataProfile): Promise; updateProfile(profile: IUserDataProfile, updateOptions: IUserDataProfileUpdateOptions): Promise; switchProfile(profile: IUserDataProfile): Promise; + getBuiltinProfileTemplates(): Promise; } @@ -87,6 +93,8 @@ export interface IUserDataProfileImportExportService { exportProfile(): Promise; importProfile(uri: URI, options?: IProfileImportOptions): Promise; showProfileContents(): Promise; + createProfile(from?: IUserDataProfile | URI): Promise; + editProfile(profile: IUserDataProfile): Promise; createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise; createTroubleshootProfile(): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; From 980c60b2c682a60a0943db3316bb43ad80b7311b Mon Sep 17 00:00:00 2001 From: John Murray Date: Fri, 21 Jul 2023 17:01:54 +0100 Subject: [PATCH 131/216] Use `go-to-file` codicon for test output inline action (fix #186369) (#188472) --- src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 9d737a611845e..8bc667b3ebdb9 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -2174,7 +2174,7 @@ export class OpenMessageInEditorAction extends Action2 { id: OpenMessageInEditorAction.ID, f1: false, title: { value: localize('testing.openMessageInEditor', "Open in Editor"), original: 'Open in Editor' }, - icon: Codicon.linkExternal, + icon: Codicon.goToFile, category: Categories.Test, menu: [{ id: MenuId.TestPeekTitle }], }); From 7943f8de4b9ed85e3215c5a04027e3a2f71c9a29 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 21 Jul 2023 18:04:15 +0200 Subject: [PATCH 132/216] Fixes https://github.com/microsoft/vscode-internalbacklog/issues/4480 (#188512) --- src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts | 5 ++++- src/vs/editor/common/diff/standardLinesDiffComputer.ts | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts b/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts index 0886c2da1e6a9..61a05010ea2d8 100644 --- a/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts +++ b/src/vs/editor/common/diff/algorithms/joinSequenceDiffs.ts @@ -33,8 +33,11 @@ export function smoothenSequenceDiffs(sequence1: ISequence, sequence2: ISequence return result; } -export function randomRandomMatches(sequence1: LinesSliceCharSequence, sequence2: LinesSliceCharSequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] { +export function removeRandomMatches(sequence1: LinesSliceCharSequence, sequence2: LinesSliceCharSequence, sequenceDiffs: SequenceDiff[]): SequenceDiff[] { let diffs = sequenceDiffs; + if (diffs.length === 0) { + return diffs; + } let counter = 0; let shouldRepeat: boolean; diff --git a/src/vs/editor/common/diff/standardLinesDiffComputer.ts b/src/vs/editor/common/diff/standardLinesDiffComputer.ts index 046e8ce0ca6af..d73dda91bc13e 100644 --- a/src/vs/editor/common/diff/standardLinesDiffComputer.ts +++ b/src/vs/editor/common/diff/standardLinesDiffComputer.ts @@ -11,7 +11,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { DateTimeout, ISequence, ITimeout, InfiniteTimeout, SequenceDiff } from 'vs/editor/common/diff/algorithms/diffAlgorithm'; import { DynamicProgrammingDiffing } from 'vs/editor/common/diff/algorithms/dynamicProgrammingDiffing'; -import { optimizeSequenceDiffs, randomRandomMatches, smoothenSequenceDiffs } from 'vs/editor/common/diff/algorithms/joinSequenceDiffs'; +import { optimizeSequenceDiffs, removeRandomMatches, smoothenSequenceDiffs } from 'vs/editor/common/diff/algorithms/joinSequenceDiffs'; import { MyersDiffAlgorithm } from 'vs/editor/common/diff/algorithms/myersDiffAlgorithm'; import { ILinesDiffComputer, ILinesDiffComputerOptions, LineRangeMapping, LinesDiff, MovedText, RangeMapping, SimpleLineRangeMapping } from 'vs/editor/common/diff/linesDiffComputer'; @@ -213,7 +213,7 @@ export class StandardLinesDiffComputer implements ILinesDiffComputer { diffs = optimizeSequenceDiffs(slice1, slice2, diffs); diffs = coverFullWords(slice1, slice2, diffs); diffs = smoothenSequenceDiffs(slice1, slice2, diffs); - diffs = randomRandomMatches(slice1, slice2, diffs); + diffs = removeRandomMatches(slice1, slice2, diffs); const result = diffs.map( (d) => From fbbbb55ac889fcd4a6b62bd1593e556a1e1f00f8 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Fri, 21 Jul 2023 18:09:38 +0200 Subject: [PATCH 133/216] Fixes monaco diff editor color var issues (#188513) --- .../widget/diffEditorWidget2/diffEditorWidget2.contribution.ts | 1 - .../browser/widget/diffEditorWidget2/diffEditorWidget2.ts | 1 + src/vs/editor/standalone/browser/standaloneThemeService.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts index ff8040b83ec07..2e9aaa5590257 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts @@ -14,7 +14,6 @@ import { ILocalizedString } from 'vs/platform/action/common/action'; import { Action2, MenuId, MenuRegistry, registerAction2 } from 'vs/platform/actions/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import './colors'; export class ToggleCollapseUnchangedRegions extends Action2 { constructor() { diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts index 0a9671f772c76..aff313ade82c5 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.ts @@ -46,6 +46,7 @@ import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { LengthObj } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; import { Range } from 'vs/editor/common/core/range'; +import './colors'; export class DiffEditorWidget2 extends DelegatingEditor implements IDiffEditor { private readonly elements = h('div.monaco-diff-editor.side-by-side', { style: { position: 'relative', height: '100%' } }, [ diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts index ec49af70ecebf..a391249e6e0cb 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeService.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts @@ -392,7 +392,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe colorVariables.push(`${asCssVariableName(item.id)}: ${color.toString()};`); } } - ruleCollector.addRule(`.monaco-editor { ${colorVariables.join('\n')} }`); + ruleCollector.addRule(`.monaco-editor, .monaco-diff-editor { ${colorVariables.join('\n')} }`); const colorMap = this._colorMapOverride || this._theme.tokenTheme.getColorMap(); ruleCollector.addRule(generateTokensCSSForColorMap(colorMap)); From b5038f81d146ab9b49a58a7dace7dfd9afbbd136 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 21 Jul 2023 09:32:20 -0700 Subject: [PATCH 134/216] cli: allow exec server to listen on a port and require token authentication (#188434) * cli: allow exec server to listen on a port and require token authentication For remote ssh on Windows where pipe forwarding doesn't work * fix linux build --- cli/build.rs | 2 +- cli/src/async_pipe.rs | 36 ++++++++++++++++++++- cli/src/commands/args.rs | 6 ++++ cli/src/commands/tunnels.rs | 54 ++++++++++++++++++++----------- cli/src/msgpack_rpc.rs | 2 +- cli/src/tunnels.rs | 2 +- cli/src/tunnels/control_server.rs | 52 +++++++++++++++++++---------- cli/src/tunnels/protocol.rs | 5 +++ cli/src/util/errors.rs | 2 ++ 9 files changed, 121 insertions(+), 40 deletions(-) diff --git a/cli/build.rs b/cli/build.rs index bcf1bf27e0ac3..41e289774e94f 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -25,7 +25,7 @@ fn apply_build_environment_variables() { } let pkg_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); - let mut cmd = Command::new("node"); + let mut cmd = Command::new(env::var("NODE_PATH").unwrap_or_else(|_| "node".to_string())); cmd.arg("../build/azure-pipelines/cli/prepare.js"); cmd.current_dir(&pkg_dir); cmd.env("VSCODE_CLI_PREPARE_OUTPUT", "json"); diff --git a/cli/src/async_pipe.rs b/cli/src/async_pipe.rs index dcbe0d1601739..6c7c918967af6 100644 --- a/cli/src/async_pipe.rs +++ b/cli/src/async_pipe.rs @@ -4,7 +4,10 @@ *--------------------------------------------------------------------------------------------*/ use crate::{constants::APPLICATION_NAME, util::errors::CodeError}; +use async_trait::async_trait; use std::path::{Path, PathBuf}; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::net::TcpListener; use uuid::Uuid; // todo: we could probably abstract this into some crate, if one doesn't already exist @@ -39,7 +42,7 @@ cfg_if::cfg_if! { pipe.into_split() } } else { - use tokio::{time::sleep, io::{AsyncRead, AsyncWrite, ReadBuf}}; + use tokio::{time::sleep, io::ReadBuf}; use tokio::net::windows::named_pipe::{ClientOptions, ServerOptions, NamedPipeClient, NamedPipeServer}; use std::{time::Duration, pin::Pin, task::{Context, Poll}, io}; use pin_project::pin_project; @@ -181,3 +184,34 @@ pub fn get_socket_name() -> PathBuf { } } } + +pub type AcceptedRW = ( + Box, + Box, +); + +#[async_trait] +pub trait AsyncRWAccepter { + async fn accept_rw(&mut self) -> Result; +} + +#[async_trait] +impl AsyncRWAccepter for AsyncPipeListener { + async fn accept_rw(&mut self) -> Result { + let pipe = self.accept().await?; + let (read, write) = socket_stream_split(pipe); + Ok((Box::new(read), Box::new(write))) + } +} + +#[async_trait] +impl AsyncRWAccepter for TcpListener { + async fn accept_rw(&mut self) -> Result { + let (stream, _) = self + .accept() + .await + .map_err(CodeError::AsyncPipeListenerFailed)?; + let (read, write) = tokio::io::split(stream); + Ok((Box::new(read), Box::new(write))) + } +} diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index d34519d6810b0..e253130573b8f 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -182,6 +182,12 @@ pub struct CommandShellArgs { /// Listen on a socket instead of stdin/stdout. #[clap(long)] pub on_socket: bool, + /// Listen on a port instead of stdin/stdout. + #[clap(long)] + pub on_port: bool, + /// Require the given token string to be given in the handshake. + #[clap(long)] + pub require_token: Option, } #[derive(Args, Debug, Clone)] diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index 9831de6e4269d..578114611c010 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -20,7 +20,7 @@ use super::{ }; use crate::{ - async_pipe::{get_socket_name, listen_socket_rw_stream, socket_stream_split}, + async_pipe::{get_socket_name, listen_socket_rw_stream, AsyncRWAccepter}, auth::Auth, constants::{APPLICATION_NAME, TUNNEL_CLI_LOCK_NAME, TUNNEL_SERVICE_LOCK_NAME}, log, @@ -35,7 +35,7 @@ use crate::{ singleton_server::{ make_singleton_server, start_singleton_server, BroadcastLogSink, SingletonServerArgs, }, - Next, ServeStreamParams, ServiceContainer, ServiceManager, + AuthRequired, Next, ServeStreamParams, ServiceContainer, ServiceManager, }, util::{ app_lock::AppMutex, @@ -128,36 +128,52 @@ pub async fn command_shell(ctx: CommandContext, args: CommandShellArgs) -> Resul log: ctx.log, launcher_paths: ctx.paths, platform, - requires_auth: true, + requires_auth: args + .require_token + .map(AuthRequired::VSDAWithToken) + .unwrap_or(AuthRequired::VSDA), exit_barrier: ShutdownRequest::create_rx([ShutdownRequest::CtrlC]), code_server_args: (&ctx.args).into(), }; - if !args.on_socket { - serve_stream(tokio::io::stdin(), tokio::io::stderr(), params).await; - return Ok(0); - } + let mut listener: Box = match (args.on_port, args.on_socket) { + (_, true) => { + let socket = get_socket_name(); + let listener = listen_socket_rw_stream(&socket) + .await + .map_err(|e| wrap(e, "error listening on socket"))?; - let socket = get_socket_name(); - let mut listener = listen_socket_rw_stream(&socket) - .await - .map_err(|e| wrap(e, "error listening on socket"))?; + params + .log + .result(format!("Listening on {}", socket.display())); + + Box::new(listener) + } + (true, _) => { + let listener = tokio::net::TcpListener::bind("127.0.0.1:0") + .await + .map_err(|e| wrap(e, "error listening on port"))?; - params - .log - .result(format!("Listening on {}", socket.display())); + params + .log + .result(format!("Listening on {}", listener.local_addr().unwrap())); + + Box::new(listener) + } + _ => { + serve_stream(tokio::io::stdin(), tokio::io::stderr(), params).await; + return Ok(0); + } + }; let mut servers = FuturesUnordered::new(); loop { tokio::select! { Some(_) = servers.next() => {}, - socket = listener.accept() => { + socket = listener.accept_rw() => { match socket { - Ok(s) => { - let (read, write) = socket_stream_split(s); - servers.push(serve_stream(read, write, params.clone())); - }, + Ok((read, write)) => servers.push(serve_stream(read, write, params.clone())), Err(e) => { error!(params.log, &format!("Error accepting connection: {}", e)); return Ok(1); diff --git a/cli/src/msgpack_rpc.rs b/cli/src/msgpack_rpc.rs index 219c923cdf2ab..ef6b7782074c3 100644 --- a/cli/src/msgpack_rpc.rs +++ b/cli/src/msgpack_rpc.rs @@ -122,7 +122,7 @@ pub struct MsgPackCodec { impl MsgPackCodec { pub fn new() -> Self { Self { - _marker: std::marker::PhantomData::default(), + _marker: std::marker::PhantomData, } } } diff --git a/cli/src/tunnels.rs b/cli/src/tunnels.rs index 5d97b757afc1d..63ccad22382ba 100644 --- a/cli/src/tunnels.rs +++ b/cli/src/tunnels.rs @@ -34,7 +34,7 @@ mod service_macos; mod service_windows; mod socket_signal; -pub use control_server::{serve, serve_stream, Next, ServeStreamParams}; +pub use control_server::{serve, serve_stream, Next, ServeStreamParams, AuthRequired}; pub use nosleep::SleepInhibitor; pub use service::{ create_service_manager, ServiceContainer, ServiceManager, SERVICE_LOG_FILE_NAME, diff --git a/cli/src/tunnels/control_server.rs b/cli/src/tunnels/control_server.rs index 8577f9668e9a4..6f8c1060e1f44 100644 --- a/cli/src/tunnels/control_server.rs +++ b/cli/src/tunnels/control_server.rs @@ -48,11 +48,11 @@ use super::dev_tunnels::ActiveTunnel; use super::paths::prune_stopped_servers; use super::port_forwarder::{PortForwarding, PortForwardingProcessor}; use super::protocol::{ - AcquireCliParams, CallServerHttpParams, CallServerHttpResult, ChallengeIssueResponse, - ChallengeVerifyParams, ClientRequestMethod, EmptyObject, ForwardParams, ForwardResult, - FsStatRequest, FsStatResponse, GetEnvResponse, GetHostnameResponse, HttpBodyParams, - HttpHeadersParams, ServeParams, ServerLog, ServerMessageParams, SpawnParams, SpawnResult, - ToClientRequest, UnforwardParams, UpdateParams, UpdateResult, VersionResponse, + AcquireCliParams, CallServerHttpParams, CallServerHttpResult, ChallengeIssueParams, + ChallengeIssueResponse, ChallengeVerifyParams, ClientRequestMethod, EmptyObject, ForwardParams, + ForwardResult, FsStatRequest, FsStatResponse, GetEnvResponse, GetHostnameResponse, + HttpBodyParams, HttpHeadersParams, ServeParams, ServerLog, ServerMessageParams, SpawnParams, + SpawnResult, ToClientRequest, UnforwardParams, UpdateParams, UpdateResult, VersionResponse, METHOD_CHALLENGE_VERIFY, }; use super::server_bridge::ServerBridge; @@ -94,8 +94,8 @@ struct HandlerContext { /// Handler auth state. enum AuthState { - /// Auth is required, we're waiting for the client to send its challenge. - WaitingForChallenge, + /// Auth is required, we're waiting for the client to send its challenge optionally bearing a token. + WaitingForChallenge(Option), /// A challenge has been issued. Waiting for a verification. ChallengeIssued(String), /// Auth is no longer required. @@ -215,7 +215,7 @@ pub async fn serve( code_server_args: own_code_server_args, platform, exit_barrier: own_exit, - requires_auth: false, + requires_auth: AuthRequired::None, }).with_context(cx.clone()).await; cx.span().add_event( @@ -233,13 +233,20 @@ pub async fn serve( } } +#[derive(Clone)] +pub enum AuthRequired { + None, + VSDA, + VSDAWithToken(String), +} + #[derive(Clone)] pub struct ServeStreamParams { pub log: log::Logger, pub launcher_paths: LauncherPaths, pub code_server_args: CodeServerArgs, pub platform: Platform, - pub requires_auth: bool, + pub requires_auth: AuthRequired, pub exit_barrier: Barrier, } @@ -269,7 +276,7 @@ fn make_socket_rpc( launcher_paths: LauncherPaths, code_server_args: CodeServerArgs, port_forwarding: Option, - requires_auth: bool, + requires_auth: AuthRequired, platform: Platform, ) -> RpcDispatcher { let http_requests = Arc::new(std::sync::Mutex::new(HashMap::new())); @@ -277,8 +284,9 @@ fn make_socket_rpc( let mut rpc = RpcBuilder::new(MsgPackSerializer {}).methods(HandlerContext { did_update: Arc::new(AtomicBool::new(false)), auth_state: Arc::new(std::sync::Mutex::new(match requires_auth { - true => AuthState::WaitingForChallenge, - false => AuthState::Authenticated, + AuthRequired::VSDAWithToken(t) => AuthState::WaitingForChallenge(Some(t)), + AuthRequired::VSDA => AuthState::WaitingForChallenge(None), + AuthRequired::None => AuthState::Authenticated, })), socket_tx, log: log.clone(), @@ -305,8 +313,8 @@ fn make_socket_rpc( ensure_auth(&c.auth_state)?; handle_get_env() }); - rpc.register_sync(METHOD_CHALLENGE_ISSUE, |_: EmptyObject, c| { - handle_challenge_issue(&c.auth_state) + rpc.register_sync(METHOD_CHALLENGE_ISSUE, |p: ChallengeIssueParams, c| { + handle_challenge_issue(p, &c.auth_state) }); rpc.register_sync(METHOD_CHALLENGE_VERIFY, |p: ChallengeVerifyParams, c| { handle_challenge_verify(p.response, &c.auth_state) @@ -423,6 +431,7 @@ async fn process_socket( let rx_counter = Arc::new(AtomicUsize::new(0)); let http_requests = Arc::new(std::sync::Mutex::new(HashMap::new())); + let already_authed = matches!(requires_auth, AuthRequired::None); let rpc = make_socket_rpc( log.clone(), socket_tx.clone(), @@ -440,7 +449,7 @@ async fn process_socket( let socket_tx = socket_tx.clone(); let exit_barrier = exit_barrier.clone(); tokio::spawn(async move { - if !requires_auth { + if already_authed { send_version(&socket_tx).await; } @@ -826,13 +835,22 @@ fn handle_get_env() -> Result { } fn handle_challenge_issue( + params: ChallengeIssueParams, auth_state: &Arc>, ) -> Result { let challenge = create_challenge(); let mut auth_state = auth_state.lock().unwrap(); - *auth_state = AuthState::ChallengeIssued(challenge.clone()); + if let AuthState::WaitingForChallenge(Some(s)) = &*auth_state { + println!("looking for token {}, got {:?}", s, params.token); + match ¶ms.token { + Some(t) if s != t => return Err(CodeError::AuthChallengeBadToken.into()), + None => return Err(CodeError::AuthChallengeBadToken.into()), + _ => {} + } + } + *auth_state = AuthState::ChallengeIssued(challenge.clone()); Ok(ChallengeIssueResponse { challenge }) } @@ -844,7 +862,7 @@ fn handle_challenge_verify( match &*auth_state { AuthState::Authenticated => Ok(EmptyObject {}), - AuthState::WaitingForChallenge => Err(CodeError::AuthChallengeNotIssued.into()), + AuthState::WaitingForChallenge(_) => Err(CodeError::AuthChallengeNotIssued.into()), AuthState::ChallengeIssued(c) => match verify_challenge(c, &response) { false => Err(CodeError::AuthChallengeNotIssued.into()), true => { diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs index eb20afe0ce588..b9d93761364a2 100644 --- a/cli/src/tunnels/protocol.rs +++ b/cli/src/tunnels/protocol.rs @@ -199,6 +199,11 @@ pub struct SpawnResult { pub const METHOD_CHALLENGE_ISSUE: &str = "challenge_issue"; pub const METHOD_CHALLENGE_VERIFY: &str = "challenge_verify"; +#[derive(Serialize, Deserialize)] +pub struct ChallengeIssueParams { + pub token: Option, +} + #[derive(Serialize, Deserialize)] pub struct ChallengeIssueResponse { pub challenge: String, diff --git a/cli/src/util/errors.rs b/cli/src/util/errors.rs index ca6d4bf3d8a57..abd4ef2419311 100644 --- a/cli/src/util/errors.rs +++ b/cli/src/util/errors.rs @@ -509,6 +509,8 @@ pub enum CodeError { ServerAuthRequired, #[error("challenge not yet issued")] AuthChallengeNotIssued, + #[error("challenge token is invalid")] + AuthChallengeBadToken, #[error("unauthorized client refused")] AuthMismatch, #[error("keyring communication timed out after 5s")] From 9434e79f46a3424fcea74bd64350ba8867dbf3ce Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 09:40:52 -0700 Subject: [PATCH 135/216] fix #186676 --- .../browser/inlineChat.contribution.ts | 51 ++++++++++++++++++- .../browser/inlineChatController.ts | 4 ++ .../inlineChat/browser/inlineChatWidget.ts | 11 +++- .../contrib/inlineChat/common/inlineChat.ts | 1 + 4 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index daef8334fe59b..51b70ee6389ed 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -7,14 +7,20 @@ import { registerAction2 } from 'vs/platform/actions/common/actions'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; -import { IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_MESSAGE_FOCUSED, IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { IInlineChatSessionService, InlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IWorkbenchContributionsRegistry, Extensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { InlineChatNotebookContribution } from 'vs/workbench/contrib/inlineChat/browser/inlineChatNotebook'; +import { AccessibilityVerbositySettingId, AccessibleViewAction } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { AccessibleViewType, IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { localize } from 'vs/nls'; +import { Extensions, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; registerSingleton(IInlineChatService, InlineChatServiceImpl, InstantiationType.Delayed); registerSingleton(IInlineChatSessionService, InlineChatSessionService, InstantiationType.Delayed); @@ -51,3 +57,44 @@ registerAction2(InlineChatActions.CopyRecordings); Registry.as(Extensions.Workbench) .registerWorkbenchContribution(InlineChatNotebookContribution, LifecyclePhase.Restored); + + +class InlineChatAccessibleViewContribution extends Disposable { + static ID: 'chatAccessibleViewContribution'; + constructor() { + super(); + this._register(AccessibleViewAction.addImplementation(100, 'inlineChat', accessor => { + const accessibleViewService = accessor.get(IAccessibleViewService); + const codeEditorService = accessor.get(ICodeEditorService); + + const editor = (codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor()); + if (!editor) { + return false; + } + const controller = InlineChatController.get(editor); + if (!controller) { + return false; + } + const inputFocused = editor.hasWidgetFocus(); + const responseContent = controller?.getMessage(); + if (!responseContent) { + return false; + } + accessibleViewService.show({ + verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, + provideContent(): string { return responseContent; }, + onClose() { + if (inputFocused) { + controller.focus(); + } + }, + + options: { ariaLabel: localize('inlineChatAccessibleView', "Inline Chat Accessible View"), type: AccessibleViewType.View } + }); + return true; + }, ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_MESSAGE_FOCUSED))); + } +} + +const workbenchContributionsRegistry = Registry.as(WorkbenchExtensions.Workbench); +workbenchContributionsRegistry.registerWorkbenchContribution(InlineChatAccessibleViewContribution, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 425347ef060da..72d1aefee44c1 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -158,6 +158,10 @@ export class InlineChatController implements IEditorContribution { } } + getMessage(): string | undefined { + return this._zone.value.widget.message; + } + getId(): string { return INLINE_CHAT_ID; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 78a6e72367545..44668de3fabb5 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -12,7 +12,7 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_MESSAGE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { EventType, Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; @@ -163,6 +163,7 @@ export class InlineChatWidget { private readonly _ctxInnerCursorStart: IContextKey; private readonly _ctxInnerCursorEnd: IContextKey; private readonly _ctxInputEditorFocused: IContextKey; + private readonly _ctxMessageFocused: IContextKey; private readonly _progressBar: ProgressBar; @@ -215,6 +216,9 @@ export class InlineChatWidget { this._store.add(this._inputEditor.onDidChangeModelContent(() => this._onDidChangeInput.fire(this))); this._store.add(this._inputEditor.onDidLayoutChange(() => this._onDidChangeHeight.fire())); this._store.add(this._inputEditor.onDidContentSizeChange(() => this._onDidChangeHeight.fire())); + this._store.add(addDisposableListener(this._elements.message, 'focus', () => this._ctxMessageFocused.set(true))); + this._store.add(addDisposableListener(this._elements.message, 'blur', () => this._ctxMessageFocused.reset())); + this._store.add(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) { this._updateAriaLabel(); @@ -235,6 +239,7 @@ export class InlineChatWidget { this._ctxInnerCursorStart = CTX_INLINE_CHAT_INNER_CURSOR_START.bindTo(this._contextKeyService); this._ctxInnerCursorEnd = CTX_INLINE_CHAT_INNER_CURSOR_END.bindTo(this._contextKeyService); this._ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(this._contextKeyService); + this._ctxMessageFocused = CTX_INLINE_CHAT_MESSAGE_FOCUSED.bindTo(this._contextKeyService); // (1) inner cursor position (last/first line selected) const updateInnerCursorFirstLast = () => { @@ -489,6 +494,10 @@ export class InlineChatWidget { this._preferredExpansionState = expansionState; } + get message(): string | undefined { + return this._elements.markdownMessage.textContent ?? undefined; + } + updateMarkdownMessage(message: Node | undefined) { this._elements.markdownMessage.classList.toggle('hidden', !message); let expansionState: ExpansionState; diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index f11624e761e06..8621c241b18c0 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -121,6 +121,7 @@ export const INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID = 'interactiveEditorAccess export const CTX_INLINE_CHAT_HAS_PROVIDER = new RawContextKey('inlineChatHasProvider', false, localize('inlineChatHasProvider', "Whether a provider for interactive editors exists")); export const CTX_INLINE_CHAT_VISIBLE = new RawContextKey('inlineChatVisible', false, localize('inlineChatVisible', "Whether the interactive editor input is visible")); export const CTX_INLINE_CHAT_FOCUSED = new RawContextKey('inlineChatFocused', false, localize('inlineChatFocused', "Whether the interactive editor input is focused")); +export const CTX_INLINE_CHAT_MESSAGE_FOCUSED = new RawContextKey('inlineChatMessageFocused', false, localize('inlineChatMessageFocused', "Whether the interactive widget's message is focused")); export const CTX_INLINE_CHAT_EMPTY = new RawContextKey('inlineChatEmpty', false, localize('inlineChatEmpty', "Whether the interactive editor input is empty")); export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey('inlineChatInnerCursorFirst', false, localize('inlineChatInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line")); export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line")); From da4ac459e212776454a0adb5d7b5b0fdabe06387 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 09:42:16 -0700 Subject: [PATCH 136/216] Revert "fix #188442" This reverts commit a9c39b8e79b324598fa14b230a769657b174c8dd. --- .../contrib/codeEditor/browser/accessibility/accessibility.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index d10802456cbab..1b57154caed3f 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -28,7 +28,7 @@ class ToggleScreenReaderMode extends Action2 { when: accessibilityHelpIsShown }, { - primary: KeyMod.Alt | KeyCode.F1 | KeyMod.Shift, + primary: KeyMod.Alt | KeyCode.F3, weight: KeybindingWeight.WorkbenchContrib + 10, }] }); From 205cb143ffc42358b1f22d1bd0558fb03f7f990c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 09:43:15 -0700 Subject: [PATCH 137/216] tweak --- .../contrib/inlineChat/browser/inlineChat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index 51b70ee6389ed..bf78cccc72f52 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -60,7 +60,7 @@ Registry.as(Extensions.Workbench) class InlineChatAccessibleViewContribution extends Disposable { - static ID: 'chatAccessibleViewContribution'; + static ID: 'inlineChatAccessibleViewContribution'; constructor() { super(); this._register(AccessibleViewAction.addImplementation(100, 'inlineChat', accessor => { From 2a00cb2849f960707f9ce721655598bc1e21d086 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 09:44:41 -0700 Subject: [PATCH 138/216] focus --- .../contrib/inlineChat/browser/inlineChat.contribution.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index bf78cccc72f52..ca7308126ab6c 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -75,7 +75,7 @@ class InlineChatAccessibleViewContribution extends Disposable { if (!controller) { return false; } - const inputFocused = editor.hasWidgetFocus(); + const inputFocused = editor.hasTextFocus(); const responseContent = controller?.getMessage(); if (!responseContent) { return false; From 9d1581ae3e9e0d9aeeb2b2f56407f027db50ff6b Mon Sep 17 00:00:00 2001 From: Dave Nicolson Date: Fri, 21 Jul 2023 19:17:11 +0200 Subject: [PATCH 139/216] Fix walkthroughs style (#179497) * Fix walkthroughs style * Apply button text selection behavior * Revert "Apply button text selection behavior" This reverts commit 0e24a32ba8f3e94121ef036c1ec508713d12dc84. * Revert link button style changes --- .../welcomeGettingStarted/browser/media/gettingStarted.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css index 9c5207bfcfc1e..db98aa06c59ea 100644 --- a/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css +++ b/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css @@ -627,10 +627,6 @@ display: inline; } -.monaco-workbench .part.editor>.content .gettingStartedContainer .index-list.getting-started div { - text-align: center; -} - .monaco-workbench .part.editor>.content .gettingStartedContainer.noExtensions .index-list.featured-extensions { display: none; } From 92189656ba5da6a338511c03cee27fcc73207614 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 19:21:13 +0200 Subject: [PATCH 140/216] add telemetry (#188518) --- .../userDataProfileImportExportService.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 0bf06dbf851df..ed0198c048a2b 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -239,11 +239,20 @@ export class UserDataProfileImportExportService extends Disposable implements IU owner: 'sandy081'; comment: 'Report when profile is about to be saved'; }; + type CreateProfileInfoClassification = { + owner: 'sandy081'; + comment: 'Report when profile is about to be created'; + source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Type of profile source' }; + }; + type CreateProfileInfoEvent = { + source: string | undefined; + }; + const createProfileTelemetryData: CreateProfileInfoEvent = { source: source instanceof URI ? 'template' : isUserDataProfile(source) ? 'profile' : source ? 'external' : undefined }; if (profile) { this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.startEdit'); } else { - this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.startCreate'); + this.telemetryService.publicLog2('userDataProfile.startCreate', createProfileTelemetryData); } const disposables = new DisposableStore(); @@ -395,7 +404,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU if (profile) { this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.cancelEdit'); } else { - this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.cancelCreate'); + this.telemetryService.publicLog2('userDataProfile.cancelCreate', createProfileTelemetryData); } return; } @@ -414,16 +423,17 @@ export class UserDataProfileImportExportService extends Disposable implements IU await this.userDataProfileManagementService.updateProfile(profile, { name: result.name, useDefaultFlags: profile.useDefaultFlags && !useDefaultFlags ? {} : useDefaultFlags }); } else { if (source instanceof URI) { - this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.createFromTemplate'); + this.telemetryService.publicLog2('userDataProfile.createFromTemplate', createProfileTelemetryData); await this.importProfile(source, { mode: 'apply', name: result.name, useDefaultFlags }); } else if (isUserDataProfileTemplate(source)) { source.name = result.name; + this.telemetryService.publicLog2('userDataProfile.createFromExternalTemplate', createProfileTelemetryData); await this.createAndSwitch(source, false, true, { useDefaultFlags }, localize('create profile', "Create Profile")); } else if (source) { - this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.createFromProfile'); + this.telemetryService.publicLog2('userDataProfile.createFromProfile', createProfileTelemetryData); await this.createFromProfile(source, result.name, { useDefaultFlags }); } else { - this.telemetryService.publicLog2<{}, SaveProfileInfoClassification>('userDataProfile.createEmptyProfile'); + this.telemetryService.publicLog2('userDataProfile.createEmptyProfile', createProfileTelemetryData); await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags }); } } From 723714bc1659bc9eff58302167abe90590db35d1 Mon Sep 17 00:00:00 2001 From: John Murray Date: Fri, 21 Jul 2023 18:22:03 +0100 Subject: [PATCH 141/216] Show placeholder text in testing view progress area (fix #141181) (#188484) * Show placeholder text in testing view progress area (fix #141181) * Use blank placeholder --- src/vs/workbench/contrib/testing/browser/testingExplorerView.ts | 1 + .../contrib/testing/browser/testingProgressUiService.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index d5121a326aa19..b7ca6213607ab 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -290,6 +290,7 @@ export class TestingExplorerView extends ViewPane { this.countSummary = text; this.renderActivityCount(); })); + this.testProgressService.update(); const listContainer = dom.append(this.container, dom.$('.test-explorer-tree')); this.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility); diff --git a/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts b/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts index 63a83e504dc63..4d9037d9b4387 100644 --- a/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts +++ b/src/vs/workbench/contrib/testing/browser/testingProgressUiService.ts @@ -115,7 +115,7 @@ export class TestingProgressUiService extends Disposable implements ITestingProg this.updateCountsEmitter.fire(collected); this.updateTextEmitter.fire(getTestProgressText(false, collected)); } else { - this.updateTextEmitter.fire(''); + this.updateTextEmitter.fire('\xA0'); this.updateCountsEmitter.fire(collectTestStateCounts(false)); } From df58df88958c78515b25dbfbbc71f0787d3255dc Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 21 Jul 2023 11:10:28 -0700 Subject: [PATCH 142/216] Only fire once (#188523) Fixes https://github.com/microsoft/vscode/issues/188460 --- src/vs/platform/secrets/common/secrets.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/secrets/common/secrets.ts b/src/vs/platform/secrets/common/secrets.ts index 6c6b822ebc25d..4f9b9a49f9fce 100644 --- a/src/vs/platform/secrets/common/secrets.ts +++ b/src/vs/platform/secrets/common/secrets.ts @@ -128,7 +128,14 @@ export abstract class BaseSecretStorageService implements ISecretStorageService } this._onDidChangeValueDisposable?.dispose(); - this._onDidChangeValueDisposable = storageService.onDidChangeValue(e => this.onDidChangeValue(e.key)); + this._onDidChangeValueDisposable = storageService.onDidChangeValue(e => { + // We only care about changes to the application scope since SecretStorage + // only stores secrets in the application scope but this seems to fire + // 2 events. Once for APP scope and once for PROFILE scope. ref #188460 + if (e.scope === StorageScope.APPLICATION) { + this.onDidChangeValue(e.key); + } + }); return storageService; } From c7195cef9b3bbdd99d09f3275da56d23a1012891 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Fri, 21 Jul 2023 11:13:58 -0700 Subject: [PATCH 143/216] cli: recycle tunnels more correctly (#188522) Previously we only tried to recycle a tunnel once, so if the tunnel limit changed and was reduced by >1 we'd fail even though we should have actually just recycled multiple tunnels. Also, only trigger recycling if the specific tunnel limit is hit. --- cli/src/tunnels/dev_tunnels.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index a04bdbcaf6d34..6ca1e7c60c2d9 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -33,6 +33,8 @@ use tunnels::management::{ use super::wsl_detect::is_wsl_installed; +static TUNNEL_COUNT_LIMIT_NAME: &str = "TunnelsPerUserPerLocation"; + #[derive(Clone, Serialize, Deserialize)] pub struct PersistedTunnel { pub name: String, @@ -458,8 +460,6 @@ impl DevTunnels { self.check_is_name_free(name).await?; - let mut tried_recycle = false; - let new_tunnel = Tunnel { tags: vec![ name.to_string(), @@ -480,13 +480,14 @@ impl DevTunnels { Err(HttpError::ResponseError(e)) if e.status_code == StatusCode::TOO_MANY_REQUESTS => { - if !tried_recycle && self.try_recycle_tunnel().await? { - tried_recycle = true; - continue; - } - if let Some(d) = e.get_details() { let detail = d.detail.unwrap_or_else(|| "unknown".to_string()); + if detail.contains(TUNNEL_COUNT_LIMIT_NAME) + && self.try_recycle_tunnel().await? + { + continue; + } + return Err(AnyError::from(TunnelCreationFailed( name.to_string(), detail, From 19ea2a0dec02548690fbcb8cd72dbcf07eb2a078 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Fri, 21 Jul 2023 11:21:03 -0700 Subject: [PATCH 144/216] hover sticky line CSS --- .../browser/media/notebookEditorStickyScroll.css | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css index a97c09ef13a31..9f8b4b69c8258 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css @@ -11,6 +11,19 @@ box-sizing: border-box; z-index: var(--z-index-notebook-sticky-scroll); width: 100%; - padding-left: 12px; font-family: var(--notebook-cell-input-preview-font-family); } +.monaco-workbench + .notebookOverlay + .notebook-sticky-scroll-container + .notebook-sticky-scroll-line { + padding-left: 12px; +} + +.monaco-workbench + .notebookOverlay + .notebook-sticky-scroll-container + .notebook-sticky-scroll-line:hover { + background-color: var(--vscode-editorStickyScrollHover-background); + cursor: pointer; +} From 3a931d91652a928d2af1ac16cae8704598ac1b84 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 21 Jul 2023 11:31:34 -0700 Subject: [PATCH 145/216] Don't show dialog if closing tab after Continue On (#187324) * Don't show dialog if closing tab after Continue On --- src/vs/workbench/browser/window.ts | 10 +++++++--- .../services/host/browser/browserHostService.ts | 10 ++++++++++ src/vs/workbench/services/host/browser/host.ts | 6 ++++++ .../host/electron-sandbox/nativeHostService.ts | 4 ++++ src/vs/workbench/test/browser/workbenchTestServices.ts | 3 +++ 5 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index b5b325c51ef07..db5aa59f80eda 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -26,6 +26,7 @@ import { IBrowserWorkbenchEnvironmentService } from 'vs/workbench/services/envir import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { BrowserLifecycleService } from 'vs/workbench/services/lifecycle/browser/lifecycleService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class BrowserWindow extends Disposable { @@ -37,7 +38,8 @@ export class BrowserWindow extends Disposable { @IProductService private readonly productService: IProductService, @IBrowserWorkbenchEnvironmentService private readonly environmentService: IBrowserWorkbenchEnvironmentService, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IHostService private readonly hostService: IHostService ) { super(); @@ -231,13 +233,15 @@ export class BrowserWindow extends Disposable { ); } - await this.dialogService.prompt({ + // While this dialog shows, closing the tab will not display a confirmation dialog + // to avoid showing the user two dialogs at once + await this.hostService.withExpectedShutdown(() => this.dialogService.prompt({ type: Severity.Info, message: localize('openExternalDialogTitle', "All done. You can close this tab now."), detail, buttons, cancelButton: true - }); + })); }; // We cannot know whether the protocol handler succeeded. diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts index c9ae3c52101fd..911ad41e1ed40 100644 --- a/src/vs/workbench/services/host/browser/browserHostService.ts +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -521,6 +521,16 @@ export class BrowserHostService extends Disposable implements IHostService { window.close(); } + async withExpectedShutdown(expectedShutdownTask: () => Promise): Promise { + const previousShutdownReason = this.shutdownReason; + try { + this.shutdownReason = HostShutdownReason.Api; + return await expectedShutdownTask(); + } finally { + this.shutdownReason = previousShutdownReason; + } + } + private async handleExpectedShutdown(reason: ShutdownReason): Promise { // Update shutdown reason in a way that we do diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts index 06e5f15ef00e4..6072cff2843d7 100644 --- a/src/vs/workbench/services/host/browser/host.ts +++ b/src/vs/workbench/services/host/browser/host.ts @@ -88,5 +88,11 @@ export interface IHostService { */ close(): Promise; + /** + * Execute an asynchronous `expectedShutdownTask`. While this task is + * in progress, attempts to quit the application will not be vetoed with a dialog. + */ + withExpectedShutdown(expectedShutdownTask: () => Promise): Promise; + //#endregion } diff --git a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts index 72bb003e22f69..131972fa5385e 100644 --- a/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts +++ b/src/vs/workbench/services/host/electron-sandbox/nativeHostService.ts @@ -135,6 +135,10 @@ class WorkbenchHostService extends Disposable implements IHostService { return this.nativeHostService.closeWindow(); } + async withExpectedShutdown(expectedShutdownTask: () => Promise): Promise { + return await expectedShutdownTask(); + } + //#endregion } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index b350719272050..7b9f31a10e456 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1435,6 +1435,9 @@ export class TestHostService implements IHostService { async restart(): Promise { } async reload(): Promise { } async close(): Promise { } + async withExpectedShutdown(expectedShutdownTask: () => Promise): Promise { + return await expectedShutdownTask(); + } async focus(options?: { force: boolean }): Promise { } From 4926415ed8e7541d04db5bc2d570ee52a559e60b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 20:34:20 +0200 Subject: [PATCH 146/216] revert (#188521) --- .../browser/extensions.contribution.ts | 43 ++--- .../browser/preferences.contribution.ts | 168 ++++++++---------- .../browser/commands/configureSnippets.ts | 159 ++++++++--------- .../snippets/browser/snippets.contribution.ts | 4 +- .../tasks/browser/task.contribution.ts | 56 +++--- 5 files changed, 185 insertions(+), 245 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index cf4045bf3ac96..840cfeb520658 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -78,7 +78,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStringDictionary } from 'vs/base/common/collections'; import { CONTEXT_KEYBINDINGS_EDITOR } from 'vs/workbench/contrib/preferences/common/preferences'; import { DeprecatedExtensionsChecker } from 'vs/workbench/contrib/extensions/browser/deprecatedExtensionsChecker'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService, InstantiationType.Eager /* Auto updates extensions */); @@ -473,7 +472,6 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi @IInstantiationService private readonly instantiationService: IInstantiationService, @IDialogService private readonly dialogService: IDialogService, @ICommandService private readonly commandService: ICommandService, - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, ) { super(); const hasGalleryContext = CONTEXT_HAS_GALLERY.bindTo(contextKeyService); @@ -517,31 +515,22 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi // Global actions private registerGlobalActions(): void { - const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.extensions - ? `${title} (${localize('default profile', "Default Profile")})` - : title; - const registerOpenExtensionsActionDisposables = this._register(new DisposableStore()); - const registerOpenExtensionsAction = () => { - registerOpenExtensionsActionDisposables.clear(); - registerOpenExtensionsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { - id: VIEWLET_ID, - title: getTitle(localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions")) - }, - group: '2_configuration', - order: 3 - })); - registerOpenExtensionsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - command: { - id: VIEWLET_ID, - title: getTitle(localize('showExtensions', "Extensions")) - }, - group: '2_configuration', - order: 3 - })); - }; - registerOpenExtensionsAction(); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => registerOpenExtensionsAction())); + this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id: VIEWLET_ID, + title: localize({ key: 'miPreferencesExtensions', comment: ['&& denotes a mnemonic'] }, "&&Extensions") + }, + group: '2_configuration', + order: 3 + })); + this._register(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + command: { + id: VIEWLET_ID, + title: localize('showExtensions', "Extensions") + }, + group: '2_configuration', + order: 3 + })); this.registerExtensionAction({ id: 'workbench.extensions.action.installExtensions', diff --git a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts index 0d392743fcfc4..71973bd765387 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferences.contribution.ts @@ -187,46 +187,37 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon } private registerSettingsActions() { - const registerOpenSettingsActionDisposables = this._register(new DisposableStore()); - const registerOpenSettingsAction = () => { - registerOpenSettingsActionDisposables.clear(); - const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.settings - ? `${title} (${nls.localize('default profile', "Default Profile")})` - : title; - registerOpenSettingsActionDisposables.add(registerAction2(class extends Action2 { - constructor() { - super({ - id: SETTINGS_COMMAND_OPEN_SETTINGS, - title: { - value: getTitle(nls.localize('settings', "Settings")), - mnemonicTitle: getTitle(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings")), - original: 'Settings' - }, - keybinding: { - weight: KeybindingWeight.WorkbenchContrib, - when: null, - primary: KeyMod.CtrlCmd | KeyCode.Comma, - }, - menu: [{ - id: MenuId.GlobalActivity, - group: '2_configuration', - order: 1 - }, { - id: MenuId.MenubarPreferencesMenu, - group: '2_configuration', - order: 1 - }], - }); - } - run(accessor: ServicesAccessor, args: string | IOpenSettingsActionOptions) { - // args takes a string for backcompat - const opts = typeof args === 'string' ? { query: args } : sanitizeOpenSettingsArgs(args); - return accessor.get(IPreferencesService).openSettings(opts); - } - })); - }; - registerOpenSettingsAction(); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => registerOpenSettingsAction())); + this._register(registerAction2(class extends Action2 { + constructor() { + super({ + id: SETTINGS_COMMAND_OPEN_SETTINGS, + title: { + value: nls.localize('settings', "Settings"), + mnemonicTitle: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings"), + original: 'Settings' + }, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + when: null, + primary: KeyMod.CtrlCmd | KeyCode.Comma, + }, + menu: [{ + id: MenuId.GlobalActivity, + group: '2_configuration', + order: 1 + }, { + id: MenuId.MenubarPreferencesMenu, + group: '2_configuration', + order: 1 + }], + }); + } + run(accessor: ServicesAccessor, args: string | IOpenSettingsActionOptions) { + // args takes a string for backcompat + const opts = typeof args === 'string' ? { query: args } : sanitizeOpenSettingsArgs(args); + return accessor.get(IPreferencesService).openSettings(opts); + } + })); registerAction2(class extends Action2 { constructor() { super({ @@ -305,13 +296,13 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon } }); - const registerOpenUserSettingsEditorFromJsonActionDisposable = this._register(new MutableDisposable()); + const registerOpenUserSettingsEditorFromJsonActionDisposables = this._register(new MutableDisposable()); const openUserSettingsEditorWhen = ContextKeyExpr.and( ContextKeyExpr.or(ResourceContextKey.Resource.isEqualTo(this.userDataProfileService.currentProfile.settingsResource.toString()), ResourceContextKey.Resource.isEqualTo(this.userDataProfilesService.defaultProfile.settingsResource.toString())), ContextKeyExpr.not('isInDiffEditor')); const registerOpenUserSettingsEditorFromJsonAction = () => { - registerOpenUserSettingsEditorFromJsonActionDisposable.value = registerAction2(class extends Action2 { + registerOpenUserSettingsEditorFromJsonActionDisposables.value = registerAction2(class extends Action2 { constructor() { super({ id: '_workbench.openUserSettingsEditor', @@ -815,58 +806,49 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon private registerKeybindingsActions() { const that = this; const category = { value: nls.localize('preferences', "Preferences"), original: 'Preferences' }; - const registerOpenGlobalKeybindingsActionDisposables = this._register(new DisposableStore()); - const registerOpenGlobalKeybindingsAction = () => { - registerOpenGlobalKeybindingsActionDisposables.clear(); - const id = 'workbench.action.openGlobalKeybindings'; - const shortTitle = !that.userDataProfileService.currentProfile.isDefault && that.userDataProfileService.currentProfile.useDefaultFlags?.keybindings - ? nls.localize('keyboardShortcutsFromDefault', "Keyboard Shortcuts ({0})", nls.localize('default profile', "Default Profile")) - : nls.localize('keyboardShortcuts', "Keyboard Shortcuts"); - registerOpenGlobalKeybindingsActionDisposables.add(registerAction2(class extends Action2 { - constructor() { - super({ - id, - title: { value: nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"), original: 'Open Keyboard Shortcuts' }, - shortTitle, - category, - icon: preferencesOpenSettingsIcon, - keybinding: { - when: null, - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyS) - }, - menu: [ - { id: MenuId.CommandPalette }, - { - id: MenuId.EditorTitle, - when: ResourceContextKey.Resource.isEqualTo(that.userDataProfileService.currentProfile.keybindingsResource.toString()), - group: 'navigation', - order: 1, - }, - { - id: MenuId.GlobalActivity, - group: '2_configuration', - order: 3 - } - ] - }); - } - run(accessor: ServicesAccessor, args: string | undefined) { - const query = typeof args === 'string' ? args : undefined; - return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query }); - } - })); - registerOpenGlobalKeybindingsActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { + const id = 'workbench.action.openGlobalKeybindings'; + this._register(registerAction2(class extends Action2 { + constructor() { + super({ id, - title: shortTitle, - }, - group: '2_configuration', - order: 3 - })); - }; - registerOpenGlobalKeybindingsAction(); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => registerOpenGlobalKeybindingsAction())); + title: { value: nls.localize('openGlobalKeybindings', "Open Keyboard Shortcuts"), original: 'Open Keyboard Shortcuts' }, + shortTitle: nls.localize('keyboardShortcuts', "Keyboard Shortcuts"), + category, + icon: preferencesOpenSettingsIcon, + keybinding: { + when: null, + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, KeyMod.CtrlCmd | KeyCode.KeyS) + }, + menu: [ + { id: MenuId.CommandPalette }, + { + id: MenuId.EditorTitle, + when: ResourceContextKey.Resource.isEqualTo(that.userDataProfileService.currentProfile.keybindingsResource.toString()), + group: 'navigation', + order: 1, + }, + { + id: MenuId.GlobalActivity, + group: '2_configuration', + order: 3 + } + ] + }); + } + run(accessor: ServicesAccessor, args: string | undefined) { + const query = typeof args === 'string' ? args : undefined; + return accessor.get(IPreferencesService).openGlobalKeybindingSettings(false, { query }); + } + })); + this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id, + title: nls.localize('keyboardShortcuts', "Keyboard Shortcuts"), + }, + group: '2_configuration', + order: 3 + })); registerAction2(class extends Action2 { constructor() { super({ diff --git a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts index 0b86f1c4fa2fe..9d2a199fe186e 100644 --- a/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/commands/configureSnippets.ts @@ -4,20 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { isValidBasename } from 'vs/base/common/extpath'; -import { Disposable, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { extname } from 'vs/base/common/path'; import { basename, joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ILanguageService } from 'vs/editor/common/languages/language'; import * as nls from 'vs/nls'; -import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { MenuId } from 'vs/platform/actions/common/actions'; import { IFileService } from 'vs/platform/files/common/files'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/abstractSnippetsActions'; import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets'; import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; @@ -223,97 +221,80 @@ async function createLanguageSnippetFile(pick: ISnippetPick, fileService: IFileS await textFileService.write(pick.filepath, contents); } -export class ConfigureSnippetsActions extends Disposable implements IWorkbenchContribution { - - constructor( - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - ) { - super(); - const disposable = this._register(new MutableDisposable()); - disposable.value = this.registerAction(); - this._register(userDataProfileService.onDidChangeCurrentProfile(() => disposable.value = this.registerAction())); +export class ConfigureSnippetsAction extends SnippetsAction { + constructor() { + super({ + id: 'workbench.action.openSnippets', + title: { + value: nls.localize('openSnippet.label', "Configure User Snippets"), + original: 'Configure User Snippets' + }, + shortTitle: { + value: nls.localize('userSnippets', "User Snippets"), + mnemonicTitle: nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), + original: 'User Snippets' + }, + f1: true, + menu: [ + { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', order: 4 }, + { id: MenuId.GlobalActivity, group: '2_configuration', order: 4 }, + ] + }); } - private registerAction(): IDisposable { - const getTitle = (title: string) => !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.snippets - ? `${title} (${nls.localize('default', "Default Profile")})` - : title; - return registerAction2(class extends SnippetsAction { - constructor() { - super({ - id: 'workbench.action.openSnippets', - title: { - value: nls.localize('openSnippet.label', "Configure User Snippets"), - original: 'Configure User Snippets' - }, - shortTitle: { - value: getTitle(nls.localize('userSnippets', "User Snippets")), - mnemonicTitle: getTitle(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets")), - original: 'User Snippets' - }, - f1: true, - menu: [ - { id: MenuId.MenubarPreferencesMenu, group: '2_configuration', order: 4 }, - { id: MenuId.GlobalActivity, group: '2_configuration', order: 4 }, - ] - }); - } - - async run(accessor: ServicesAccessor): Promise { - - const snippetService = accessor.get(ISnippetsService); - const quickInputService = accessor.get(IQuickInputService); - const opener = accessor.get(IOpenerService); - const languageService = accessor.get(ILanguageService); - const userDataProfileService = accessor.get(IUserDataProfileService); - const workspaceService = accessor.get(IWorkspaceContextService); - const fileService = accessor.get(IFileService); - const textFileService = accessor.get(ITextFileService); - const labelService = accessor.get(ILabelService); - - const picks = await computePicks(snippetService, userDataProfileService, languageService, labelService); - const existing: QuickPickInput[] = picks.existing; - - type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; - const globalSnippetPicks: SnippetPick[] = [{ - scope: nls.localize('new.global_scope', 'global'), - label: nls.localize('new.global', "New Global Snippets file..."), - uri: userDataProfileService.currentProfile.snippetsHome - }]; - - const workspaceSnippetPicks: SnippetPick[] = []; - for (const folder of workspaceService.getWorkspace().folders) { - workspaceSnippetPicks.push({ - scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name), - label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name), - uri: folder.toResource('.vscode') - }); - } + async run(accessor: ServicesAccessor): Promise { + + const snippetService = accessor.get(ISnippetsService); + const quickInputService = accessor.get(IQuickInputService); + const opener = accessor.get(IOpenerService); + const languageService = accessor.get(ILanguageService); + const userDataProfileService = accessor.get(IUserDataProfileService); + const workspaceService = accessor.get(IWorkspaceContextService); + const fileService = accessor.get(IFileService); + const textFileService = accessor.get(ITextFileService); + const labelService = accessor.get(ILabelService); + + const picks = await computePicks(snippetService, userDataProfileService, languageService, labelService); + const existing: QuickPickInput[] = picks.existing; + + type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; + const globalSnippetPicks: SnippetPick[] = [{ + scope: nls.localize('new.global_scope', 'global'), + label: nls.localize('new.global', "New Global Snippets file..."), + uri: userDataProfileService.currentProfile.snippetsHome + }]; + + const workspaceSnippetPicks: SnippetPick[] = []; + for (const folder of workspaceService.getWorkspace().folders) { + workspaceSnippetPicks.push({ + scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name), + label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name), + uri: folder.toResource('.vscode') + }); + } - if (existing.length > 0) { - existing.unshift({ type: 'separator', label: nls.localize('group.global', "Existing Snippets") }); - existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); - } else { - existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); - } + if (existing.length > 0) { + existing.unshift({ type: 'separator', label: nls.localize('group.global', "Existing Snippets") }); + existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); + } else { + existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); + } - const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), { - placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"), - matchOnDescription: true - }); - - if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) { - return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); - } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) { - return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); - } else if (ISnippetPick.is(pick)) { - if (pick.hint) { - await createLanguageSnippetFile(pick, fileService, textFileService); - } - return opener.open(pick.filepath); - } + const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), { + placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"), + matchOnDescription: true + }); + if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) { + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); + } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) { + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, quickInputService, fileService, textFileService, opener); + } else if (ISnippetPick.is(pick)) { + if (pick.hint) { + await createLanguageSnippetFile(pick, fileService, textFileService); } - }); + return opener.open(pick.filepath); + } + } } diff --git a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index b78f735e1fcfb..0ad6445077f6d 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -11,7 +11,7 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; -import { ConfigureSnippetsActions } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets'; +import { ConfigureSnippetsAction } from 'vs/workbench/contrib/snippets/browser/commands/configureSnippets'; import { ApplyFileSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/fileTemplateSnippets'; import { InsertSnippetAction } from 'vs/workbench/contrib/snippets/browser/commands/insertSnippet'; import { SurroundWithSnippetEditorAction } from 'vs/workbench/contrib/snippets/browser/commands/surroundWithSnippet'; @@ -32,10 +32,10 @@ registerAction2(InsertSnippetAction); CommandsRegistry.registerCommandAlias('editor.action.showSnippets', 'editor.action.insertSnippet'); registerAction2(SurroundWithSnippetEditorAction); registerAction2(ApplyFileSnippetAction); +registerAction2(ConfigureSnippetsAction); // workbench contribs const workbenchContribRegistry = Registry.as(WorkbenchExtensions.Workbench); -workbenchContribRegistry.registerWorkbenchContribution(ConfigureSnippetsActions, LifecyclePhase.Restored); workbenchContribRegistry.registerWorkbenchContribution(SnippetCodeActions, LifecyclePhase.Restored); // config diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index 5051478d34ac0..d20bb9d437083 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; -import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { MenuRegistry, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -39,7 +39,6 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { TerminalMenuBarGroup } from 'vs/workbench/contrib/terminal/browser/terminalMenus'; import { isString } from 'vs/base/common/types'; -import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; const workbenchRegistry = Registry.as(WorkbenchExtensions.Workbench); workbenchRegistry.registerWorkbenchContribution(RunAutomaticTasks, LifecyclePhase.Eventually); @@ -355,43 +354,32 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { class UserTasksGlobalActionContribution extends Disposable implements IWorkbenchContribution { - constructor( - @IUserDataProfileService private readonly userDataProfileService: IUserDataProfileService, - ) { + constructor() { super(); this.registerActions(); } private registerActions() { - const registerOpenUserTasksActionDisposables = this._register(new DisposableStore()); - const registerOpenSettingsAction = () => { - registerOpenUserTasksActionDisposables.clear(); - const id = 'workbench.action.tasks.openUserTasks'; - const userTasksTitle = nls.localize('userTasks', "User Tasks"); - const title = !this.userDataProfileService.currentProfile.isDefault && this.userDataProfileService.currentProfile.useDefaultFlags?.tasks - ? `${userTasksTitle} (${nls.localize('default profile', "Default Profile")})` - : userTasksTitle; - registerOpenUserTasksActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { - command: { - id, - title - }, - when: TaskExecutionSupportedContext, - group: '2_configuration', - order: 4 - })); - registerOpenUserTasksActionDisposables.add(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { - command: { - id, - title - }, - when: TaskExecutionSupportedContext, - group: '2_configuration', - order: 4 - })); - }; - registerOpenSettingsAction(); - this._register(this.userDataProfileService.onDidChangeCurrentProfile(() => registerOpenSettingsAction())); + const id = 'workbench.action.tasks.openUserTasks'; + const title = nls.localize('userTasks', "User Tasks"); + this._register(MenuRegistry.appendMenuItem(MenuId.GlobalActivity, { + command: { + id, + title + }, + when: TaskExecutionSupportedContext, + group: '2_configuration', + order: 4 + })); + this._register(MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { + command: { + id, + title + }, + when: TaskExecutionSupportedContext, + group: '2_configuration', + order: 4 + })); } } workbenchRegistry.registerWorkbenchContribution(UserTasksGlobalActionContribution, LifecyclePhase.Restored); From 8716df3f215c2e3bd5896bf407f1e05aa8689cd0 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Fri, 21 Jul 2023 11:42:27 -0700 Subject: [PATCH 147/216] Add a "both" option to `chat.experimental.defaultMode` (#188525) This will show the chat in the Activity Bar and the Title Bar. cc @isidorn --- .../actions/quickQuestionActions/quickQuestionAction.ts | 2 +- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 7 ++++++- .../contrib/chat/browser/chatContributionServiceImpl.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts index 6f40a200bd5d0..a76eb9f6636f1 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts @@ -53,7 +53,7 @@ export class AskQuickQuestionAction extends Action2 { menu: { id: MenuId.LayoutControlMenu, group: '0_workbench_toggles', - when: ContextKeyExpr.equals('config.chat.experimental.defaultMode', 'quickQuestion'), + when: ContextKeyExpr.notEquals('config.chat.experimental.defaultMode', 'chatView'), order: 0 } }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index eda5d692605f6..595defa31c777 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -81,7 +81,12 @@ configurationRegistry.registerConfiguration({ 'chat.experimental.defaultMode': { type: 'string', tags: ['experimental'], - enum: ['chatView', 'quickQuestion'], + enum: ['chatView', 'quickQuestion', 'both'], + enumDescriptions: [ + nls.localize('interactiveSession.defaultMode.chatView', "Use the chat view as the default mode. Displays the chat icon in the Activity Bar."), + nls.localize('interactiveSession.defaultMode.quickQuestion', "Use the quick question as the default mode. Displays the chat icon in the Title Bar."), + nls.localize('interactiveSession.defaultMode.both', "Displays the chat icon in the Activity Bar and the Title Bar which open their respective chat modes.") + ], description: nls.localize('interactiveSession.defaultMode', "Controls the default mode of the chat experience."), default: 'chatView' }, diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index e9d90a2093d71..0176f7c10b652 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -127,7 +127,7 @@ export class ChatContributionService implements IChatContributionService { ctorDescriptor: new SyncDescriptor(ChatViewPane, [{ providerId: providerDescriptor.id }]), when: ContextKeyExpr.and( ContextKeyExpr.deserialize(providerDescriptor.when), - ContextKeyExpr.equals('config.chat.experimental.defaultMode', 'chatView') + ContextKeyExpr.notEquals('config.chat.experimental.defaultMode', 'quickQuestion') ) }]; Registry.as(ViewExtensions.ViewsRegistry).registerViews(viewDescriptor, viewContainer); From 8b9f9993e05365b5d4eb9b2056e433d09d1eae27 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 12:24:38 -0700 Subject: [PATCH 148/216] add hint to aria label --- .../contrib/accessibility/browser/accessibleView.ts | 12 ++++-------- .../contrib/inlineChat/browser/inlineChatWidget.ts | 3 +++ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index 4c918f1486bdd..6b3971cf561dd 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -53,7 +53,7 @@ export interface IAccessibleViewService { * If the setting is enabled, provides the open accessible view hint as a localized string. * @param verbositySettingKey The setting key for the verbosity of the feature */ - getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | undefined; + getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | null; } export const enum AccessibleViewType { @@ -279,15 +279,11 @@ export class AccessibleViewService extends Disposable implements IAccessibleView previous(): void { this._accessibleView?.previous(); } - getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | undefined { + getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | null { if (!this._configurationService.getValue(verbositySettingKey)) { - return; + return null; } - let hint = ''; const keybinding = this._keybindingService.lookupKeybinding(AccessibleViewAction.id)?.getAriaLabel(); - if (this._configurationService.getValue(verbositySettingKey)) { - hint = keybinding ? localize('chatAccessibleViewHint', "Inspect this in the accessible view with {0}", keybinding) : localize('chatAccessibleViewHintNoKb', "Inspect this in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); - } - return hint; + return keybinding ? localize('chatAccessibleViewHint', "Inspect this in the accessible view with {0}", keybinding) : localize('chatAccessibleViewHintNoKb', "Inspect this in the accessible view via the command Open Accessible View which is currently not triggerable via keybinding"); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 44668de3fabb5..487023e2fe516 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -51,6 +51,7 @@ import * as aria from 'vs/base/browser/ui/aria/aria'; import { IMenuWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platform/actions/browser/buttonbar'; import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -199,6 +200,7 @@ export class InlineChatWidget { @IAccessibilityService private readonly _accessibilityService: IAccessibilityService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService ) { // input editor logic @@ -364,6 +366,7 @@ export class InlineChatWidget { this._previewCreateEditor = new IdleValue(() => this._store.add(_instantiationService.createInstance(EmbeddedCodeEditorWidget, this._elements.previewCreate, _previewEditorEditorOptions, codeEditorWidgetOptions, parentEditor))); this._elements.message.tabIndex = 0; + this._elements.message.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat); this._elements.statusLabel.tabIndex = 0; const markdownMessageToolbar = this._instantiationService.createInstance(MenuWorkbenchToolBar, this._elements.messageActions, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, workbenchToolbarOptions); this._store.add(markdownMessageToolbar.onDidChangeMenuItems(() => this._onDidChangeHeight.fire())); From fe0cbe47d02021bcd16869ce44d04c73023b70fc Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 12:35:02 -0700 Subject: [PATCH 149/216] add info to help menus --- .../contrib/chat/browser/actions/chatAccessibilityHelp.ts | 3 +++ .../codeEditor/browser/accessibility/accessibility.ts | 2 +- .../contrib/inlineChat/browser/inlineChat.contribution.ts | 5 +---- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts index 04647bb8edf24..bd75a451666ee 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatAccessibilityHelp.ts @@ -18,9 +18,11 @@ import { AccessibleDiffViewerNext } from 'vs/editor/browser/widget/diffEditor.co export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'panelChat' | 'inlineChat'): string { const keybindingService = accessor.get(IKeybindingService); const content = []; + const openAccessibleViewKeybinding = keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel(); if (type === 'panelChat') { content.push(localize('chat.overview', 'The chat view is comprised of an input box and a request/response list. The input box is used to make requests and the list is used to display responses.')); content.push(localize('chat.requestHistory', 'In the input box, use up and down arrows to navigate your request history. Edit input and use enter or the submit button to run a new request.')); + content.push(openAccessibleViewKeybinding ? localize('chat.inspectResponse', 'In the input box, inspect the last response in the accessible view via {0}', openAccessibleViewKeybinding) : localize('chat.inspectResponseNoKb', 'With the input box focused, inspect the last response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); content.push(localize('chat.announcement', 'Chat responses will be announced as they come in. A response will indicate the number of code blocks, if any, and then the rest of the response.')); content.push(descriptionForCommand('chat.action.focus', localize('workbench.action.chat.focus', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke The Focus Chat command ({0}).',), localize('workbench.action.chat.focusNoKb', 'To focus the chat request/response list, which can be navigated with up and down arrows, invoke The Focus Chat List command, which is currently not triggerable by a keybinding.'), keybindingService)); content.push(descriptionForCommand('workbench.action.chat.focusInput', localize('workbench.action.chat.focusInput', 'To focus the input box for chat requests, invoke the Focus Chat Input command ({0})'), localize('workbench.action.interactiveSession.focusInputNoKb', 'To focus the input box for chat requests, invoke the Focus Chat Input command, which is currently not triggerable by a keybinding.'), keybindingService)); @@ -35,6 +37,7 @@ export function getAccessibilityHelpText(accessor: ServicesAccessor, type: 'pane if (upHistoryKeybinding && downHistoryKeybinding) { content.push(localize('inlineChat.requestHistory', 'In the input box, use {0} and {1} to navigate your request history. Edit input and use enter or the submit button to run a new request.', upHistoryKeybinding, downHistoryKeybinding)); } + content.push(openAccessibleViewKeybinding ? localize('inlineChat.inspectResponse', 'In the input box, inspect the response in the accessible view via {0}', openAccessibleViewKeybinding) : localize('inlineChat.inspectResponseNoKb', 'With the input box focused, inspect the response in the accessible view via the Open Accessible View command, which is currently not triggerable by a keybinding.')); content.push(localize('inlineChat.contextActions', "Context menu actions may run a request prefixed with a /. Type / to discover such ready-made commands.")); content.push(localize('inlineChat.fix', "If a fix action is invoked, a response will indicate the problem with the current code. A diff editor will be rendered and can be reached by tabbing.")); const diffReviewKeybinding = keybindingService.lookupKeybinding(AccessibleDiffViewerNext.id)?.getAriaLabel(); diff --git a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 1b57154caed3f..d10802456cbab 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -28,7 +28,7 @@ class ToggleScreenReaderMode extends Action2 { when: accessibilityHelpIsShown }, { - primary: KeyMod.Alt | KeyCode.F3, + primary: KeyMod.Alt | KeyCode.F1 | KeyMod.Shift, weight: KeybindingWeight.WorkbenchContrib + 10, }] }); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index ca7308126ab6c..b304d1c5ade1a 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -75,7 +75,6 @@ class InlineChatAccessibleViewContribution extends Disposable { if (!controller) { return false; } - const inputFocused = editor.hasTextFocus(); const responseContent = controller?.getMessage(); if (!responseContent) { return false; @@ -84,9 +83,7 @@ class InlineChatAccessibleViewContribution extends Disposable { verbositySettingKey: AccessibilityVerbositySettingId.InlineChat, provideContent(): string { return responseContent; }, onClose() { - if (inputFocused) { - controller.focus(); - } + controller.focus(); }, options: { ariaLabel: localize('inlineChatAccessibleView', "Inline Chat Accessible View"), type: AccessibleViewType.View } From 29aef1b77dfee92f8adadc3acfccb67303f1f20b Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 12:37:47 -0700 Subject: [PATCH 150/216] message -> response --- .../inlineChat/browser/inlineChat.contribution.ts | 4 ++-- .../inlineChat/browser/inlineChatController.ts | 2 +- .../contrib/inlineChat/browser/inlineChatWidget.ts | 12 ++++++------ .../contrib/inlineChat/common/inlineChat.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts index b304d1c5ade1a..93742f3e7d8ad 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.contribution.ts @@ -7,7 +7,7 @@ import { registerAction2 } from 'vs/platform/actions/common/actions'; import { EditorContributionInstantiation, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; import * as InlineChatActions from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_MESSAGE_FOCUSED, IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED, IInlineChatService, INLINE_CHAT_ID, INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl'; import { IInlineChatSessionService, InlineChatSessionService } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession'; @@ -89,7 +89,7 @@ class InlineChatAccessibleViewContribution extends Disposable { options: { ariaLabel: localize('inlineChatAccessibleView', "Inline Chat Accessible View"), type: AccessibleViewType.View } }); return true; - }, ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_MESSAGE_FOCUSED))); + }, ContextKeyExpr.or(CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_RESPONSE_FOCUSED))); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 72d1aefee44c1..b661e993b4804 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -159,7 +159,7 @@ export class InlineChatController implements IEditorContribution { } getMessage(): string | undefined { - return this._zone.value.widget.message; + return this._zone.value.widget.responseContent; } getId(): string { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index 487023e2fe516..cb3862bc7f76d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -12,7 +12,7 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; -import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_MESSAGE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; import { EventType, Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; @@ -164,7 +164,7 @@ export class InlineChatWidget { private readonly _ctxInnerCursorStart: IContextKey; private readonly _ctxInnerCursorEnd: IContextKey; private readonly _ctxInputEditorFocused: IContextKey; - private readonly _ctxMessageFocused: IContextKey; + private readonly _ctxResponseFocused: IContextKey; private readonly _progressBar: ProgressBar; @@ -218,8 +218,8 @@ export class InlineChatWidget { this._store.add(this._inputEditor.onDidChangeModelContent(() => this._onDidChangeInput.fire(this))); this._store.add(this._inputEditor.onDidLayoutChange(() => this._onDidChangeHeight.fire())); this._store.add(this._inputEditor.onDidContentSizeChange(() => this._onDidChangeHeight.fire())); - this._store.add(addDisposableListener(this._elements.message, 'focus', () => this._ctxMessageFocused.set(true))); - this._store.add(addDisposableListener(this._elements.message, 'blur', () => this._ctxMessageFocused.reset())); + this._store.add(addDisposableListener(this._elements.message, 'focus', () => this._ctxResponseFocused.set(true))); + this._store.add(addDisposableListener(this._elements.message, 'blur', () => this._ctxResponseFocused.reset())); this._store.add(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) { @@ -241,7 +241,7 @@ export class InlineChatWidget { this._ctxInnerCursorStart = CTX_INLINE_CHAT_INNER_CURSOR_START.bindTo(this._contextKeyService); this._ctxInnerCursorEnd = CTX_INLINE_CHAT_INNER_CURSOR_END.bindTo(this._contextKeyService); this._ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(this._contextKeyService); - this._ctxMessageFocused = CTX_INLINE_CHAT_MESSAGE_FOCUSED.bindTo(this._contextKeyService); + this._ctxResponseFocused = CTX_INLINE_CHAT_RESPONSE_FOCUSED.bindTo(this._contextKeyService); // (1) inner cursor position (last/first line selected) const updateInnerCursorFirstLast = () => { @@ -497,7 +497,7 @@ export class InlineChatWidget { this._preferredExpansionState = expansionState; } - get message(): string | undefined { + get responseContent(): string | undefined { return this._elements.markdownMessage.textContent ?? undefined; } diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 8621c241b18c0..0249658958bc0 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -121,7 +121,7 @@ export const INTERACTIVE_EDITOR_ACCESSIBILITY_HELP_ID = 'interactiveEditorAccess export const CTX_INLINE_CHAT_HAS_PROVIDER = new RawContextKey('inlineChatHasProvider', false, localize('inlineChatHasProvider', "Whether a provider for interactive editors exists")); export const CTX_INLINE_CHAT_VISIBLE = new RawContextKey('inlineChatVisible', false, localize('inlineChatVisible', "Whether the interactive editor input is visible")); export const CTX_INLINE_CHAT_FOCUSED = new RawContextKey('inlineChatFocused', false, localize('inlineChatFocused', "Whether the interactive editor input is focused")); -export const CTX_INLINE_CHAT_MESSAGE_FOCUSED = new RawContextKey('inlineChatMessageFocused', false, localize('inlineChatMessageFocused', "Whether the interactive widget's message is focused")); +export const CTX_INLINE_CHAT_RESPONSE_FOCUSED = new RawContextKey('inlineChatResponseFocused', false, localize('inlineChatResponseFocused', "Whether the interactive widget's response is focused")); export const CTX_INLINE_CHAT_EMPTY = new RawContextKey('inlineChatEmpty', false, localize('inlineChatEmpty', "Whether the interactive editor input is empty")); export const CTX_INLINE_CHAT_INNER_CURSOR_FIRST = new RawContextKey('inlineChatInnerCursorFirst', false, localize('inlineChatInnerCursorFirst', "Whether the cursor of the iteractive editor input is on the first line")); export const CTX_INLINE_CHAT_INNER_CURSOR_LAST = new RawContextKey('inlineChatInnerCursorLast', false, localize('inlineChatInnerCursorLast', "Whether the cursor of the iteractive editor input is on the last line")); From 5a04b141b8068df487b0da7f5702fdcea943db30 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 21:39:02 +0200 Subject: [PATCH 151/216] fix #187947 (#188529) --- .../abstractExtensionManagementService.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts index 96195aae781e1..16744885ac788 100644 --- a/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts +++ b/src/vs/platform/extensionManagement/common/abstractExtensionManagementService.ts @@ -494,33 +494,46 @@ export abstract class AbstractExtensionManagementService extends Disposable impl } private async checkAndGetCompatibleVersion(extension: IGalleryExtension, sameVersion: boolean, installPreRelease: boolean): Promise<{ extension: IGalleryExtension; manifest: IExtensionManifest }> { + let compatibleExtension: IGalleryExtension | null; + const extensionsControlManifest = await this.getExtensionsControlManifest(); if (extensionsControlManifest.malicious.some(identifier => areSameExtensions(extension.identifier, identifier))) { throw new ExtensionManagementError(nls.localize('malicious extension', "Can't install '{0}' extension since it was reported to be problematic.", extension.identifier.id), ExtensionManagementErrorCode.Malicious); } - if (!await this.canInstall(extension)) { - const targetPlatform = await this.getTargetPlatform(); - throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform); + const deprecationInfo = extensionsControlManifest.deprecated[extension.identifier.id.toLowerCase()]; + if (deprecationInfo?.extension?.autoMigrate) { + this.logService.info(`The '${extension.identifier.id}' extension is deprecated, fetching the compatible '${deprecationInfo.extension.id}' extension instead.`); + compatibleExtension = (await this.galleryService.getExtensions([{ id: deprecationInfo.extension.id, preRelease: deprecationInfo.extension.preRelease }], { targetPlatform: await this.getTargetPlatform(), compatible: true }, CancellationToken.None))[0]; + if (!compatibleExtension) { + throw new ExtensionManagementError(nls.localize('notFoundDeprecatedReplacementExtension', "Can't install '{0}' extension since it was deprecated and the replacement extension '{1}' can't be found.", extension.identifier.id, deprecationInfo.extension.id), ExtensionManagementErrorCode.Deprecated); + } } - const compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease); - if (!compatibleExtension) { - /** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */ - if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) { - throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound); + else { + if (!await this.canInstall(extension)) { + const targetPlatform = await this.getTargetPlatform(); + throw new ExtensionManagementError(nls.localize('incompatible platform', "The '{0}' extension is not available in {1} for {2}.", extension.identifier.id, this.productService.nameLong, TargetPlatformToString(targetPlatform)), ExtensionManagementErrorCode.IncompatibleTargetPlatform); + } + + compatibleExtension = await this.getCompatibleVersion(extension, sameVersion, installPreRelease); + if (!compatibleExtension) { + /** If no compatible release version is found, check if the extension has a release version or not and throw relevant error */ + if (!installPreRelease && extension.properties.isPreReleaseVersion && (await this.galleryService.getExtensions([extension.identifier], CancellationToken.None))[0]) { + throw new ExtensionManagementError(nls.localize('notFoundReleaseExtension', "Can't install release version of '{0}' extension because it has no release version.", extension.identifier.id), ExtensionManagementErrorCode.ReleaseVersionNotFound); + } + throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible); } - throw new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Can't install '{0}' extension because it is not compatible with the current version of {1} (version {2}).", extension.identifier.id, this.productService.nameLong, this.productService.version), ExtensionManagementErrorCode.Incompatible); } this.logService.info('Getting Manifest...', compatibleExtension.identifier.id); const manifest = await this.galleryService.getManifest(compatibleExtension, CancellationToken.None); if (manifest === null) { - throw new ExtensionManagementError(`Missing manifest for extension ${extension.identifier.id}`, ExtensionManagementErrorCode.Invalid); + throw new ExtensionManagementError(`Missing manifest for extension ${compatibleExtension.identifier.id}`, ExtensionManagementErrorCode.Invalid); } if (manifest.version !== compatibleExtension.version) { - throw new ExtensionManagementError(`Cannot install '${extension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid); + throw new ExtensionManagementError(`Cannot install '${compatibleExtension.identifier.id}' extension because of version mismatch in Marketplace`, ExtensionManagementErrorCode.Invalid); } return { extension: compatibleExtension, manifest }; From 432b958cd437a2ef94081d4e927e5b0fd05cce45 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Jul 2023 21:59:37 +0200 Subject: [PATCH 152/216] fix #188131 (#188531) --- .../contrib/extensions/browser/extensionsWorkbenchService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index de295f7e1565d..96659a5f22864 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -1127,7 +1127,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension if (isSameExtensionRunning) { // Different version or target platform of same extension is running. Requires reload to run the current version - if (extension.version !== runningExtension.version || extension.local.targetPlatform !== runningExtension.targetPlatform) { + if (!runningExtension.isUnderDevelopment && (extension.version !== runningExtension.version || extension.local.targetPlatform !== runningExtension.targetPlatform)) { return nls.localize('postUpdateTooltip', "Please reload Visual Studio Code to enable the updated extension."); } From 2dccf7db76203fe9621652e54e69ec7b29679d98 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 21 Jul 2023 13:31:03 -0700 Subject: [PATCH 153/216] fix tests --- .vscode/notebooks/my-endgame.github-issues | 5 +++++ .../inlineChat/test/browser/inlineChatController.test.ts | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index 92fddeef220c2..fef2cbf66624b 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -44,6 +44,11 @@ "language": "github-issues", "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, + { + "kind": 1, + "language": "markdown", + "value": "" + }, { "kind": 1, "language": "markdown", diff --git a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts index d491c19523468..49db6a4ff686e 100644 --- a/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts +++ b/src/vs/workbench/contrib/inlineChat/test/browser/inlineChatController.test.ts @@ -27,6 +27,8 @@ import { equals } from 'vs/base/common/arrays'; import { timeout } from 'vs/base/common/async'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; +import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; suite('InteractiveChatController', function () { @@ -104,6 +106,11 @@ suite('InteractiveChatController', function () { [IChatAccessibilityService, new class extends mock() { override acceptResponse(response?: IChatResponseViewModel): void { } override acceptRequest(): void { } + }], + [IAccessibleViewService, new class extends mock() { + override getOpenAriaHint(verbositySettingKey: AccessibilityVerbositySettingId): string | null { + return null; + } }] ); From be83e994666165f089ce39ee867c852cdb984944 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Fri, 21 Jul 2023 16:08:32 -0700 Subject: [PATCH 154/216] drop shadow css + sticky line model + click sticky to focus cell (#188545) * add model tracking currently rendered nb sticky lines * drop shadow css + sticky line model + click sticky to focus cell --- .../browser/diff/notebookDiffEditor.ts | 8 +- .../notebook/browser/diff/notebookDiffList.ts | 2 +- .../media/notebookEditorStickyScroll.css | 14 +- .../notebook/browser/notebookBrowser.ts | 5 + .../notebook/browser/notebookEditorWidget.ts | 20 +-- .../browser/view/cellParts/cellDnd.ts | 8 +- .../notebook/browser/view/notebookCellList.ts | 10 -- .../browser/view/notebookRenderingCommon.ts | 1 - .../viewParts/notebookEditorStickyScroll.ts | 126 +++++++++++++++--- 9 files changed, 143 insertions(+), 51 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index e21aba1d2ec85..38210f5785b40 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -368,7 +368,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD // output is already gone removedItems.push(key); } else { - const cellTop = this._list.getAbsoluteTopOfElement(value.cellInfo.diffElement); + const cellTop = this._list.getCellViewScrollTop(value.cellInfo.diffElement); const outputIndex = cell.outputsViewModels.indexOf(key); const outputOffset = value.cellInfo.diffElement.getOutputOffsetInCell(diffSide, outputIndex); updateItems.push({ @@ -872,10 +872,10 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } if (!activeWebview.insetMapping.has(output.source)) { - const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); + const cellTop = this._list.getCellViewScrollTop(cellDiffViewModel); 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 cellTop = this._list.getCellViewScrollTop(cellDiffViewModel); const outputIndex = cellViewModel.outputsViewModels.indexOf(output.source); const outputOffset = cellDiffViewModel.getOutputOffsetInCell(diffSide, outputIndex); activeWebview.updateScrollTops([{ @@ -927,7 +927,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return; } - const cellTop = this._list.getAbsoluteTopOfElement(cellDiffViewModel); + const cellTop = this._list.getCellViewScrollTop(cellDiffViewModel); const outputIndex = cellViewModel.outputsViewModels.indexOf(displayOutput); const outputOffset = cellDiffViewModel.getOutputOffsetInCell(diffSide, outputIndex); activeWebview.updateScrollTops([{ diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index d07f14414960a..8b12840f440f4 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -322,7 +322,7 @@ export class NotebookTextDiffList extends WorkbenchList= this.length) { // this._getViewIndexUpperBound(element); diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css index 9f8b4b69c8258..8107bfb128222 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebookEditorStickyScroll.css @@ -7,8 +7,6 @@ display: none; position: absolute; background-color: var(--vscode-notebook-editorBackground); - border-bottom: solid 1px var(--vscode-notebook-cellToolbarSeparator); - box-sizing: border-box; z-index: var(--z-index-notebook-sticky-scroll); width: 100%; font-family: var(--notebook-cell-input-preview-font-family); @@ -27,3 +25,15 @@ background-color: var(--vscode-editorStickyScrollHover-background); cursor: pointer; } + +.monaco-workbench + .notebookOverlay + .notebook-sticky-scroll-container + .notebook-shadow { + display: block; + top: 0; + left: 3px; + height: 3px; + width: 100%; + box-shadow: var(--vscode-scrollbar-shadow) 0 6px 6px -6px inset; +} diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index fa9048616fcdd..71bd138fdf85c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -566,6 +566,11 @@ export interface INotebookEditor { */ removeClassName(className: string): void; + /** + * Set scrollTop value of the notebook editor. + */ + setScrollTop(scrollTop: number): void; + /** * The range will be revealed with as little scrolling as possible. */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 99ec30b3a35fa..c1d0acf52f34c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -94,7 +94,7 @@ import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/no import { Schemas } from 'vs/base/common/network'; import { DropIntoEditorController } from 'vs/editor/contrib/dropOrPasteInto/browser/dropIntoEditorController'; import { CopyPasteController } from 'vs/editor/contrib/dropOrPasteInto/browser/copyPasteController'; -import { NotebookEditorStickyScroll } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll'; +import { NotebookStickyScroll } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll'; import { NotebookCellOutlineProvider } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -1046,7 +1046,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } private _registerNotebookStickyScroll() { - this._register(this.instantiationService.createInstance(NotebookEditorStickyScroll, this._notebookStickyScrollContainer, this, this._notebookOutline, this._list)); + this._register(this.instantiationService.createInstance(NotebookStickyScroll, this._notebookStickyScrollContainer, this, this._notebookOutline, this._list)); } private _updateOutputRenderers() { @@ -2038,13 +2038,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } getAbsoluteTopOfElement(cell: ICellViewModel) { - return this._list.getAbsoluteTopOfElement(cell); + return this._list.getCellViewScrollTop(cell); } scrollToBottom() { this._list.scrollToBottom(); } + setScrollTop(scrollTop: number): void { + this._list.scrollTop = scrollTop; + } + revealCellRangeInView(range: ICellRange) { return this._list.revealCellsInView(range); } @@ -2629,7 +2633,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10); const top = !!webviewTop ? (0 - webviewTop) : 0; - const cellTop = this._list.getAbsoluteTopOfElement(cell); + const cellTop = this._list.getCellViewScrollTop(cell); await this._webview.showMarkupPreview({ mime: cell.mime, cellHandle: cell.handle, @@ -2723,7 +2727,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10); const top = !!webviewTop ? (0 - webviewTop) : 0; - const cellTop = this._list.getAbsoluteTopOfElement(cell) + top; + const cellTop = this._list.getCellViewScrollTop(cell) + top; const existingOutput = this._webview.insetMapping.get(output.source); if (!existingOutput @@ -2781,7 +2785,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const webviewTop = parseInt(this._list.webviewElement.domNode.style.top, 10); const top = !!webviewTop ? (0 - webviewTop) : 0; - const cellTop = this._list.getAbsoluteTopOfElement(cell) + top; + const cellTop = this._list.getCellViewScrollTop(cell) + top; this._webview.updateOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset); }); } @@ -2889,7 +2893,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD removedItems.push(key); } - const cellTop = this._list.getAbsoluteTopOfElement(cell); + const cellTop = this._list.getCellViewScrollTop(cell); const outputIndex = cell.outputsViewModels.indexOf(key); const outputOffset = cell.getOutputOffset(outputIndex); updateItems.push({ @@ -2907,7 +2911,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD for (const cellId of this._webview.markupPreviewMapping.keys()) { const cell = this.viewModel?.viewCells.find(cell => cell.id === cellId); if (cell) { - const cellTop = this._list.getAbsoluteTopOfElement(cell); + const cellTop = this._list.getCellViewScrollTop(cell); // markdownUpdateItems.push({ id: cellId, top: cellTop }); markdownUpdateItems.push({ id: cellId, top: cellTop + top }); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts index 007ed9dc801c4..5531da7b673db 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts @@ -140,7 +140,7 @@ export class CellDragAndDropController extends Disposable { return undefined; } - const cellTop = this.list.getAbsoluteTopOfElement(draggedOverCell); + const cellTop = this.list.getCellViewScrollTop(draggedOverCell); const cellHeight = this.list.elementHeight(draggedOverCell); const dragPosInElement = dragOffset - cellTop; @@ -228,7 +228,7 @@ export class CellDragAndDropController extends Disposable { } private _dropImpl(draggedCell: ICellViewModel, dropDirection: 'above' | 'below', ctx: { ctrlKey: boolean; altKey: boolean }, draggedOverCell: ICellViewModel) { - const cellTop = this.list.getAbsoluteTopOfElement(draggedOverCell); + const cellTop = this.list.getCellViewScrollTop(draggedOverCell); const cellHeight = this.list.elementHeight(draggedOverCell); const insertionIndicatorAbsolutePos = dropDirection === 'above' ? cellTop : cellTop + cellHeight; const { bottomToolbarGap } = this.notebookEditor.notebookOptions.computeBottomToolbarDimensions(this.notebookEditor.textModel?.viewType); @@ -358,7 +358,7 @@ export class CellDragAndDropController extends Disposable { const target = this.list.elementAt(dragOffsetY); if (target && target !== cell) { - const cellTop = this.list.getAbsoluteTopOfElement(target); + const cellTop = this.list.getCellViewScrollTop(target); const cellHeight = this.list.elementHeight(target); const dropDirection = this.getExplicitDragDropDirection(dragOffsetY, cellTop, cellHeight); @@ -400,7 +400,7 @@ export class CellDragAndDropController extends Disposable { return; } - const cellTop = this.list.getAbsoluteTopOfElement(target); + const cellTop = this.list.getCellViewScrollTop(target); const cellHeight = this.list.elementHeight(target); const dropDirection = this.getExplicitDragDropDirection(ctx.dragOffsetY, cellTop, cellHeight); this._dropImpl(cell, dropDirection, ctx, target); diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 80b657b5aad4f..4e3eb4b10a5e4 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -1131,16 +1131,6 @@ export class NotebookCellList extends WorkbenchList implements ID this.view.domNode.focus(); } - getAbsoluteTopOfElement(element: ICellViewModel): number { - const index = this._getViewIndexUpperBound(element); - if (index === undefined || index < 0 || index >= this.length) { - this._getViewIndexUpperBound(element); - throw new ListError(this.listUser, `Invalid index ${index}`); - } - - return this.view.elementTop(index); - } - triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) { this.view.delegateScrollFromMouseWheelEvent(browserEvent); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts index 66926b44cf65e..68a9cc8af769f 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon.ts @@ -74,7 +74,6 @@ export interface INotebookCellList { setHiddenAreas(_ranges: ICellRange[], triggerViewUpdate: boolean): boolean; domElementOfElement(element: ICellViewModel): HTMLElement | null; focusView(): void; - getAbsoluteTopOfElement(element: ICellViewModel): number; triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent): void; updateElementHeight2(element: ICellViewModel, size: number, anchorElementIndex?: number | null): void; domFocus(): void; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index 37987fed8b624..953b32e1151c7 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -10,9 +10,47 @@ import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/no import { NotebookCellOutlineProvider, OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +class NotebookStickyLine extends Disposable { + constructor( + public readonly element: HTMLElement, + public readonly entry: OutlineEntry, + public readonly notebookEditor: INotebookEditor, + ) { + super(); + this._register(DOM.addDisposableListener(this.element, DOM.EventType.CLICK, (e) => { + console.log('click on sticky line'); + this.focusCell(); + })); + } -export class NotebookEditorStickyScroll extends Disposable { + private focusCell() { + this.notebookEditor.focusNotebookCell(this.entry.cell, 'container'); + const cellScrollTop = this.notebookEditor.getAbsoluteTopOfElement(this.entry.cell); + const parentCount = this.getParentCount(); + // 1.1 addresses visible cell padding, to make sure we don't focus md cell and also render its sticky line + this.notebookEditor.setScrollTop(cellScrollTop - (parentCount + 1.1) * 22); + } + + private getParentCount() { + let count = 0; + let entry = this.entry; + while (entry.parent) { + count++; + entry = entry.parent; + } + return count; + } + +} + + +export class NotebookStickyScroll extends Disposable { private readonly _disposables = new DisposableStore(); + private currentStickyLines = new Map(); + + getDomNode(): HTMLElement { + return this.domNode; + } constructor( private readonly domNode: HTMLElement, @@ -22,6 +60,7 @@ export class NotebookEditorStickyScroll extends Disposable { ) { super(); + if (this.notebookEditor.notebookOptions.getLayoutConfiguration().stickyScroll) { this.init(); } @@ -111,6 +150,8 @@ export class NotebookEditorStickyScroll extends Disposable { let sectionBottom = 0; for (let i = visibleRange.start; i < visibleRange.end; i++) { if (i === 0) { // don't show headers when you're viewing the top cell + this.updateDisplay(); + this.currentStickyLines = new Map(); return; } const cell = this.notebookEditor.cellAt(i); @@ -157,7 +198,12 @@ export class NotebookEditorStickyScroll extends Disposable { // compute the space available for sticky lines, and render sticky lines const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); - this.renderStickyLines(trackedEntry?.parent, this.domNode, linesToRender); + let newMap: Map | undefined = new Map(); + newMap = this.renderStickyLines(trackedEntry?.parent, this.domNode, linesToRender, newMap); + if (!newMap) { + newMap = new Map(); + } + this.currentStickyLines = newMap; this.updateDisplay(); } @@ -165,16 +211,23 @@ export class NotebookEditorStickyScroll extends Disposable { private updateContent() { // find first code cell in visible range. this marks the start of the first section // find the last code cell in the first section of the visible range, store the bottom scroll position in a const sectionBottom - // compute sticky scroll height, and check if editorScrolltop + stickyScrollHeight < sectionBottom // ? maybe use 22 instead of stickyscrollheight, as we don't necessarily render each line + // compute sticky scroll height, and check if editorScrolltop + stickyScrollHeight < sectionBottom // if that condition is true, break out of the loop with that cell as the tracked cell // if that condition is false, continue to next cell DOM.clearNode(this.domNode); + // iterate over current map and dispose each notebookstickyline + this.currentStickyLines.forEach((value) => { + value.dispose(); + }); + const editorScrollTop = this.notebookEditor.scrollTop; // find last code cell of section, store bottom scroll position in sectionBottom const visibleRange = this.notebookEditor.visibleRanges[0]; if (!visibleRange) { + this.updateDisplay(); + this.currentStickyLines = new Map(); return; } @@ -183,6 +236,7 @@ export class NotebookEditorStickyScroll extends Disposable { for (let i = visibleRange.start; i < visibleRange.end; i++) { if (i === 0) { // don't show headers when you're viewing the top cell this.updateDisplay(); + this.currentStickyLines = new Map(); return; } const cell = this.notebookEditor.cellAt(i); @@ -210,7 +264,12 @@ export class NotebookEditorStickyScroll extends Disposable { const currentSectionStickyHeight = this.computeStickyHeight(entry!); if (editorScrollTop + currentSectionStickyHeight < sectionBottom) { const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); - this.renderStickyLines(entry?.parent, this.domNode, linesToRender); + let newMap: Map | undefined = new Map(); + newMap = this.renderStickyLines(entry?.parent, this.domNode, linesToRender, newMap); + if (!newMap) { + newMap = new Map(); + } + this.currentStickyLines = newMap; break; } @@ -229,11 +288,21 @@ export class NotebookEditorStickyScroll extends Disposable { // if the current section and the next section share a parent, then we can render the next section's sticky lines to avoid pop-in between if (entry?.parent?.parent === nextSectionEntry?.parent) { const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22) + 1; - this.renderStickyLines(nextSectionEntry?.parent, this.domNode, linesToRender); + let newMap: Map | undefined = new Map(); + newMap = this.renderStickyLines(nextSectionEntry?.parent, this.domNode, linesToRender, newMap); + if (!newMap) { + newMap = new Map(); + } + this.currentStickyLines = newMap; break; } else if (Math.abs(currentSectionStickyHeight - nextSectionStickyHeight) > 22) { // only shrink sticky const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); - this.renderStickyLines(entry?.parent, this.domNode, linesToRender); + let newMap: Map | undefined = new Map(); + newMap = this.renderStickyLines(entry?.parent, this.domNode, linesToRender, newMap); + if (!newMap) { + newMap = new Map(); + } + this.currentStickyLines = newMap; break; } } @@ -242,7 +311,14 @@ export class NotebookEditorStickyScroll extends Disposable { sectionBottom = this.notebookEditor.scrollTop + this.notebookEditor.getLayoutInfo().scrollHeight; trackedEntry = this.getVisibleOutlineEntry(i); const linesToRender = Math.floor((sectionBottom - editorScrollTop) / 22); - this.renderStickyLines(trackedEntry?.parent, this.domNode, linesToRender); + + let newMap: Map | undefined = new Map(); + newMap = this.renderStickyLines(trackedEntry?.parent, this.domNode, linesToRender, newMap); + if (!newMap) { + newMap = new Map(); + } + this.currentStickyLines = newMap; + break; } } // cell loop close @@ -268,27 +344,35 @@ export class NotebookEditorStickyScroll extends Disposable { return height; } - private renderStickyLines(entry: OutlineEntry | undefined, containerElement: HTMLElement, linesToRender: number) { + private renderStickyLines(entry: OutlineEntry | undefined, containerElement: HTMLElement, numLinesToRender: number, newMap: Map) { const partial = false; - if (!entry) { - return; - } - if (entry.parent) { - this.renderStickyLines(entry.parent, containerElement, linesToRender); + let currentEntry = entry; + + const elementsToRender = []; + while (currentEntry) { + const lineToRender = this.createStickyElement(currentEntry, partial); + newMap.set(currentEntry, lineToRender); + elementsToRender.unshift(lineToRender); + currentEntry = currentEntry.parent; } - const numStickyLines = containerElement.children.length; - if (numStickyLines >= linesToRender) { - return; + // iterate over elements to render, and append to container + // break when we reach numLinesToRender + for (let i = 0; i < elementsToRender.length; i++) { + if (i >= numLinesToRender) { + break; + } + containerElement.append(elementsToRender[i].element); } - DOM.append(containerElement, this.createStickyElement(entry, partial)); + containerElement.append(DOM.$('div', { class: 'notebook-shadow' })); // ensure we have dropShadow at base of sticky scroll + return newMap; } private createStickyElement(entry: OutlineEntry, partial: boolean) { - const stickyLine = document.createElement('div'); - stickyLine.classList.add('notebook-sticky-scroll-line'); - stickyLine.innerText = '#'.repeat(entry.level) + ' ' + entry.label; + const stickyElement = document.createElement('div'); + stickyElement.classList.add('notebook-sticky-scroll-line'); + stickyElement.innerText = '#'.repeat(entry.level) + ' ' + entry.label; // todo: partial line rendering for animater if (partial) { @@ -296,7 +380,7 @@ export class NotebookEditorStickyScroll extends Disposable { // stickyLine.style.height = `${partialHeight}px`; } - return stickyLine; + return new NotebookStickyLine(stickyElement, entry, this.notebookEditor); } override dispose() { From 176dfcaefe7c4494a52223d060dddc2e9f4ac883 Mon Sep 17 00:00:00 2001 From: Michael Lively Date: Fri, 21 Jul 2023 16:17:17 -0700 Subject: [PATCH 155/216] add context menu toggle + command pallette toggle sticky --- src/vs/platform/actions/common/actions.ts | 1 + .../viewParts/notebookEditorStickyScroll.ts | 54 ++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index a6b2f612b3c04..9aa9e402f12c9 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -145,6 +145,7 @@ export class MenuId { static readonly InteractiveCellExecute = new MenuId('InteractiveCellExecute'); static readonly InteractiveInputExecute = new MenuId('InteractiveInputExecute'); static readonly NotebookToolbar = new MenuId('NotebookToolbar'); + static readonly NotebookStickyScrollContext = new MenuId('NotebookStickyScrollContext'); static readonly NotebookCellTitle = new MenuId('NotebookCellTitle'); static readonly NotebookCellDelete = new MenuId('NotebookCellDelete'); static readonly NotebookCellInsert = new MenuId('NotebookCellInsert'); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index 953b32e1151c7..4e13acad89df1 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -3,13 +3,50 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; +import { Categories } from 'vs/platform/action/common/actionCommonCategories'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { NotebookCellOutlineProvider, OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +export class ToggleNotebookStickyScroll extends Action2 { + + constructor() { + super({ + id: 'notebook.action.toggleNotebookStickyScroll', + title: { + value: localize('toggleStickyScroll', "Toggle Notebook Sticky Scroll"), + mnemonicTitle: localize({ key: 'mitoggleStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Toggle Notebook Sticky Scroll"), + original: 'Toggle Notebook Sticky Scroll', + }, + category: Categories.View, + toggled: { + condition: ContextKeyExpr.equals('config.notebook.stickyScroll.enabled', true), + title: localize('notebookStickyScroll', "Notebook Sticky Scroll"), + mnemonicTitle: localize({ key: 'miNotebookStickyScroll', comment: ['&& denotes a mnemonic'] }, "&&Notebook Sticky Scroll"), + }, + menu: [ + { id: MenuId.CommandPalette }, + { id: MenuId.NotebookStickyScrollContext } + ] + }); + } + + override async run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + const newValue = !configurationService.getValue('notebook.stickyScroll.enabled'); + return configurationService.updateValue('notebook.stickyScroll.enabled', newValue); + } +} + class NotebookStickyLine extends Disposable { constructor( public readonly element: HTMLElement, @@ -56,11 +93,11 @@ export class NotebookStickyScroll extends Disposable { private readonly domNode: HTMLElement, private readonly notebookEditor: INotebookEditor, private readonly notebookOutline: NotebookCellOutlineProvider, - private readonly notebookCellList: INotebookCellList + private readonly notebookCellList: INotebookCellList, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, ) { super(); - if (this.notebookEditor.notebookOptions.getLayoutConfiguration().stickyScroll) { this.init(); } @@ -73,6 +110,17 @@ export class NotebookStickyScroll extends Disposable { this.setTop(); } })); + + this._register(DOM.addDisposableListener(this.domNode, DOM.EventType.CONTEXT_MENU, async (event: MouseEvent) => { + this.onContextMenu(event); + })); + } + + private onContextMenu(event: MouseEvent) { + this._contextMenuService.showContextMenu({ + menuId: MenuId.NotebookStickyScrollContext, + getAnchor: () => event, + }); } private updateConfig() { @@ -388,3 +436,5 @@ export class NotebookStickyScroll extends Disposable { super.dispose(); } } + +registerAction2(ToggleNotebookStickyScroll); From 10ade73bf2c1430a96db466c2aee900d9c4298e3 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 21 Jul 2023 16:33:22 -0700 Subject: [PATCH 156/216] Support chat progress with transient content (#188526) --- .../workbench/api/browser/mainThreadChat.ts | 27 +++++++- .../workbench/api/common/extHost.protocol.ts | 6 +- src/vs/workbench/api/common/extHostChat.ts | 19 +++++- .../contrib/chat/common/chatModel.ts | 66 +++++++++++++++++-- .../contrib/chat/common/chatService.ts | 2 +- .../contrib/chat/common/chatServiceImpl.ts | 2 + .../vscode.proposed.interactive.d.ts | 7 +- 7 files changed, 113 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index e252df07d6761..dac2ff338d5bb 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { DeferredPromise } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ExtHostChatShape, ExtHostContext, IChatRequestDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatShape, ExtHostContext, IChatRequestDto, IChatResponseProgressDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { IChat, IChatDynamicRequest, IChatProgress, IChatRequest, IChatResponse, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; @@ -16,11 +17,14 @@ import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/ext export class MainThreadChat extends Disposable implements MainThreadChatShape { private readonly _providerRegistrations = this._register(new DisposableMap()); - private readonly _activeRequestProgressCallbacks = new Map void>(); + private readonly _activeRequestProgressCallbacks = new Map (DeferredPromise | void)>(); private readonly _stateEmitters = new Map>(); private readonly _proxy: ExtHostChatShape; + private _responsePartHandlePool = 0; + private readonly _activeResponsePartPromises = new Map>(); + constructor( extHostContext: IExtHostContext, @IChatService private readonly _chatService: IChatService, @@ -133,8 +137,25 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { this._providerRegistrations.set(handle, unreg); } - $acceptResponseProgress(handle: number, sessionId: number, progress: IChatProgress): void { + async $acceptResponseProgress(handle: number, sessionId: number, progress: IChatResponseProgressDto, responsePartHandle?: number): Promise { const id = `${handle}_${sessionId}`; + + if ('placeholder' in progress) { + const responsePartId = `${id}_${++this._responsePartHandlePool}`; + const deferredContentPromise = new DeferredPromise(); + this._activeResponsePartPromises.set(responsePartId, deferredContentPromise); + this._activeRequestProgressCallbacks.get(id)?.({ ...progress, resolvedContent: deferredContentPromise.p }); + return this._responsePartHandlePool; + } else if (responsePartHandle) { + // Complete an existing deferred promise with resolved content + const responsePartId = `${id}_${responsePartHandle}`; + const deferredContentPromise = this._activeResponsePartPromises.get(responsePartId); + if (deferredContentPromise && 'content' in progress) { + deferredContentPromise.complete(progress.content); + this._activeResponsePartPromises.delete(responsePartId); + } + } + this._activeRequestProgressCallbacks.get(id)?.(progress); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f2c2bc7893fde..f70ba20ea667c 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -51,7 +51,7 @@ import { SaveReason } from 'vs/workbench/common/editor'; import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; -import { IChatProgress, IChatResponseErrorDetails, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatResponseErrorDetails, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; import * as notebookCommon from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService'; import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; @@ -1166,13 +1166,15 @@ export interface IChatResponseDto { }; } +export type IChatResponseProgressDto = { content: string } | { requestId: string } | { placeholder: string }; + export interface MainThreadChatShape extends IDisposable { $registerChatProvider(handle: number, id: string): Promise; $acceptChatState(sessionId: number, state: any): Promise; $addRequest(context: any): void; $sendRequestToProvider(providerId: string, message: IChatDynamicRequest): void; $unregisterChatProvider(handle: number): Promise; - $acceptResponseProgress(handle: number, sessionId: number, progress: IChatProgress): void; + $acceptResponseProgress(handle: number, sessionId: number, progress: IChatResponseProgressDto, responsePartHandle?: number): Promise; $transferChatSession(sessionId: number, toWorkspace: UriComponents): void; $registerSlashCommandProvider(handle: number, chatProviderId: string): Promise; diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index 70ccff3a6cd33..6a4dd119dc9f4 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; @@ -14,7 +15,7 @@ import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/exte import { ILogService } from 'vs/platform/log/common/log'; import { ExtHostChatShape, IChatRequestDto, IChatResponseDto, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { IChatFollowup, IChatProgress, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; import type * as vscode from 'vscode'; class ChatProviderWrapper { @@ -214,8 +215,20 @@ export class ExtHostChat implements ExtHostChatShape { firstProgress = stopWatch.elapsed(); } - const vscodeProgress: IChatProgress = 'responseId' in progress ? { requestId: progress.responseId } : progress; - this._proxy.$acceptResponseProgress(handle, sessionId, vscodeProgress); + if ('responseId' in progress) { + this._proxy.$acceptResponseProgress(handle, sessionId, { requestId: progress.responseId }); + } else if ('placeholder' in progress && 'resolvedContent' in progress) { + const resolvedContent = Promise.all([this._proxy.$acceptResponseProgress(handle, sessionId, { placeholder: progress.placeholder }), progress.resolvedContent]); + raceCancellation(resolvedContent, token).then((res) => { + if (!res) { + return; /* Cancelled */ + } + const [progressHandle, progressContent] = res; + this._proxy.$acceptResponseProgress(handle, sessionId, progressContent, progressHandle ?? undefined); + }); + } else { + this._proxy.$acceptResponseProgress(handle, sessionId, progress); + } } }; let result: vscode.InteractiveResponseForProgress | undefined | null; diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 875cc21505594..f8f6a9c25b5f1 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -81,6 +81,53 @@ export class ChatRequestModel implements IChatRequestModel { } } + +interface ResponsePart { string: IMarkdownString; resolving?: boolean } +class Response { + private _responseParts: ResponsePart[]; + private _responseRepr: IMarkdownString; + + get value(): IMarkdownString { + return this._responseRepr; + } + + constructor(value: IMarkdownString) { + this._responseRepr = value; + this._responseParts = [{ string: value }]; + } + + updateContent(responsePart: string | { placeholder: string; resolvedContent?: Promise }): void { + if (typeof responsePart === 'string') { + const responsePartLength = this._responseParts.length - 1; + const lastResponsePart = this._responseParts[responsePartLength]; + + if (lastResponsePart.resolving === true) { + // The last part is resolving, start a new part + this._responseParts.push({ string: new MarkdownString(responsePart) }); + } else { + // Combine this part with the last, non-resolving part + this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart) }; + } + + this._updateRepr(); + } else { + // Add a new resolving part + const responsePosition = this._responseParts.push({ string: new MarkdownString(responsePart.placeholder), resolving: true }); + this._updateRepr(); + + responsePart.resolvedContent?.then((content) => { + // Replace the resolving part's content with the resolved response + this._responseParts[responsePosition] = { string: new MarkdownString(content) }; + this._updateRepr(); + }); + } + } + + private _updateRepr() { + this._responseRepr = new MarkdownString(this._responseParts.map(r => r.string.value).join('\n')); + } +} + export class ChatResponseModel extends Disposable implements IChatResponseModel { private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; @@ -112,8 +159,9 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._followups; } + private _response: Response; public get response(): IMarkdownString { - return this._response; + return this._response.value; } public get errorDetails(): IChatResponseErrorDetails | undefined { @@ -133,7 +181,7 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel } constructor( - private _response: IMarkdownString, + _response: IMarkdownString, public readonly session: ChatModel, private _isComplete: boolean = false, private _isCanceled = false, @@ -143,13 +191,17 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _followups?: IChatFollowup[] ) { super(); + this._response = new Response(_response); this._id = 'response_' + ChatResponseModel.nextId++; } - updateContent(responsePart: string, quiet?: boolean) { - this._response = new MarkdownString(this.response.value + responsePart); - if (!quiet) { - this._onDidChange.fire(); + updateContent(responsePart: string | { placeholder: string; resolvedContent?: Promise }, quiet?: boolean) { + try { + this._response.updateContent(responsePart); + } finally { + if (!quiet) { + this._onDidChange.fire(); + } } } @@ -453,6 +505,8 @@ export class ChatModel extends Disposable implements IChatModel { if ('content' in progress) { request.response.updateContent(progress.content, quiet); + } else if ('placeholder' in progress) { + request.response.updateContent(progress, quiet); } else { request.setProviderRequestId(progress.requestId); request.response.setProviderResponseId(progress.requestId); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index a93be123a49f2..7bcc7212fc10d 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -43,7 +43,7 @@ export interface IChatResponse { } export type IChatProgress = - { content: string } | { requestId: string }; + { content: string } | { requestId: string } | { placeholder: string; resolvedContent: Promise }; export interface IPersistedChatState { } export interface IChatProvider { diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 68a14aaf8b7db..1188245429428 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -424,6 +424,8 @@ export class ChatService extends Disposable implements IChatService { gotProgress = true; if ('content' in progress) { this.trace('sendRequest', `Provider returned progress for session ${model.sessionId}, ${progress.content.length} chars`); + } else if ('placeholder' in progress) { + this.trace('sendRequest', `Provider returned placeholder for session ${model.sessionId}, ${progress.placeholder}`); } else { this.trace('sendRequest', `Provider returned id for session ${model.sessionId}, ${progress.requestId}`); } diff --git a/src/vscode-dts/vscode.proposed.interactive.d.ts b/src/vscode-dts/vscode.proposed.interactive.d.ts index a0cb74cb49107..c9fda66ae715d 100644 --- a/src/vscode-dts/vscode.proposed.interactive.d.ts +++ b/src/vscode-dts/vscode.proposed.interactive.d.ts @@ -126,7 +126,12 @@ declare module 'vscode' { responseId: string; } - export type InteractiveProgress = InteractiveProgressContent | InteractiveProgressId; + export interface InteractiveProgressTask { + placeholder: string; + resolvedContent: Thenable; + } + + export type InteractiveProgress = InteractiveProgressContent | InteractiveProgressId | InteractiveProgressTask; export interface InteractiveResponseCommand { commandId: string; From a5fa6ad389aface52d3a4983e44008ab1ba8bb36 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Fri, 21 Jul 2023 17:00:14 -0700 Subject: [PATCH 157/216] Fix: fire change event when chat content resolves (#188553) --- .../contrib/chat/common/chatModel.ts | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index f8f6a9c25b5f1..215ab3a46b2d4 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -84,6 +84,11 @@ export class ChatRequestModel implements IChatRequestModel { interface ResponsePart { string: IMarkdownString; resolving?: boolean } class Response { + private _onDidChangeValue = new Emitter(); + public get onDidChangeValue() { + return this._onDidChangeValue.event; + } + private _responseParts: ResponsePart[]; private _responseRepr: IMarkdownString; @@ -96,7 +101,7 @@ class Response { this._responseParts = [{ string: value }]; } - updateContent(responsePart: string | { placeholder: string; resolvedContent?: Promise }): void { + updateContent(responsePart: string | { placeholder: string; resolvedContent?: Promise }, quiet?: boolean): void { if (typeof responsePart === 'string') { const responsePartLength = this._responseParts.length - 1; const lastResponsePart = this._responseParts[responsePartLength]; @@ -109,22 +114,25 @@ class Response { this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart) }; } - this._updateRepr(); + this._updateRepr(quiet); } else { // Add a new resolving part const responsePosition = this._responseParts.push({ string: new MarkdownString(responsePart.placeholder), resolving: true }); - this._updateRepr(); + this._updateRepr(quiet); responsePart.resolvedContent?.then((content) => { // Replace the resolving part's content with the resolved response this._responseParts[responsePosition] = { string: new MarkdownString(content) }; - this._updateRepr(); + this._updateRepr(quiet); }); } } - private _updateRepr() { + private _updateRepr(quiet?: boolean) { this._responseRepr = new MarkdownString(this._responseParts.map(r => r.string.value).join('\n')); + if (!quiet) { + this._onDidChangeValue.fire(); + } } } @@ -192,17 +200,12 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel ) { super(); this._response = new Response(_response); + this._register(this._response.onDidChangeValue(() => this._onDidChange.fire())); this._id = 'response_' + ChatResponseModel.nextId++; } updateContent(responsePart: string | { placeholder: string; resolvedContent?: Promise }, quiet?: boolean) { - try { - this._response.updateContent(responsePart); - } finally { - if (!quiet) { - this._onDidChange.fire(); - } - } + this._response.updateContent(responsePart, quiet); } setProviderResponseId(providerResponseId: string) { From 9cb428f8fe893747242c23c3ba98a2fe7733e9d9 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 24 Jul 2023 07:03:40 +0200 Subject: [PATCH 158/216] fix #186575 (#188575) --- .../browser/userDataProfileImportExportService.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index ed0198c048a2b..906e163e9c100 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -1129,9 +1129,9 @@ abstract class UserDataProfileImportExportState extends Disposable implements IT const children = await (element).getChildren(); if (children) { for (const child of children) { - child.checkbox = child.parent.checkbox && child.checkbox - ? { ...child.checkbox, isChecked: child.parent.checkbox.isChecked ? child.checkbox.isChecked : false } - : undefined; + if (child.parent.checkbox && child.checkbox) { + child.checkbox.isChecked = child.parent.checkbox.isChecked && child.checkbox.isChecked; + } } } return children; @@ -1213,7 +1213,10 @@ abstract class UserDataProfileImportExportState extends Disposable implements IT } private isSelected(treeItem: IProfileResourceTreeItem): boolean { - return treeItem.checkbox?.isChecked ?? true; + if (treeItem.checkbox) { + return treeItem.checkbox.isChecked || !!treeItem.children?.some(child => child.checkbox?.isChecked ?? true); + } + return true; } protected abstract fetchRoots(): Promise; From b3d9a1759b706061fca4fd392396318138972876 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 24 Jul 2023 08:57:58 +0200 Subject: [PATCH 159/216] do not show switch to pre-release if already opted into pre-release (#188620) --- .../contrib/extensions/browser/extensions.contribution.ts | 2 +- .../workbench/contrib/extensions/browser/extensionsActions.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 840cfeb520658..78f7de871fa2e 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -1308,7 +1308,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi id: MenuId.ExtensionContext, group: INSTALL_ACTIONS_GROUP, order: 2, - when: ContextKeyExpr.and(ContextKeyExpr.not('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) + when: ContextKeyExpr.and(ContextKeyExpr.not('installedExtensionIsPreReleaseVersion'), ContextKeyExpr.not('installedExtensionIsOptedTpPreRelease'), ContextKeyExpr.has('extensionHasPreReleaseVersion'), ContextKeyExpr.not('inExtensionEditor'), ContextKeyExpr.equals('extensionStatus', 'installed'), ContextKeyExpr.not('isBuiltinExtension')) }, run: async (accessor: ServicesAccessor, id: string) => { const extensionWorkbenchService = accessor.get(IExtensionsWorkbenchService); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index 8ad613804b58c..fd41ea7368474 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -1006,6 +1006,7 @@ async function getContextMenuActionsGroups(extension: IExtension | undefined | n cksOverlay.push(['extensionStatus', 'installed']); } cksOverlay.push(['installedExtensionIsPreReleaseVersion', !!extension.local?.isPreReleaseVersion]); + cksOverlay.push(['installedExtensionIsOptedTpPreRelease', !!extension.local?.preRelease]); cksOverlay.push(['galleryExtensionIsPreReleaseVersion', !!extension.gallery?.properties.isPreReleaseVersion]); cksOverlay.push(['extensionHasPreReleaseVersion', extension.hasPreReleaseVersion]); cksOverlay.push(['extensionHasReleaseVersion', extension.hasReleaseVersion]); @@ -1194,7 +1195,7 @@ export class SwitchToPreReleaseVersionAction extends ExtensionAction { } update(): void { - this.enabled = !!this.extension && !this.extension.isBuiltin && !this.extension.local?.isPreReleaseVersion && this.extension.hasPreReleaseVersion && this.extension.state === ExtensionState.Installed; + this.enabled = !!this.extension && !this.extension.isBuiltin && !this.extension.local?.isPreReleaseVersion && !this.extension.local?.preRelease && this.extension.hasPreReleaseVersion && this.extension.state === ExtensionState.Installed; } override async run(): Promise { From 82ced13b61599c33a5fce3641283408b902f724e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 08:58:11 +0200 Subject: [PATCH 160/216] Retry playwright installs (fix #188453) (#188646) --- build/azure-pipelines/darwin/product-build-darwin-test.yml | 1 + build/azure-pipelines/linux/product-build-linux-test.yml | 1 + build/azure-pipelines/win32/product-build-win32-test.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/build/azure-pipelines/darwin/product-build-darwin-test.yml b/build/azure-pipelines/darwin/product-build-darwin-test.yml index 955b19749a61f..1ca8c9ec1a9ec 100644 --- a/build/azure-pipelines/darwin/product-build-darwin-test.yml +++ b/build/azure-pipelines/darwin/product-build-darwin-test.yml @@ -13,6 +13,7 @@ steps: env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright + retryCountOnTaskFailure: 3 - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index 504bc0b304ba7..bb75be37e4b5b 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -13,6 +13,7 @@ steps: env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright + retryCountOnTaskFailure: 3 - ${{ if ne(parameters.VSCODE_QUALITY, 'oss') }}: - script: | diff --git a/build/azure-pipelines/win32/product-build-win32-test.yml b/build/azure-pipelines/win32/product-build-win32-test.yml index 630e226a742ac..3a24e95657ad6 100644 --- a/build/azure-pipelines/win32/product-build-win32-test.yml +++ b/build/azure-pipelines/win32/product-build-win32-test.yml @@ -15,6 +15,7 @@ steps: env: GITHUB_TOKEN: "$(github-distro-mixin-password)" displayName: Download Electron and Playwright + retryCountOnTaskFailure: 3 - ${{ if eq(parameters.VSCODE_RUN_UNIT_TESTS, true) }}: - ${{ if eq(parameters.VSCODE_QUALITY, 'oss') }}: From c36954584c533291d99c69b09be4bac363e1fdd5 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 24 Jul 2023 08:58:51 +0200 Subject: [PATCH 161/216] fix #186305 (#188602) --- src/vs/workbench/services/issue/browser/issueTroubleshoot.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts b/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts index 2d2e398a91f68..d57f5b3d0e7df 100644 --- a/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts +++ b/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts @@ -161,6 +161,11 @@ class TroubleshootIssueService extends Disposable implements ITroubleshootIssueS } private async reproduceIssueWithExtensionsDisabled(): Promise { + if (!(await this.extensionManagementService.getInstalled(ExtensionType.User)).length) { + this.state = new TroubleShootState(TroubleshootStage.WORKBENCH, this.state!.profile); + return; + } + const result = await this.askToReproduceIssue(localize('profile.extensions.disabled', "Issue troubleshooting is active and has temporarily disabled all installed extensions. Check if you can still reproduce the problem and proceed by selecting from these options.")); if (result === 'good') { const profile = this.userDataProfilesService.profiles.find(p => p.id === this.state!.profile) ?? this.userDataProfilesService.defaultProfile; From 8c3e6da391fb5c1beca65971e166910ee5c47c48 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 24 Jul 2023 08:59:53 +0200 Subject: [PATCH 162/216] reset the last synced state when cleaning up (#188583) * reset the last synced state when cleaning up. Re #188579 * trigger sync when profiles change --- .../userDataSync/common/userDataProfilesManifestSync.ts | 1 + src/vs/platform/userDataSync/common/userDataSyncService.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts index 606983a1c0acf..d655cf5984e2b 100644 --- a/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts +++ b/src/vs/platform/userDataSync/common/userDataProfilesManifestSync.ts @@ -52,6 +52,7 @@ export class UserDataProfilesManifestSynchroniser extends AbstractSynchroniser i @IUriIdentityService uriIdentityService: IUriIdentityService, ) { super({ syncResource: SyncResource.Profiles, profile }, collection, fileService, environmentService, storageService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService, uriIdentityService); + this._register(userDataProfilesService.onDidChangeProfiles(() => this.triggerLocalChange())); } async getLastSyncedProfiles(): Promise { diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index 32cbc4dba51b1..a58320c263efd 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -423,9 +423,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } const updatedRemoteProfiles = remoteProfiles.filter(profile => allCollections.includes(profile.collection)); if (updatedRemoteProfiles.length !== remoteProfiles.length) { - this.logService.info(`Updating remote profiles with invalid collections on server`); const profileManifestSynchronizer = this.instantiationService.createInstance(UserDataProfilesManifestSynchroniser, this.userDataProfilesService.defaultProfile, undefined); try { + this.logService.info('Resetting the last synced state of profiles'); + await profileManifestSynchronizer.resetLocal(); + this.logService.info('Did reset the last synced state of profiles'); + this.logService.info(`Updating remote profiles with invalid collections on server`); await profileManifestSynchronizer.updateRemoteProfiles(updatedRemoteProfiles, null); this.logService.info(`Updated remote profiles on server`); } finally { From 5cec5c67e9591c40b1a49f5ff140754f855e2691 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Mon, 24 Jul 2023 09:01:06 +0200 Subject: [PATCH 163/216] fix #187226 (#188577) --- .../userDataProfileImportExportService.ts | 24 +++++++++++++++---- .../userDataProfile/common/userDataProfile.ts | 1 - 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 906e163e9c100..4954225307845 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -425,13 +425,13 @@ export class UserDataProfileImportExportService extends Disposable implements IU if (source instanceof URI) { this.telemetryService.publicLog2('userDataProfile.createFromTemplate', createProfileTelemetryData); await this.importProfile(source, { mode: 'apply', name: result.name, useDefaultFlags }); + } else if (isUserDataProfile(source)) { + this.telemetryService.publicLog2('userDataProfile.createFromProfile', createProfileTelemetryData); + await this.createFromProfile(source, result.name, { useDefaultFlags }); } else if (isUserDataProfileTemplate(source)) { source.name = result.name; this.telemetryService.publicLog2('userDataProfile.createFromExternalTemplate', createProfileTelemetryData); await this.createAndSwitch(source, false, true, { useDefaultFlags }, localize('create profile', "Create Profile")); - } else if (source) { - this.telemetryService.publicLog2('userDataProfile.createFromProfile', createProfileTelemetryData); - await this.createFromProfile(source, result.name, { useDefaultFlags }); } else { this.telemetryService.publicLog2('userDataProfile.createEmptyProfile', createProfileTelemetryData); await this.userDataProfileManagementService.createAndEnterProfile(result.name, { useDefaultFlags }); @@ -471,11 +471,25 @@ export class UserDataProfileImportExportService extends Disposable implements IU } } - async createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise { + private async createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise { const userDataProfilesExportState = this.instantiationService.createInstance(UserDataProfileExportState, profile); try { const profileTemplate = await userDataProfilesExportState.getProfileTemplate(name, undefined); - await this.createAndSwitch(profileTemplate, false, true, options, localize('create profile', "Create Profile")); + await this.progressService.withProgress({ + location: ProgressLocation.Notification, + delay: 500, + sticky: true, + }, async progress => { + const reportProgress = (message: string) => progress.report({ message: localize('create from profile', "Create Profile: {0}", message) }); + const profile = await this.doCreateProfile(profileTemplate, false, false, { useDefaultFlags: this.userDataProfileService.currentProfile.useDefaultFlags }, reportProgress); + if (profile) { + reportProgress(localize('progress extensions', "Applying Extensions...")); + await this.instantiationService.createInstance(ExtensionsResource).copy(this.userDataProfileService.currentProfile, profile, false); + + reportProgress(localize('switching profile', "Switching Profile...")); + await this.userDataProfileManagementService.switchProfile(profile); + } + }); } finally { userDataProfilesExportState.dispose(); } diff --git a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts index b66d15b26d304..25347a80b98ed 100644 --- a/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts +++ b/src/vs/workbench/services/userDataProfile/common/userDataProfile.ts @@ -95,7 +95,6 @@ export interface IUserDataProfileImportExportService { showProfileContents(): Promise; createProfile(from?: IUserDataProfile | URI): Promise; editProfile(profile: IUserDataProfile): Promise; - createFromProfile(profile: IUserDataProfile, name: string, options?: IUserDataProfileOptions): Promise; createTroubleshootProfile(): Promise; setProfile(profile: IUserDataProfileTemplate): Promise; } From 642ff50c6429567eb1ea629274a495028c77c080 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 09:06:26 +0200 Subject: [PATCH 164/216] Set more errors we throw to be excluded from telemetry (fix #187452) (#188650) --- src/vs/workbench/api/browser/mainThreadDocuments.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 1ef0daed53e89..4d28ce7f8e9e3 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -21,6 +21,7 @@ import { Emitter } from 'vs/base/common/event'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ResourceMap } from 'vs/base/common/map'; import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; +import { ErrorNoTelemetry } from 'vs/base/common/errors'; export class BoundModelReferenceCollection { @@ -212,7 +213,7 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen async $tryOpenDocument(uriData: UriComponents): Promise { const inputUri = URI.revive(uriData); if (!inputUri.scheme || !(inputUri.fsPath || inputUri.authority)) { - throw new Error(`Invalid uri. Scheme and authority or path must be set.`); + throw new ErrorNoTelemetry(`Invalid uri. Scheme and authority or path must be set.`); } const canonicalUri = this._uriIdentityService.asCanonicalUri(inputUri); @@ -232,14 +233,14 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen try { documentUri = await promise; } catch (err) { - throw new Error(`cannot open ${canonicalUri.toString()}. Detail: ${toErrorMessage(err)}`); + throw new ErrorNoTelemetry(`cannot open ${canonicalUri.toString()}. Detail: ${toErrorMessage(err)}`); } if (!documentUri) { - throw new Error(`cannot open ${canonicalUri.toString()}`); + throw new ErrorNoTelemetry(`cannot open ${canonicalUri.toString()}`); } else if (!extUri.isEqual(documentUri, canonicalUri)) { - throw new Error(`cannot open ${canonicalUri.toString()}. Detail: Actual document opened as ${documentUri.toString()}`); + throw new ErrorNoTelemetry(`cannot open ${canonicalUri.toString()}. Detail: Actual document opened as ${documentUri.toString()}`); } else if (!this._modelTrackers.has(canonicalUri)) { - throw new Error(`cannot open ${canonicalUri.toString()}. Detail: Files above 50MB cannot be synchronized with extensions.`); + throw new ErrorNoTelemetry(`cannot open ${canonicalUri.toString()}. Detail: Files above 50MB cannot be synchronized with extensions.`); } else { return canonicalUri; } From e2693cfe823a5e0dad3ad256994f266c843f6093 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 09:06:47 +0200 Subject: [PATCH 165/216] Method not found: toJSON (fix #186623) (#188651) --- src/vs/base/parts/ipc/common/ipc.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index d71baaeeeb571..747e8513b1f01 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -8,7 +8,7 @@ import { CancelablePromise, createCancelablePromise, timeout } from 'vs/base/com import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { memoize } from 'vs/base/common/decorators'; -import { CancellationError } from 'vs/base/common/errors'; +import { CancellationError, ErrorNoTelemetry } from 'vs/base/common/errors'; import { Emitter, Event, EventMultiplexer, Relay } from 'vs/base/common/event'; import { combinedDisposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; @@ -1102,7 +1102,7 @@ export namespace ProxyChannel { } } - throw new Error(`Event not found: ${event}`); + throw new ErrorNoTelemetry(`Event not found: ${event}`); } call(_: unknown, command: string, args?: any[]): Promise { @@ -1119,7 +1119,7 @@ export namespace ProxyChannel { return target.apply(handler, args); } - throw new Error(`Method not found: ${command}`); + throw new ErrorNoTelemetry(`Method not found: ${command}`); } }; } @@ -1185,7 +1185,7 @@ export namespace ProxyChannel { }; } - throw new Error(`Property not found: ${String(propKey)}`); + throw new ErrorNoTelemetry(`Property not found: ${String(propKey)}`); } }) as T; } From de6839abe8ba06d86bd1f0827dc2b60e53e5dac4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 10:36:36 +0200 Subject: [PATCH 166/216] Workspace level `window.zoomLevel` does not reset after closing project (fix #187982) (#188655) --- src/vs/base/browser/browser.ts | 6 ++-- .../window/electron-sandbox/window.ts | 5 +--- .../electron-sandbox/desktop.main.ts | 29 ++++++++++++++----- src/vs/workbench/electron-sandbox/window.ts | 20 ++----------- 4 files changed, 28 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 4194d976db257..2ac4671e49786 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -16,7 +16,7 @@ class WindowManager { public getZoomLevel(): number { return this._zoomLevel; } - public setZoomLevel(zoomLevel: number, isTrusted: boolean): void { + public setZoomLevel(zoomLevel: number): void { if (this._zoomLevel === zoomLevel) { return; } @@ -159,8 +159,8 @@ export function addMatchMediaChangeListener(query: string | MediaQueryList, call export const PixelRatio = new PixelRatioFacade(); /** A zoom index, e.g. 1, 2, 3 */ -export function setZoomLevel(zoomLevel: number, isTrusted: boolean): void { - WindowManager.INSTANCE.setZoomLevel(zoomLevel, isTrusted); +export function setZoomLevel(zoomLevel: number): void { + WindowManager.INSTANCE.setZoomLevel(zoomLevel); } export function getZoomLevel(): number { return WindowManager.INSTANCE.getZoomLevel(); diff --git a/src/vs/platform/window/electron-sandbox/window.ts b/src/vs/platform/window/electron-sandbox/window.ts index 7ffa0edf69cef..90f1371bdeee3 100644 --- a/src/vs/platform/window/electron-sandbox/window.ts +++ b/src/vs/platform/window/electron-sandbox/window.ts @@ -14,10 +14,7 @@ import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; export function applyZoom(zoomLevel: number): void { webFrame.setZoomLevel(zoomLevel); setZoomFactor(zoomLevelToZoomFactor(zoomLevel)); - // Cannot be trusted because the webFrame might take some time - // until it really applies the new zoom level - // See https://github.com/microsoft/vscode/issues/26151 - setZoomLevel(zoomLevel, false /* isTrusted */); + setZoomLevel(zoomLevel); } export function zoomIn(): void { diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index 9609e5f96bb7d..d2a26882677c2 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -5,10 +5,10 @@ import { localize } from 'vs/nls'; import product from 'vs/platform/product/common/product'; -import { INativeWindowConfiguration, zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; +import { INativeWindowConfiguration, IWindowsConfiguration } from 'vs/platform/window/common/window'; import { Workbench } from 'vs/workbench/browser/workbench'; import { NativeWindow } from 'vs/workbench/electron-sandbox/window'; -import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; +import { setFullscreen } from 'vs/base/browser/browser'; import { domContentLoaded } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; @@ -58,6 +58,8 @@ import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/c import { BrowserSocketFactory } from 'vs/platform/remote/browser/browserSocketFactory'; import { RemoteSocketFactoryService, IRemoteSocketFactoryService } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { ElectronRemoteResourceLoader } from 'vs/platform/remote/electron-sandbox/electronRemoteResourceLoader'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { applyZoom } from 'vs/platform/window/electron-sandbox/window'; export class DesktopMain extends Disposable { @@ -74,10 +76,7 @@ export class DesktopMain extends Disposable { // Massage configuration file URIs this.reviveUris(); - // Browser config - const zoomLevel = this.configuration.zoomLevel || 0; - setZoomFactor(zoomLevelToZoomFactor(zoomLevel)); - setZoomLevel(zoomLevel, true /* isTrusted */); + // Apply fullscreen early if configured setFullscreen(!!this.configuration.fullscreen); } @@ -112,6 +111,13 @@ export class DesktopMain extends Disposable { // Init services and wait for DOM to be ready in parallel const [services] = await Promise.all([this.initServices(), domContentLoaded()]); + // Apply zoom level early once we have a configuration service + // and before the workbench is created to prevent flickering. + // We also need to respect that zoom level can be configured per + // workspace, so we need the resolved configuration service. + // (fixes https://github.com/microsoft/vscode/issues/187982) + this.applyConfiguredWindowZoomLevel(services.configurationService); + // Create Workbench const workbench = new Workbench(document.body, { extraClasses: this.getExtraClasses() }, services.serviceCollection, services.logService); @@ -125,6 +131,13 @@ export class DesktopMain extends Disposable { this._register(instantiationService.createInstance(NativeWindow)); } + private applyConfiguredWindowZoomLevel(configurationService: IConfigurationService) { + const windowConfig = configurationService.getValue(); + const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; + + applyZoom(windowZoomLevel); + } + private getExtraClasses(): string[] { if (isMacintosh) { if (this.configuration.os.release > '20.0.0') { @@ -142,7 +155,7 @@ export class DesktopMain extends Disposable { this._register(workbench.onDidShutdown(() => this.dispose())); } - private async initServices(): Promise<{ serviceCollection: ServiceCollection; logService: ILogService; storageService: NativeWorkbenchStorageService }> { + private async initServices(): Promise<{ serviceCollection: ServiceCollection; logService: ILogService; storageService: NativeWorkbenchStorageService; configurationService: IConfigurationService }> { const serviceCollection = new ServiceCollection(); @@ -310,7 +323,7 @@ export class DesktopMain extends Disposable { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - return { serviceCollection, logService, storageService }; + return { serviceCollection, logService, storageService, configurationService }; } private resolveWorkspaceIdentifier(environmentService: INativeWorkbenchEnvironmentService): IAnyWorkspaceIdentifier { diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 12859500a5882..7ab81debcbe34 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -79,8 +79,6 @@ export class NativeWindow extends Disposable { private readonly customTitleContextMenuDisposable = this._register(new DisposableStore()); - private previousConfiguredZoomLevel: number | undefined; - private readonly addFoldersScheduler = this._register(new RunOnceScheduler(() => this.doAddFolders(), 100)); private pendingFoldersToAdd: URI[] = []; @@ -320,7 +318,6 @@ export class NativeWindow extends Disposable { }); // Zoom level changes - this.updateWindowZoomLevel(); this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('window.zoomLevel')) { this.updateWindowZoomLevel(); @@ -605,21 +602,10 @@ export class NativeWindow extends Disposable { private updateWindowZoomLevel(): void { const windowConfig = this.configurationService.getValue(); + const windowZoomLevel = typeof windowConfig.window?.zoomLevel === 'number' ? windowConfig.window.zoomLevel : 0; - let configuredZoomLevel = 0; - if (windowConfig.window && typeof windowConfig.window.zoomLevel === 'number') { - configuredZoomLevel = windowConfig.window.zoomLevel; - - // Leave early if the configured zoom level did not change (https://github.com/microsoft/vscode/issues/1536) - if (this.previousConfiguredZoomLevel === configuredZoomLevel) { - return; - } - - this.previousConfiguredZoomLevel = configuredZoomLevel; - } - - if (getZoomLevel() !== configuredZoomLevel) { - applyZoom(configuredZoomLevel); + if (getZoomLevel() !== windowZoomLevel) { + applyZoom(windowZoomLevel); } } From 8632050b5762c6f2b891344119fe9cc56e959918 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 24 Jul 2023 12:30:07 +0200 Subject: [PATCH 167/216] API proposal for markdown tree message Part of #153936 --- .../api/browser/mainThreadTreeViews.ts | 5 ++-- .../workbench/api/common/extHost.protocol.ts | 2 +- .../workbench/api/common/extHostTreeViews.ts | 18 ++++++----- .../browser/parts/views/media/views.css | 4 +++ .../workbench/browser/parts/views/treeView.ts | 30 ++++++++++++++----- src/vs/workbench/common/views.ts | 2 +- .../common/extensionsApiProposals.ts | 1 + ...code.proposed.treeViewMarkdownMessage.d.ts | 28 +++++++++++++++++ 8 files changed, 71 insertions(+), 19 deletions(-) create mode 100644 src/vscode-dts/vscode.proposed.treeViewMarkdownMessage.d.ts diff --git a/src/vs/workbench/api/browser/mainThreadTreeViews.ts b/src/vs/workbench/api/browser/mainThreadTreeViews.ts index 04170a3c9a06b..279a4f05c2859 100644 --- a/src/vs/workbench/api/browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/browser/mainThreadTreeViews.ts @@ -18,6 +18,7 @@ import { createStringDataTransferItem, VSDataTransfer } from 'vs/base/common/dat import { VSBuffer } from 'vs/base/common/buffer'; import { DataTransferFileCache } from 'vs/workbench/api/common/shared/dataTransferCache'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { @@ -90,8 +91,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie return Promise.resolve(); } - $setMessage(treeViewId: string, message: string): void { - this.logService.trace('MainThreadTreeViews#$setMessage', treeViewId, message); + $setMessage(treeViewId: string, message: string | IMarkdownString): void { + this.logService.trace('MainThreadTreeViews#$setMessage', treeViewId, message.toString()); const viewer = this.getTreeView(treeViewId); if (viewer) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index f70ba20ea667c..ec414fd926973 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -279,7 +279,7 @@ export interface MainThreadTreeViewsShape extends IDisposable { $registerTreeViewDataProvider(treeViewId: string, options: { showCollapseAll: boolean; canSelectMany: boolean; dropMimeTypes: readonly string[]; dragMimeTypes: readonly string[]; hasHandleDrag: boolean; hasHandleDrop: boolean; manuallyManageCheckboxes: boolean }): Promise; $refresh(treeViewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise; $reveal(treeViewId: string, itemInfo: { item: ITreeItem; parentChain: ITreeItem[] } | undefined, options: IRevealOptions): Promise; - $setMessage(treeViewId: string, message: string): void; + $setMessage(treeViewId: string, message: string | IMarkdownString): void; $setTitle(treeViewId: string, title: string, description: string | undefined): void; $setBadge(treeViewId: string, badge: IViewBadge | undefined): void; $resolveDropFileData(destinationViewId: string, requestId: number, dataItemId: string): Promise; diff --git a/src/vs/workbench/api/common/extHostTreeViews.ts b/src/vs/workbench/api/common/extHostTreeViews.ts index 3aaa6a38b29c0..3f33b7d7b62a0 100644 --- a/src/vs/workbench/api/common/extHostTreeViews.ts +++ b/src/vs/workbench/api/common/extHostTreeViews.ts @@ -20,7 +20,7 @@ import { equals, coalesce } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { MarkdownString, ViewBadge, DataTransfer } from 'vs/workbench/api/common/extHostTypeConverters'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ITreeViewsDnDService, TreeViewsDnDService } from 'vs/editor/common/services/treeViewsDnd'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; @@ -95,7 +95,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { const treeView = this.createExtHostTreeView(viewId, options, extension); const proxyOptions = { showCollapseAll: !!options.showCollapseAll, canSelectMany: !!options.canSelectMany, dropMimeTypes, dragMimeTypes, hasHandleDrag, hasHandleDrop, manuallyManageCheckboxes: !!options.manageCheckboxStateManually }; const registerPromise = this._proxy.$registerTreeViewDataProvider(viewId, proxyOptions); - return { + const view = { get onDidCollapseElement() { return treeView.onDidCollapseElement; }, get onDidExpandElement() { return treeView.onDidExpandElement; }, get selection() { return treeView.selectedElements; }, @@ -114,7 +114,10 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { return treeView.onDidChangeCheckboxState; }, get message() { return treeView.message; }, - set message(message: string) { + set message(message: string | vscode.MarkdownString) { + if (isMarkdownString(message)) { + checkProposedApiEnabled(extension, 'treeViewMarkdownMessage'); + } treeView.message = message; }, get title() { return treeView.title; }, @@ -150,6 +153,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { treeView.dispose(); } }; + return view as vscode.TreeView; } $getChildren(treeViewId: string, treeItemHandle?: string): Promise { @@ -382,7 +386,7 @@ class ExtHostTreeView extends Disposable { }); } if (message) { - this.proxy.$setMessage(this.viewId, this._message); + this.proxy.$setMessage(this.viewId, MarkdownString.fromStrict(this._message) ?? ''); } })); } @@ -427,12 +431,12 @@ class ExtHostTreeView extends Disposable { } } - private _message: string = ''; - get message(): string { + private _message: string | vscode.MarkdownString = ''; + get message(): string | vscode.MarkdownString { return this._message; } - set message(message: string) { + set message(message: string | vscode.MarkdownString) { this._message = message; this._onDidChangeData.fire({ message: true, element: false }); } diff --git a/src/vs/workbench/browser/parts/views/media/views.css b/src/vs/workbench/browser/parts/views/media/views.css index 80b65021a1020..05fb4fc8c93eb 100644 --- a/src/vs/workbench/browser/parts/views/media/views.css +++ b/src/vs/workbench/browser/parts/views/media/views.css @@ -46,6 +46,10 @@ padding-left: 24px; } +.monaco-workbench .tree-explorer-viewlet-tree-view .message a { + color: var(--vscode-textLink-foreground); +} + .monaco-workbench .tree-explorer-viewlet-tree-view .message.hide { display: none; } diff --git a/src/vs/workbench/browser/parts/views/treeView.ts b/src/vs/workbench/browser/parts/views/treeView.ts index 77b9e62a7b73b..aedd476a89d9d 100644 --- a/src/vs/workbench/browser/parts/views/treeView.ts +++ b/src/vs/workbench/browser/parts/views/treeView.ts @@ -21,7 +21,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { isCancellationError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Mimes } from 'vs/base/common/mime'; import { Schemas } from 'vs/base/common/network'; @@ -72,6 +72,7 @@ import { AriaRole } from 'vs/base/browser/ui/aria/aria'; import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITreeViewsDnDService } from 'vs/editor/common/services/treeViewsDndService'; import { DraggedTreeItemsIdentifier } from 'vs/editor/common/services/treeViewsDnd'; +import { IMarkdownRenderResult, MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer'; export class TreeViewPane extends ViewPane { @@ -182,6 +183,10 @@ function isTreeCommandEnabled(treeCommand: TreeCommand, contextKeyService: ICont return true; } +function isRenderedMessageValue(messageValue: string | IMarkdownRenderResult | undefined): messageValue is IMarkdownRenderResult { + return !!messageValue && typeof messageValue !== 'string' && 'element' in messageValue && 'dispose' in messageValue; +} + const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); export const RawCustomTreeViewContextKey = new RawContextKey('customTreeView', false); @@ -204,7 +209,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private focused: boolean = false; private domNode!: HTMLElement; private treeContainer: HTMLElement | undefined; - private _messageValue: string | undefined; + private _messageValue: string | { element: HTMLElement; dispose: () => void } | undefined; private _canSelectMany: boolean = false; private _manuallyManageCheckboxes: boolean = false; private messageElement: HTMLElement | undefined; @@ -214,6 +219,7 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { private _container: HTMLElement | undefined; private root: ITreeItem; + private markdownRenderer: MarkdownRenderer | undefined; private elementsToRefresh: ITreeItem[] = []; private lastSelection: readonly ITreeItem[] = []; private lastActive: ITreeItem; @@ -388,12 +394,12 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { this._onDidChangeWelcomeState.fire(); } - private _message: string | undefined; - get message(): string | undefined { + private _message: string | IMarkdownString | undefined; + get message(): string | IMarkdownString | undefined { return this._message; } - set message(message: string | undefined) { + set message(message: string | IMarkdownString | undefined) { this._message = message; this.updateMessage(); this._onDidChangeWelcomeState.fire(); @@ -825,15 +831,23 @@ abstract class AbstractTreeView extends Disposable implements ITreeView { this.updateContentAreas(); } - private showMessage(message: string): void { - this._messageValue = message; + private showMessage(message: string | IMarkdownString): void { + if (isRenderedMessageValue(this._messageValue)) { + this._messageValue.dispose(); + } + if (isMarkdownString(message) && !this.markdownRenderer) { + this.markdownRenderer = this.instantiationService.createInstance(MarkdownRenderer, {}); + } + this._messageValue = isMarkdownString(message) ? this.markdownRenderer!.render(message) : message; if (!this.messageElement) { return; } this.messageElement.classList.remove('hide'); this.resetMessageElement(); - if (!isFalsyOrWhitespace(this._message)) { + if (typeof this._messageValue === 'string' && !isFalsyOrWhitespace(this._messageValue)) { this.messageElement.textContent = this._messageValue; + } else if (isRenderedMessageValue(this._messageValue)) { + this.messageElement.appendChild(this._messageValue.element); } this.layout(this._height, this._width); } diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index f86f9cb92bb8e..8306ee74803d4 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -653,7 +653,7 @@ export interface ITreeView extends IDisposable { manuallyManageCheckboxes: boolean; - message?: string; + message?: string | IMarkdownString; title: string; diff --git a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts index 1017371144275..347eeaefd5169 100644 --- a/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts +++ b/src/vs/workbench/services/extensions/common/extensionsApiProposals.ts @@ -92,6 +92,7 @@ export const allApiProposals = Object.freeze({ timeline: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.timeline.d.ts', tokenInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tokenInformation.d.ts', treeViewActiveItem: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewActiveItem.d.ts', + treeViewMarkdownMessage: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewMarkdownMessage.d.ts', treeViewReveal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.treeViewReveal.d.ts', tunnelFactory: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnelFactory.d.ts', tunnels: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.tunnels.d.ts', diff --git a/src/vscode-dts/vscode.proposed.treeViewMarkdownMessage.d.ts b/src/vscode-dts/vscode.proposed.treeViewMarkdownMessage.d.ts new file mode 100644 index 0000000000000..ad4655d9bcae8 --- /dev/null +++ b/src/vscode-dts/vscode.proposed.treeViewMarkdownMessage.d.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'vscode' { + + export interface TreeView2 extends Disposable { + readonly onDidExpandElement: Event>; + readonly onDidCollapseElement: Event>; + readonly selection: readonly T[]; + readonly onDidChangeSelection: Event>; + readonly visible: boolean; + readonly onDidChangeVisibility: Event; + readonly onDidChangeCheckboxState: Event>; + title?: string; + description?: string; + badge?: ViewBadge | undefined; + reveal(element: T, options?: { select?: boolean; focus?: boolean; expand?: boolean | number }): Thenable; + + /** + * An optional human-readable message that will be rendered in the view. + * Only a subset of markdown is supported. + * Setting the message to null, undefined, or empty string will remove the message from the view. + */ + message?: string | MarkdownString; + } +} From 5083bb50a54c31d181d9d64177b2752538012e9a Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Mon, 24 Jul 2023 19:48:52 +0900 Subject: [PATCH 168/216] chore: bump node-pty@1.1.0-beta3 --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index eced648db3bb6..9a3923a90add0 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "native-is-elevated": "0.7.0", "native-keymap": "^3.3.2", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta1", + "node-pty": "1.1.0-beta3", "tas-client-umd": "0.1.8", "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.7.0", diff --git a/remote/package.json b/remote/package.json index c68f4105b30f4..7f645b5b04477 100644 --- a/remote/package.json +++ b/remote/package.json @@ -22,7 +22,7 @@ "keytar": "7.9.0", "minimist": "^1.2.6", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta1", + "node-pty": "1.1.0-beta3", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index 0034d6a8a56bf..85d87e20fb52a 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -560,10 +560,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@1.1.0-beta1: - version "1.1.0-beta1" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta1.tgz#95d4baf406c043b78042f951b325e9713df2beac" - integrity sha512-h+1E/gX/brFqsp3yZKGERHOhdo1POG1rrsI+8tEuocqdEddHd029471gq8KOuiHKicd52h2pSU8Gtqb3Vo2PfQ== +node-pty@1.1.0-beta3: + version "1.1.0-beta3" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta3.tgz#c750621a4effddd499fcbd15a2cf7679bee7b60f" + integrity sha512-/QnWzklx8QcPcUUTWZSlY/YIz2IXo61KKx7O1H7aZ1YtMvmU1EFS/+hppmUSWV7+9WRCfAdov4K22u8knZ+IzA== dependencies: nan "^2.17.0" diff --git a/yarn.lock b/yarn.lock index 651bda6187cae..50da7ab847022 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7155,10 +7155,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@1.1.0-beta1: - version "1.1.0-beta1" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta1.tgz#95d4baf406c043b78042f951b325e9713df2beac" - integrity sha512-h+1E/gX/brFqsp3yZKGERHOhdo1POG1rrsI+8tEuocqdEddHd029471gq8KOuiHKicd52h2pSU8Gtqb3Vo2PfQ== +node-pty@1.1.0-beta3: + version "1.1.0-beta3" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta3.tgz#c750621a4effddd499fcbd15a2cf7679bee7b60f" + integrity sha512-/QnWzklx8QcPcUUTWZSlY/YIz2IXo61KKx7O1H7aZ1YtMvmU1EFS/+hppmUSWV7+9WRCfAdov4K22u8knZ+IzA== dependencies: nan "^2.17.0" From 741de09ada0ed58b520da1342fc63bfb97180242 Mon Sep 17 00:00:00 2001 From: Hylke Bons Date: Mon, 24 Jul 2023 13:41:30 +0200 Subject: [PATCH 169/216] themes: Fall back to Light/Dark+ themes for diff colors --- extensions/theme-defaults/themes/dark_modern.json | 4 ---- extensions/theme-defaults/themes/light_modern.json | 4 ---- 2 files changed, 8 deletions(-) diff --git a/extensions/theme-defaults/themes/dark_modern.json b/extensions/theme-defaults/themes/dark_modern.json index 14fc09e91c1da..646e2779236b9 100644 --- a/extensions/theme-defaults/themes/dark_modern.json +++ b/extensions/theme-defaults/themes/dark_modern.json @@ -23,10 +23,6 @@ "checkbox.border": "#ffffff1f", "debugToolBar.background": "#181818", "descriptionForeground": "#8b949e", - "diffEditor.insertedLineBackground": "#23863633", - "diffEditor.insertedTextBackground": "#2386364d", - "diffEditor.removedLineBackground": "#da363333", - "diffEditor.removedTextBackground": "#da36334d", "dropdown.background": "#313131", "dropdown.border": "#ffffff1f", "dropdown.foreground": "#cccccc", diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json index 5640a255dc156..c9e486fbab4cb 100644 --- a/extensions/theme-defaults/themes/light_modern.json +++ b/extensions/theme-defaults/themes/light_modern.json @@ -22,10 +22,6 @@ "checkbox.background": "#f8f8f8", "checkbox.border": "#CECECE", "descriptionForeground": "#3b3b3b", - "diffEditor.insertedLineBackground": "#23863633", - "diffEditor.insertedTextBackground": "#2386364d", - "diffEditor.removedLineBackground": "#da363333", - "diffEditor.removedTextBackground": "#da36334d", "dropdown.background": "#ffffff", "dropdown.border": "#CECECE", "dropdown.foreground": "#3b3b3b", From a964308616e62fd31ce1c3415f347a82ac8bb8b3 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 14:59:02 +0200 Subject: [PATCH 170/216] context menu - allow to use `StandardMouseEvent` as anchor --- src/vs/base/browser/contextmenu.ts | 6 ++++-- src/vs/base/browser/ui/contextview/contextview.ts | 10 +++++++++- src/vs/base/common/types.ts | 4 ++++ src/vs/platform/contextview/browser/contextView.ts | 6 ++++-- .../browser/parts/activitybar/activitybarActions.ts | 6 +----- src/vs/workbench/browser/parts/compositeBar.ts | 2 +- .../browser/parts/editor/editorGroupView.ts | 5 ++--- .../browser/parts/editor/tabsTitleControl.ts | 5 ++--- .../workbench/browser/parts/editor/titleControl.ts | 5 ++--- .../workbench/browser/parts/sidebar/sidebarPart.ts | 3 +-- .../browser/parts/statusbar/statusbarPart.ts | 2 +- .../browser/parts/titlebar/titlebarPart.ts | 3 +-- .../browser/parts/views/viewPaneContainer.ts | 6 ++---- .../electron-sandbox/contextmenuService.ts | 13 ++++++++++--- 14 files changed, 44 insertions(+), 32 deletions(-) diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 8f67c23030664..ac9ff50af14a5 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -3,11 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { AnchorAlignment, AnchorAxisAlignment, IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { IAction, IActionRunner } from 'vs/base/common/actions'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; +import { OmitOptional } from 'vs/base/common/types'; export interface IContextMenuEvent { readonly shiftKey?: boolean; @@ -17,7 +19,7 @@ export interface IContextMenuEvent { } export interface IContextMenuDelegate { - getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number }; + getAnchor(): HTMLElement | StandardMouseEvent | OmitOptional; getActions(): readonly IAction[]; getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox'; getActionViewItem?(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 04931cca3f09d..ee2d6c45dcccf 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -5,6 +5,7 @@ import { BrowserFeatures } from 'vs/base/browser/canIUse'; import * as DOM from 'vs/base/browser/dom'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { Range } from 'vs/base/common/range'; @@ -36,7 +37,7 @@ export const enum AnchorAxisAlignment { } export interface IDelegate { - getAnchor(): HTMLElement | IAnchor; + getAnchor(): HTMLElement | StandardMouseEvent | IAnchor; render(container: HTMLElement): IDisposable | null; focus?(): void; layout?(): void; @@ -271,6 +272,13 @@ export class ContextView extends Disposable { width: elementPosition.width * zoom, height: elementPosition.height * zoom }; + } else if (anchor instanceof StandardMouseEvent) { + around = { + top: anchor.posy, + left: anchor.posx, + width: 1, + height: 2 + }; } else { around = { top: anchor.y, diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index c35da047d1f6f..c66ad01809794 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -225,6 +225,10 @@ export type AddFirstParameterToFunctions }> = Partial & U[keyof U]; +/** + * Only picks the non-optional properties of a type. + */ +export type OmitOptional = { [K in keyof T as T[K] extends Required[K] ? K : never]: T[K] }; /** * A type that removed readonly-less from all properties of `T` diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 505d76eabfbdd..464e7c5c9b1cb 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; -import { AnchorAlignment, AnchorAxisAlignment, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { AnchorAlignment, AnchorAxisAlignment, IAnchor, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { OmitOptional } from 'vs/base/common/types'; import { IMenuActionOptions, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -29,7 +31,7 @@ export interface IContextViewDelegate { canRelayout?: boolean; // Default: true - getAnchor(): HTMLElement | { x: number; y: number; width?: number; height?: number }; + getAnchor(): HTMLElement | StandardMouseEvent | OmitOptional; render(container: HTMLElement): IDisposable; onDOMEvent?(e: any, activeElement: HTMLElement): void; onHide?(data?: any): void; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index d09cfa07930cd..70b39c12b83a3 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -149,13 +149,9 @@ abstract class AbstractGlobalActivityActionViewItem extends ActivityActionViewIt const actions = await this.resolveContextMenuActions(disposables); const event = new StandardMouseEvent(e); - const anchor = { - x: event.posx, - y: event.posy - }; this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => event, getActions: () => actions, onHide: () => disposables.dispose() }); diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 366941297647c..afa384aa88ca0 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -656,7 +656,7 @@ export class CompositeBar extends Widget implements ICompositeBar { const event = new StandardMouseEvent(e); this.contextMenuService.showContextMenu({ - getAnchor: () => { return { x: event.posx, y: event.posy }; }, + getAnchor: () => event, getActions: () => this.getContextMenuActions(e) }); } diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 740e037f6d408..898bd528d2468 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -374,10 +374,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Find target anchor - let anchor: HTMLElement | { x: number; y: number } = this.element; + let anchor: HTMLElement | StandardMouseEvent = this.element; if (e instanceof MouseEvent) { - const event = new StandardMouseEvent(e); - anchor = { x: event.posx, y: event.posy }; + anchor = new StandardMouseEvent(e); } // Show it diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index ecb998d62740a..3b2fc2ad97073 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -454,10 +454,9 @@ export class TabsTitleControl extends TitleControl { EventHelper.stop(e); // Find target anchor - let anchor: HTMLElement | { x: number; y: number } = tabsContainer; + let anchor: HTMLElement | StandardMouseEvent = tabsContainer; if (e instanceof MouseEvent) { - const event = new StandardMouseEvent(e); - anchor = { x: event.posx, y: event.posy }; + anchor = new StandardMouseEvent(e); } // Show it diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 5d77c66dfc5e0..ab517fcd4bebe 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -379,10 +379,9 @@ export abstract class TitleControl extends Themable { applyAvailableEditorIds(this.editorAvailableEditorIds, editor, this.editorResolverService); // Find target anchor - let anchor: HTMLElement | { x: number; y: number } = node; + let anchor: HTMLElement | StandardMouseEvent = node; if (e instanceof MouseEvent) { - const event = new StandardMouseEvent(e); - anchor = { x: event.posx, y: event.posy }; + anchor = new StandardMouseEvent(e); } // Show it diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 67a88f8eb1784..ceaf9b73e901d 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -282,9 +282,8 @@ export class SidebarPart extends CompositePart implements IPaneCo if (activeViewlet) { const contextMenuActions = activeViewlet ? activeViewlet.getContextMenuActions() : []; if (contextMenuActions.length) { - const anchor: { x: number; y: number } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => event, getActions: () => contextMenuActions.slice(), getActionViewItem: action => this.actionViewItemProvider(action), actionRunner: activeViewlet.getActionRunner(), diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 66e1c66e166e5..1c949d44a0056 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -453,7 +453,7 @@ export class StatusbarPart extends Part implements IStatusbarService { let actions: IAction[] | undefined = undefined; this.contextMenuService.showContextMenu({ - getAnchor: () => ({ x: event.posx, y: event.posy }), + getAnchor: () => event, getActions: () => { actions = this.getContextMenuActions(event); diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 64a6afb239d5e..9211cd3decf33 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -399,11 +399,10 @@ export class TitlebarPart extends Part implements ITitleService { protected onContextMenu(e: MouseEvent, menuId: MenuId): void { // Find target anchor const event = new StandardMouseEvent(e); - const anchor = { x: event.posx, y: event.posy }; // Show it this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => event, menuId, contextKeyService: this.contextKeyService, domForShadowRoot: isMacintosh && isNative ? event.target : undefined diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index ff766b9acb815..1f593cd65c3ad 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -577,9 +577,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { event.stopPropagation(); event.preventDefault(); - const anchor: { x: number; y: number } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => event, getActions: () => this.menuActions?.getContextMenuActions() ?? [] }); } @@ -743,9 +742,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const actions: IAction[] = viewPane.menuActions.getContextMenuActions(); - const anchor: { x: number; y: number } = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => event, getActions: () => actions }); } diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 073f4766e9f51..b95d6196ee80f 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -27,6 +27,7 @@ import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/context import { IMenuService } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Disposable } from 'vs/base/common/lifecycle'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; export class ContextMenuService implements IContextMenuService { @@ -156,9 +157,15 @@ class NativeContextMenuService extends Disposable implements IContextMenuService y += 4 / zoom; } } else { - const pos: { x: number; y: number } = anchor; - x = pos.x + 1; /* prevent first item from being selected automatically under mouse */ - y = pos.y; + if (anchor instanceof StandardMouseEvent) { + x = anchor.posx; + y = anchor.posy; + } else { + x = anchor.x; + y = anchor.y; + } + + x++; // prevent first item from being selected automatically under mouse } x *= zoom; From d7f414660aaa3eef6fd23d1ade9f19e6de3f1283 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 15:57:35 +0200 Subject: [PATCH 171/216] context menu - more adoptions of mouse event --- src/vs/base/browser/contextmenu.ts | 4 +-- .../browser/ui/contextview/contextview.ts | 26 ++++++++++++------- .../browser/stickyScrollController.ts | 5 +++- src/vs/platform/actions/browser/toolbar.ts | 10 ++++--- .../contextview/browser/contextView.ts | 4 +-- .../browser/actions/textInputActions.ts | 5 +++- .../browser/editorLineNumberMenu.ts | 3 +-- .../contrib/comments/browser/commentNode.ts | 5 +++- .../comments/browser/commentThreadHeader.ts | 4 ++- .../comments/browser/commentsController.ts | 4 +-- .../browser/breakpointEditorContribution.ts | 3 +-- .../inlineChat/browser/inlineChatWidget.ts | 4 ++- .../viewParts/notebookEditorStickyScroll.ts | 4 ++- .../browser/preferencesRenderers.ts | 3 +-- .../terminal/browser/terminalContextMenu.ts | 3 +-- .../electron-sandbox/contextmenuService.ts | 12 ++++----- 16 files changed, 58 insertions(+), 41 deletions(-) diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index ac9ff50af14a5..072eef79fde4f 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { AnchorAlignment, AnchorAxisAlignment, IAnchor } from 'vs/base/browser/ui/contextview/contextview'; @@ -19,7 +19,7 @@ export interface IContextMenuEvent { } export interface IContextMenuDelegate { - getAnchor(): HTMLElement | StandardMouseEvent | OmitOptional; + getAnchor(): HTMLElement | IMouseEvent | OmitOptional; getActions(): readonly IAction[]; getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox'; getActionViewItem?(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index ee2d6c45dcccf..0a1934d91d9c3 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -5,7 +5,7 @@ import { BrowserFeatures } from 'vs/base/browser/canIUse'; import * as DOM from 'vs/base/browser/dom'; -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { Range } from 'vs/base/common/range'; @@ -24,6 +24,12 @@ export interface IAnchor { height?: number; } +export function isAnchor(obj: unknown): obj is IAnchor { + const anchor = obj as IAnchor | undefined; + + return !!anchor && typeof anchor.x === 'number' && typeof anchor.y === 'number'; +} + export const enum AnchorAlignment { LEFT, RIGHT } @@ -37,7 +43,7 @@ export const enum AnchorAxisAlignment { } export interface IDelegate { - getAnchor(): HTMLElement | StandardMouseEvent | IAnchor; + getAnchor(): HTMLElement | IMouseEvent | IAnchor; render(container: HTMLElement): IDisposable | null; focus?(): void; layout?(): void; @@ -272,20 +278,20 @@ export class ContextView extends Disposable { width: elementPosition.width * zoom, height: elementPosition.height * zoom }; - } else if (anchor instanceof StandardMouseEvent) { - around = { - top: anchor.posy, - left: anchor.posx, - width: 1, - height: 2 - }; - } else { + } else if (isAnchor(anchor)) { around = { top: anchor.y, left: anchor.x, width: anchor.width || 1, height: anchor.height || 2 }; + } else { + around = { + top: anchor.posy, + left: anchor.posx, + width: 1, + height: 2 + }; } const viewSizeWidth = DOM.getTotalWidth(this.view); diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index fb7adfb064a0f..132fb4ff46cee 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -26,6 +26,7 @@ import { ILanguageConfigurationService } from 'vs/editor/common/languages/langua import { ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce'; import * as dom from 'vs/base/browser/dom'; import { StickyRange } from 'vs/editor/contrib/stickyScroll/browser/stickyScrollElement'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; interface CustomMouseEvent { detail: string; @@ -290,7 +291,9 @@ export class StickyScrollController extends Disposable implements IEditorContrib return linkGestureStore; } - private _onContextMenu(event: MouseEvent) { + private _onContextMenu(e: MouseEvent) { + const event = new StandardMouseEvent(e); + this._contextMenuService.showContextMenu({ menuId: MenuId.StickyScrollContext, getAnchor: () => event, diff --git a/src/vs/platform/actions/browser/toolbar.ts b/src/vs/platform/actions/browser/toolbar.ts index 6df086344a7f3..e0a3a879b2052 100644 --- a/src/vs/platform/actions/browser/toolbar.ts +++ b/src/vs/platform/actions/browser/toolbar.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { addDisposableListener } from 'vs/base/browser/dom'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IToolBarOptions, ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IAction, Separator, SubmenuAction, toAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { coalesceInPlace } from 'vs/base/common/arrays'; @@ -170,13 +171,14 @@ export class WorkbenchToolBar extends ToolBar { // add context menu for toggle actions if (toggleActions.length > 0) { this._sessionDisposables.add(addDisposableListener(this.getElement(), 'contextmenu', e => { + const event = new StandardMouseEvent(e); - const action = this.getItemAction(e.target); + const action = this.getItemAction(event.target); if (!(action)) { return; } - e.preventDefault(); - e.stopPropagation(); + event.preventDefault(); + event.stopPropagation(); let noHide = false; @@ -232,7 +234,7 @@ export class WorkbenchToolBar extends ToolBar { } this._contextMenuService.showContextMenu({ - getAnchor: () => e, + getAnchor: () => event, getActions: () => actions, // add context menu actions (iff appicable) menuId: this._options?.contextMenu, diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 464e7c5c9b1cb..1ca41049391b8 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { AnchorAlignment, AnchorAxisAlignment, IAnchor, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; @@ -31,7 +31,7 @@ export interface IContextViewDelegate { canRelayout?: boolean; // Default: true - getAnchor(): HTMLElement | StandardMouseEvent | OmitOptional; + getAnchor(): HTMLElement | IMouseEvent | OmitOptional; render(container: HTMLElement): IDisposable; onDOMEvent?(e: any, activeElement: HTMLElement): void; onHide?(data?: any): void; diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index f8722e3d06d85..c52e15228ca14 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -14,6 +14,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { isNative } from 'vs/base/common/platform'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; export class TextInputActionsProvider extends Disposable implements IWorkbenchContribution { @@ -90,8 +91,10 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo EventHelper.stop(e, true); + const event = new StandardMouseEvent(e); + this.contextMenuService.showContextMenu({ - getAnchor: () => e, + getAnchor: () => event, getActions: () => this.textInputActions, getActionsContext: () => target, }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/editorLineNumberMenu.ts b/src/vs/workbench/contrib/codeEditor/browser/editorLineNumberMenu.ts index 43eea66d38cdd..5cb892f95cf00 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/editorLineNumberMenu.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/editorLineNumberMenu.ts @@ -77,7 +77,6 @@ export class EditorLineNumberContextMenu extends Disposable implements IEditorCo return; } - const anchor = { x: e.event.posx, y: e.event.posy }; const lineNumber = e.target.position.lineNumber; const contextKeyService = this.contextKeyService.createOverlay([['editorLineNumber', lineNumber]]); @@ -122,7 +121,7 @@ export class EditorLineNumberContextMenu extends Disposable implements IEditorCo } this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => e.event, getActions: () => Separator.join(...allActions.map((a) => a[1])), onHide: () => menu.dispose(), }); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 82f7cd850af9c..7c30e5d516fc5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -47,6 +47,7 @@ import { DomEmitter } from 'vs/base/browser/event'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; import { FileAccess } from 'vs/base/common/network'; import { COMMENTS_SECTION, ICommentsConfiguration } from 'vs/workbench/contrib/comments/common/commentsConfiguration'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; class CommentsActionRunner extends ActionRunner { protected override async runAction(action: IAction, context: any[]): Promise { @@ -690,8 +691,10 @@ export class CommentNode extends Disposable { private onContextMenu(e: MouseEvent) { + const event = new StandardMouseEvent(e); + this.contextMenuService.showContextMenu({ - getAnchor: () => e, + getAnchor: () => event, menuId: MenuId.CommentThreadCommentContext, menuActionOptions: { shouldForwardArgs: true }, contextKeyService: this._contextKeyService, diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts index 302bec8e1b609..f83748a565dad 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts @@ -21,6 +21,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { MarshalledId } from 'vs/base/common/marshallingIds'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; const collapseIcon = registerIcon('review-comment-collapse', Codicon.chevronUp, nls.localize('collapseIcon', 'Icon to collapse a review comment.')); const COLLAPSE_ACTION_CLASS = 'expand-review-action ' + ThemeIcon.asClassName(collapseIcon); @@ -116,8 +117,9 @@ export class CommentThreadHeader extends Disposable { if (!actions.length) { return; } + const event = new StandardMouseEvent(e); this._contextMenuService.showContextMenu({ - getAnchor: () => e, + getAnchor: () => event, getActions: () => actions, actionRunner: new ActionRunner(), getActionsContext: () => { diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 7e6fe3265cd26..b596a2c27dea3 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -829,10 +829,8 @@ export class CommentController implements IEditorContribution { if (newCommentInfos.length > 1) { if (e && range) { - const anchor = { x: e.event.posx, y: e.event.posy }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => e.event, getActions: () => this.getContextMenuActions(newCommentInfos, range), getActionsContext: () => newCommentInfos.length ? newCommentInfos[0] : undefined, onHide: () => { this._addInProgress = false; } diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 0fc7574fbeaa2..8c7e0c62b0eb6 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -728,10 +728,9 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { })); this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CONTEXT_MENU, e => { const event = new StandardMouseEvent(e); - const anchor = { x: event.posx, y: event.posy }; const actions = this.getContextMenuActions(); this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => event, getActions: () => actions, getActionsContext: () => this.breakpoint, onHide: () => disposeIfDisposable(actions) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index cb3862bc7f76d..560ba87afc7ae 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -52,6 +52,7 @@ import { IMenuWorkbenchButtonBarOptions, MenuWorkbenchButtonBar } from 'vs/platf import { SlashCommandContentWidget } from 'vs/workbench/contrib/chat/browser/chatSlashCommandContentWidget'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; const defaultAriaLabel = localize('aria-label', "Inline Chat Input"); @@ -377,7 +378,8 @@ export class InlineChatWidget { })); } - private _onContextMenu(event: MouseEvent) { + private _onContextMenu(e: MouseEvent) { + const event = new StandardMouseEvent(e); this._contextMenuService.showContextMenu({ menuId: MENU_INLINE_CHAT_WIDGET_TOGGLE, getAnchor: () => event, diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index 4e13acad89df1..1855cc0413f48 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -16,6 +16,7 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB import { INotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookRenderingCommon'; import { NotebookCellOutlineProvider, OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookOutlineProvider'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; export class ToggleNotebookStickyScroll extends Action2 { @@ -116,7 +117,8 @@ export class NotebookStickyScroll extends Disposable { })); } - private onContextMenu(event: MouseEvent) { + private onContextMenu(e: MouseEvent) { + const event = new StandardMouseEvent(e); this._contextMenuService.showContextMenu({ menuId: MenuId.NotebookStickyScrollContext, getAnchor: () => event, diff --git a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index c80d1d641c896..19a69d1f1a0f4 100644 --- a/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -355,11 +355,10 @@ class EditSettingRenderer extends Disposable { private onEditSettingClicked(editPreferenceWidget: EditPreferenceWidget, e: IEditorMouseEvent): void { EventHelper.stop(e.event, true); - const anchor = { x: e.event.posx, y: e.event.posy }; const actions = this.getSettings(editPreferenceWidget.getLine()).length === 1 ? this.getActions(editPreferenceWidget.preferences[0], this.getConfigurationsMap()[editPreferenceWidget.preferences[0].key]) : editPreferenceWidget.preferences.map(setting => new SubmenuAction(`preferences.submenu.${setting.key}`, setting.key, this.getActions(setting, this.getConfigurationsMap()[setting.key]))); this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => e.event, getActions: () => actions }); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts index d847bcdff562f..d3ec1a63d6b32 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts @@ -12,7 +12,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView export function openContextMenu(event: MouseEvent, parent: HTMLElement, menu: IMenu, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { const standardEvent = new StandardMouseEvent(event); - const anchor: { x: number; y: number } = { x: standardEvent.posx, y: standardEvent.posy }; const actions: IAction[] = []; createAndFillInContextMenuActions(menu, undefined, actions); @@ -22,7 +21,7 @@ export function openContextMenu(event: MouseEvent, parent: HTMLElement, menu: IM } contextMenuService.showContextMenu({ - getAnchor: () => anchor, + getAnchor: () => standardEvent, getActions: () => actions, getActionsContext: () => parent, }); diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index b95d6196ee80f..8879f82755433 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -23,11 +23,11 @@ import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/ import { stripIcons } from 'vs/base/common/iconLabels'; import { coalesce } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; -import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { AnchorAlignment, AnchorAxisAlignment, isAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { IMenuService } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Disposable } from 'vs/base/common/lifecycle'; -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; export class ContextMenuService implements IContextMenuService { @@ -157,12 +157,12 @@ class NativeContextMenuService extends Disposable implements IContextMenuService y += 4 / zoom; } } else { - if (anchor instanceof StandardMouseEvent) { - x = anchor.posx; - y = anchor.posy; - } else { + if (isAnchor(anchor)) { x = anchor.x; y = anchor.y; + } else { + x = (anchor as IMouseEvent).posx; + y = (anchor as IMouseEvent).posy; } x++; // prevent first item from being selected automatically under mouse From e6fe7f67d842d97a7f028cdd526d6caae5148b6c Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 16:05:34 +0200 Subject: [PATCH 172/216] context menu - adopt mouse event for list/tree --- src/vs/base/browser/ui/list/list.ts | 3 ++- src/vs/base/browser/ui/list/listWidget.ts | 3 ++- src/vs/base/browser/ui/tree/tree.ts | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index f776ba7ecb2ea..2a77409cd830f 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -5,6 +5,7 @@ import { IDragAndDropData } from 'vs/base/browser/dnd'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { GestureEvent } from 'vs/base/browser/touch'; export interface IListVirtualDelegate { @@ -61,7 +62,7 @@ export interface IListContextMenuEvent { readonly browserEvent: UIEvent; readonly element: T | undefined; readonly index: number | undefined; - readonly anchor: HTMLElement | { readonly x: number; readonly y: number }; + readonly anchor: HTMLElement | IMouseEvent; } export interface IIdentityProvider { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index e8ec5372e18f5..36c5a74a94816 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -27,6 +27,7 @@ import { isNumber } from 'vs/base/common/types'; import 'vs/css!./list'; import { IIdentityProvider, IKeyboardNavigationDelegate, IKeyboardNavigationLabelProvider, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IListEvent, IListGestureEvent, IListMouseEvent, IListRenderer, IListTouchEvent, IListVirtualDelegate, ListError } from './list'; import { IListView, IListViewAccessibilityProvider, IListViewDragAndDrop, IListViewOptions, IListViewOptionsUpdate, ListView } from './listView'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; interface ITraitChangeEvent { indexes: number[]; @@ -1354,7 +1355,7 @@ export class List implements ISpliceable, IDisposable { const fromMouse = this.disposables.add(Event.chain(this.view.onContextMenu)) .filter(_ => !didJustPressContextMenuKey) - .map(({ element, index, browserEvent }) => ({ element, index, anchor: { x: browserEvent.pageX + 1, y: browserEvent.pageY }, browserEvent })) + .map(({ element, index, browserEvent }) => ({ element, index, anchor: new StandardMouseEvent(browserEvent), browserEvent })) .event; return Event.any>(fromKeyDown, fromKeyUp, fromMouse); diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 94e8650f2defa..262badedf9fd2 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from 'vs/base/browser/dnd'; +import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListDragAndDrop, IListDragOverReaction, IListRenderer, ListDragOverEffect } from 'vs/base/browser/ui/list/list'; import { Event } from 'vs/base/common/event'; @@ -176,7 +177,7 @@ export interface ITreeMouseEvent { export interface ITreeContextMenuEvent { readonly browserEvent: UIEvent; readonly element: T | null; - readonly anchor: HTMLElement | { readonly x: number; readonly y: number }; + readonly anchor: HTMLElement | IMouseEvent; } export interface ITreeNavigator { From d604c5cbf1d00474abb6c65375d926210c0d1619 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 16:24:48 +0200 Subject: [PATCH 173/216] context menu - adopt for editor context menu --- .../contrib/contextmenu/browser/contextmenu.ts | 15 ++++++++------- .../contrib/feedback/browser/feedback.ts | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index 4eef122381b1f..e6f6c2b49e4b2 100644 --- a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -5,7 +5,7 @@ import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { IMouseEvent, IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { IAction, Separator, SubmenuAction } from 'vs/base/common/actions'; @@ -103,7 +103,7 @@ export class ContextMenuController implements IEditorContribution { e.event.stopPropagation(); if (e.target.type === MouseTargetType.SCROLLBAR) { - return this._showScrollbarContextMenu({ x: e.event.posx - 1, width: 2, y: e.event.posy - 1, height: 2 }); + return this._showScrollbarContextMenu(e.event); } if (e.target.type !== MouseTargetType.CONTENT_TEXT && e.target.type !== MouseTargetType.CONTENT_EMPTY && e.target.type !== MouseTargetType.TEXTAREA) { @@ -129,16 +129,16 @@ export class ContextMenuController implements IEditorContribution { } // Unless the user triggerd the context menu through Shift+F10, use the mouse position as menu position - let anchor: IAnchor | null = null; + let anchor: IMouseEvent | null = null; if (e.target.type !== MouseTargetType.TEXTAREA) { - anchor = { x: e.event.posx - 1, width: 2, y: e.event.posy - 1, height: 2 }; + anchor = e.event; } // Show the context menu this.showContextMenu(anchor); } - public showContextMenu(anchor?: IAnchor | null): void { + public showContextMenu(anchor?: IMouseEvent | null): void { if (!this._editor.getOption(EditorOption.contextmenu)) { return; // Context menu is turned off through configuration } @@ -193,7 +193,7 @@ export class ContextMenuController implements IEditorContribution { return result; } - private _doShowContextMenu(actions: IAction[], anchor: IAnchor | null = null): void { + private _doShowContextMenu(actions: IAction[], event: IMouseEvent | null = null): void { if (!this._editor.hasModel()) { return; } @@ -206,6 +206,7 @@ export class ContextMenuController implements IEditorContribution { } }); + let anchor: IMouseEvent | IAnchor | null = event; if (!anchor) { // Ensure selection is visible this._editor.revealPosition(this._editor.getPosition(), ScrollType.Immediate); @@ -259,7 +260,7 @@ export class ContextMenuController implements IEditorContribution { }); } - private _showScrollbarContextMenu(anchor: IAnchor): void { + private _showScrollbarContextMenu(anchor: IMouseEvent): void { if (!this._editor.hasModel()) { return; } diff --git a/src/vs/workbench/contrib/feedback/browser/feedback.ts b/src/vs/workbench/contrib/feedback/browser/feedback.ts index cc2b21ddd26b9..e7c5a31c6191c 100644 --- a/src/vs/workbench/contrib/feedback/browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/browser/feedback.ts @@ -100,7 +100,7 @@ export class FeedbackWidget extends Disposable { })); } - private getAnchor(): HTMLElement | IAnchor { + private getAnchor(): IAnchor { const dimension = this.layoutService.dimension; return { From e6e6cd54c28292259ba5dd58caedb3c0a241804f Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Mon, 24 Jul 2023 15:22:53 +0200 Subject: [PATCH 174/216] Log types of authentication requests (#187456) --- src/vs/workbench/api/node/proxyResolver.ts | 31 +++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/api/node/proxyResolver.ts b/src/vs/workbench/api/node/proxyResolver.ts index 475367d27b597..c78016251f280 100644 --- a/src/vs/workbench/api/node/proxyResolver.ts +++ b/src/vs/workbench/api/node/proxyResolver.ts @@ -32,7 +32,7 @@ export function connectProxyResolver( const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote; const params: ProxyAgentParams = { resolveProxy: url => extHostWorkspace.resolveProxy(url), - lookupProxyAuthorization: lookupProxyAuthorization.bind(undefined, extHostLogService, configProvider, {}), + lookupProxyAuthorization: lookupProxyAuthorization.bind(undefined, extHostLogService, mainThreadTelemetry, configProvider, {}, initData.remote.isRemote), getProxyURL: () => configProvider.getConfiguration('http').get('proxy'), getProxySupport: () => configProvider.getConfiguration('http').get('proxySupport') || 'off', getSystemCertificatesV1: () => certSettingV1(configProvider), @@ -119,8 +119,10 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku async function lookupProxyAuthorization( extHostLogService: ILogService, + mainThreadTelemetry: MainThreadTelemetryShape, configProvider: ExtHostConfigProvider, proxyAuthenticateCache: Record, + isRemote: boolean, proxyURL: string, proxyAuthenticate: string | string[] | undefined, state: { kerberosRequested?: boolean } @@ -132,6 +134,7 @@ async function lookupProxyAuthorization( extHostLogService.trace('ProxyResolver#lookupProxyAuthorization callback', `proxyURL:${proxyURL}`, `proxyAuthenticate:${proxyAuthenticate}`, `proxyAuthenticateCache:${cached}`); const header = proxyAuthenticate || cached; const authenticate = Array.isArray(header) ? header : typeof header === 'string' ? [header] : []; + sendTelemetry(mainThreadTelemetry, authenticate, isRemote); if (authenticate.some(a => /^(Negotiate|Kerberos)( |$)/i.test(a)) && !state.kerberosRequested) { try { state.kerberosRequested = true; @@ -149,3 +152,29 @@ async function lookupProxyAuthorization( } return undefined; } + +type ProxyAuthenticationClassification = { + owner: 'chrmarti'; + comment: 'Data about proxy authentication requests'; + authenticationType: { classification: 'PublicNonPersonalData'; purpose: 'FeatureInsight'; comment: 'Type of the authentication requested' }; + extensionHostType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Type of the extension host' }; +}; + +type ProxyAuthenticationEvent = { + authenticationType: string; + extensionHostType: string; +}; + +let telemetrySent = false; + +function sendTelemetry(mainThreadTelemetry: MainThreadTelemetryShape, authenticate: string[], isRemote: boolean) { + if (telemetrySent || !authenticate.length) { + return; + } + telemetrySent = true; + + mainThreadTelemetry.$publicLog2('proxyAuthenticationRequest', { + authenticationType: authenticate.map(a => a.split(' ')[0]).join(','), + extensionHostType: isRemote ? 'remote' : 'local', + }); +} From fbefd6c2ee9dff54b78778716b824d156a8eedeb Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 16:35:28 +0200 Subject: [PATCH 175/216] context menu - let electron position menu for cursor --- .../browser/ui/contextview/contextview.ts | 3 +- .../electron-sandbox/contextmenuService.ts | 31 +++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 0a1934d91d9c3..841f83f80c107 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -9,6 +9,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { Range } from 'vs/base/common/range'; +import { OmitOptional } from 'vs/base/common/types'; import 'vs/css!./contextview'; export const enum ContextViewDOMPosition { @@ -24,7 +25,7 @@ export interface IAnchor { height?: number; } -export function isAnchor(obj: unknown): obj is IAnchor { +export function isAnchor(obj: unknown): obj is IAnchor | OmitOptional { const anchor = obj as IAnchor | undefined; return !!anchor && typeof anchor.x === 'number' && typeof anchor.y === 'number'; diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 8879f82755433..8f4f1313737ad 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -27,7 +27,6 @@ import { AnchorAlignment, AnchorAxisAlignment, isAnchor } from 'vs/base/browser/ import { IMenuService } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; export class ContextMenuService implements IContextMenuService { @@ -104,8 +103,8 @@ class NativeContextMenuService extends Disposable implements IContextMenuService const menu = this.createMenu(delegate, actions, onHide); const anchor = delegate.getAnchor(); - let x: number; - let y: number; + let x: number | undefined; + let y: number | undefined; let zoom = getZoomFactor(); if (dom.isHTMLElement(anchor)) { @@ -156,26 +155,20 @@ class NativeContextMenuService extends Disposable implements IContextMenuService if (isMacintosh) { y += 4 / zoom; } - } else { - if (isAnchor(anchor)) { - x = anchor.x; - y = anchor.y; - } else { - x = (anchor as IMouseEvent).posx; - y = (anchor as IMouseEvent).posy; - } + } else if (isAnchor(anchor)) { + x = anchor.x; + y = anchor.y; + } - x++; // prevent first item from being selected automatically under mouse + if (typeof x === 'number') { + x = Math.floor(x * zoom); } - x *= zoom; - y *= zoom; + if (typeof y === 'number') { + y = Math.floor(y * zoom); + } - popup(menu, { - x: Math.floor(x), - y: Math.floor(y), - positioningItem: delegate.autoSelectFirstItem ? 0 : undefined, - }, () => onHide()); + popup(menu, { x, y, positioningItem: delegate.autoSelectFirstItem ? 0 : undefined, }, () => onHide()); this._onDidShowContextMenu.fire(); } From e6648263fa337ddeae412852f5c098ca1bbd83b0 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 16:36:45 +0200 Subject: [PATCH 176/216] context menu - add comment for when position is undefined --- .../contextmenu/electron-sandbox/contextmenuService.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 8f4f1313737ad..0bb744bc47ae6 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -158,6 +158,9 @@ class NativeContextMenuService extends Disposable implements IContextMenuService } else if (isAnchor(anchor)) { x = anchor.x; y = anchor.y; + } else { + // We leave x/y undefined in this case which will result in + // Electron taking care of opening the menu at the cursor position. } if (typeof x === 'number') { From 16cfcb05f2d94b6bad0eda8d746f4465627bee66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Mon, 24 Jul 2023 16:07:16 +0100 Subject: [PATCH 177/216] alternative commands: respect motion reduced (#188103) * alternative commands: respect motion reduced * fix compile errors --- .../dropdownWithPrimaryActionViewItem.ts | 6 ++-- .../browser/menuEntryActionViewItem.ts | 33 ++++++++++--------- .../notebook/browser/diff/diffComponents.ts | 4 ++- .../notebook/browser/diff/notebookDiffList.ts | 4 ++- .../contrib/terminal/browser/terminalView.ts | 9 +++-- 5 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts index ea41cc6e226a6..ea66a1e2c502e 100644 --- a/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts +++ b/src/vs/platform/actions/browser/dropdownWithPrimaryActionViewItem.ts @@ -18,6 +18,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export interface IDropdownWithPrimaryActionViewItemOptions { getKeyBinding?: (action: IAction) => ResolvedKeybinding | undefined; @@ -43,10 +44,11 @@ export class DropdownWithPrimaryActionViewItem extends BaseActionViewItem { @IKeybindingService _keybindingService: IKeybindingService, @INotificationService _notificationService: INotificationService, @IContextKeyService _contextKeyService: IContextKeyService, - @IThemeService _themeService: IThemeService + @IThemeService _themeService: IThemeService, + @IAccessibilityService _accessibilityService: IAccessibilityService ) { super(null, primaryAction); - this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuProvider); + this._primaryAction = new MenuEntryActionViewItem(primaryAction, undefined, _keybindingService, _notificationService, _contextKeyService, _themeService, _contextMenuProvider, _accessibilityService); this._dropdown = new DropdownMenuActionViewItem(dropdownAction, dropdownMenuActions, this._contextMenuProvider, { menuAsChild: true, classNames: className ? ['codicon', 'codicon-chevron-down', className] : ['codicon', 'codicon-chevron-down'], diff --git a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts index 54400220a8e02..356ff5e101311 100644 --- a/src/vs/platform/actions/browser/menuEntryActionViewItem.ts +++ b/src/vs/platform/actions/browser/menuEntryActionViewItem.ts @@ -30,6 +30,7 @@ import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { assertType } from 'vs/base/common/types'; import { asCssVariable, selectBorder } from 'vs/platform/theme/common/colorRegistry'; import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[] }, primaryGroup?: string): void { const groups = menu.getActions(options); @@ -126,7 +127,8 @@ export class MenuEntryActionViewItem extends ActionViewItem { @INotificationService protected _notificationService: INotificationService, @IContextKeyService protected _contextKeyService: IContextKeyService, @IThemeService protected _themeService: IThemeService, - @IContextMenuService protected _contextMenuService: IContextMenuService + @IContextMenuService protected _contextMenuService: IContextMenuService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { super(undefined, action, { icon: !!(action.class || action.item.icon), label: !action.class && !action.item.icon, draggable: options?.draggable, keybinding: options?.keybinding, hoverDelegate: options?.hoverDelegate }); this._altKey = ModifierKeyEmitter.getInstance(); @@ -160,13 +162,14 @@ export class MenuEntryActionViewItem extends ActionViewItem { } if (this._menuItemAction.alt) { - let mouseOverOnWindowsOrLinux = false; + let isMouseOver = false; const updateAltState = () => { - const wantsAltCommand = !!this._menuItemAction.alt?.enabled && ( - this._altKey.keyStatus.altKey - || (this._altKey.keyStatus.shiftKey && mouseOverOnWindowsOrLinux) - ); + const wantsAltCommand = !!this._menuItemAction.alt?.enabled && + (!this._accessibilityService.isMotionReduced() || isMouseOver) && ( + this._altKey.keyStatus.altKey || + (this._altKey.keyStatus.shiftKey && isMouseOver) + ); if (wantsAltCommand !== this._wantsAltCommand) { this._wantsAltCommand = wantsAltCommand; @@ -178,17 +181,15 @@ export class MenuEntryActionViewItem extends ActionViewItem { this._register(this._altKey.event(updateAltState)); - if (isWindows || isLinux) { - this._register(addDisposableListener(container, 'mouseleave', _ => { - mouseOverOnWindowsOrLinux = false; - updateAltState(); - })); + this._register(addDisposableListener(container, 'mouseleave', _ => { + isMouseOver = false; + updateAltState(); + })); - this._register(addDisposableListener(container, 'mouseenter', _ => { - mouseOverOnWindowsOrLinux = true; - updateAltState(); - })); - } + this._register(addDisposableListener(container, 'mouseenter', _ => { + isMouseOver = true; + updateAltState(); + })); updateAltState(); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index afcefd23bb6e9..60f3e15045044 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -41,6 +41,7 @@ import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { fixedDiffEditorOptions, fixedEditorOptions, fixedEditorPadding } from 'vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityContribution'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export function getOptimizedNestedCodeEditorWidgetOptions(): ICodeEditorWidgetOptions { return { @@ -86,6 +87,7 @@ class PropertyHeader extends Disposable { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService private readonly themeService: IThemeService, @ITelemetryService private readonly telemetryService: ITelemetryService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { super(); } @@ -117,7 +119,7 @@ class PropertyHeader extends Disposable { this._toolbar = new WorkbenchToolBar(cellToolbarContainer, { actionViewItemProvider: action => { if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService); + const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); return item; } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts index 8b12840f440f4..7cff07828c400 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffList.ts @@ -29,6 +29,7 @@ import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { PixelRatio } from 'vs/base/browser/browser'; import { WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; import { fixedDiffEditorOptions, fixedEditorOptions } from 'vs/workbench/contrib/notebook/browser/diff/diffCellEditorOptions'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export class NotebookCellTextDiffListDelegate implements IListVirtualDelegate { private readonly lineHeight: number; @@ -168,6 +169,7 @@ export class CellDiffSideBySideRenderer implements IListRenderer { if (action instanceof MenuItemAction) { - const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService); + const item = new CodiconActionViewItem(action, undefined, this.keybindingService, this.notificationService, this.contextKeyService, this.themeService, this.contextMenuService, this.accessibilityService); return item; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalView.ts b/src/vs/workbench/contrib/terminal/browser/terminalView.ts index f91f800c2ad0b..1f70e8d088a19 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalView.ts @@ -47,6 +47,7 @@ import { defaultSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles' import { Event } from 'vs/base/common/event'; import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export class TerminalViewPane extends ViewPane { private _fontStyleElement: HTMLElement | undefined; @@ -77,7 +78,8 @@ export class TerminalViewPane extends ViewPane { @IMenuService private readonly _menuService: IMenuService, @ITerminalProfileService private readonly _terminalProfileService: ITerminalProfileService, @ITerminalProfileResolverService private readonly _terminalProfileResolverService: ITerminalProfileResolverService, - @IThemeService private readonly _themeService: IThemeService + @IThemeService private readonly _themeService: IThemeService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { super(options, keybindingService, _contextMenuService, _configurationService, _contextKeyService, viewDescriptorService, _instantiationService, openerService, themeService, telemetryService); this._register(this._terminalService.onDidRegisterProcessSupport(() => { @@ -245,7 +247,7 @@ export class TerminalViewPane extends ViewPane { if (action instanceof MenuItemAction) { const actions = getTerminalActionBarArgs(TerminalLocation.Panel, this._terminalProfileService.availableProfiles, this._getDefaultProfileName(), this._terminalProfileService.contributedProfiles, this._terminalService, this._dropdownMenu); this._newDropdown?.dispose(); - this._newDropdown = new DropdownWithPrimaryActionViewItem(action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, {}, this._keybindingService, this._notificationService, this._contextKeyService, this._themeService); + this._newDropdown = new DropdownWithPrimaryActionViewItem(action, actions.dropdownAction, actions.dropdownMenuActions, actions.className, this._contextMenuService, {}, this._keybindingService, this._notificationService, this._contextKeyService, this._themeService, this._accessibilityService); this._updateTabActionBar(this._terminalProfileService.availableProfiles); return this._newDropdown; } @@ -360,11 +362,12 @@ class SingleTerminalTabActionViewItem extends MenuEntryActionViewItem { @IContextMenuService contextMenuService: IContextMenuService, @ICommandService private readonly _commandService: ICommandService, @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IAccessibilityService _accessibilityService: IAccessibilityService ) { super(action, { draggable: true, hoverDelegate: _instantiationService.createInstance(SingleTabHoverDelegate) - }, keybindingService, notificationService, contextKeyService, themeService, contextMenuService); + }, keybindingService, notificationService, contextKeyService, themeService, contextMenuService, _accessibilityService); // Register listeners to update the tab this._register(Event.debounce>(Event.any( From 854121fd96f4a6bd419c32990d97dfc805e8e4c7 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Mon, 24 Jul 2023 08:10:42 -0700 Subject: [PATCH 178/216] Show Remote Close action as the last item in the quick pick (#188590) Show Remote close item as the last item in the quick pick --- .../contrib/remote/browser/remoteIndicator.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 1450fb17d1719..a5edf30c6cf22 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -725,6 +725,27 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr } } + if (this.extensionGalleryService.isEnabled() && this.remoteMetadataInitialized) { + + const notInstalledItems: QuickPickItem[] = []; + for (const metadata of this.remoteExtensionMetadata) { + if (!metadata.installed && metadata.isPlatformCompatible) { + // Create Install QuickPick with a help link + const label = metadata.startConnectLabel; + const buttons: IQuickInputButton[] = [{ + iconClass: ThemeIcon.asClassName(infoIcon), + tooltip: nls.localize('remote.startActions.help', "Learn More") + }]; + notInstalledItems.push({ type: 'item', id: metadata.id, label: label, buttons: buttons }); + } + } + + items.push({ + type: 'separator', label: nls.localize('remote.startActions.install', 'Install') + }); + items.push(...notInstalledItems); + } + items.push({ type: 'separator' }); @@ -759,27 +780,6 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr items.pop(); // remove the separator again } - if (this.extensionGalleryService.isEnabled() && this.remoteMetadataInitialized) { - - const notInstalledItems: QuickPickItem[] = []; - for (const metadata of this.remoteExtensionMetadata) { - if (!metadata.installed && metadata.isPlatformCompatible) { - // Create Install QuickPick with a help link - const label = metadata.startConnectLabel; - const buttons: IQuickInputButton[] = [{ - iconClass: ThemeIcon.asClassName(infoIcon), - tooltip: nls.localize('remote.startActions.help', "Learn More") - }]; - notInstalledItems.push({ type: 'item', id: metadata.id, label: label, buttons: buttons }); - } - } - - items.push({ - type: 'separator', label: nls.localize('remote.startActions.install', 'Install') - }); - items.push(...notInstalledItems); - } - return items; }; From c09f39f96037b8cb5c381f1661edcdb72746aac7 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 24 Jul 2023 09:20:27 -0700 Subject: [PATCH 179/216] fix #186619 --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 595defa31c777..5885fa84d9872 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -153,10 +153,8 @@ class ChatAccessibleViewContribution extends Disposable { if (!widget) { return false; } - - const chatInputFocused = initialRender && !!(codeEditorService.getActiveCodeEditor() || codeEditorService.getFocusedCodeEditor()); - - if (chatInputFocused) { + const chatInputFocused = initialRender && !!codeEditorService.getFocusedCodeEditor(); + if (initialRender && chatInputFocused) { widget.focusLastMessage(); widget = widgetService.lastFocusedWidget; } From b0e5290c3c158d8b9e8c552549df7897a536b92c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 24 Jul 2023 09:23:38 -0700 Subject: [PATCH 180/216] clean up --- src/vs/workbench/contrib/chat/browser/chat.contribution.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index 5885fa84d9872..b02cb345f131f 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -149,14 +149,13 @@ class ChatAccessibleViewContribution extends Disposable { const codeEditorService = accessor.get(ICodeEditorService); return renderAccessibleView(accessibleViewService, widgetService, codeEditorService, true); function renderAccessibleView(accessibleViewService: IAccessibleViewService, widgetService: IChatWidgetService, codeEditorService: ICodeEditorService, initialRender?: boolean): boolean { - let widget = widgetService.lastFocusedWidget; + const widget = widgetService.lastFocusedWidget; if (!widget) { return false; } const chatInputFocused = initialRender && !!codeEditorService.getFocusedCodeEditor(); if (initialRender && chatInputFocused) { widget.focusLastMessage(); - widget = widgetService.lastFocusedWidget; } if (!widget) { @@ -181,10 +180,10 @@ class ChatAccessibleViewContribution extends Disposable { verbositySettingKey: AccessibilityVerbositySettingId.Chat, provideContent(): string { return responseContent; }, onClose() { + verifiedWidget.reveal(focusedItem); if (chatInputFocused) { verifiedWidget.focusInput(); } else { - verifiedWidget.reveal(focusedItem); verifiedWidget.focus(focusedItem); } }, From 8d8f8420d7459b82d5668f5c5a3cac09f57e3813 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Mon, 24 Jul 2023 09:52:10 -0700 Subject: [PATCH 181/216] better fix --- src/vs/workbench/contrib/chat/browser/actions/chatActions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 142a93a0ca761..dcbb44802fd64 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -111,7 +111,7 @@ export function registerChatActions() { return; } runAccessibilityHelpAction(accessor, codeEditor, 'panelChat'); - }, CONTEXT_IN_CHAT_INPUT)); + }, CONTEXT_IN_CHAT_SESSION)); } } From 7b0a86b86b712a50bbc6397b0a5122528d87e0ee Mon Sep 17 00:00:00 2001 From: SteVen Batten Date: Mon, 24 Jul 2023 09:54:59 -0700 Subject: [PATCH 182/216] use capture on zone widget click to prevent interference (#187736) --- src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index cb3862bc7f76d..9bbdeef1d71b9 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -797,7 +797,7 @@ export class InlineChatZoneWidget extends ZoneWidget { if (!this.widget.hasFocus()) { this.widget.focus(); } - })); + }, true)); // todo@jrieken listen ONLY when showing const updateCursorIsAboveContextKey = () => { From 1ec506bf0e900feb23a8c744855a22dcc6b78073 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Mon, 24 Jul 2023 10:13:11 -0700 Subject: [PATCH 183/216] Use `basic` for `password-store` (#188692) * Use `basic` for `password-store` Fixes https://github.com/microsoft/vscode/issues/188059 * add to node argv.ts --- .../encryption/common/encryptionService.ts | 16 ++++++++++++++++ .../electron-main/encryptionMainService.ts | 4 ++-- src/vs/platform/environment/common/argv.ts | 1 + src/vs/platform/environment/node/argv.ts | 1 + .../electron-sandbox/secretStorageService.ts | 4 ++-- 5 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/encryption/common/encryptionService.ts b/src/vs/platform/encryption/common/encryptionService.ts index 59d6b38501475..c00a32a663e77 100644 --- a/src/vs/platform/encryption/common/encryptionService.ts +++ b/src/vs/platform/encryption/common/encryptionService.ts @@ -25,6 +25,19 @@ export interface ICommonEncryptionService { isEncryptionAvailable(): Promise; } +// The values provided to the `password-store` command line switch. +// Notice that they are not the same as the values returned by +// `getSelectedStorageBackend` in the `safeStorage` API. +export const enum PasswordStoreCLIOption { + kwallet = 'kwallet', + kwallet5 = 'kwallet5', + gnome = 'gnome', + gnomeKeyring = 'gnome-keyring', + gnomeLibsecret = 'gnome-libsecret', + basic = 'basic' +} + +// The values returned by `getSelectedStorageBackend` in the `safeStorage` API. export const enum KnownStorageProvider { unknown = 'unknown', basicText = 'basic_text', @@ -37,6 +50,9 @@ export const enum KnownStorageProvider { kwallet5 = 'kwallet5', kwallet6 = 'kwallet6', + // The rest of these are not returned by `getSelectedStorageBackend` + // but these were added for platform completeness. + // Windows dplib = 'dpapi', diff --git a/src/vs/platform/encryption/electron-main/encryptionMainService.ts b/src/vs/platform/encryption/electron-main/encryptionMainService.ts index 0589dc473b943..215ef85d41f83 100644 --- a/src/vs/platform/encryption/electron-main/encryptionMainService.ts +++ b/src/vs/platform/encryption/electron-main/encryptionMainService.ts @@ -5,7 +5,7 @@ import { safeStorage as safeStorageElectron, app } from 'electron'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; -import { KnownStorageProvider, IEncryptionMainService } from 'vs/platform/encryption/common/encryptionService'; +import { KnownStorageProvider, IEncryptionMainService, PasswordStoreCLIOption } from 'vs/platform/encryption/common/encryptionService'; import { ILogService } from 'vs/platform/log/common/log'; // These APIs are currently only supported in our custom build of electron so @@ -25,7 +25,7 @@ export class EncryptionMainService implements IEncryptionMainService { @ILogService private readonly logService: ILogService ) { // if this commandLine switch is set, the user has opted in to using basic text encryption - if (app.commandLine.getSwitchValue('password-store') === 'basic_text') { + if (app.commandLine.getSwitchValue('password-store') === PasswordStoreCLIOption.basic) { safeStorage.setUsePlainTextEncryption?.(true); } } diff --git a/src/vs/platform/environment/common/argv.ts b/src/vs/platform/environment/common/argv.ts index 2bbfd487d7546..17476d941872f 100644 --- a/src/vs/platform/environment/common/argv.ts +++ b/src/vs/platform/environment/common/argv.ts @@ -88,6 +88,7 @@ export interface NativeParsedArgs { 'install-source'?: string; 'disable-updates'?: boolean; 'disable-keytar'?: boolean; + 'password-store'?: string; 'disable-workspace-trust'?: boolean; 'disable-crash-reporter'?: boolean; 'crash-reporter-directory'?: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index fbae6c3e4ed81..b3e423e20f9cf 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -139,6 +139,7 @@ export const OPTIONS: OptionDescriptions> = { 'disable-telemetry': { type: 'boolean' }, 'disable-updates': { type: 'boolean' }, 'disable-keytar': { type: 'boolean' }, + 'password-store': { type: 'string' }, 'disable-workspace-trust': { type: 'boolean' }, 'disable-crash-reporter': { type: 'boolean' }, 'crash-reporter-directory': { type: 'string' }, diff --git a/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts b/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts index 44d57e2549302..d89abd9110dd2 100644 --- a/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts +++ b/src/vs/workbench/services/secrets/electron-sandbox/secretStorageService.ts @@ -8,7 +8,7 @@ import { isLinux } from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEncryptionService, KnownStorageProvider, isGnome, isKwallet } from 'vs/platform/encryption/common/encryptionService'; +import { IEncryptionService, KnownStorageProvider, PasswordStoreCLIOption, isGnome, isKwallet } from 'vs/platform/encryption/common/encryptionService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; @@ -72,7 +72,7 @@ export class NativeSecretStorageService extends BaseSecretStorageService { label: localize('usePlainText', "Use weaker encryption"), run: async () => { await this._encryptionService.setUsePlainTextEncryption(); - await this._jsonEditingService.write(this._environmentService.argvResource, [{ path: ['password-store'], value: 'basic_text' }], true); + await this._jsonEditingService.write(this._environmentService.argvResource, [{ path: ['password-store'], value: PasswordStoreCLIOption.basic }], true); this.reinitialize(); } }; From ce2579718d6c6defd3382aaa3114ffac20c8a3c1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 24 Jul 2023 10:14:32 -0700 Subject: [PATCH 184/216] Revert "chore: bump node-pty@1.1.0-beta3" This reverts commit 5083bb50a54c31d181d9d64177b2752538012e9a. This update caused macOS task integration tests to consistently fail. --- package.json | 2 +- remote/package.json | 2 +- remote/yarn.lock | 8 ++++---- yarn.lock | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 9a3923a90add0..eced648db3bb6 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ "native-is-elevated": "0.7.0", "native-keymap": "^3.3.2", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta3", + "node-pty": "1.1.0-beta1", "tas-client-umd": "0.1.8", "v8-inspect-profiler": "^0.1.0", "vscode-oniguruma": "1.7.0", diff --git a/remote/package.json b/remote/package.json index 7f645b5b04477..c68f4105b30f4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -22,7 +22,7 @@ "keytar": "7.9.0", "minimist": "^1.2.6", "native-watchdog": "^1.4.1", - "node-pty": "1.1.0-beta3", + "node-pty": "1.1.0-beta1", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", diff --git a/remote/yarn.lock b/remote/yarn.lock index 85d87e20fb52a..0034d6a8a56bf 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -560,10 +560,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@1.1.0-beta3: - version "1.1.0-beta3" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta3.tgz#c750621a4effddd499fcbd15a2cf7679bee7b60f" - integrity sha512-/QnWzklx8QcPcUUTWZSlY/YIz2IXo61KKx7O1H7aZ1YtMvmU1EFS/+hppmUSWV7+9WRCfAdov4K22u8knZ+IzA== +node-pty@1.1.0-beta1: + version "1.1.0-beta1" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta1.tgz#95d4baf406c043b78042f951b325e9713df2beac" + integrity sha512-h+1E/gX/brFqsp3yZKGERHOhdo1POG1rrsI+8tEuocqdEddHd029471gq8KOuiHKicd52h2pSU8Gtqb3Vo2PfQ== dependencies: nan "^2.17.0" diff --git a/yarn.lock b/yarn.lock index 50da7ab847022..651bda6187cae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7155,10 +7155,10 @@ node-gyp-build@^4.3.0: resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.3.0.tgz#9f256b03e5826150be39c764bf51e993946d71a3" integrity sha512-iWjXZvmboq0ja1pUGULQBexmxq8CV4xBhX7VDOTbL7ZR4FOowwY/VOtRxBN/yKxmdGoIp4j5ysNT4u3S2pDQ3Q== -node-pty@1.1.0-beta3: - version "1.1.0-beta3" - resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta3.tgz#c750621a4effddd499fcbd15a2cf7679bee7b60f" - integrity sha512-/QnWzklx8QcPcUUTWZSlY/YIz2IXo61KKx7O1H7aZ1YtMvmU1EFS/+hppmUSWV7+9WRCfAdov4K22u8knZ+IzA== +node-pty@1.1.0-beta1: + version "1.1.0-beta1" + resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.1.0-beta1.tgz#95d4baf406c043b78042f951b325e9713df2beac" + integrity sha512-h+1E/gX/brFqsp3yZKGERHOhdo1POG1rrsI+8tEuocqdEddHd029471gq8KOuiHKicd52h2pSU8Gtqb3Vo2PfQ== dependencies: nan "^2.17.0" From 489f0814c587b3a52b355c41f4fc70a727fe8e39 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:18:50 -0700 Subject: [PATCH 185/216] Add margin-bottom, fixes #172487 (#188541) --- .../contrib/preferences/browser/media/settingsEditor2.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css index eacb0db0e0583..ccac1bfdf60f1 100644 --- a/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsEditor2.css @@ -492,6 +492,9 @@ .settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown * { margin: 0px; } +.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown *:not(:last-child) { + margin-bottom: 8px; +} .settings-editor > .settings-body .settings-tree-container .setting-item-contents .edit-in-settings-button { opacity: 0.9; From 004a5267d2d34012f30d51f5c0ed99047f8feb5e Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Mon, 24 Jul 2023 20:21:12 +0200 Subject: [PATCH 186/216] context menu - add a `window.experimental.nativeContextMenuLocation` setting to toggle behaviour --- .../browser/ui/contextview/contextview.ts | 2 +- .../electron-sandbox/desktop.contribution.ts | 6 +++++ .../electron-sandbox/contextmenuService.ts | 25 ++++++++++++++++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 841f83f80c107..3def6b9e8ade8 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -26,7 +26,7 @@ export interface IAnchor { } export function isAnchor(obj: unknown): obj is IAnchor | OmitOptional { - const anchor = obj as IAnchor | undefined; + const anchor = obj as IAnchor | OmitOptional | undefined; return !!anchor && typeof anchor.x === 'number' && typeof anchor.y === 'number'; } diff --git a/src/vs/workbench/electron-sandbox/desktop.contribution.ts b/src/vs/workbench/electron-sandbox/desktop.contribution.ts index 108a13bdfb1f7..ec3b40e33cb55 100644 --- a/src/vs/workbench/electron-sandbox/desktop.contribution.ts +++ b/src/vs/workbench/electron-sandbox/desktop.contribution.ts @@ -229,6 +229,12 @@ import { applicationConfigurationNodeBase } from 'vs/workbench/common/configurat 'description': localize('windowControlsOverlay', "Use window controls provided by the platform instead of our HTML-based window controls. Changes require a full restart to apply."), 'included': isWindows }, + 'window.experimental.nativeContextMenuLocation': { // TODO@bpasero remove me eventually + 'type': 'boolean', + 'default': false, + 'scope': ConfigurationScope.APPLICATION, + 'description': localize('nativeContextMenuLocation', "Let the OS handle positioning of the context menu in cases where it should appear under the mouse.") + }, 'window.dialogStyle': { 'type': 'string', 'enum': ['native', 'custom'], diff --git a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts index 0bb744bc47ae6..66b9fb03b1901 100644 --- a/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-sandbox/contextmenuService.ts @@ -54,7 +54,7 @@ export class ContextMenuService implements IContextMenuService { // Native context menu: otherwise else { - this.impl = new NativeContextMenuService(notificationService, telemetryService, keybindingService, menuService, contextKeyService); + this.impl = new NativeContextMenuService(notificationService, telemetryService, keybindingService, menuService, contextKeyService, configurationService); } } @@ -77,14 +77,28 @@ class NativeContextMenuService extends Disposable implements IContextMenuService private readonly _onDidHideContextMenu = this._store.add(new Emitter()); readonly onDidHideContextMenu = this._onDidHideContextMenu.event; + private useNativeContextMenuLocation = false; + constructor( @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IMenuService private readonly menuService: IMenuService, @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(); + + this.updateUseNativeContextMenuLocation(); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('window.experimental.nativeContextMenuLocation')) { + this.updateUseNativeContextMenuLocation(); + } + })); + } + + private updateUseNativeContextMenuLocation(): void { + this.useNativeContextMenuLocation = this.configurationService.getValue('window.experimental.nativeContextMenuLocation') === true; } showContextMenu(delegate: IContextMenuDelegate | IContextMenuMenuDelegate): void { @@ -159,8 +173,13 @@ class NativeContextMenuService extends Disposable implements IContextMenuService x = anchor.x; y = anchor.y; } else { - // We leave x/y undefined in this case which will result in - // Electron taking care of opening the menu at the cursor position. + if (this.useNativeContextMenuLocation) { + // We leave x/y undefined in this case which will result in + // Electron taking care of opening the menu at the cursor position. + } else { + x = anchor.posx + 1; // prevent first item from being selected automatically under mouse + y = anchor.posy; + } } if (typeof x === 'number') { From 8d0e9d87aa3def7aea58093f83062d48197ac12f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Mon, 24 Jul 2023 11:24:50 -0700 Subject: [PATCH 187/216] Update distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eced648db3bb6..99c283ecba2b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.81.0", - "distro": "73eefb31f3f577c9082133b1befd33b4dff1fbb6", + "distro": "c03b11187a93f4e9ea2653faf78704c25bc8d50c", "author": { "name": "Microsoft Corporation" }, From 2b5bf2176ed9acf83442a4914ae01dde65d2b91b Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Mon, 24 Jul 2023 13:02:48 -0700 Subject: [PATCH 188/216] Call toLower on a few places to align navigator.language behavior (#188714) ref https://github.com/microsoft/vscode/issues/187795 --- src/vs/code/browser/workbench/workbench.html | 4 +++- .../workbench/services/localization/browser/localeService.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index a41b5dbf7bdc6..addc28f0c4d37 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -43,7 +43,9 @@ // Set up nls if the user is not using the default language (English) const nlsConfig = {}; - const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language; + // Normalize locale to lowercase because translationServiceUrl is case-sensitive. + // ref: https://github.com/microsoft/vscode/issues/187795 + const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase(); if (!locale.startsWith('en')) { nlsConfig['vs/nls'] = { availableLanguages: { diff --git a/src/vs/workbench/services/localization/browser/localeService.ts b/src/vs/workbench/services/localization/browser/localeService.ts index 60ae11d8cc95d..37a957772616b 100644 --- a/src/vs/workbench/services/localization/browser/localeService.ts +++ b/src/vs/workbench/services/localization/browser/localeService.ts @@ -28,7 +28,7 @@ export class WebLocaleService implements ILocaleService { async setLocale(languagePackItem: ILanguagePackItem, _skipDialog = false): Promise { const locale = languagePackItem.id; - if (locale === Language.value() || (!locale && Language.value() === navigator.language)) { + if (locale === Language.value() || (!locale && Language.value() === navigator.language.toLowerCase())) { return; } if (locale) { @@ -57,7 +57,7 @@ export class WebLocaleService implements ILocaleService { window.localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_LOCALE_KEY); window.localStorage.removeItem(WebLocaleService._LOCAL_STORAGE_EXTENSION_ID_KEY); - if (Language.value() === navigator.language) { + if (Language.value() === navigator.language.toLowerCase()) { return; } From 67e47344d235cffdfd7d0f860fb6e5b2a520c385 Mon Sep 17 00:00:00 2001 From: Andrea Mah <31675041+andreamah@users.noreply.github.com> Date: Mon, 24 Jul 2023 14:53:58 -0700 Subject: [PATCH 189/216] Closed notebook search: current result not highlighted on notebook open (#188719) Fixes #188558 --- .../contrib/search/browser/searchModel.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/search/browser/searchModel.ts b/src/vs/workbench/contrib/search/browser/searchModel.ts index 46c2d29079f3f..0c539b4a8aa08 100644 --- a/src/vs/workbench/contrib/search/browser/searchModel.ts +++ b/src/vs/workbench/contrib/search/browser/searchModel.ts @@ -625,6 +625,12 @@ export class FileMatch extends Disposable implements IFileMatch { setSelectedMatch(match: Match | null): void { if (match) { + + if (!this.isMatchSelected(match) && match instanceof MatchInNotebook) { + this._selectedMatch = match; + return; + } + if (!this._textMatches.has(match.id())) { return; } @@ -765,6 +771,9 @@ export class FileMatch extends Disposable implements IFileMatch { this._findMatchDecorationModel?.stopWebviewFind(); this._findMatchDecorationModel?.dispose(); this._findMatchDecorationModel = new FindMatchDecorationModel(this._notebookEditorWidget, this.searchInstanceID); + if (this._selectedMatch instanceof MatchInNotebook) { + this.highlightCurrentFindMatchDecoration(this._selectedMatch); + } } private _removeNotebookHighlights(): void { @@ -780,6 +789,7 @@ export class FileMatch extends Disposable implements IFileMatch { return; } + const oldCellMatches = new Map(this._cellMatches); if (this._notebookEditorWidget.getId() !== this._lastEditorWidgetIdForUpdate) { this._cellMatches.clear(); this._lastEditorWidgetIdForUpdate = this._notebookEditorWidget.getId(); @@ -790,10 +800,9 @@ export class FileMatch extends Disposable implements IFileMatch { let existingCell = this._cellMatches.get(match.cell.id); if (this._notebookEditorWidget && !existingCell) { const index = this._notebookEditorWidget.getCellIndex(match.cell); - const existingRawCell = this._cellMatches.get(`${rawCellPrefix}${index}`); + const existingRawCell = oldCellMatches.get(`${rawCellPrefix}${index}`); if (existingRawCell) { existingRawCell.setCellModel(match.cell); - this._cellMatches.delete(`${rawCellPrefix}${index}`); existingCell = existingRawCell; } } @@ -805,6 +814,9 @@ export class FileMatch extends Disposable implements IFileMatch { }); this._findMatchDecorationModel?.setAllFindMatchesDecorations(matches); + if (this._selectedMatch instanceof MatchInNotebook) { + this.highlightCurrentFindMatchDecoration(this._selectedMatch); + } this._onChange.fire({ forceUpdateModel: modelChange }); } From 5a14d85f488440b7465d8f74cd16ffea38d5b11a Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 24 Jul 2023 15:12:21 -0700 Subject: [PATCH 190/216] cli: adding forwarding for local port for remote tunnels (#188715) This reuses a lot of the logic we use for the normal VS Code Server tunnel to do port forwarding. It does use a _different_ tunnel than what Remote Tunnels would otherwise use for the control server. The reason for this is that ports exist on a tunnel instance, and if we reused the same tunnel then a client would expect all CLI hosts to serve all tunnels, where the port forwarding instance would not provide the VS Code server. It also reuses the singleton logic so all ports on a machine are handled by a single CLI instance for the same reason: we can't have different instances hosting subsets of ports on a single tunnel. Currently all ports are under the default privacy: support for public/private tunnels is either later today or next iteration. --- cli/src/bin/code/main.rs | 3 + cli/src/commands/args.rs | 14 ++ cli/src/commands/tunnels.rs | 116 ++++++++++-- cli/src/singleton.rs | 10 +- cli/src/state.rs | 8 + cli/src/tunnels.rs | 1 + cli/src/tunnels/dev_tunnels.rs | 81 +++++--- cli/src/tunnels/forwarding.rs | 284 ++++++++++++++++++++++++++++ cli/src/tunnels/port_forwarder.rs | 2 +- cli/src/tunnels/protocol.rs | 18 ++ cli/src/tunnels/singleton_server.rs | 2 +- cli/src/util/errors.rs | 4 +- 12 files changed, 500 insertions(+), 43 deletions(-) create mode 100644 cli/src/tunnels/forwarding.rs diff --git a/cli/src/bin/code/main.rs b/cli/src/bin/code/main.rs index 62e4195c7e4b1..8c32ee14d89f1 100644 --- a/cli/src/bin/code/main.rs +++ b/cli/src/bin/code/main.rs @@ -114,6 +114,9 @@ async fn main() -> Result<(), std::convert::Infallible> { Some(args::TunnelSubcommand::Service(service_args)) => { tunnels::service(context_no_logger(), service_args).await } + Some(args::TunnelSubcommand::ForwardInternal(forward_args)) => { + tunnels::forward(context_no_logger(), forward_args).await + } None => tunnels::serve(context_no_logger(), tunnel_args.serve_args).await, }, }, diff --git a/cli/src/commands/args.rs b/cli/src/commands/args.rs index e253130573b8f..0051f72f3957d 100644 --- a/cli/src/commands/args.rs +++ b/cli/src/commands/args.rs @@ -640,6 +640,10 @@ pub enum TunnelSubcommand { /// (Preview) Manages the tunnel when installed as a system service, #[clap(subcommand)] Service(TunnelServiceSubCommands), + + /// (Preview) Forwards local port using the dev tunnel + #[clap(hide = true)] + ForwardInternal(TunnelForwardArgs), } #[derive(Subcommand, Debug, Clone)] @@ -675,6 +679,16 @@ pub struct TunnelRenameArgs { pub name: String, } +#[derive(Args, Debug, Clone)] +pub struct TunnelForwardArgs { + /// One or more ports to forward. + pub ports: Vec, + + /// Login args -- used for convenience so the forwarding call is a single action. + #[clap(flatten)] + pub login: LoginArgs, +} + #[derive(Subcommand, Debug, Clone)] pub enum TunnelUserSubCommands { /// Log in to port forwarding service diff --git a/cli/src/commands/tunnels.rs b/cli/src/commands/tunnels.rs index 578114611c010..95f9b12a3d423 100644 --- a/cli/src/commands/tunnels.rs +++ b/cli/src/commands/tunnels.rs @@ -10,11 +10,15 @@ use serde::Serialize; use sha2::{Digest, Sha256}; use std::{str::FromStr, time::Duration}; use sysinfo::Pid; +use tokio::{ + io::{AsyncBufReadExt, BufReader}, + sync::watch, +}; use super::{ args::{ - AuthProvider, CliCore, CommandShellArgs, ExistingTunnelArgs, TunnelRenameArgs, - TunnelServeArgs, TunnelServiceSubCommands, TunnelUserSubCommands, + AuthProvider, CliCore, CommandShellArgs, ExistingTunnelArgs, TunnelForwardArgs, + TunnelRenameArgs, TunnelServeArgs, TunnelServiceSubCommands, TunnelUserSubCommands, }, CommandContext, }; @@ -22,12 +26,16 @@ use super::{ use crate::{ async_pipe::{get_socket_name, listen_socket_rw_stream, AsyncRWAccepter}, auth::Auth, - constants::{APPLICATION_NAME, TUNNEL_CLI_LOCK_NAME, TUNNEL_SERVICE_LOCK_NAME}, + constants::{ + APPLICATION_NAME, CONTROL_PORT, IS_A_TTY, TUNNEL_CLI_LOCK_NAME, TUNNEL_SERVICE_LOCK_NAME, + }, log, state::LauncherPaths, tunnels::{ code_server::CodeServerArgs, - create_service_manager, dev_tunnels, legal, + create_service_manager, + dev_tunnels::{self, DevTunnels}, + forwarding, legal, paths::get_all_servers, protocol, serve_stream, shutdown_signal::ShutdownRequest, @@ -200,7 +208,7 @@ pub async fn service( if let Some(name) = &args.name { // ensure the name matches, and tunnel exists - dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths) + dev_tunnels::DevTunnels::new_remote_tunnel(&ctx.log, auth, &ctx.paths) .rename_tunnel(name) .await?; } else { @@ -274,7 +282,7 @@ pub async fn user(ctx: CommandContext, user_args: TunnelUserSubCommands) -> Resu /// Remove the tunnel used by this tunnel, if any. pub async fn rename(ctx: CommandContext, rename_args: TunnelRenameArgs) -> Result { let auth = Auth::new(&ctx.paths, ctx.log.clone()); - let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths); + let mut dt = dev_tunnels::DevTunnels::new_remote_tunnel(&ctx.log, auth, &ctx.paths); dt.rename_tunnel(&rename_args.name).await?; ctx.log.result(format!( "Successfully renamed this tunnel to {}", @@ -287,7 +295,7 @@ pub async fn rename(ctx: CommandContext, rename_args: TunnelRenameArgs) -> Resul /// Remove the tunnel used by this tunnel, if any. pub async fn unregister(ctx: CommandContext) -> Result { let auth = Auth::new(&ctx.paths, ctx.log.clone()); - let mut dt = dev_tunnels::DevTunnels::new(&ctx.log, auth, &ctx.paths); + let mut dt = dev_tunnels::DevTunnels::new_remote_tunnel(&ctx.log, auth, &ctx.paths); dt.remove_tunnel().await?; Ok(0) } @@ -395,6 +403,88 @@ pub async fn serve(ctx: CommandContext, gateway_args: TunnelServeArgs) -> Result result } +/// Internal command used by port forwarding. It reads requests for forwarded ports +/// on lines from stdin, as JSON. It uses singleton logic as well (though on +/// a different tunnel than the main one used for the control server) so that +/// all forward requests on a single machine go through a single hosted tunnel +/// process. Without singleton logic, requests could get routed to processes +/// that aren't forwarding a given port and then fail. +pub async fn forward( + ctx: CommandContext, + mut forward_args: TunnelForwardArgs, +) -> Result { + // Spooky: check IS_A_TTY before starting the stdin reader, since IS_A_TTY will + // access stdin but a lock will later be held on stdin by the line-reader. + if *IS_A_TTY { + trace!(ctx.log, "port forwarding is an internal preview feature"); + } + + // #region stdin reading logic: + let (own_ports_tx, own_ports_rx) = watch::channel(vec![]); + let ports_process_log = ctx.log.clone(); + tokio::spawn(async move { + let mut lines = BufReader::new(tokio::io::stdin()).lines(); + while let Ok(Some(line)) = lines.next_line().await { + match serde_json::from_str(&line) { + Ok(p) => { + let _ = own_ports_tx.send(p); + } + Err(e) => warning!(ports_process_log, "error parsing ports: {}", e), + } + } + }); + + // #region singleton acquisition + let shutdown = ShutdownRequest::create_rx([ShutdownRequest::CtrlC]); + let server = loop { + if shutdown.is_open() { + return Ok(0); + } + + match acquire_singleton(&ctx.paths.forwarding_lockfile()).await { + Ok(SingletonConnection::Client(stream)) => { + debug!(ctx.log, "starting as client to singleton"); + let r = forwarding::client(forwarding::SingletonClientArgs { + log: ctx.log.clone(), + shutdown: shutdown.clone(), + stream, + port_requests: own_ports_rx.clone(), + }) + .await; + if let Err(e) = r { + warning!(ctx.log, "error contacting forwarding singleton: {}", e); + } + } + Ok(SingletonConnection::Singleton(server)) => break server, + Err(e) => { + warning!(ctx.log, "error access singleton, retrying: {}", e); + tokio::time::sleep(Duration::from_secs(2)).await + } + } + }; + + // #region singleton handler + let auth = Auth::new(&ctx.paths, ctx.log.clone()); + println!("preauth {:?}", forward_args.login); + if let (Some(p), Some(at)) = ( + forward_args.login.provider.take(), + forward_args.login.access_token.take(), + ) { + auth.login(Some(p.into()), Some(at)).await?; + } + println!("auth done"); + + let mut tunnels = DevTunnels::new_port_forwarding(&ctx.log, auth, &ctx.paths); + let tunnel = tunnels + .start_new_launcher_tunnel(None, true, &forward_args.ports) + .await?; + println!("made tunnel"); + + forwarding::server(ctx.log, tunnel, server, own_ports_rx, shutdown).await?; + + Ok(0) +} + fn get_connection_token(tunnel: &ActiveTunnel) -> String { let mut hash = Sha256::new(); hash.update(tunnel.id.as_bytes()); @@ -442,7 +532,7 @@ async fn serve_with_csa( return Ok(0); } - match acquire_singleton(paths.tunnel_lockfile()).await { + match acquire_singleton(&paths.tunnel_lockfile()).await { Ok(SingletonConnection::Client(stream)) => { debug!(log, "starting as client to singleton"); let should_exit = start_singleton_client(SingletonClientArgs { @@ -471,15 +561,19 @@ async fn serve_with_csa( let _lock = app_mutex_name.map(AppMutex::new); let auth = Auth::new(&paths, log.clone()); - let mut dt = dev_tunnels::DevTunnels::new(&log, auth, &paths); + let mut dt = dev_tunnels::DevTunnels::new_remote_tunnel(&log, auth, &paths); loop { let tunnel = if let Some(t) = fulfill_existing_tunnel_args(gateway_args.tunnel.clone(), &gateway_args.name) { dt.start_existing_tunnel(t).await } else { - dt.start_new_launcher_tunnel(gateway_args.name.as_deref(), gateway_args.random_name) - .await + dt.start_new_launcher_tunnel( + gateway_args.name.as_deref(), + gateway_args.random_name, + &[CONTROL_PORT], + ) + .await }?; csa.connection_token = Some(get_connection_token(&tunnel)); diff --git a/cli/src/singleton.rs b/cli/src/singleton.rs index 0ea9cda2a8a43..635c400fb0f6d 100644 --- a/cli/src/singleton.rs +++ b/cli/src/singleton.rs @@ -53,12 +53,12 @@ struct LockFileMatter { /// Tries to acquire the singleton homed at the given lock file, either starting /// a new singleton if it doesn't exist, or connecting otherwise. -pub async fn acquire_singleton(lock_file: PathBuf) -> Result { +pub async fn acquire_singleton(lock_file: &Path) -> Result { let file = OpenOptions::new() .read(true) .write(true) .create(true) - .open(&lock_file) + .open(lock_file) .map_err(CodeError::SingletonLockfileOpenFailed)?; match FileLock::acquire(file) { @@ -158,7 +158,7 @@ mod tests { #[tokio::test] async fn test_acquires_singleton() { let dir = tempfile::tempdir().expect("expected to make temp dir"); - let s = acquire_singleton(dir.path().join("lock")) + let s = acquire_singleton(&dir.path().join("lock")) .await .expect("expected to acquire"); @@ -172,7 +172,7 @@ mod tests { async fn test_acquires_client() { let dir = tempfile::tempdir().expect("expected to make temp dir"); let lockfile = dir.path().join("lock"); - let s1 = acquire_singleton(lockfile.clone()) + let s1 = acquire_singleton(&lockfile) .await .expect("expected to acquire1"); match s1 { @@ -182,7 +182,7 @@ mod tests { _ => panic!("expected to be singleton"), }; - let s2 = acquire_singleton(lockfile) + let s2 = acquire_singleton(&lockfile) .await .expect("expected to acquire2"); match s2 { diff --git a/cli/src/state.rs b/cli/src/state.rs index af0d18e160b34..1b1ff343da5cd 100644 --- a/cli/src/state.rs +++ b/cli/src/state.rs @@ -187,6 +187,14 @@ impl LauncherPaths { )) } + /// Lockfile for port forwarding + pub fn forwarding_lockfile(&self) -> PathBuf { + self.root.join(format!( + "forwarding-{}.lock", + VSCODE_CLI_QUALITY.unwrap_or("oss") + )) + } + /// Suggested path for tunnel service logs, when using file logs pub fn service_log_file(&self) -> PathBuf { self.root.join("tunnel-service.log") diff --git a/cli/src/tunnels.rs b/cli/src/tunnels.rs index 63ccad22382ba..700516abb1f75 100644 --- a/cli/src/tunnels.rs +++ b/cli/src/tunnels.rs @@ -11,6 +11,7 @@ pub mod protocol; pub mod shutdown_signal; pub mod singleton_client; pub mod singleton_server; +pub mod forwarding; mod wsl_detect; mod challenge; diff --git a/cli/src/tunnels/dev_tunnels.rs b/cli/src/tunnels/dev_tunnels.rs index 6ca1e7c60c2d9..3bdc5cf189bed 100644 --- a/cli/src/tunnels/dev_tunnels.rs +++ b/cli/src/tunnels/dev_tunnels.rs @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ use crate::auth; -use crate::constants::{ - CONTROL_PORT, IS_INTERACTIVE_CLI, PROTOCOL_VERSION_TAG, TUNNEL_SERVICE_USER_AGENT, -}; +use crate::constants::{IS_INTERACTIVE_CLI, PROTOCOL_VERSION_TAG, TUNNEL_SERVICE_USER_AGENT}; use crate::state::{LauncherPaths, PersistedState}; use crate::util::errors::{ - wrap, AnyError, DevTunnelError, InvalidTunnelName, TunnelCreationFailed, WrappedError, + wrap, AnyError, CodeError, DevTunnelError, InvalidTunnelName, TunnelCreationFailed, + WrappedError, }; use crate::util::input::prompt_placeholder; use crate::{debug, info, log, spanf, trace, warning}; @@ -136,6 +135,7 @@ pub struct DevTunnels { log: log::Logger, launcher_tunnel: PersistedState>, client: TunnelManagementClient, + tag: &'static str, } /// Representation of a tunnel returned from the `start` methods. @@ -164,30 +164,43 @@ impl ActiveTunnel { } /// Forwards a port over TCP. - pub async fn add_port_tcp(&mut self, port_number: u16) -> Result<(), AnyError> { + pub async fn add_port_tcp(&self, port_number: u16) -> Result<(), AnyError> { self.manager.add_port_tcp(port_number).await?; Ok(()) } /// Removes a forwarded port TCP. - pub async fn remove_port(&mut self, port_number: u16) -> Result<(), AnyError> { + pub async fn remove_port(&self, port_number: u16) -> Result<(), AnyError> { self.manager.remove_port(port_number).await?; Ok(()) } + /// Gets the template string for forming forwarded port web URIs.. + pub fn get_port_format(&self) -> Result { + if let Some(details) = &*self.manager.endpoint_rx.borrow() { + return details + .as_ref() + .map(|r| { + r.base + .port_uri_format + .clone() + .expect("expected to have port format") + }) + .map_err(|e| e.clone().into()); + } + + Err(CodeError::NoTunnelEndpoint.into()) + } + /// Gets the public URI on which a forwarded port can be access in browser. - pub async fn get_port_uri(&mut self, port: u16) -> Result { - let endpoint = self.manager.get_endpoint().await?; - let format = endpoint - .base - .port_uri_format - .expect("expected to have port format"); - - Ok(format.replace(PORT_TOKEN, &port.to_string())) + pub fn get_port_uri(&self, port: u16) -> Result { + self.get_port_format() + .map(|f| f.replace(PORT_TOKEN, &port.to_string())) } } const VSCODE_CLI_TUNNEL_TAG: &str = "vscode-server-launcher"; +const VSCODE_CLI_FORWARDING_TAG: &str = "vscode-port-forward"; const MAX_TUNNEL_NAME_LENGTH: usize = 20; fn get_host_token_from_tunnel(tunnel: &Tunnel) -> String { @@ -244,7 +257,29 @@ pub struct ExistingTunnel { } impl DevTunnels { - pub fn new(log: &log::Logger, auth: auth::Auth, paths: &LauncherPaths) -> DevTunnels { + /// Creates a new DevTunnels client used for port forwarding. + pub fn new_port_forwarding( + log: &log::Logger, + auth: auth::Auth, + paths: &LauncherPaths, + ) -> DevTunnels { + let mut client = new_tunnel_management(&TUNNEL_SERVICE_USER_AGENT); + client.authorization_provider(auth); + + DevTunnels { + log: log.clone(), + client: client.into(), + launcher_tunnel: PersistedState::new(paths.root().join("port_forwarding_tunnel.json")), + tag: VSCODE_CLI_FORWARDING_TAG, + } + } + + /// Creates a new DevTunnels client used for the Remote Tunnels extension to access the VS Code Server. + pub fn new_remote_tunnel( + log: &log::Logger, + auth: auth::Auth, + paths: &LauncherPaths, + ) -> DevTunnels { let mut client = new_tunnel_management(&TUNNEL_SERVICE_USER_AGENT); client.authorization_provider(auth); @@ -252,6 +287,7 @@ impl DevTunnels { log: log.clone(), client: client.into(), launcher_tunnel: PersistedState::new(paths.root().join("code_tunnel.json")), + tag: VSCODE_CLI_TUNNEL_TAG, } } @@ -366,6 +402,7 @@ impl DevTunnels { &mut self, preferred_name: Option<&str>, use_random_name: bool, + preserve_ports: &[u16], ) -> Result { let (mut tunnel, persisted) = match self.launcher_tunnel.load() { Some(mut persisted) => { @@ -409,7 +446,7 @@ impl DevTunnels { for port_to_delete in tunnel .ports .iter() - .filter(|p| p.port_number != CONTROL_PORT) + .filter(|p: &&TunnelPort| !preserve_ports.contains(&p.port_number)) { let output_fut = self.client.delete_tunnel_port( &locator, @@ -461,11 +498,7 @@ impl DevTunnels { self.check_is_name_free(name).await?; let new_tunnel = Tunnel { - tags: vec![ - name.to_string(), - PROTOCOL_VERSION_TAG.to_string(), - VSCODE_CLI_TUNNEL_TAG.to_string(), - ], + tags: self.get_tags(name), ..Default::default() }; @@ -524,7 +557,7 @@ impl DevTunnels { let mut tags = vec![ name.to_string(), PROTOCOL_VERSION_TAG.to_string(), - VSCODE_CLI_TUNNEL_TAG.to_string(), + self.tag.to_string(), ]; if is_wsl_installed(&self.log) { @@ -616,7 +649,7 @@ impl DevTunnels { self.log, self.log.span("dev-tunnel.listall"), self.client.list_all_tunnels(&TunnelRequestOptions { - tags: vec![VSCODE_CLI_TUNNEL_TAG.to_string()], + tags: vec![self.tag.to_string()], require_all_tags: true, ..Default::default() }) @@ -631,7 +664,7 @@ impl DevTunnels { self.log, self.log.span("dev-tunnel.rename.search"), self.client.list_all_tunnels(&TunnelRequestOptions { - tags: vec![VSCODE_CLI_TUNNEL_TAG.to_string(), name.to_string()], + tags: vec![self.tag.to_string(), name.to_string()], require_all_tags: true, ..Default::default() }) diff --git a/cli/src/tunnels/forwarding.rs b/cli/src/tunnels/forwarding.rs new file mode 100644 index 0000000000000..1557b97c04e03 --- /dev/null +++ b/cli/src/tunnels/forwarding.rs @@ -0,0 +1,284 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use tokio::{ + pin, + sync::{mpsc, watch}, +}; + +use crate::{ + async_pipe::{socket_stream_split, AsyncPipe}, + json_rpc::{new_json_rpc, start_json_rpc}, + log, + singleton::SingletonServer, + util::{errors::CodeError, sync::Barrier}, +}; + +use super::{ + dev_tunnels::ActiveTunnel, + protocol::{ + self, + forward_singleton::{PortList, SetPortsResponse}, + }, + shutdown_signal::ShutdownSignal, +}; + +type PortMap = HashMap; + +/// The PortForwardingHandle is given out to multiple consumers to allow +/// them to set_ports that they want to be forwarded. +struct PortForwardingSender { + /// Todo: when `SyncUnsafeCell` is no longer nightly, we can use it here with + /// the following comment: + /// + /// SyncUnsafeCell is used and safe here because PortForwardingSender is used + /// exclusively in synchronous dispatch *and* we create a new sender in the + /// context for each connection, in `serve_singleton_rpc`. + /// + /// If PortForwardingSender is ever used in a different context, this should + /// be refactored, e.g. to use locks or `&mut self` in set_ports` + /// + /// see https://doc.rust-lang.org/stable/std/cell/struct.SyncUnsafeCell.html + current: Mutex, + sender: Arc>>, +} + +impl PortForwardingSender { + pub fn set_ports(&self, ports: PortList) { + let mut current = self.current.lock().unwrap(); + self.sender.lock().unwrap().send_modify(|v| { + for p in current.iter() { + if !ports.contains(p) { + match v.get(p) { + Some(1) => { + v.remove(p); + } + Some(n) => { + v.insert(*p, n - 1); + } + None => unreachable!("removed port not in map"), + } + } + } + + for p in ports.iter() { + if !current.contains(p) { + match v.get(p) { + Some(n) => v.insert(*p, n + 1), + None => v.insert(*p, 1), + }; + } + } + + current.splice(.., ports); + }); + } +} + +impl Clone for PortForwardingSender { + fn clone(&self) -> Self { + Self { + current: Mutex::new(vec![]), + sender: self.sender.clone(), + } + } +} + +impl Drop for PortForwardingSender { + fn drop(&mut self) { + self.set_ports(vec![]); + } +} + +struct PortForwardingReceiver { + receiver: watch::Receiver, +} + +impl PortForwardingReceiver { + pub fn new() -> (PortForwardingSender, Self) { + let (sender, receiver) = watch::channel(HashMap::new()); + let handle = PortForwardingSender { + current: Mutex::new(vec![]), + sender: Arc::new(Mutex::new(sender)), + }; + + let tracker = Self { receiver }; + + (handle, tracker) + } + + /// Applies all changes from PortForwardingHandles to the tunnel. + pub async fn apply_to(&mut self, log: log::Logger, tunnel: Arc) { + let mut current = vec![]; + while self.receiver.changed().await.is_ok() { + let next = self.receiver.borrow().keys().copied().collect::>(); + + for p in current.iter() { + if !next.contains(p) { + match tunnel.remove_port(*p).await { + Ok(_) => info!(log, "stopped forwarding port {}", p), + Err(e) => error!(log, "failed to stop forwarding port {}: {}", p, e), + } + } + } + for p in next.iter() { + if !current.contains(p) { + match tunnel.add_port_tcp(*p).await { + Ok(_) => info!(log, "forwarding port {}", p), + Err(e) => error!(log, "failed to forward port {}: {}", p, e), + } + } + } + + current = next; + } + } +} + +pub struct SingletonClientArgs { + pub log: log::Logger, + pub stream: AsyncPipe, + pub shutdown: Barrier, + pub port_requests: watch::Receiver, +} + +#[derive(Clone)] +struct SingletonServerContext { + log: log::Logger, + handle: PortForwardingSender, + tunnel: Arc, +} + +/// Serves a client singleton for port forwarding. +pub async fn client(args: SingletonClientArgs) -> Result<(), std::io::Error> { + let mut rpc = new_json_rpc(); + let (msg_tx, msg_rx) = mpsc::unbounded_channel(); + let SingletonClientArgs { + log, + shutdown, + stream, + mut port_requests, + } = args; + + debug!( + log, + "An existing port forwarding process is running on this machine, connecting to it..." + ); + + let caller = rpc.get_caller(msg_tx); + let rpc = rpc.methods(()).build(log.clone()); + let (read, write) = socket_stream_split(stream); + + let serve = start_json_rpc(rpc, read, write, msg_rx, shutdown); + let forward = async move { + while port_requests.changed().await.is_ok() { + let ports = port_requests.borrow().clone(); + let r = caller + .call::<_, _, protocol::forward_singleton::SetPortsResponse>( + protocol::forward_singleton::METHOD_SET_PORTS, + protocol::forward_singleton::SetPortsParams { ports }, + ) + .await + .unwrap(); + + match r { + Err(e) => error!(log, "failed to set ports: {:?}", e), + Ok(r) => print_forwarding_addr(&r), + }; + } + }; + + tokio::select! { + r = serve => r.map(|_| ()), + _ = forward => Ok(()), + } +} + +/// Serves a port-forwarding singleton. +pub async fn server( + log: log::Logger, + tunnel: ActiveTunnel, + server: SingletonServer, + mut port_requests: watch::Receiver, + shutdown_rx: Barrier, +) -> Result<(), CodeError> { + let tunnel = Arc::new(tunnel); + let (forward_tx, mut forward_rx) = PortForwardingReceiver::new(); + + let forward_own_tunnel = tunnel.clone(); + let forward_own_tx = forward_tx.clone(); + let forward_own = async move { + while port_requests.changed().await.is_ok() { + forward_own_tx.set_ports(port_requests.borrow().clone()); + print_forwarding_addr(&SetPortsResponse { + port_format: forward_own_tunnel.get_port_format().ok(), + }); + } + }; + + tokio::select! { + _ = forward_own => Ok(()), + _ = forward_rx.apply_to(log.clone(), tunnel.clone()) => Ok(()), + r = serve_singleton_rpc(server, log, tunnel, forward_tx, shutdown_rx) => r, + } +} + +async fn serve_singleton_rpc( + mut server: SingletonServer, + log: log::Logger, + tunnel: Arc, + forward_tx: PortForwardingSender, + shutdown_rx: Barrier, +) -> Result<(), CodeError> { + let mut own_shutdown = shutdown_rx.clone(); + let shutdown_fut = own_shutdown.wait(); + pin!(shutdown_fut); + + loop { + let cnx = tokio::select! { + c = server.accept() => c?, + _ = &mut shutdown_fut => return Ok(()), + }; + + let (read, write) = socket_stream_split(cnx); + let shutdown_rx = shutdown_rx.clone(); + + let handle = forward_tx.clone(); + let log = log.clone(); + let tunnel = tunnel.clone(); + tokio::spawn(async move { + // we make an rpc for the connection instead of re-using a dispatcher + // so that we can have the "handle" drop when the connection drops. + let rpc = new_json_rpc(); + let mut rpc = rpc.methods(SingletonServerContext { + log: log.clone(), + handle, + tunnel, + }); + + rpc.register_sync( + protocol::forward_singleton::METHOD_SET_PORTS, + |p: protocol::forward_singleton::SetPortsParams, ctx| { + info!(ctx.log, "client setting ports to {:?}", p.ports); + ctx.handle.set_ports(p.ports); + Ok(SetPortsResponse { + port_format: ctx.tunnel.get_port_format().ok(), + }) + }, + ); + + let _ = start_json_rpc(rpc.build(log), read, write, (), shutdown_rx).await; + }); + } +} + +fn print_forwarding_addr(r: &SetPortsResponse) { + eprintln!("{}\n", serde_json::to_string(r).unwrap()); +} diff --git a/cli/src/tunnels/port_forwarder.rs b/cli/src/tunnels/port_forwarder.rs index bb60a670dea9f..0f489f541d7c5 100644 --- a/cli/src/tunnels/port_forwarder.rs +++ b/cli/src/tunnels/port_forwarder.rs @@ -91,7 +91,7 @@ impl PortForwardingProcessor { self.forwarded.insert(port); } - tunnel.get_port_uri(port).await + tunnel.get_port_uri(port) } } diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs index b9d93761364a2..cf01917ee1eca 100644 --- a/cli/src/tunnels/protocol.rs +++ b/cli/src/tunnels/protocol.rs @@ -214,6 +214,24 @@ pub struct ChallengeVerifyParams { pub response: String, } +pub mod forward_singleton { + use serde::{Deserialize, Serialize}; + + pub const METHOD_SET_PORTS: &str = "set_ports"; + + pub type PortList = Vec; + + #[derive(Serialize, Deserialize)] + pub struct SetPortsParams { + pub ports: PortList, + } + + #[derive(Serialize, Deserialize)] + pub struct SetPortsResponse { + pub port_format: Option, + } +} + pub mod singleton { use crate::log; use serde::{Deserialize, Serialize}; diff --git a/cli/src/tunnels/singleton_server.rs b/cli/src/tunnels/singleton_server.rs index 703c09f51201e..77cc701ca46d0 100644 --- a/cli/src/tunnels/singleton_server.rs +++ b/cli/src/tunnels/singleton_server.rs @@ -217,7 +217,7 @@ impl BroadcastLogSink { } } - fn get_brocaster(&self) -> broadcast::Sender> { + pub fn get_brocaster(&self) -> broadcast::Sender> { self.tx.clone() } diff --git a/cli/src/util/errors.rs b/cli/src/util/errors.rs index abd4ef2419311..c82e14acc8b02 100644 --- a/cli/src/util/errors.rs +++ b/cli/src/util/errors.rs @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - use crate::{ +use crate::{ constants::{APPLICATION_NAME, CONTROL_PORT, DOCUMENTATION_URL, QUALITYLESS_PRODUCT_NAME}, rpc::ResponseError, }; @@ -515,6 +515,8 @@ pub enum CodeError { AuthMismatch, #[error("keyring communication timed out after 5s")] KeyringTimeout, + #[error("no host is connected to the tunnel relay")] + NoTunnelEndpoint, } makeAnyError!( From 56557cbe35cfb2355f221966b6d636f0a312f744 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Mon, 24 Jul 2023 15:15:02 -0700 Subject: [PATCH 191/216] Move ExtHostAuthentication tests to integrationTest (#188723) ref https://github.com/microsoft/vscode/issues/149712 --- ...ntication.test.ts => extHostAuthentication.integrationTest.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/vs/workbench/api/test/browser/{extHostAuthentication.test.ts => extHostAuthentication.integrationTest.ts} (100%) diff --git a/src/vs/workbench/api/test/browser/extHostAuthentication.test.ts b/src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts similarity index 100% rename from src/vs/workbench/api/test/browser/extHostAuthentication.test.ts rename to src/vs/workbench/api/test/browser/extHostAuthentication.integrationTest.ts From 4aa6972baf75b2096e1d7ff23491648bcf2ed527 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 24 Jul 2023 15:23:55 -0700 Subject: [PATCH 192/216] No more top padding workaround for cell toolbar sticky scroll (#188724) * No more top padding workaround for cell toolbar sticky scroll * Update execution progress visibility chec. --- .../browser/contrib/execute/executionEditorProgress.ts | 4 +--- .../browser/view/cellParts/cellToolbarStickyScroll.ts | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress.ts b/src/vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress.ts index 312f780362447..ccda24744d23e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress.ts @@ -42,8 +42,6 @@ export class ExecutionEditorProgressController extends Disposable implements INo return; } - const scrollPadding = this._notebookEditor.notebookOptions.computeTopInsertToolbarHeight(this._notebookEditor.textModel.viewType); - const cellExecutions = this._notebookExecutionStateService.getCellExecutionsForNotebook(this._notebookEditor.textModel?.uri) .filter(exe => exe.state === NotebookCellExecutionState.Executing); const notebookExecution = this._notebookExecutionStateService.getExecution(this._notebookEditor.textModel?.uri); @@ -52,7 +50,7 @@ export class ExecutionEditorProgressController extends Disposable implements INo for (const cell of this._notebookEditor.getCellsInRange(range)) { if (cell.handle === exe.cellHandle) { const top = this._notebookEditor.getAbsoluteTopOfElement(cell); - if (this._notebookEditor.scrollTop < top + scrollPadding + 5) { + if (this._notebookEditor.scrollTop < top + 5) { return true; } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll.ts index 227be9967a271..f425ea10dcbfe 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbarStickyScroll.ts @@ -15,8 +15,7 @@ export function registerCellToolbarStickyScroll(notebookEditor: INotebookEditor, if (cell.isInputCollapsed) { element.style.top = ''; } else { - const scrollPadding = notebookEditor.notebookOptions.computeTopInsertToolbarHeight(notebookEditor.textModel?.viewType); - const scrollTop = notebookEditor.scrollTop - scrollPadding; + const scrollTop = notebookEditor.scrollTop; const elementTop = notebookEditor.getAbsoluteTopOfElement(cell); const diff = scrollTop - elementTop + extraOffset; const maxTop = cell.layoutInfo.editorHeight + cell.layoutInfo.statusBarHeight - 45; // subtract roughly the height of the execution order label plus padding From f991a1ad7b3b99b399c261ad3bd88b98f026dabf Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Mon, 24 Jul 2023 15:25:46 -0700 Subject: [PATCH 193/216] Fix restoring welcome message avatar icon. (#188722) Need to pull it from the real session, not just stale persisted data Fix microsoft/vscode-copilot#608 --- src/vs/workbench/contrib/chat/common/chatModel.ts | 15 +++++++++++++-- .../contrib/chat/common/chatServiceImpl.ts | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 215ab3a46b2d4..59f14f3cbf91a 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -427,7 +427,7 @@ export class ChatModel extends Disposable implements IChatModel { if (obj.welcomeMessage) { const content = obj.welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item); - this._welcomeMessage = new ChatWelcomeMessageModel(content, obj.responderUsername, obj.responderAvatarIconUri && URI.revive(obj.responderAvatarIconUri)); + this._welcomeMessage = new ChatWelcomeMessageModel(this, content); } return requests.map((raw: ISerializableChatRequestData) => { @@ -638,7 +638,18 @@ export class ChatWelcomeMessageModel implements IChatWelcomeMessageModel { return this._id; } - constructor(public readonly content: IChatWelcomeMessageContent[], public readonly username: string, public readonly avatarIconUri?: URI) { + constructor( + private readonly session: ChatModel, + public readonly content: IChatWelcomeMessageContent[], + ) { this._id = 'welcome_' + ChatWelcomeMessageModel.nextId++; } + + public get username(): string { + return this.session.responderUsername; + } + + public get avatarIconUri(): URI | undefined { + return this.session.responderAvatarIconUri; + } } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 1188245429428..f76145ce4c333 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -346,7 +346,7 @@ export class ChatService extends Disposable implements IChatService { const welcomeMessage = model.welcomeMessage ? undefined : withNullAsUndefined(await provider.provideWelcomeMessage?.(token)); const welcomeModel = welcomeMessage && new ChatWelcomeMessageModel( - welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item as IChatReplyFollowup[]), session.responderUsername, session.responderAvatarIconUri); + model, welcomeMessage.map(item => typeof item === 'string' ? new MarkdownString(item) : item as IChatReplyFollowup[])); model.initialize(session, welcomeModel); } From 87afa166d0a66366ecd381ca2916d9fdd34869cf Mon Sep 17 00:00:00 2001 From: Meghan Kulkarni Date: Mon, 24 Jul 2023 16:25:19 -0700 Subject: [PATCH 194/216] markdown link smart pasting (#188437) * making markdown link pasting feature smarter * update validateLink --- .../src/commands/insertResource.ts | 6 +- .../languageFeatures/copyFiles/copyFiles.ts | 2 +- .../languageFeatures/copyFiles/copyPaste.ts | 5 +- .../copyFiles/copyPasteLinks.ts | 29 +-- .../src/languageFeatures/copyFiles/shared.ts | 79 +++++--- .../src/test/markdownLink.test.ts | 169 +++++++++++++----- .../src/util/document.ts | 12 +- 7 files changed, 213 insertions(+), 89 deletions(-) diff --git a/extensions/markdown-language-features/src/commands/insertResource.ts b/extensions/markdown-language-features/src/commands/insertResource.ts index 8977d6ad39ed8..776899f0ac716 100644 --- a/extensions/markdown-language-features/src/commands/insertResource.ts +++ b/extensions/markdown-language-features/src/commands/insertResource.ts @@ -60,7 +60,7 @@ export class InsertImageFromWorkspace implements Command { } function getDefaultUri(document: vscode.TextDocument) { - const docUri = getParentDocumentUri(document); + const docUri = getParentDocumentUri(document.uri); if (docUri.scheme === Schemes.untitled) { return vscode.workspace.workspaceFolders?.[0]?.uri; } @@ -76,10 +76,10 @@ async function insertLink(activeEditor: vscode.TextEditor, selectedFiles: vscode await vscode.workspace.applyEdit(edit); } -function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0, smartPaste = false, isExternalLink = false) { +function createInsertLinkEdit(activeEditor: vscode.TextEditor, selectedFiles: vscode.Uri[], insertAsMedia: boolean, title = '', placeholderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false) { const snippetEdits = coalesce(activeEditor.selections.map((selection, i): vscode.SnippetTextEdit | undefined => { const selectionText = activeEditor.document.getText(selection); - const snippet = createUriListSnippet(activeEditor.document, selectedFiles, title, placeholderValue, smartPaste, isExternalLink, { + const snippet = createUriListSnippet(activeEditor.document, selectedFiles, title, placeholderValue, pasteAsMarkdownLink, isExternalLink, { insertAsMedia, placeholderText: selectionText, placeholderStartIndex: (i + 1) * selectedFiles.length, diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts index cb6a77e8c8d1e..c8f44fad2bccb 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyFiles.ts @@ -81,7 +81,7 @@ export class NewFilePathGenerator { } function getDesiredNewFilePath(config: CopyFileConfiguration, document: vscode.TextDocument, file: vscode.DataTransferFile): vscode.Uri { - const docUri = getParentDocumentUri(document); + const docUri = getParentDocumentUri(document.uri); for (const [rawGlob, rawDest] of Object.entries(config.destination)) { for (const glob of parseGlob(rawGlob)) { if (picomatch.isMatch(docUri.path, glob, { dot: true })) { diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts index 73839caeba261..727216c02bc92 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPaste.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { Schemes } from '../../util/schemes'; -import { createEditForMediaFiles, createEditAddingLinksForUriList, mediaMimes } from './shared'; +import { createEditForMediaFiles, createEditAddingLinksForUriList, mediaMimes, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink } from './shared'; class PasteEditProvider implements vscode.DocumentPasteEditProvider { @@ -32,7 +32,8 @@ class PasteEditProvider implements vscode.DocumentPasteEditProvider { if (!urlList) { return; } - const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, token, false); + const pasteUrlSetting = await getPasteUrlAsFormattedLinkSetting(document); + const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, false, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token); if (!pasteEdit) { return; } diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts index 61aa3a12067a6..e596e0c2c8229 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/copyPasteLinks.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { externalUriSchemes, createEditAddingLinksForUriList } from './shared'; +import { externalUriSchemes, createEditAddingLinksForUriList, getPasteUrlAsFormattedLinkSetting, PasteUrlAsFormattedLink } from './shared'; class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { readonly id = 'insertMarkdownLink'; @@ -14,8 +14,8 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { dataTransfer: vscode.DataTransfer, token: vscode.CancellationToken, ): Promise { - const enabled = vscode.workspace.getConfiguration('markdown', document).get<'always' | 'smart' | 'never'>('editor.pasteUrlAsFormattedLink.enabled', 'smart'); - if (enabled === 'never') { + const pasteUrlSetting = await getPasteUrlAsFormattedLinkSetting(document); + if (pasteUrlSetting === PasteUrlAsFormattedLink.Never) { return; } @@ -26,7 +26,7 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { return; } - if (!validateLink(urlList)) { + if (!validateLink(urlList).isValid) { return; } @@ -34,7 +34,8 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { if (!urlList) { return undefined; } - const pasteEdit = await createEditAddingLinksForUriList(document, ranges, urlList, token, true); + + const pasteEdit = await createEditAddingLinksForUriList(document, ranges, validateLink(urlList).cleanedUrlList, true, pasteUrlSetting === PasteUrlAsFormattedLink.Smart, token); if (!pasteEdit) { return; } @@ -45,12 +46,20 @@ class PasteLinkEditProvider implements vscode.DocumentPasteEditProvider { } } -export function validateLink(urlList: string): boolean { - const url = urlList?.split(/\s+/); - if (url.length > 1 || !externalUriSchemes.includes(vscode.Uri.parse(url[0]).scheme)) { - return false; +export function validateLink(urlList: string): { isValid: boolean; cleanedUrlList: string } { + let isValid = false; + let uri = undefined; + const trimmedUrlList = urlList?.trim(); //remove leading and trailing whitespace and new lines + try { + uri = vscode.Uri.parse(trimmedUrlList); + } catch (error) { + return { isValid: false, cleanedUrlList: urlList }; + } + const splitUrlList = trimmedUrlList.split(' ').filter(item => item !== ''); //split on spaces and remove empty strings + if (uri) { + isValid = splitUrlList.length === 1 && !splitUrlList[0].includes('\n') && externalUriSchemes.includes(vscode.Uri.parse(splitUrlList[0]).scheme); } - return true; + return { isValid, cleanedUrlList: splitUrlList[0] }; } export function registerLinkPasteSupport(selector: vscode.DocumentSelector,) { diff --git a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts index a6072fa5937d1..1a0d4f01ae737 100644 --- a/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts +++ b/extensions/markdown-language-features/src/languageFeatures/copyFiles/shared.ts @@ -21,7 +21,6 @@ export const externalUriSchemes = [ 'http', 'https', 'mailto', - 'ftp', ]; export const mediaFileExtensions = new Map([ @@ -64,14 +63,21 @@ export const mediaMimes = new Set([ ]); const smartPasteRegexes = [ - { regex: /\[.*\]\(.*\)/g, is_markdown_link: true }, // Is a Markdown Link - { regex: /!\[.*\]\(.*\)/g, is_markdown_link: true }, // Is a Markdown Image Link - { regex: /\[([^\]]*)\]\(([^)]*)\)/g, is_markdown_link: false }, // In a Markdown link - { regex: /^```[\s\S]*?```$/gm, is_markdown_link: false }, // In a fenced code block - { regex: /^\$\$[\s\S]*?\$\$$/gm, is_markdown_link: false }, // In a fenced math block - { regex: /`[^`]*`/g, is_markdown_link: false }, // In inline code - { regex: /\$[^$]*\$/g, is_markdown_link: false }, // In inline math + { regex: /\[.*\]\(.*\)/g, isMarkdownLink: true, isInline: true }, // Is a Markdown Link + { regex: /!\[.*\]\(.*\)/g, isMarkdownLink: true, isInline: true }, // Is a Markdown Image Link + { regex: /\[([^\]]*)\]\(([^)]*)\)/g, isMarkdownLink: false, isInline: true }, // In a Markdown link + { regex: /^```[\s\S]*?```$/gm, isMarkdownLink: false, isInline: false }, // In a fenced code block + { regex: /^\$\$[\s\S]*?\$\$$/gm, isMarkdownLink: false, isInline: false }, // In a fenced math block + { regex: /`[^`]*`/g, isMarkdownLink: false, isInline: true }, // In inline code + { regex: /\$[^$]*\$/g, isMarkdownLink: false, isInline: true }, // In inline math ]; + +export interface SkinnyTextDocument { + offsetAt(position: vscode.Position): number; + getText(range?: vscode.Range): string; + readonly uri: vscode.Uri; +} + export interface SmartPaste { /** @@ -86,22 +92,43 @@ export interface SmartPaste { } -export async function createEditAddingLinksForUriList(document: vscode.TextDocument, ranges: readonly vscode.Range[], urlList: string, token: vscode.CancellationToken, isExternalLink: boolean): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string } | undefined> { +export enum PasteUrlAsFormattedLink { + Always = 'always', + Smart = 'smart', + Never = 'never' +} + +export async function getPasteUrlAsFormattedLinkSetting(document: vscode.TextDocument): Promise { + return vscode.workspace.getConfiguration('markdown', document).get('editor.pasteUrlAsFormattedLink.enabled', PasteUrlAsFormattedLink.Smart); +} + +export async function createEditAddingLinksForUriList( + document: SkinnyTextDocument, + ranges: readonly vscode.Range[], + urlList: string, + isExternalLink: boolean, + useSmartPaste: boolean, + token: vscode.CancellationToken, +): Promise<{ additionalEdits: vscode.WorkspaceEdit; label: string } | undefined> { + if (ranges.length === 0) { return; } - const enabled = vscode.workspace.getConfiguration('markdown', document).get<'always' | 'smart' | 'never'>('editor.pasteUrlAsFormattedLink.enabled', 'always'); const edits: vscode.SnippetTextEdit[] = []; let placeHolderValue: number = ranges.length; let label: string = ''; let smartPaste = { pasteAsMarkdownLink: true, updateTitle: false }; - for (let i = 0; i < ranges.length; i++) { + for (const range of ranges) { + let title = document.getText(range); + const selectedRange: vscode.Range = new vscode.Range( + new vscode.Position(range.start.line, document.offsetAt(range.start)), + new vscode.Position(range.end.line, document.offsetAt(range.end)) + ); - let title = document.getText(ranges[i]); - if (enabled === 'smart') { - smartPaste = checkSmartPaste(document.getText(), document.offsetAt(ranges[i].start), document.offsetAt(ranges[i].end)); - title = smartPaste.updateTitle ? '' : document.getText(ranges[i]); + if (useSmartPaste) { + smartPaste = checkSmartPaste(document, selectedRange); + title = smartPaste.updateTitle ? '' : document.getText(range); } const snippet = await tryGetUriListSnippet(document, urlList, token, title, placeHolderValue, smartPaste.pasteAsMarkdownLink, isExternalLink); @@ -111,7 +138,7 @@ export async function createEditAddingLinksForUriList(document: vscode.TextDocum smartPaste.pasteAsMarkdownLink = true; placeHolderValue--; - edits.push(new vscode.SnippetTextEdit(ranges[i], snippet.snippet)); + edits.push(new vscode.SnippetTextEdit(range, snippet.snippet)); label = snippet.label; } @@ -121,15 +148,15 @@ export async function createEditAddingLinksForUriList(document: vscode.TextDocum return { additionalEdits, label }; } -export function checkSmartPaste(documentText: string, start: number, end: number): SmartPaste { +export function checkSmartPaste(document: SkinnyTextDocument, selectedRange: vscode.Range): SmartPaste { const SmartPaste: SmartPaste = { pasteAsMarkdownLink: true, updateTitle: false }; for (const regex of smartPasteRegexes) { - const matches = [...documentText.matchAll(regex.regex)]; + const matches = [...document.getText().matchAll(regex.regex)]; for (const match of matches) { if (match.index !== undefined) { - const useDefaultPaste = start > match.index && end < match.index + match[0].length; + const useDefaultPaste = selectedRange.start.character > match.index && selectedRange.end.character < match.index + match[0].length; SmartPaste.pasteAsMarkdownLink = !useDefaultPaste; - SmartPaste.updateTitle = regex.is_markdown_link && start === match.index && end === match.index + match[0].length; + SmartPaste.updateTitle = regex.isMarkdownLink && selectedRange.start.character === match.index && selectedRange.end.character === match.index + match[0].length; if (!SmartPaste.pasteAsMarkdownLink || SmartPaste.updateTitle) { return SmartPaste; } @@ -139,7 +166,7 @@ export function checkSmartPaste(documentText: string, start: number, end: number return SmartPaste; } -export async function tryGetUriListSnippet(document: vscode.TextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> { +export async function tryGetUriListSnippet(document: SkinnyTextDocument, urlList: String, token: vscode.CancellationToken, title = '', placeHolderValue = 0, pasteAsMarkdownLink = true, isExternalLink = false): Promise<{ snippet: vscode.SnippetString; label: string } | undefined> { if (token.isCancellationRequested) { return undefined; } @@ -171,7 +198,8 @@ interface UriListSnippetOptions { readonly separator?: string; } -export function createLinkSnippet( +export function appendToLinkSnippet( + snippet: vscode.SnippetString, pasteAsMarkdownLink: boolean, mdPath: string, title: string, @@ -180,7 +208,6 @@ export function createLinkSnippet( isExternalLink: boolean, ): vscode.SnippetString { const uriString = uri.toString(true); - const snippet = new vscode.SnippetString(); if (pasteAsMarkdownLink) { snippet.appendText('['); snippet.appendPlaceholder(escapeBrackets(title) || 'Title', placeholderValue); @@ -192,7 +219,7 @@ export function createLinkSnippet( } export function createUriListSnippet( - document: vscode.TextDocument, + document: SkinnyTextDocument, uris: readonly vscode.Uri[], title = '', placeholderValue = 0, @@ -204,7 +231,7 @@ export function createUriListSnippet( return; } - const documentDir = getDocumentDir(document); + const documentDir = getDocumentDir(document.uri); let snippet = new vscode.SnippetString(); let insertedLinkCount = 0; @@ -244,7 +271,7 @@ export function createUriListSnippet( } } else { insertedLinkCount++; - snippet = createLinkSnippet(pasteAsMarkdownLink, mdPath, title, uri, placeholderValue, isExternalLink); + snippet = appendToLinkSnippet(snippet, pasteAsMarkdownLink, mdPath, title, uri, placeholderValue, isExternalLink); } if (i < uris.length - 1 && uris.length > 1) { diff --git a/extensions/markdown-language-features/src/test/markdownLink.test.ts b/extensions/markdown-language-features/src/test/markdownLink.test.ts index 9999a61108fb6..8568f21d87b6f 100644 --- a/extensions/markdown-language-features/src/test/markdownLink.test.ts +++ b/extensions/markdown-language-features/src/test/markdownLink.test.ts @@ -5,128 +5,215 @@ import * as vscode from 'vscode'; import * as assert from 'assert'; import 'mocha'; -import { checkSmartPaste, createLinkSnippet } from '../languageFeatures/copyFiles/shared'; +import { SkinnyTextDocument, checkSmartPaste, createEditAddingLinksForUriList, appendToLinkSnippet } from '../languageFeatures/copyFiles/shared'; import { validateLink } from '../languageFeatures/copyFiles/copyPasteLinks'; - suite('createEditAddingLinksForUriList', () => { - // end to end test of checkSmartPaste & createLinkSnippet - // check multicursor (end to end) + test('Markdown Link Pasting should occur for a valid link (end to end)', async () => { + // createEditAddingLinksForUriList -> checkSmartPaste -> tryGetUriListSnippet -> createUriListSnippet -> createLinkSnippet + + const skinnyDocument: SkinnyTextDocument = { + uri: vscode.Uri.parse('file:///path/to/your/file'), + offsetAt: function () { return 0; }, + getText: function () { return 'hello world!'; }, + // lineAt: function (position: vscode.Position) { + // return { + // lineNumber: 0, + // text: 'hello world!', + // range: new vscode.Range(position, position), + // rangeIncludingLineBreak: new vscode.Range(position, position), + // firstNonWhitespaceCharacterIndex: 0, + // isEmptyOrWhitespace: false + // } as vscode.TextLine; + // } + }; + + const result = await createEditAddingLinksForUriList(skinnyDocument, [new vscode.Range(0, 0, 0, 12)], 'https://www.microsoft.com/', true, true, new vscode.CancellationTokenSource().token); + // need to check the actual result -> snippet value + assert.strictEqual(result?.label, 'Insert Markdown Link'); + }); suite('validateLink', () => { test('Markdown pasting should occur for a valid link.', () => { - const isLink = validateLink('https://www.microsoft.com'); + const isLink = validateLink('https://www.microsoft.com/').isValid; + assert.strictEqual(isLink, true); + }); + + test('Markdown pasting should occur for a valid link preceded by a new line.', () => { + const isLink = validateLink('\r\nhttps://www.microsoft.com/').isValid; + assert.strictEqual(isLink, true); + }); + + test('Markdown pasting should occur for a valid link followed by a new line.', () => { + const isLink = validateLink('https://www.microsoft.com/\r\n').isValid; assert.strictEqual(isLink, true); }); test('Markdown pasting should not occur for a valid hostname and invalid protool.', () => { - const isLink = validateLink('invalid://www.microsoft.com'); + const isLink = validateLink('invalid:www.microsoft.com').isValid; assert.strictEqual(isLink, false); }); test('Markdown pasting should not occur for plain text.', () => { - const isLink = validateLink('hello world!'); + const isLink = validateLink('hello world!').isValid; assert.strictEqual(isLink, false); }); test('Markdown pasting should not occur for plain text including a colon.', () => { - const isLink = validateLink('hello: world!'); + const isLink = validateLink('hello: world!').isValid; assert.strictEqual(isLink, false); }); test('Markdown pasting should not occur for plain text including a slashes.', () => { - const isLink = validateLink('hello//world!'); + const isLink = validateLink('helloworld!').isValid; assert.strictEqual(isLink, false); }); test('Markdown pasting should not occur for a link followed by text.', () => { - const isLink = validateLink('https://www.microsoft.com hello world!'); + const isLink = validateLink('https://www.microsoft.com/ hello world!').isValid; assert.strictEqual(isLink, false); }); - test('Markdown pasting should not occur for a link preceded or followed by spaces.', () => { - const isLink = validateLink(' https://www.microsoft.com '); - assert.strictEqual(isLink, false); + test('Markdown pasting should occur for a link preceded or followed by spaces.', () => { + const isLink = validateLink(' https://www.microsoft.com/ ').isValid; + assert.strictEqual(isLink, true); }); test('Markdown pasting should not occur for a link with an invalid scheme.', () => { - const isLink = validateLink('hello://www.microsoft.com'); + const isLink = validateLink('hello:www.microsoft.com').isValid; assert.strictEqual(isLink, false); }); test('Markdown pasting should not occur for multiple links being pasted.', () => { - const isLink = validateLink('https://www.microsoft.com\r\nhttps://www.microsoft.com\r\nhttps://www.microsoft.com\r\nhttps://www.microsoft.com'); + const isLink = validateLink('https://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/').isValid; assert.strictEqual(isLink, false); }); + test('Markdown pasting should not occur for multiple links with spaces being pasted.', () => { + const isLink = validateLink('https://www.microsoft.com/ \r\nhttps://www.microsoft.com/\r\nhttps://www.microsoft.com/\r\n hello \r\nhttps://www.microsoft.com/').isValid; + assert.strictEqual(isLink, false); + }); }); - suite('createLinkSnippet', () => { + suite('appendToLinkSnippet', () => { test('Should not create Markdown link snippet when pasteAsMarkdownLink is false', () => { - const uri = vscode.Uri.parse('https://www.microsoft.com'); - const snippet = createLinkSnippet(false, 'https://www.microsoft.com', '', uri, 0, true); + const uri = vscode.Uri.parse('https://www.microsoft.com/'); + const snippet = appendToLinkSnippet(new vscode.SnippetString(''), false, 'https:/www.microsoft.com', '', uri, 0, true); assert.strictEqual(snippet?.value, 'https://www.microsoft.com/'); }); test('Should create Markdown link snippet when pasteAsMarkdownLink is true', () => { - const uri = vscode.Uri.parse('https://www.microsoft.com'); - const snippet = createLinkSnippet(true, 'https://www.microsoft.com', '', uri, 0, true); + const uri = vscode.Uri.parse('https://www.microsoft.com/'); + const snippet = appendToLinkSnippet(new vscode.SnippetString(''), true, 'https:/www.microsoft.com', '', uri, 0, true); assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com/)'); }); test('Should use an unencoded URI string in Markdown link when passing in an external browser link', () => { - const uri = vscode.Uri.parse('https://www.microsoft.com'); - const snippet = createLinkSnippet(true, 'https://www.microsoft.com', '', uri, 0, true); + const uri = vscode.Uri.parse('https://www.microsoft.com/'); + const snippet = appendToLinkSnippet(new vscode.SnippetString(''), true, 'https:/www.microsoft.com', '', uri, 0, true); assert.strictEqual(snippet?.value, '[${0:Title}](https://www.microsoft.com/)'); }); }); - suite('pasteAsMarkdownLink', () => { + + suite('checkSmartPaste', () => { + + const skinnyDocument: SkinnyTextDocument = { + uri: vscode.Uri.file('/path/to/your/file'), + offsetAt: function () { return 0; }, + getText: function () { return 'hello world!'; }, + // lineAt: function (position: vscode.Position) { + // return { + // lineNumber: 0, + // text: 'hello world!', + // range: new vscode.Range(position, position), + // rangeIncludingLineBreak: new vscode.Range(position, position), + // firstNonWhitespaceCharacterIndex: 0, + // isEmptyOrWhitespace: false + // } as vscode.TextLine; + // } + }; test('Should evaluate pasteAsMarkdownLink as true for selected plain text', () => { - const smartPaste = checkSmartPaste("hello! world", 0, 5); + const range = new vscode.Range(0, 5, 0, 5); + const smartPaste = checkSmartPaste(skinnyDocument, range); assert.strictEqual(smartPaste.pasteAsMarkdownLink, true); }); + test('Should evaluate pasteAsMarkdownLink as false for pasting within a code block', () => { + skinnyDocument.getText = function () { return '```\r\n\r\n```'; }; + const range = new vscode.Range(0, 5, 0, 5); + const smartPaste = checkSmartPaste(skinnyDocument, range); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); + }); - test('Should evaluate updateTitle as true for pasting over a Markdown link', () => { - const smartPaste = checkSmartPaste("[a](bc)", 0, 7); - assert.strictEqual(smartPaste.updateTitle, true); + test('Should evaluate pasteAsMarkdownLink as false for pasting within a math block', () => { + skinnyDocument.getText = function () { return '$$$\r\n\r\n$$$'; }; + const range = new vscode.Range(0, 5, 0, 5); + const smartPaste = checkSmartPaste(skinnyDocument, range); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); }); - test('Should evaluate updateTitle as true for pasting over a Markdown image link', () => { - const smartPaste = checkSmartPaste("![a](bc)", 0, 8); + const linkSkinnyDoc: SkinnyTextDocument = { + uri: vscode.Uri.file('/path/to/your/file'), + offsetAt: function () { return 0; }, + getText: function () { return '[a](bcdef)'; }, + }; + + test('Should evaluate updateTitle as true for pasting over a Markdown link', () => { + const range = new vscode.Range(0, 0, 0, 10); + const smartPaste = checkSmartPaste(linkSkinnyDoc, range); assert.strictEqual(smartPaste.updateTitle, true); }); test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown link', () => { - const smartPaste = checkSmartPaste("[a](bcdef)", 4, 6); + const range = new vscode.Range(0, 4, 0, 6); + const smartPaste = checkSmartPaste(linkSkinnyDoc, range); + assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown image link', () => { - const smartPaste = checkSmartPaste('![alt](https://)', 7, 15); - assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); + + const imageLinkSkinnyDoc: SkinnyTextDocument = { + uri: vscode.Uri.file('/path/to/your/file'), + offsetAt: function () { return 0; }, + getText: function () { return '![a](bcdef)'; }, + }; + + test('Should evaluate updateTitle as true for pasting over a Markdown image link', () => { + const range = new vscode.Range(0, 0, 0, 11); + const smartPaste = checkSmartPaste(imageLinkSkinnyDoc, range); + assert.strictEqual(smartPaste.updateTitle, true); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within a code block', () => { - const smartPaste = checkSmartPaste('```\r\n\r\n```', 5, 5); + test('Should evaluate pasteAsMarkdownLink as false for pasting within a Markdown image link', () => { + const range = new vscode.Range(0, 5, 0, 10); + const smartPaste = checkSmartPaste(imageLinkSkinnyDoc, range); assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); }); + const inlineCodeSkinnyCode: SkinnyTextDocument = { + uri: vscode.Uri.file('/path/to/your/file'), + offsetAt: function () { return 0; }, + getText: function () { return '``'; }, + }; + test('Should evaluate pasteAsMarkdownLink as false for pasting within inline code', () => { - const smartPaste = checkSmartPaste('``', 1, 1); + const range = new vscode.Range(0, 1, 0, 1); + const smartPaste = checkSmartPaste(inlineCodeSkinnyCode, range); assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); }); - test('Should evaluate pasteAsMarkdownLink as false for pasting within a math block', () => { - const smartPaste = checkSmartPaste('$$$\r\n\r\n$$$', 5, 5); - assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); - }); + const inlineMathSkinnyDoc: SkinnyTextDocument = { + uri: vscode.Uri.file('/path/to/your/file'), + offsetAt: function () { return 0; }, + getText: function () { return '$$'; }, + }; test('Should evaluate pasteAsMarkdownLink as false for pasting within inline math', () => { - const smartPaste = checkSmartPaste('$$', 1, 1); + const range = new vscode.Range(0, 1, 0, 1); + const smartPaste = checkSmartPaste(inlineMathSkinnyDoc, range); assert.strictEqual(smartPaste.pasteAsMarkdownLink, false); }); }); diff --git a/extensions/markdown-language-features/src/util/document.ts b/extensions/markdown-language-features/src/util/document.ts index 9c192227ee3b3..856226a737692 100644 --- a/extensions/markdown-language-features/src/util/document.ts +++ b/extensions/markdown-language-features/src/util/document.ts @@ -7,24 +7,24 @@ import * as vscode from 'vscode'; import { Schemes } from './schemes'; import { Utils } from 'vscode-uri'; -export function getDocumentDir(document: vscode.TextDocument): vscode.Uri | undefined { - const docUri = getParentDocumentUri(document); +export function getDocumentDir(uri: vscode.Uri): vscode.Uri | undefined { + const docUri = getParentDocumentUri(uri); if (docUri.scheme === Schemes.untitled) { return vscode.workspace.workspaceFolders?.[0]?.uri; } return Utils.dirname(docUri); } -export function getParentDocumentUri(document: vscode.TextDocument): vscode.Uri { - if (document.uri.scheme === Schemes.notebookCell) { +export function getParentDocumentUri(uri: vscode.Uri): vscode.Uri { + if (uri.scheme === Schemes.notebookCell) { for (const notebook of vscode.workspace.notebookDocuments) { for (const cell of notebook.getCells()) { - if (cell.document === document) { + if (cell.document.uri.toString() === uri.toString()) { return notebook.uri; } } } } - return document.uri; + return uri; } From 261fcf2d57f33bdd5b5145c25df7829bb12d9d07 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 24 Jul 2023 16:46:01 -0700 Subject: [PATCH 195/216] Fix #188428. Top Insert Toolbar height workaround is no longer needed. (#188726) --- .../notebook/browser/view/notebookCellList.ts | 9 ++------ .../test/browser/notebookCellList.test.ts | 22 ++++++------------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 4e3eb4b10a5e4..0da4dd257425d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -135,8 +135,6 @@ export class NotebookCellList extends WorkbenchList implements ID private _isInLayout: boolean = false; - private readonly _viewContext: ViewContext; - private _webviewElement: FastDomNode | null = null; get webviewElement() { @@ -161,7 +159,6 @@ export class NotebookCellList extends WorkbenchList implements ID ) { super(listUser, container, delegate, renderers, options, contextKeyService, listService, configurationService, instantiationService); NOTEBOOK_CELL_LIST_FOCUSED.bindTo(this.contextKeyService).set(true); - this._viewContext = viewContext; this._previousFocusedElements = this.getFocusedElements(); this._localDisposableStore.add(this.onDidChangeFocus((e) => { this._previousFocusedElements.forEach(element => { @@ -826,9 +823,8 @@ export class NotebookCellList extends WorkbenchList implements ID const scrollHeight = this.view.scrollHeight; const scrollTop = this.getViewScrollTop(); const wrapperBottom = this.getViewScrollBottom(); - const topInsertToolbarHeight = this._viewContext.notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); - this.view.setScrollTop(scrollHeight - (wrapperBottom - scrollTop) - topInsertToolbarHeight); + this.view.setScrollTop(scrollHeight - (wrapperBottom - scrollTop)); } //#region Reveal Cell synchronously @@ -1226,8 +1222,7 @@ export class NotebookCellList extends WorkbenchList implements ID } getViewScrollBottom() { - const topInsertToolbarHeight = this._viewContext.notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType); - return this.getViewScrollTop() + this.view.renderHeight - topInsertToolbarHeight; + return this.getViewScrollTop() + this.view.renderHeight; } setCellEditorSelection(cell: ICellViewModel, range: Range) { diff --git a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts index b8f060ed210eb..0d8e843411ff0 100644 --- a/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts +++ b/src/vs/workbench/contrib/notebook/test/browser/notebookCellList.test.ts @@ -5,25 +5,17 @@ import * as assert from 'assert'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService'; -import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookOptions'; import { createNotebookCellList, setupInstantiationService, withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor'; suite('NotebookCellList', () => { let disposables: DisposableStore; let instantiationService: TestInstantiationService; - let notebookDefaultOptions: NotebookOptions; - let topInsertToolbarHeight: number; suiteSetup(() => { disposables = new DisposableStore(); instantiationService = setupInstantiationService(disposables); - notebookDefaultOptions = new NotebookOptions(instantiationService.get(IConfigurationService), instantiationService.get(INotebookExecutionStateService), false); - topInsertToolbarHeight = notebookDefaultOptions.computeTopInsertToolbarHeight(); - }); suiteTeardown(() => disposables.dispose()); @@ -51,7 +43,7 @@ suite('NotebookCellList', () => { cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell - cellList.layout(210 + topInsertToolbarHeight, 100); + cellList.layout(210, 100); // scroll a bit, scrollTop to bottom: 5, 215 cellList.scrollTop = 5; @@ -98,7 +90,7 @@ suite('NotebookCellList', () => { cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell - cellList.layout(210 + topInsertToolbarHeight, 100); + cellList.layout(210, 100); // init scrollTop and scrollBottom assert.deepStrictEqual(cellList.scrollTop, 0); @@ -144,7 +136,7 @@ suite('NotebookCellList', () => { cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell - cellList.layout(210 + topInsertToolbarHeight, 100); + cellList.layout(210, 100); // init scrollTop and scrollBottom assert.deepStrictEqual(cellList.scrollTop, 0); @@ -179,7 +171,7 @@ suite('NotebookCellList', () => { cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell - cellList.layout(210 + topInsertToolbarHeight, 100); + cellList.layout(210, 100); // init scrollTop and scrollBottom assert.deepStrictEqual(cellList.scrollTop, 0); @@ -221,7 +213,7 @@ suite('NotebookCellList', () => { cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell - cellList.layout(210 + topInsertToolbarHeight, 100); + cellList.layout(210, 100); // init scrollTop and scrollBottom assert.deepStrictEqual(cellList.scrollTop, 0); @@ -274,7 +266,7 @@ suite('NotebookCellList', () => { cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell - cellList.layout(210 + topInsertToolbarHeight, 100); + cellList.layout(210, 100); // init scrollTop and scrollBottom assert.deepStrictEqual(cellList.scrollTop, 0); @@ -310,7 +302,7 @@ suite('NotebookCellList', () => { cellList.attachViewModel(viewModel); // render height 210, it can render 3 full cells and 1 partial cell - cellList.layout(210 + topInsertToolbarHeight, 100); + cellList.layout(210, 100); // init scrollTop and scrollBottom assert.deepStrictEqual(cellList.scrollTop, 0); From f9db237986e4cf161b407e63853eba3bd0988f73 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Mon, 24 Jul 2023 16:51:52 -0700 Subject: [PATCH 196/216] chore: bump distro for api proposals (#188731) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 99c283ecba2b7..cf88e2f823357 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.81.0", - "distro": "c03b11187a93f4e9ea2653faf78704c25bc8d50c", + "distro": "b2c259af8019f3c29da8405ffd817c387bf1d303", "author": { "name": "Microsoft Corporation" }, From 885dba39a5f546c5077196a8c31b03d03a293b31 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Tue, 25 Jul 2023 04:06:31 +0200 Subject: [PATCH 197/216] fix #184756 (#188603) --- .../contrib/userDataProfile/browser/userDataProfilePreview.ts | 2 +- .../browser/userDataProfileImportExportService.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts index 095948fa26f55..4dd3d92a76173 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfilePreview.ts @@ -20,7 +20,7 @@ export class UserDataProfilePreviewContribution extends Disposable implements IW ) { super(); if (environmentService.options?.profileToPreview) { - userDataProfileImportExportService.importProfile(URI.revive(environmentService.options.profileToPreview), { mode: 'preview' }) + userDataProfileImportExportService.importProfile(URI.revive(environmentService.options.profileToPreview), { mode: 'both' }) .then(null, error => logService.error('Error while previewing the profile', getErrorMessage(error))); } } diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 4954225307845..35e72984a83d2 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -648,12 +648,12 @@ export class UserDataProfileImportExportService extends Disposable implements IU return that.progressService.withProgress({ location: IMPORT_PROFILE_PREVIEW_VIEW, }, async progress => { - disposable.dispose(); view.setMessage(undefined); const profileTemplate = await userDataProfileImportState.getProfileTemplateToImport(); if (profileTemplate.extensions) { await that.instantiationService.createInstance(ExtensionsResource).apply(profileTemplate.extensions, importedProfile); } + disposable.dispose(); }); } })); From fda972663af87b4d1dab87845e36c357738b44f9 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 25 Jul 2023 00:31:13 -0700 Subject: [PATCH 198/216] Update correct chat response part with content (#188744) --- src/vs/workbench/contrib/chat/common/chatModel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 59f14f3cbf91a..a9be887b287a5 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -117,7 +117,7 @@ class Response { this._updateRepr(quiet); } else { // Add a new resolving part - const responsePosition = this._responseParts.push({ string: new MarkdownString(responsePart.placeholder), resolving: true }); + const responsePosition = this._responseParts.push({ string: new MarkdownString(responsePart.placeholder), resolving: true }) - 1; this._updateRepr(quiet); responsePart.resolvedContent?.then((content) => { @@ -129,7 +129,7 @@ class Response { } private _updateRepr(quiet?: boolean) { - this._responseRepr = new MarkdownString(this._responseParts.map(r => r.string.value).join('\n')); + this._responseRepr = new MarkdownString(this._responseParts.map(r => r.string.value).join('\n\n')); if (!quiet) { this._onDidChangeValue.fire(); } From 4f161d4dac46c54c25cb8ecc958e0f87377db767 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 25 Jul 2023 15:54:26 +0200 Subject: [PATCH 199/216] Use soft assertion instead of hard error. (#188660) --- src/vs/editor/common/model/textModelTokens.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index e308aa9f78ad8..5d30184f6ef7f 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { assertFn } from 'vs/base/common/assert'; import { IdleDeadline, runWhenIdle } from 'vs/base/common/async'; import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { setTimeout0 } from 'vs/base/common/platform'; @@ -68,7 +69,7 @@ export class TokenizerWithStateStoreAndTextModel const r = safeTokenize(this._languageIdCodec, languageId, this.tokenizationSupport, text, true, lineToTokenize.startState); builder.add(lineToTokenize.lineNumber, r.tokens); - this!.store.setEndState(lineToTokenize.lineNumber, r.endState as TState); + this.store.setEndState(lineToTokenize.lineNumber, r.endState as TState); } } @@ -220,9 +221,9 @@ export class TrackingTokenizationStateStore { if (!state) { throw new BugIndicatingError('Cannot set null/undefined state'); } - if (lineNumber > 1 && !this.tokenizationStateStore.getEndState(lineNumber - 1)) { - throw new BugIndicatingError('Cannot set state before setting previous state'); - } + + // Cannot set state before setting previous state + assertFn(() => lineNumber === 1 || !!this.tokenizationStateStore.getEndState(lineNumber - 1)); while (true) { const min = this._invalidEndStatesLineNumbers.min; From e206774d7c1b256b47fec66ef2f4d4bd090435fa Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Tue, 25 Jul 2023 16:09:57 +0200 Subject: [PATCH 200/216] Bump distro --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cf88e2f823357..4148bf17533be 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.81.0", - "distro": "b2c259af8019f3c29da8405ffd817c387bf1d303", + "distro": "912d48f29160a34a4bfe3ebc36b01e5e47433efd", "author": { "name": "Microsoft Corporation" }, From 9c238d6de4da14b615d11ebd1950b39dbcd5210f Mon Sep 17 00:00:00 2001 From: Logan Ramos Date: Tue, 25 Jul 2023 07:35:44 -0700 Subject: [PATCH 201/216] Remove empty markdown cell from Endgame NB (#188790) Remove empty markdown cell from nb --- .vscode/notebooks/my-endgame.github-issues | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.vscode/notebooks/my-endgame.github-issues b/.vscode/notebooks/my-endgame.github-issues index fef2cbf66624b..92fddeef220c2 100644 --- a/.vscode/notebooks/my-endgame.github-issues +++ b/.vscode/notebooks/my-endgame.github-issues @@ -44,11 +44,6 @@ "language": "github-issues", "value": "$REPOS $MILESTONE $MINE is:issue is:closed reason:completed label:feature-request -label:verification-needed -label:on-testplan -label:verified -label:*duplicate" }, - { - "kind": 1, - "language": "markdown", - "value": "" - }, { "kind": 1, "language": "markdown", From 08235aac22b640c5e25b83bf25831a0f566e3fd1 Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 25 Jul 2023 08:34:53 -0700 Subject: [PATCH 202/216] Don't duplicate panel chat resolved content (#188802) --- src/vs/workbench/contrib/chat/common/chatModel.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index a9be887b287a5..28e304809a21b 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -122,7 +122,7 @@ class Response { responsePart.resolvedContent?.then((content) => { // Replace the resolving part's content with the resolved response - this._responseParts[responsePosition] = { string: new MarkdownString(content) }; + this._responseParts[responsePosition] = { string: new MarkdownString(content), resolving: true }; this._updateRepr(quiet); }); } From 4767096e4052e08e89e1f1867668cfc71585f317 Mon Sep 17 00:00:00 2001 From: Bhavya U Date: Tue, 25 Jul 2023 10:07:37 -0700 Subject: [PATCH 203/216] Save input value as IChatTransferData for restoring after transfer (#188543) * Store input value in IChatTransferData * Get widget by chat session id and other changes --- .../workbench/api/browser/mainThreadChat.ts | 9 ++++- src/vs/workbench/contrib/chat/browser/chat.ts | 2 + .../contrib/chat/browser/chatViewPane.ts | 13 ++++-- .../contrib/chat/browser/chatWidget.ts | 4 ++ .../contrib/chat/common/chatService.ts | 10 ++++- .../contrib/chat/common/chatServiceImpl.ts | 40 +++++++++++-------- 6 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index dac2ff338d5bb..19f04231d8eae 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -58,7 +58,14 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { } $transferChatSession(sessionId: number, toWorkspace: UriComponents): void { - this._chatService.transferChatSession(sessionId, URI.revive(toWorkspace)); + const sessionIdStr = this._chatService.getSessionId(sessionId); + if (!sessionIdStr) { + throw new Error(`Failed to transfer session. Unknown session provider ID: ${sessionId}`); + } + + const widget = this._chatWidgetService.getWidgetBySessionId(sessionIdStr); + const inputValue = widget?.inputEditor.getValue() ?? ''; + this._chatService.transferChatSession({ sessionId: sessionIdStr, inputValue: inputValue }, URI.revive(toWorkspace)); } async $registerChatProvider(handle: number, id: string): Promise { diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index a2ad6867efa8a..da8714f61d782 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -28,6 +28,8 @@ export interface IChatWidgetService { revealViewForProvider(providerId: string): Promise; getWidgetByInputUri(uri: URI): IChatWidget | undefined; + + getWidgetBySessionId(sessionId: string): IChatWidget | undefined; } diff --git a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts index 5f7a05375d9ca..410a8e46b3079 100644 --- a/src/vs/workbench/contrib/chat/browser/chatViewPane.ts +++ b/src/vs/workbench/contrib/chat/browser/chatViewPane.ts @@ -71,8 +71,8 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { private updateModel(model?: IChatModel | undefined): void { this.modelDisposables.clear(); - model = model ?? (this.chatService.transferredSessionId - ? this.chatService.getOrRestoreSession(this.chatService.transferredSessionId) + model = model ?? (this.chatService.transferredSessionData?.sessionId + ? this.chatService.getOrRestoreSession(this.chatService.transferredSessionData.sessionId) : this.chatService.startSession(this.chatViewOptions.providerId, CancellationToken.None)); if (!model) { throw new Error('Could not start chat session'); @@ -102,7 +102,14 @@ export class ChatViewPane extends ViewPane implements IChatViewPane { })); this._widget.render(parent); - const sessionId = this.chatService.transferredSessionId ?? this.viewState.sessionId; + let sessionId: string | undefined; + if (this.chatService.transferredSessionData) { + sessionId = this.chatService.transferredSessionData.sessionId; + this.viewState.inputValue = this.chatService.transferredSessionData.inputValue; + } else { + sessionId = this.viewState.sessionId; + } + const initialModel = sessionId ? this.chatService.getOrRestoreSession(sessionId) : undefined; this.updateModel(initialModel); } catch (e) { diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index 1f57d98b9589e..4586375277c44 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -574,6 +574,10 @@ export class ChatWidgetService implements IChatWidgetService { return this._widgets.find(w => isEqual(w.inputUri, uri)); } + getWidgetBySessionId(sessionId: string): ChatWidget | undefined { + return this._widgets.find(w => w.viewModel?.sessionId === sessionId); + } + async revealViewForProvider(providerId: string): Promise { const viewId = this.chatContributionService.getViewIdForProvider(providerId); const view = await this.viewsService.openView(viewId); diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 7bcc7212fc10d..511446cce93b4 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -189,11 +189,16 @@ export interface IChatProviderInfo { displayName: string; } +export interface IChatTransferredSessionData { + sessionId: string; + inputValue: string; +} + export const IChatService = createDecorator('IChatService'); export interface IChatService { _serviceBrand: undefined; - transferredSessionId: string | undefined; + transferredSessionData: IChatTransferredSessionData | undefined; onDidSubmitSlashCommand: Event<{ slashCommand: string; sessionId: string }>; registerProvider(provider: IChatProvider): IDisposable; @@ -201,6 +206,7 @@ export interface IChatService { getProviderInfos(): IChatProviderInfo[]; startSession(providerId: string, token: CancellationToken): ChatModel | undefined; getSession(sessionId: string): IChatModel | undefined; + getSessionId(sessionProviderId: number): string | undefined; getOrRestoreSession(sessionId: string): IChatModel | undefined; loadSessionFromContent(data: ISerializableChatData): IChatModel | undefined; @@ -221,5 +227,5 @@ export interface IChatService { onDidPerformUserAction: Event; notifyUserAction(event: IChatUserActionEvent): void; - transferChatSession(sessionProviderId: number, toWorkspace: URI): void; + transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void; } diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index f76145ce4c333..fad8eff20973b 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -22,7 +22,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { ChatModel, ChatWelcomeMessageModel, IChatModel, ISerializableChatData, ISerializableChatsData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatService, IChatUserActionEvent, ISlashCommand, ISlashCommandProvider, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatProgress, IChatProvider, IChatProviderInfo, IChatReplyFollowup, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, ISlashCommandProvider, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; const serializedChatKey = 'interactive.sessions'; @@ -32,6 +32,7 @@ interface IChatTransfer { toWorkspace: UriComponents; timestampInMilliseconds: number; chat: ISerializableChatData; + inputValue: string; } const SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS = 1000 * 60; @@ -127,9 +128,9 @@ export class ChatService extends Disposable implements IChatService { private readonly _persistedSessions: ISerializableChatsData; private readonly _hasProvider: IContextKey; - private _transferred: ISerializableChatData | undefined; - public get transferredSessionId(): string | undefined { - return this._transferred?.sessionId; + private _transferredSessionData: IChatTransferredSessionData | undefined; + public get transferredSessionData(): IChatTransferredSessionData | undefined { + return this._transferredSessionData; } private readonly _onDidPerformUserAction = this._register(new Emitter()); @@ -162,10 +163,12 @@ export class ChatService extends Disposable implements IChatService { this._persistedSessions = {}; } - this._transferred = this.getTransferredSession(); - if (this._transferred) { - this.trace('constructor', `Transferred session ${this._transferred.sessionId}`); - this._persistedSessions[this._transferred.sessionId] = this._transferred; + const transferredData = this.getTransferredSessionData(); + const transferredChat = transferredData?.chat; + if (transferredChat) { + this.trace('constructor', `Transferred session ${transferredChat.sessionId}`); + this._persistedSessions[transferredChat.sessionId] = transferredChat; + this._transferredSessionData = { sessionId: transferredChat.sessionId, inputValue: transferredData.inputValue }; } this._register(storageService.onWillSaveState(() => this.saveState())); @@ -252,7 +255,7 @@ export class ChatService extends Disposable implements IChatService { } } - private getTransferredSession(): ISerializableChatData | undefined { + private getTransferredSessionData(): IChatTransfer | undefined { const data: IChatTransfer[] = this.storageService.getObject(globalChatKey, StorageScope.PROFILE, []); const workspaceUri = this.workspaceContextService.getWorkspace().folders[0]?.uri; if (!workspaceUri) { @@ -266,7 +269,7 @@ export class ChatService extends Disposable implements IChatService { // Keep data that isn't for the current workspace and that hasn't expired yet const filtered = data.filter(item => URI.revive(item.toWorkspace).toString() !== thisWorkspace && (currentTime - item.timestampInMilliseconds < SESSION_TRANSFER_EXPIRATION_IN_MILLISECONDS)); this.storageService.store(globalChatKey, JSON.stringify(filtered), StorageScope.PROFILE, StorageTarget.MACHINE); - return transferred?.chat; + return transferred; } getHistory(): IChatDetail[] { @@ -355,6 +358,10 @@ export class ChatService extends Disposable implements IChatService { return this._sessionModels.get(sessionId); } + getSessionId(sessionProviderId: number): string | undefined { + return Iterable.find(this._sessionModels.values(), model => model.session?.id === sessionProviderId)?.sessionId; + } + getOrRestoreSession(sessionId: string): ChatModel | undefined { const model = this._sessionModels.get(sessionId); if (model) { @@ -366,8 +373,8 @@ export class ChatService extends Disposable implements IChatService { return undefined; } - if (sessionId === this.transferredSessionId) { - this._transferred = undefined; + if (sessionId === this.transferredSessionData?.sessionId) { + this._transferredSessionData = undefined; } return this._startSession(sessionData.providerId, sessionData, CancellationToken.None); @@ -671,17 +678,18 @@ export class ChatService extends Disposable implements IChatService { }); } - transferChatSession(sessionProviderId: number, toWorkspace: URI): void { - const model = Iterable.find(this._sessionModels.values(), model => model.session?.id === sessionProviderId); + transferChatSession(transferredSessionData: IChatTransferredSessionData, toWorkspace: URI): void { + const model = Iterable.find(this._sessionModels.values(), model => model.sessionId === transferredSessionData.sessionId); if (!model) { - throw new Error(`Failed to transfer session. Unknown session provider ID: ${sessionProviderId}`); + throw new Error(`Failed to transfer session. Unknown session ID: ${transferredSessionData.sessionId}`); } const existingRaw: IChatTransfer[] = this.storageService.getObject(globalChatKey, StorageScope.PROFILE, []); existingRaw.push({ chat: model.toJSON(), timestampInMilliseconds: Date.now(), - toWorkspace: toWorkspace + toWorkspace: toWorkspace, + inputValue: transferredSessionData.inputValue, }); this.storageService.store(globalChatKey, JSON.stringify(existingRaw), StorageScope.PROFILE, StorageTarget.MACHINE); From 66fc27c5e554d4f41c9914e283a9ced4bb909d15 Mon Sep 17 00:00:00 2001 From: Tyler James Leonhardt Date: Tue, 25 Jul 2023 11:58:19 -0700 Subject: [PATCH 204/216] Fix casing of string (#188843) Fixes https://github.com/microsoft/vscode/issues/188796 --- .../quickQuestionActions/multipleByScrollQuickQuestionAction.ts | 2 +- .../actions/quickQuestionActions/singleQuickQuestionAction.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts index 450e309742bf0..b7357eb1366ba 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts @@ -86,7 +86,7 @@ class BaseChatQuickQuestionMode implements IQuickQuestionMode { clearButton, { iconClass: ThemeIcon.asClassName(Codicon.commentDiscussion), - tooltip: localize('openInChat', "Open in chat view"), + tooltip: localize('openInChat', "Open In Chat View"), } ]; this._input.title = providerInfo.displayName; diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts index b436449a51a0a..606a67c79f0fb 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts @@ -61,7 +61,7 @@ class AskSingleQuickQuestionMode implements IQuickQuestionMode { // Setup toggle that will be used to open the chat view const openInChat = new Toggle({ - title: 'Open in chat view', + title: 'Open In Chat View', icon: Codicon.commentDiscussion, isChecked: false, inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder), From 15b8f1f9f6990d5535197ec41f0a112bf0e5dd31 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Tue, 25 Jul 2023 21:12:58 +0200 Subject: [PATCH 205/216] Fixes #188645 (#188818) Fixes https://github.com/microsoft/vscode/issues/188645 --- src/vs/editor/common/model/textModelTokens.ts | 83 +++++++++++-------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index 5d30184f6ef7f..0139d8b20558b 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { assertFn } from 'vs/base/common/assert'; import { IdleDeadline, runWhenIdle } from 'vs/base/common/async'; import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { setTimeout0 } from 'vs/base/common/platform'; @@ -205,8 +204,13 @@ export class TokenizerWithStateStoreAndTextModel } } +/** + * **Invariant:** + * If the text model is retokenized from line 1 to {@link getFirstInvalidEndStateLineNumber}() - 1, + * then the recomputed end state for line l will be equal to {@link getEndState}(l). + */ export class TrackingTokenizationStateStore { - private readonly tokenizationStateStore = new TokenizationStateStore(); + private readonly _tokenizationStateStore = new TokenizationStateStore(); private readonly _invalidEndStatesLineNumbers = new RangePriorityQueueImpl(); constructor(private lineCount: number) { @@ -214,27 +218,19 @@ export class TrackingTokenizationStateStore { } public getEndState(lineNumber: number): TState | null { - return this.tokenizationStateStore.getEndState(lineNumber); + return this._tokenizationStateStore.getEndState(lineNumber); } + /** + * @returns if the end state has changed. + */ public setEndState(lineNumber: number, state: TState): boolean { if (!state) { throw new BugIndicatingError('Cannot set null/undefined state'); } - // Cannot set state before setting previous state - assertFn(() => lineNumber === 1 || !!this.tokenizationStateStore.getEndState(lineNumber - 1)); - - while (true) { - const min = this._invalidEndStatesLineNumbers.min; - if (min === null || min > lineNumber) { - break; - } else { - this._invalidEndStatesLineNumbers.removeMin(); - } - } - - const r = this.tokenizationStateStore.setEndState(lineNumber, state); + this._invalidEndStatesLineNumbers.delete(lineNumber); + const r = this._tokenizationStateStore.setEndState(lineNumber, state); if (r && lineNumber < this.lineCount) { // because the state changed, we cannot trust the next state anymore and have to invalidate it. this._invalidEndStatesLineNumbers.addRange(new OffsetRange(lineNumber + 1, lineNumber + 2)); @@ -245,7 +241,7 @@ export class TrackingTokenizationStateStore { public acceptChange(range: LineRange, newLineCount: number): void { this.lineCount += newLineCount - range.length; - this.tokenizationStateStore.acceptChange(range, newLineCount); + this._tokenizationStateStore.acceptChange(range, newLineCount); this._invalidEndStatesLineNumbers.addRangeAndResize(new OffsetRange(range.startLineNumber, range.endLineNumberExclusive), newLineCount); } @@ -260,22 +256,16 @@ export class TrackingTokenizationStateStore { this._invalidEndStatesLineNumbers.addRange(new OffsetRange(range.startLineNumber, range.endLineNumberExclusive)); } - public getFirstInvalidEndStateLineNumber(): number | null { - return this._invalidEndStatesLineNumbers.min; - } + public getFirstInvalidEndStateLineNumber(): number | null { return this._invalidEndStatesLineNumbers.min; } public getFirstInvalidEndStateLineNumberOrMax(): number { - return this._invalidEndStatesLineNumbers.min || Number.MAX_SAFE_INTEGER; + return this.getFirstInvalidEndStateLineNumber() || Number.MAX_SAFE_INTEGER; } - public isTokenizationComplete(): boolean { - return this._invalidEndStatesLineNumbers.min === null; - } + public allStatesValid(): boolean { return this._invalidEndStatesLineNumbers.min === null; } public getStartState(lineNumber: number, initialState: TState): TState | null { - if (lineNumber === 1) { - return initialState; - } + if (lineNumber === 1) { return initialState; } return this.getEndState(lineNumber - 1); } @@ -284,7 +274,12 @@ export class TrackingTokenizationStateStore { if (lineNumber === null) { return null; } - return { lineNumber, startState: this.getStartState(lineNumber, initialState)! }; + const startState = this.getStartState(lineNumber, initialState); + if (!startState) { + throw new BugIndicatingError('Start state must be defined'); + } + + return { lineNumber, startState }; } } @@ -361,6 +356,26 @@ export class RangePriorityQueueImpl implements RangePriorityQueue { return range.start; } + public delete(value: number): void { + const idx = this._ranges.findIndex(r => r.contains(value)); + if (idx !== -1) { + const range = this._ranges[idx]; + if (range.start === value) { + if (range.endExclusive === value + 1) { + this._ranges.splice(idx, 1); + } else { + this._ranges[idx] = new OffsetRange(value + 1, range.endExclusive); + } + } else { + if (range.endExclusive === value + 1) { + this._ranges[idx] = new OffsetRange(range.start, value); + } else { + this._ranges.splice(idx, 1, new OffsetRange(range.start, value), new OffsetRange(value + 1, range.endExclusive)); + } + } + } + } + public addRange(range: OffsetRange): void { OffsetRange.addRange(range, this._ranges); } @@ -513,23 +528,23 @@ export class DefaultBackgroundTokenizer implements IBackgroundTokenizer { if (!this._tokenizerWithStateStore) { return false; } - return !this._tokenizerWithStateStore.store.isTokenizationComplete(); + return !this._tokenizerWithStateStore.store.allStatesValid(); } private _tokenizeOneInvalidLine(builder: ContiguousMultilineTokensBuilder): number { - if (!this._tokenizerWithStateStore || !this._hasLinesToTokenize()) { + const firstInvalidLine = this._tokenizerWithStateStore?.getFirstInvalidLine(); + if (!firstInvalidLine) { return this._tokenizerWithStateStore._textModel.getLineCount() + 1; } - const lineNumber = this._tokenizerWithStateStore.store.getFirstInvalidEndStateLineNumber()!; - this._tokenizerWithStateStore.updateTokensUntilLine(builder, lineNumber); - return lineNumber; + this._tokenizerWithStateStore.updateTokensUntilLine(builder, firstInvalidLine.lineNumber); + return firstInvalidLine.lineNumber; } public checkFinished(): void { if (this._isDisposed) { return; } - if (this._tokenizerWithStateStore.store.isTokenizationComplete()) { + if (this._tokenizerWithStateStore.store.allStatesValid()) { this._backgroundTokenStore.backgroundTokenizationFinished(); } } From 862fa13002d8018c328931ee1edccfd4ca76447d Mon Sep 17 00:00:00 2001 From: Joyce Er Date: Tue, 25 Jul 2023 16:03:17 -0700 Subject: [PATCH 206/216] Don't add auto-executing slash commands to input (#188866) --- .../contrib/chat/browser/actions/chatExecuteActions.ts | 3 ++- .../contrib/chat/browser/contrib/chatInputEditorContrib.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 10a24bd4b4b99..7f639db3aeba6 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -14,6 +14,7 @@ import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; export interface IChatExecuteActionContext { widget: IChatWidget; + inputValue?: string; } function isExecuteActionContext(thing: unknown): thing is IChatExecuteActionContext { @@ -48,7 +49,7 @@ export class SubmitAction extends Action2 { return; } - context.widget.acceptInput(); + context.widget.acceptInput(context.inputValue); } } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index da99185ded16e..5cf1bfd36e36c 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -229,12 +229,12 @@ class SlashCommandCompletions extends Disposable { const withSlash = `/${c.command}`; return { label: withSlash, - insertText: `${withSlash} `, + insertText: c.executeImmediately ? '' : `${withSlash} `, detail: c.detail, range: new Range(1, 1, 1, 1), sortText: c.sortText ?? c.command, kind: CompletionItemKind.Text, // The icons are disabled here anyway, - command: c.executeImmediately ? { id: SubmitAction.ID, title: withSlash, arguments: [{ widget }] } : undefined, + command: c.executeImmediately ? { id: SubmitAction.ID, title: withSlash, arguments: [{ widget, inputValue: withSlash }] } : undefined, }; }) }; From 5055118e96f96b6d135c651588dd4462fc3613e4 Mon Sep 17 00:00:00 2001 From: Benjamin Pasero Date: Wed, 26 Jul 2023 08:14:19 +0200 Subject: [PATCH 207/216] context menu - document API and make it typesafer (#188890) --- src/vs/base/browser/contextmenu.ts | 17 +++++++++++++++-- .../base/browser/ui/contextview/contextview.ts | 10 ++++++++-- .../platform/contextview/browser/contextView.ts | 11 ++++++++--- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/vs/base/browser/contextmenu.ts b/src/vs/base/browser/contextmenu.ts index 072eef79fde4f..3ab23f5bc9ff4 100644 --- a/src/vs/base/browser/contextmenu.ts +++ b/src/vs/base/browser/contextmenu.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { AnchorAlignment, AnchorAxisAlignment, IAnchor } from 'vs/base/browser/ui/contextview/contextview'; @@ -18,8 +18,21 @@ export interface IContextMenuEvent { readonly metaKey?: boolean; } +/** + * A specific context menu location to position the menu at. + * Uses some TypeScript type tricks to prevent allowing to + * pass in a `MouseEvent` and force people to use `StandardMouseEvent`. + */ +type ContextMenuLocation = OmitOptional & { getModifierState?: never }; + export interface IContextMenuDelegate { - getAnchor(): HTMLElement | IMouseEvent | OmitOptional; + /** + * The anchor where to position the context view. + * Use a `HTMLElement` to position the view at the element, + * a `StandardMouseEvent` to position it at the mouse position + * or an `ContextMenuLocation` to position it at a specific location. + */ + getAnchor(): HTMLElement | StandardMouseEvent | ContextMenuLocation; getActions(): readonly IAction[]; getCheckedActionsRepresentation?(action: IAction): 'radio' | 'checkbox'; getActionViewItem?(action: IAction, options: IActionViewItemOptions): IActionViewItem | undefined; diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 3def6b9e8ade8..7f94015706bca 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -5,7 +5,7 @@ import { BrowserFeatures } from 'vs/base/browser/canIUse'; import * as DOM from 'vs/base/browser/dom'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import { Range } from 'vs/base/common/range'; @@ -44,7 +44,13 @@ export const enum AnchorAxisAlignment { } export interface IDelegate { - getAnchor(): HTMLElement | IMouseEvent | IAnchor; + /** + * The anchor where to position the context view. + * Use a `HTMLElement` to position the view at the element, + * a `StandardMouseEvent` to position it at the mouse position + * or an `IAnchor` to position it at a specific location. + */ + getAnchor(): HTMLElement | StandardMouseEvent | IAnchor; render(container: HTMLElement): IDisposable | null; focus?(): void; layout?(): void; diff --git a/src/vs/platform/contextview/browser/contextView.ts b/src/vs/platform/contextview/browser/contextView.ts index 1ca41049391b8..10158c8d75cc4 100644 --- a/src/vs/platform/contextview/browser/contextView.ts +++ b/src/vs/platform/contextview/browser/contextView.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { AnchorAlignment, AnchorAxisAlignment, IAnchor, IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { OmitOptional } from 'vs/base/common/types'; import { IMenuActionOptions, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -31,7 +30,13 @@ export interface IContextViewDelegate { canRelayout?: boolean; // Default: true - getAnchor(): HTMLElement | IMouseEvent | OmitOptional; + /** + * The anchor where to position the context view. + * Use a `HTMLElement` to position the view at the element, + * a `StandardMouseEvent` to position it at the mouse position + * or an `IAnchor` to position it at a specific location. + */ + getAnchor(): HTMLElement | StandardMouseEvent | IAnchor; render(container: HTMLElement): IDisposable; onDOMEvent?(e: any, activeElement: HTMLElement): void; onHide?(data?: any): void; From 398e46ca68225d294c1ac2f530abada2ed86d24d Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 26 Jul 2023 08:54:48 +0200 Subject: [PATCH 208/216] Make content hover text use full possible width (#187597) * combinging the setting of max dimensions on the container and on the contents dom node * adding the css variable in order to be able to specify the max width of the content inside * cleaning the code * changing the name of the variable * cleaning the code * placing the hover max width variable into the others category of vscode-known-variables json file * defining a fall back value of 500 pixels * inlinging the code --------- Co-authored-by: Alexandru Dima --- .../lib/stylelint/vscode-known-variables.json | 1 + src/vs/base/browser/ui/hover/hover.css | 2 +- .../contrib/hover/browser/contentHover.ts | 27 +++++++++++-------- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 1b606f2ad8565..8929f43ee4a65 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -697,6 +697,7 @@ "--background-light", "--dropdown-padding-bottom", "--dropdown-padding-top", + "--hover-maxWidth", "--insert-border-color", "--last-tab-margin-right", "--monaco-monospace-font", diff --git a/src/vs/base/browser/ui/hover/hover.css b/src/vs/base/browser/ui/hover/hover.css index 0ce581993549b..f008173e71685 100644 --- a/src/vs/base/browser/ui/hover/hover.css +++ b/src/vs/base/browser/ui/hover/hover.css @@ -27,7 +27,7 @@ } .monaco-hover .markdown-hover > .hover-contents:not(.code-hover-contents) { - max-width: 500px; + max-width: var(--hover-maxWidth, 500px); word-wrap: break-word; } diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index cc0a0acd6a115..77bdf2a117031 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -556,12 +556,18 @@ export class ContentHoverWidget extends ResizableContentWidget { this._layoutContentWidget(); } - private _setContentsDomNodeMaxDimensions(width: number | string, height: number | string): void { + private static _applyMaxDimensions(container: HTMLElement, width: number | string, height: number | string) { const transformedWidth = typeof width === 'number' ? `${width}px` : width; const transformedHeight = typeof height === 'number' ? `${height}px` : height; - const contentsDomNode = this._hover.contentsDomNode; - contentsDomNode.style.maxWidth = transformedWidth; - contentsDomNode.style.maxHeight = transformedHeight; + container.style.maxWidth = transformedWidth; + container.style.maxHeight = transformedHeight; + } + + private _setHoverWidgetMaxDimensions(width: number | string, height: number | string): void { + ContentHoverWidget._applyMaxDimensions(this._hover.contentsDomNode, width, height); + ContentHoverWidget._applyMaxDimensions(this._hover.containerDomNode, width, height); + this._hover.containerDomNode.style.setProperty('--hover-maxWidth', typeof width === 'number' ? `${width}px` : width); + this._layoutContentWidget(); } private _hasHorizontalScrollbar(): boolean { @@ -579,7 +585,7 @@ export class ContentHoverWidget extends ResizableContentWidget { } private _setAdjustedHoverWidgetDimensions(size: dom.Dimension): void { - this._setContentsDomNodeMaxDimensions('none', 'none'); + this._setHoverWidgetMaxDimensions('none', 'none'); const width = size.width; const height = size.height; this._setHoverWidgetDimensions(width, height); @@ -594,6 +600,7 @@ export class ContentHoverWidget extends ResizableContentWidget { const maxRenderingWidth = this._findMaximumRenderingWidth() ?? Infinity; const maxRenderingHeight = this._findMaximumRenderingHeight() ?? Infinity; this._resizableNode.maxSize = new dom.Dimension(maxRenderingWidth, maxRenderingHeight); + this._setHoverWidgetMaxDimensions(maxRenderingWidth, maxRenderingHeight); } protected override _resize(size: dom.Dimension): void { @@ -670,13 +677,11 @@ export class ContentHoverWidget extends ResizableContentWidget { } private _layout(): void { - const height = Math.max(this._editor.getLayoutInfo().height / 4, 250, ContentHoverWidget._lastDimensions.height); - const width = Math.max(this._editor.getLayoutInfo().width * 0.66, 500, ContentHoverWidget._lastDimensions.width); const { fontSize, lineHeight } = this._editor.getOption(EditorOption.fontInfo); const contentsDomNode = this._hover.contentsDomNode; contentsDomNode.style.fontSize = `${fontSize}px`; contentsDomNode.style.lineHeight = `${lineHeight / fontSize}`; - this._setContentsDomNodeMaxDimensions(width, height); + this._updateMaxDimensions(); } private _updateFont(): void { @@ -696,17 +701,17 @@ export class ContentHoverWidget extends ResizableContentWidget { this._hover.onContentsChanged(); } - private _updateContentsDomNodeMaxDimensions() { + private _updateMaxDimensions() { const height = Math.max(this._editor.getLayoutInfo().height / 4, 250, ContentHoverWidget._lastDimensions.height); const width = Math.max(this._editor.getLayoutInfo().width * 0.66, 500, ContentHoverWidget._lastDimensions.width); - this._setContentsDomNodeMaxDimensions(width, height); + this._setHoverWidgetMaxDimensions(width, height); } private _render(node: DocumentFragment, hoverData: ContentHoverVisibleData) { this._setHoverData(hoverData); this._updateFont(); this._updateContent(node); - this._updateContentsDomNodeMaxDimensions(); + this._updateMaxDimensions(); this.onContentsChanged(); // Simply force a synchronous render on the editor // such that the widget does not really render with left = '0px' From cdf84c668586456228a270489ac744ad61c3b09a Mon Sep 17 00:00:00 2001 From: Aiday Marlen Kyzy Date: Wed, 26 Jul 2023 09:27:48 +0200 Subject: [PATCH 209/216] Setting minimum dimensions on the resizable hover when color picker is embedded (#187592) * setting the min width and text align also * after the merge, need to refactor the code to make it work correctly once again * adding code in order to limit size of the resizable hover --- .../contrib/colorPicker/browser/colorHoverParticipant.ts | 5 +++++ src/vs/editor/contrib/hover/browser/contentHover.ts | 5 +++++ src/vs/editor/contrib/hover/browser/hoverTypes.ts | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts b/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts index e321fae6dfd53..982ecbf505324 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorHoverParticipant.ts @@ -20,6 +20,7 @@ import { HoverAnchor, HoverAnchorType, IEditorHoverParticipant, IEditorHoverRend import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ISingleEditOperation } from 'vs/editor/common/core/editOperation'; import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry'; +import { Dimension } from 'vs/base/browser/dom'; export class ColorHover implements IHoverPart { @@ -181,6 +182,10 @@ function renderHoverParts(participant: ColorHoverParticipant | StandaloneColorPi if (hoverParts.length === 0 || !editor.hasModel()) { return Disposable.None; } + if (context.setMinimumDimensions) { + const minimumHeight = editor.getOption(EditorOption.lineHeight) + 8; + context.setMinimumDimensions(new Dimension(302, minimumHeight)); + } const disposables = new DisposableStore(); const colorHover = hoverParts[0]; diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 77bdf2a117031..d852003494c71 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -289,6 +289,7 @@ export class ContentHoverController extends Disposable { statusBar, setColorPicker: (widget) => colorPicker = widget, onContentsChanged: () => this._widget.onContentsChanged(), + setMinimumDimensions: (dimensions: dom.Dimension) => this._widget.setMinimumDimensions(dimensions), hide: () => this.hide() }; @@ -778,6 +779,10 @@ export class ContentHoverWidget extends ResizableContentWidget { this._setContentsDomNodeDimensions(dom.getTotalWidth(contentsDomNode), Math.min(maxRenderingHeight, height - SCROLLBAR_WIDTH)); } + public setMinimumDimensions(dimensions: dom.Dimension): void { + this._resizableNode.minSize = dimensions; + } + public onContentsChanged(): void { this._removeConstraintsRenderNormally(); const containerDomNode = this._hover.containerDomNode; diff --git a/src/vs/editor/contrib/hover/browser/hoverTypes.ts b/src/vs/editor/contrib/hover/browser/hoverTypes.ts index 93d4104b197dd..aadc1851a011f 100644 --- a/src/vs/editor/contrib/hover/browser/hoverTypes.ts +++ b/src/vs/editor/contrib/hover/browser/hoverTypes.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Dimension } from 'vs/base/browser/dom'; import { AsyncIterableObject } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -110,6 +111,10 @@ export interface IEditorHoverRenderContext { * The contents rendered inside the fragment have been changed, which means that the hover should relayout. */ onContentsChanged(): void; + /** + * Set the minimum dimensions of the resizable hover + */ + setMinimumDimensions?(dimensions: Dimension): void; /** * Hide the hover. */ From fc450f07265a5992e1024f1a9f1427c0b16de5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Moreno?= Date: Wed, 26 Jul 2023 09:45:14 +0100 Subject: [PATCH 210/216] improve grid auto sizing algorithm (#188898) * improve grid auto sizing algorithm fixes #187431 * update description --------- Co-authored-by: Benjamin Pasero --- src/vs/base/browser/ui/grid/grid.ts | 3 ++- src/vs/base/browser/ui/splitview/splitview.ts | 22 +++++++++++++------ .../browser/workbench.contribution.ts | 2 +- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index aee3cad23012e..2562b2fa153e4 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -455,7 +455,8 @@ export class Grid extends Disposable { if (sizing?.type === 'distribute') { gridViewSizing = GridViewSizing.Distribute; } else if (sizing?.type === 'auto') { - gridViewSizing = GridViewSizing.Auto(0); + const index = location[location.length - 1]; + gridViewSizing = GridViewSizing.Auto(index === 0 ? 1 : index - 1); } this.gridview.removeView(location, gridViewSizing); diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index a4edaa6a3d900..b822db43751e5 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -259,7 +259,7 @@ abstract class ViewItem { constructor( protected container: HTMLElement, - private view: IView, + readonly view: IView, size: ViewItemSize, private disposable: IDisposable ) { @@ -280,9 +280,8 @@ abstract class ViewItem { abstract layoutContainer(offset: number): void; - dispose(): IView { + dispose(): void { this.disposable.dispose(); - return this.view; } } @@ -662,13 +661,20 @@ export class SplitView extends Disposable { if (this.areViewsDistributed()) { sizing = { type: 'distribute' }; } else { - sizing = undefined; + sizing = { type: 'split', index: sizing.index }; } } + // Save referene view, in case of `split` sizing + const referenceViewItem = sizing?.type === 'split' ? this.viewItems[sizing.index] : undefined; + // Remove view - const viewItem = this.viewItems.splice(index, 1)[0]; - const view = viewItem.dispose(); + const viewItemToRemove = this.viewItems.splice(index, 1)[0]; + + // Resize reference view, in case of `split` sizing + if (referenceViewItem) { + referenceViewItem.size += viewItemToRemove.size; + } // Remove sash if (this.viewItems.length >= 1) { @@ -684,7 +690,9 @@ export class SplitView extends Disposable { this.distributeViewSizes(); } - return view; + const result = viewItemToRemove.view; + viewItemToRemove.dispose(); + return result; } /** diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 98d32c500de01..c88db4f5dafad 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -181,7 +181,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'enum': ['auto', 'distribute', 'split'], 'default': 'auto', 'enumDescriptions': [ - localize('workbench.editor.splitSizingAuto', "Splits all the editor groups to equal parts unless a part has been changed in size."), + localize('workbench.editor.splitSizingAuto', "Splits the active editor group to equal parts, unless all editor groups are already in equal parts. In that case, splits all the editor groups to equal parts."), localize('workbench.editor.splitSizingDistribute', "Splits all the editor groups to equal parts."), localize('workbench.editor.splitSizingSplit', "Splits the active editor group to equal parts.") ], From 37eb06d64e006e000080d2ca776956c77a31a8da Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Wed, 26 Jul 2023 12:45:51 +0200 Subject: [PATCH 211/216] Removes "Show Moves" Menu Entry, as the move detection feature is still very experimental and shouldn't block the diff editor v2 release. (#188906) --- .../diffEditorWidget2/diffEditorWidget2.contribution.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts index 2e9aaa5590257..a8ef3758feed4 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget2/diffEditorWidget2.contribution.ts @@ -80,6 +80,10 @@ export class ToggleShowMovedCodeBlocks extends Action2 { registerAction2(ToggleShowMovedCodeBlocks); +/* +TODO@hediet add this back once move detection is more polished. +Users can still enable this via settings.json (config.diffEditor.experimental.showMoves). + MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: new ToggleShowMovedCodeBlocks().desc.id, @@ -91,6 +95,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { group: '1_diff', when: ContextKeyEqualsExpr.create('diffEditorVersion', 2) }); +*/ const diffEditorCategory: ILocalizedString = { value: localize('diffEditor', 'Diff Editor'), From ed952bea41c7c535c00332477361a4df83936afd Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 26 Jul 2023 16:23:45 +0530 Subject: [PATCH 212/216] fix #188801 (#188900) * fix #188801 * fix casing --- src/vs/workbench/services/issue/browser/issueTroubleshoot.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts b/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts index d57f5b3d0e7df..fe0e5a9f37ed5 100644 --- a/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts +++ b/src/vs/workbench/services/issue/browser/issueTroubleshoot.ts @@ -204,11 +204,11 @@ class TroubleshootIssueService extends Disposable implements ITroubleshootIssueS private askToReproduceIssue(message: string): Promise { return new Promise((c, e) => { const goodPrompt: IPromptChoice = { - label: localize('I cannot reproduce', "I can't reproduce"), + label: localize('I cannot reproduce', "I Can't Reproduce"), run: () => c('good') }; const badPrompt: IPromptChoice = { - label: localize('This is Bad', "I can reproduce"), + label: localize('This is Bad', "I Can Reproduce"), run: () => c('bad') }; const stop: IPromptChoice = { From 1b0c1fa5b6c506de3ec67e491772efc2642fa517 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 26 Jul 2023 17:45:40 +0530 Subject: [PATCH 213/216] fix #188794 (#188908) --- .../workbench/contrib/preferences/browser/settingsTree.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts index ff00480a0ee12..684dea2a3ef8c 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -2526,8 +2526,11 @@ class ApplySettingToAllProfilesAction extends Action { } const newValue = distinct(value); - await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL); - if (!this.checked) { + if (this.checked) { + await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).userLocal?.value, ConfigurationTarget.USER_LOCAL); + await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL); + } else { + await this.configService.updateValue(APPLY_ALL_PROFILES_SETTING, newValue.length ? newValue : undefined, ConfigurationTarget.USER_LOCAL); await this.configService.updateValue(this.setting.key, this.configService.inspect(this.setting.key).userLocal?.value, ConfigurationTarget.USER_LOCAL); } } From 7beda74cc8e9818bee94dc42088e9018b7597faa Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 26 Jul 2023 17:45:57 +0530 Subject: [PATCH 214/216] fix #188817 (#188909) --- .../workbench/contrib/userDataProfile/browser/userDataProfile.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts index 60052d90c5566..4ff1c808b9e4a 100644 --- a/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/workbench/contrib/userDataProfile/browser/userDataProfile.ts @@ -192,6 +192,7 @@ export class UserDataProfilesWorkbenchContribution extends Disposable implements value: localize('edit profile', "Edit Profile..."), original: `Edit Profile...` }, + f1: true, menu: [ { id: ProfilesMenu, From 5e3392e11d19f01ba1de86c608498110b875cb8f Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 26 Jul 2023 17:46:13 +0530 Subject: [PATCH 215/216] fix #188897 (#188910) --- .../browser/userDataProfileImportExportService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts index 35e72984a83d2..1cb00b4da7c27 100644 --- a/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts +++ b/src/vs/workbench/services/userDataProfile/browser/userDataProfileImportExportService.ts @@ -481,7 +481,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU sticky: true, }, async progress => { const reportProgress = (message: string) => progress.report({ message: localize('create from profile', "Create Profile: {0}", message) }); - const profile = await this.doCreateProfile(profileTemplate, false, false, { useDefaultFlags: this.userDataProfileService.currentProfile.useDefaultFlags }, reportProgress); + const profile = await this.doCreateProfile(profileTemplate, false, false, { useDefaultFlags: options?.useDefaultFlags }, reportProgress); if (profile) { reportProgress(localize('progress extensions', "Applying Extensions...")); await this.instantiationService.createInstance(ExtensionsResource).copy(this.userDataProfileService.currentProfile, profile, false); From 0b589305f8b3ffff89635f63b89f30a4664da4e7 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 26 Jul 2023 17:46:31 +0530 Subject: [PATCH 216/216] fix #188582 (#188917) --- .../common/extensionsProfileScannerService.ts | 4 ++-- .../extensionManagement/node/extensionManagementService.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts index 30e749ac312f6..d3c27750a2cea 100644 --- a/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts +++ b/src/vs/platform/extensionManagement/common/extensionsProfileScannerService.ts @@ -127,8 +127,8 @@ export abstract class AbstractExtensionsProfileScannerService extends Disposable await this.withProfileExtensions(profileLocation, profileExtensions => { const result: IScannedProfileExtension[] = []; for (const extension of profileExtensions) { - if (extensions.some(([e]) => areSameExtensions(e.identifier, extension.identifier) && e.manifest.version !== extension.version)) { - // Remove the existing extension with different version + if (extensions.some(([e]) => areSameExtensions(e.identifier, extension.identifier))) { + // Remove the existing extension extensionsToRemove.push(extension); } else { result.push(extension); diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 5852d66a9c42e..78cbb3b772479 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -570,8 +570,10 @@ export class ExtensionsScanner extends Disposable { await this.withUninstalledExtensions(uninstalled => delete uninstalled[extensionKey.toString()]); } - removeExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { - return this.deleteExtensionFromLocation(extension.identifier.id, extension.location, type); + async removeExtension(extension: ILocalExtension | IScannedExtension, type: string): Promise { + if (this.uriIdentityService.extUri.isEqualOrParent(this.extensionsScannerService.userExtensionsLocation, extension.location)) { + return this.deleteExtensionFromLocation(extension.identifier.id, extension.location, type); + } } async removeUninstalledExtension(extension: ILocalExtension | IScannedExtension): Promise {