<commit_before>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import * as arrays from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
import { Event, chain, anyEvent, debounceEvent } from 'vs/base/common/event';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, IExtensionIdentifier, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { Severity, INotificationService } from 'vs/platform/notification/common/notification';

export interface IExtensionStatus {
	identifier: IExtensionIdentifier;
	local: ILocalExtension;
	globallyEnabled: boolean;
}

export class KeymapExtensions implements IWorkbenchContribution {

	private disposables: IDisposable[] = [];

	constructor(
		@IInstantiationService private instantiationService: IInstantiationService,
		@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
		@IExtensionTipsService private tipsService: IExtensionTipsService,
		@ILifecycleService lifecycleService: ILifecycleService,
		@INotificationService private notificationService: INotificationService,
		@ITelemetryService private telemetryService: ITelemetryService,
	) {
		this.disposables.push(
			lifecycleService.onShutdown(() => this.dispose()),
			instantiationService.invokeFunction(onExtensionChanged)((identifiers => {
				TPromise.join(identifiers.map(identifier => this.checkForOtherKeymaps(identifier)))
					.then(null, onUnexpectedError);
			}))
		);
	}

	private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): TPromise<void> {
		return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
			const keymaps = extensions.filter(extension => isKeymapExtension(this.tipsService, extension));
			const extension = arrays.first(keymaps, extension => stripVersion(extension.identifier.id) === extensionIdentifier.id);
			if (extension && extension.globallyEnabled) {
				const otherKeymaps = keymaps.filter(extension => stripVersion(extension.identifier.id) !== extensionIdentifier.id && extension.globallyEnabled);
				if (otherKeymaps.length) {
					return this.promptForDisablingOtherKeymaps(extension, otherKeymaps);
				}
			}
			return undefined;
		});
	}

	private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): void {
		const onPrompt = (confirmed: boolean) => {
			const telemetryData: { [key: string]: any; } = {
				newKeymap: newKeymap.identifier,
				oldKeymaps: oldKeymaps.map(k => k.identifier),
				confirmed
			};
			/* __GDPR__
				"disableOtherKeymaps" : {
					"newKeymap": { "${inline}": [ "${ExtensionIdentifier}" ] },
					"oldKeymaps": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
					"confirmed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
				}
			*/
			this.telemetryService.publicLog('disableOtherKeymaps', telemetryData);
			if (confirmed) {
				TPromise.join(oldKeymaps.map(keymap => {
					return this.extensionEnablementService.setEnablement(keymap.local, EnablementState.Disabled);
				}));
			}
		};

		this.notificationService.prompt(Severity.Info, localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', ')),
			[{
				label: localize('yes', "Yes"),
				run: () => onPrompt(true)
			}, {
				label: localize('no', "No"),
				run: () => onPrompt(false)
			}]
		);
	}

	dispose(): void {
		this.disposables = dispose(this.disposables);
	}
}

