diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 794d371126a2e..ac3ac7c9ae317 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -123,37 +123,35 @@ ${this.getInfos()} private getInfos(): string { let info = ''; - if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (!this._data.fileOnMarketplace && this._data.includeExtensionData && this._data.extensionData) { + const isBugOrPerformanceIssue = this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue; + const isNotOnMarketPlace = !this._data.fileOnMarketplace; + + if (isBugOrPerformanceIssue && isNotOnMarketPlace) { + if (this._data.includeExtensionData && this._data.extensionData) { info += this.getExtensionData(); } - } - if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (!this._data.fileOnMarketplace && this._data.includeSystemInfo && this._data.systemInfo) { + if (this._data.includeSystemInfo && this._data.systemInfo) { info += this.generateSystemInfoMd(); } } - if (this._data.issueType === IssueType.PerformanceIssue) { - - if (!this._data.fileOnMarketplace && this._data.includeProcessInfo) { + if (this._data.issueType === IssueType.PerformanceIssue && isNotOnMarketPlace) { + if (this._data.includeProcessInfo) { info += this.generateProcessInfoMd(); } - if (!this._data.fileOnMarketplace && this._data.includeWorkspaceInfo) { + if (this._data.includeWorkspaceInfo) { info += this.generateWorkspaceInfoMd(); } } - if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (!this._data.fileOnMarketplace && !this._data.fileOnExtension && this._data.includeExtensions) { + if (isBugOrPerformanceIssue && isNotOnMarketPlace) { + if (!this._data.fileOnExtension && this._data.includeExtensions) { info += this.generateExtensionsMd(); } - } - if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (!this._data.fileOnMarketplace && this._data.includeExperiments && this._data.experimentInfo) { + if (this._data.includeExperiments && this._data.experimentInfo) { info += this.generateExperimentsInfoMd(); } } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index afb7a654c0999..6abf883c60b14 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -277,6 +277,15 @@ export class IssueReporter extends Disposable { } } + private async sendReporterStatus(extension: IssueReporterExtensionData): Promise { + try { + const data = await this.issueMainService.$sendReporterStatus(extension.id, extension.name); + return data; + } catch (e) { + throw e; + } + } + private setEventHandlers(): void { this.addEventListener('issue-type', 'change', (event: Event) => { const issueType = parseInt((event.target).value); @@ -1129,8 +1138,19 @@ export class IssueReporter extends Disposable { const selectedExtensionId = (e.target).value; const extensions = this.issueReporterModel.getData().allExtensions; const matches = extensions.filter(extension => extension.id === selectedExtensionId); + const foundHandler = matches[0].hasIssueUriRequestHandler; + const foundProvider = matches[0].hasIssueDataProviders; if (matches.length) { this.issueReporterModel.update({ selectedExtension: matches[0] }); + + // if extension is not activated and has a provider/handler w + if (!matches[0].hasIssueDataProviders && !matches[0].hasIssueUriRequestHandler) { + const toActivate = await this.sendReporterStatus(matches[0]); + matches[0].hasIssueDataProviders = toActivate[0]; + matches[0].hasIssueUriRequestHandler = toActivate[1]; + this.renderBlocks(); + } + if (matches[0].hasIssueUriRequestHandler) { this.updateIssueReporterUri(matches[0]); } else if (matches[0].hasIssueDataProviders) { @@ -1162,6 +1182,8 @@ export class IssueReporter extends Disposable { this.clearSearchResults(); this.validateSelectedExtension(); } + matches[0].hasIssueDataProviders = foundProvider; + matches[0].hasIssueUriRequestHandler = foundHandler; this.updatePreviewButtonState(); this.renderBlocks(); }); diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 8c4edb4f4c390..dd600e164641d 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -133,5 +133,6 @@ export interface IIssueMainService { $getIssueReporterUri(extensionId: string): Promise; $getIssueReporterData(extensionId: string): Promise; $getIssueReporterTemplate(extensionId: string): Promise; + $sendReporterStatus(extensionId: string, extensionName: string): Promise; $closeReporter(): Promise; } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index b69bd4d2ee276..dd057929a624b 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -442,6 +442,26 @@ export class IssueMainService implements IIssueMainService { }); } + async $sendReporterStatus(extensionId: string, extensionName: string): Promise { + const window = this.issueReporterWindowCheck(); + const replyChannel = `vscode:sendReporterStatus`; + return Promises.withAsyncBody(async (resolve) => { + const cts = new CancellationTokenSource(); + const result = [false, false]; + window.sendWhenReady('vscode:sendReporterStatus', cts.token, { replyChannel, extensionId, extensionName }); + validatedIpcMain.on('vscode:receiveReporterStatus', (_: unknown, data: boolean[]) => { + resolve(data); + }); + try { + await timeout(2000); + cts.cancel(); + resolve(result); + } finally { + validatedIpcMain.removeHandler(replyChannel); + } + }); + } + async $closeReporter(): Promise { this.issueReporterWindow?.close(); } diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index e1308458a4007..a86be82f9fb2c 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -915,6 +915,10 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return result; } + public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { + return this._activateById(extensionId, reason); + } + public activationEventIsDone(activationEvent: string): boolean { if (!this._installedExtensionsReady.isOpen()) { return false; @@ -1331,7 +1335,7 @@ export class ExtensionHostCrashTracker { * This can run correctly only on the renderer process because that is the only place * where all extension points and all implicit activation events generators are known. */ -class ImplicitActivationAwareReader implements IActivationEventsReader { +export class ImplicitActivationAwareReader implements IActivationEventsReader { public readActivationEvents(extensionDescription: IExtensionDescription): string[] { return ImplicitActivationEvents.readActivationEvents(extensionDescription); } diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 65d59f165b580..7b4fbd85d692c 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -425,6 +425,12 @@ export interface IExtensionService { */ activateByEvent(activationEvent: string, activationKind?: ActivationKind): Promise; + /** + * Send an activation ID and activate interested extensions. + * + */ + activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; + /** * Determine if `activateByEvent(activationEvent)` has resolved already. * @@ -543,6 +549,7 @@ export class NullExtensionService implements IExtensionService { onWillStop: Event = Event.None; readonly extensions = []; activateByEvent(_activationEvent: string): Promise { return Promise.resolve(undefined); } + activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { return Promise.resolve(undefined); } activationEventIsDone(_activationEvent: string): boolean { return false; } whenInstalledExtensionsRegistered(): Promise { return Promise.resolve(true); } getExtension() { return Promise.resolve(undefined); } diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index 743f0a688d004..d5c45771c2a95 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -373,6 +373,11 @@ export const schema: IJSONSchema = { body: 'onWalkthrough:${1:walkthroughID}', description: nls.localize('vscode.extension.activationEvents.onWalkthrough', 'An activation event emitted when a specified walkthrough is opened.'), }, + { + label: 'onIssueReporterOpened', + body: 'onIssueReporterOpened:${1:issueReporterID}', + description: nls.localize('vscode.extension.activationEvents.onIssueReporterOpened', 'An activation event emitted when the issue reporter is opened.'), + }, { label: '*', description: nls.localize('vscode.extension.activationEvents.star', 'An activation event emitted on VS Code startup. To ensure a great end user experience, please use this activation event in your extension only when no other activation events combination works in your use-case.'), diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index 76b55d63acd70..a800454ad255a 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -10,7 +10,7 @@ import { platform } from 'vs/base/common/process'; import { URI } from 'vs/base/common/uri'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IIssueMainService, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -22,15 +22,17 @@ import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/co import { IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IWorkbenchExtensionEnablementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; +import { ImplicitActivationAwareReader } from 'vs/workbench/services/extensions/common/abstractExtensionService'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; import { IIssueDataProvider, IIssueUriRequestHandler, IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; -// eslint-disable-next-line local/code-import-patterns export class NativeIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; private readonly _handlers = new Map(); private readonly _providers = new Map(); + private readonly _activationEventReader = new ImplicitActivationAwareReader(); constructor( @IIssueMainService private readonly issueMainService: IIssueMainService, @@ -43,6 +45,7 @@ export class NativeIssueService implements IWorkbenchIssueService { @IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService, @IAuthenticationService private readonly authenticationService: IAuthenticationService, @IIntegrityService private readonly integrityService: IIntegrityService, + @IExtensionService private readonly extensionService: IExtensionService, ) { ipcRenderer.on('vscode:triggerIssueUriRequestHandler', async (event: unknown, request: { replyChannel: string; extensionId: string }) => { const result = await this.getIssueReporterUri(request.extensionId, CancellationToken.None); @@ -56,6 +59,23 @@ export class NativeIssueService implements IWorkbenchIssueService { const result = await this.getIssueTemplate(request.extensionId, CancellationToken.None); ipcRenderer.send(request.replyChannel, result); }); + ipcRenderer.on('vscode:sendReporterStatus', async (event, arg) => { + const extensionId = arg.extensionId; + const extension = await this.extensionService.getExtension(extensionId); + if (extension) { + // const extensionDescription = this._registry.getExtensionDescription(extension.identifier); + const activationEvents = this._activationEventReader.readActivationEvents(extension); + for (const activationEvent of activationEvents) { + if (activationEvent === 'onIssueReporterOpened') { + const eventName = `onIssueReporterOpened:${ExtensionIdentifier.toKey(extension.identifier)}`; + await this.extensionService.activateById(extension.identifier, { startup: false, extensionId: extension.identifier, activationEvent: eventName }); + break; + } + } + } + const result = [this._providers.has(extensionId.toLowerCase()), this._handlers.has(extensionId.toLowerCase())]; + ipcRenderer.send('vscode:receiveReporterStatus', result); + }); } async openReporter(dataOverrides: Partial = {}): Promise {