export function onExtensionChanged(accessor: ServicesAccessor): Event<IExtensionIdentifier[]> {
	const extensionService = accessor.get(IExtensionManagementService);
	const extensionEnablementService = accessor.get(IExtensionEnablementService);
	return debounceEvent<IExtensionIdentifier, IExtensionIdentifier[]>(anyEvent(
		chain(anyEvent(extensionService.onDidInstallExtension, extensionService.onDidUninstallExtension))
			.map(e => ({ id: stripVersion(e.identifier.id), uuid: e.identifier.uuid }))
			.event,
		extensionEnablementService.onEnablementChanged
	), (list, id) => {
		if (!list) {<commit_msg>Repeatedly seeing "Disable other keymaps" (fixes #55272)<commit_after>/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import * as arrays from 'vs/base/common/arrays';
import { localize } from 'vs/nls';
import { Event, chain, anyEvent, debounceEvent } from 'vs/base/common/event';
import { onUnexpectedError } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, IExtensionIdentifier, EnablementState, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionIdFromLocal } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { Severity, INotificationService } from 'vs/platform/notification/common/notification';

export interface IExtensionStatus {
	identifier: IExtensionIdentifier;
	local: ILocalExtension;
	globallyEnabled: boolean;
}

export class KeymapExtensions implements IWorkbenchContribution {

	private disposables: IDisposable[] = [];

	constructor(
		@IInstantiationService private instantiationService: IInstantiationService,
		@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
		@IExtensionTipsService private tipsService: IExtensionTipsService,
		@ILifecycleService lifecycleService: ILifecycleService,
		@INotificationService private notificationService: INotificationService,
		@ITelemetryService private telemetryService: ITelemetryService,
	) {
		this.disposables.push(
			lifecycleService.onShutdown(() => this.dispose()),
			instantiationService.invokeFunction(onExtensionChanged)((identifiers => {
				TPromise.join(identifiers.map(identifier => this.checkForOtherKeymaps(identifier)))
					.then(null, onUnexpectedError);
			}))
		);
	}

	private checkForOtherKeymaps(extensionIdentifier: IExtensionIdentifier): TPromise<void> {
		return this.instantiationService.invokeFunction(getInstalledExtensions).then(extensions => {
			const keymaps = extensions.filter(extension => isKeymapExtension(this.tipsService, extension));
			const extension = arrays.first(keymaps, extension => stripVersion(extension.identifier.id) === extensionIdentifier.id);
			if (extension && extension.globallyEnabled) {
				const otherKeymaps = keymaps.filter(extension => stripVersion(extension.identifier.id) !== extensionIdentifier.id && extension.globallyEnabled);
				if (otherKeymaps.length) {
					return this.promptForDisablingOtherKeymaps(extension, otherKeymaps);
				}
			}
			return undefined;
		});
	}

	private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): void {
		const onPrompt = (confirmed: boolean) => {
			const telemetryData: { [key: string]: any; } = {
				newKeymap: newKeymap.identifier,
				oldKeymaps: oldKeymaps.map(k => k.identifier),
				confirmed
			};
			/* __GDPR__
				"disableOtherKeymaps" : {
					"newKeymap": { "${inline}": [ "${ExtensionIdentifier}" ] },
					"oldKeymaps": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
					"confirmed" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
				}
			*/
			this.telemetryService.publicLog('disableOtherKeymaps', telemetryData);
			if (confirmed) {
				TPromise.join(oldKeymaps.map(keymap => {
					return this.extensionEnablementService.setEnablement(keymap.local, EnablementState.Disabled);
				}));
			}
		};

		this.notificationService.prompt(Severity.Info, localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', ')),
			[{
				label: localize('yes', "Yes"),
				run: () => onPrompt(true)
			}, {
				label: localize('no', "No"),
				run: () => onPrompt(false)
			}]
		);
	}

	dispose(): void {
		this.disposables = dispose(this.disposables);
	}
}

export function onExtensionChanged(accessor: ServicesAccessor): Event<IExtensionIdentifier[]> {
	const extensionService = accessor.get(IExtensionManagementService);
	const extensionEnablementService = accessor.get(IExtensionEnablementService);
	const onDidInstallExtension = chain(extensionService.onDidInstallExtension)
		.filter(e => e.operation === InstallOperation.Install)
		.event;
	return debounceEvent<IExtensionIdentifier, IExtensionIdentifier[]>(anyEvent(
		chain(anyEvent(onDidInstallExtension, extensionService.onDidUninstallExtension))
			.map(e => ({ id: stripVersion(e.identifier.id), uuid: e.identifier.uuid }))
			.event,
		extensionEnablementService.onEnablementChanged
	), (list, id) => {
		if (!list) {
{'fraction_non_alphanumeric': 0.14806315402974113, 'fraction_numerical': 0.0006425555351569671, 'mean_word_length': 4.879654614139234, 'pattern_counts': {'":': 16, '<': 9, '<?xml version=': 0, '>': 40, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1566213', 'n_tokens_mistral': 3300, 'n_tokens_neox': 2995, 'n_words': 566}