提交 294406d7 编写于 作者: S Sandeep Somavarapu

Fix #110121

上级 23579d81
......@@ -365,6 +365,10 @@
{
"name": "vs/workbench/services/authentication",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/extensionRecommendations",
"project": "vscode-workbench"
}
]
}
......@@ -100,7 +100,7 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
})
]);
this._register(this.extensionRecommendationsManagementService.onDidChangeIgnoredRecommendations(() => this._onDidChangeRecommendations.fire()));
this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations, this.extensionRecommendationsManagementService.onDidChangeIgnoredRecommendations)(() => this._onDidChangeRecommendations.fire()));
this._register(this.extensionRecommendationsManagementService.onDidChangeGlobalIgnoredRecommendation(({ extensionId, isRecommended }) => {
if (!isRecommended) {
const reason = this.getAllRecommendationsWithReason()[extensionId];
......@@ -111,7 +111,6 @@ export class ExtensionRecommendationsService extends Disposable implements IExte
}));
await this.promptWorkspaceRecommendations();
this._register(Event.any(this.workspaceRecommendations.onDidChangeRecommendations, this.configBasedRecommendations.onDidChangeRecommendations)(() => this.promptWorkspaceRecommendations()));
}
private isEnabled(): boolean {
......
......@@ -10,7 +10,7 @@ import { MenuRegistry, MenuId, registerAction2, Action2 } from 'vs/platform/acti
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ExtensionsLabel, ExtensionsLocalizedLabel, ExtensionsChannelId, IExtensionManagementService, IExtensionGalleryService, PreferencesLocalizedLabel } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
......@@ -18,7 +18,7 @@ import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer,
import {
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction,
EnableAllAction, EnableAllWorkspaceAction, DisableAllAction, DisableAllWorkspaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction, ClearExtensionsSearchResultsAction
EnableAllAction, EnableAllWorkspaceAction, DisableAllAction, DisableAllWorkspaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction, ClearExtensionsSearchResultsAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction
} from 'vs/workbench/contrib/extensions/browser/extensionsActions';
import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput';
import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor';
......@@ -26,7 +26,7 @@ import { StatusUpdater, MaliciousExtensionChecker, ExtensionsViewletViewsContrib
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
......@@ -62,6 +62,8 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { IAction } from 'vs/base/common/actions';
import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
import { Schemas } from 'vs/base/common/network';
// Singletons
registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService);
......@@ -929,6 +931,220 @@ class ExtensionsContributions implements IWorkbenchContribution {
}
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.extensions.action.ignoreRecommendation',
title: { value: localize('workbench.extensions.action.ignoreRecommendation', "Ignore Recommendation"), original: `Ignore Recommendation` },
menu: {
id: MenuId.ExtensionContext,
group: '3_recommendations',
when: ContextKeyExpr.has('isExtensionRecommended'),
order: 1
},
});
}
async run(accessor: ServicesAccessor, id: string): Promise<any> {
accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, true);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.extensions.action.undoIgnoredRecommendation',
title: { value: localize('workbench.extensions.action.undoIgnoredRecommendation', "Undo Ignored Recommendation"), original: `Undo Ignored Recommendation` },
menu: {
id: MenuId.ExtensionContext,
group: '3_recommendations',
when: ContextKeyExpr.has('isUserIgnoredRecommendation'),
order: 1
},
});
}
async run(accessor: ServicesAccessor, id: string): Promise<any> {
accessor.get(IExtensionIgnoredRecommendationsService).toggleGlobalIgnoredRecommendation(id, false);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.extensions.action.addExtensionToWorkspaceRecommendations',
title: { value: localize('workbench.extensions.action.addExtensionToWorkspaceRecommendations', "Add to Workspace Recommendations"), original: `Add to Workspace Recommendations` },
menu: {
id: MenuId.ExtensionContext,
group: '3_recommendations',
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended').negate(), ContextKeyExpr.has('isUserIgnoredRecommendation').negate()),
order: 2
},
});
}
run(accessor: ServicesAccessor, id: string): Promise<any> {
return accessor.get(IWorkpsaceExtensionsConfigService).toggleRecommendation(id);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.extensions.action.removeExtensionFromWorkspaceRecommendations',
title: { value: localize('workbench.extensions.action.removeExtensionFromWorkspaceRecommendations', "Remove from Workspace Recommendations"), original: `Remove from Workspace Recommendations` },
menu: {
id: MenuId.ExtensionContext,
group: '3_recommendations',
when: ContextKeyExpr.and(WorkbenchStateContext.notEqualsTo('empty'), ContextKeyExpr.has('isBuiltinExtension').negate(), ContextKeyExpr.has('isExtensionWorkspaceRecommended')),
order: 2
},
});
}
run(accessor: ServicesAccessor, id: string): Promise<any> {
return accessor.get(IWorkpsaceExtensionsConfigService).toggleRecommendation(id);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.extensions.action.addToWorkspaceRecommendations',
title: { value: localize('workbench.extensions.action.addToWorkspaceRecommendations', "Add Extension to Workspace Recommendations"), original: `Add Extension to Workspace Recommendations` },
category: localize('extensions', "Extensions"),
menu: {
id: MenuId.CommandPalette,
when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)),
},
});
}
async run(accessor: ServicesAccessor): Promise<any> {
const editorService = accessor.get(IEditorService);
const workpsaceExtensionsConfigService = accessor.get(IWorkpsaceExtensionsConfigService);
if (!(editorService.activeEditor instanceof ExtensionsInput)) {
return;
}
const extensionId = editorService.activeEditor.extension.identifier.id.toLowerCase();
const recommendations = await workpsaceExtensionsConfigService.getRecommendations();
if (recommendations.includes(extensionId)) {
return;
}
await workpsaceExtensionsConfigService.toggleRecommendation(extensionId);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.extensions.action.addToWorkspaceFolderRecommendations',
title: { value: localize('workbench.extensions.action.addToWorkspaceFolderRecommendations', "Add Extension to Workspace Folder Recommendations"), original: `Add Extension to Workspace Folder Recommendations` },
category: localize('extensions', "Extensions"),
menu: {
id: MenuId.CommandPalette,
when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)),
},
});
}
async run(accessor: ServicesAccessor): Promise<any> {
return accessor.get(ICommandService).executeCommand('workbench.extensions.action.addToWorkspaceRecommendations');
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.extensions.action.addToWorkspaceIgnoredRecommendations',
title: { value: localize('workbench.extensions.action.addToWorkspaceIgnoredRecommendations', "Add Extension to Workspace Ignored Recommendations"), original: `Add Extension to Workspace Ignored Recommendations` },
category: localize('extensions', "Extensions"),
menu: {
id: MenuId.CommandPalette,
when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('workspace'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)),
},
});
}
async run(accessor: ServicesAccessor): Promise<any> {
const editorService = accessor.get(IEditorService);
const workpsaceExtensionsConfigService = accessor.get(IWorkpsaceExtensionsConfigService);
if (!(editorService.activeEditor instanceof ExtensionsInput)) {
return;
}
const extensionId = editorService.activeEditor.extension.identifier.id.toLowerCase();
const unwatedRecommendations = await workpsaceExtensionsConfigService.getUnwantedRecommendations();
if (unwatedRecommendations.includes(extensionId)) {
return;
}
await workpsaceExtensionsConfigService.toggleUnwantedRecommendation(extensionId);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations',
title: { value: localize('workbench.extensions.action.addToWorkspaceFolderIgnoredRecommendations', "Add Extension to Workspace Folder Ignored Recommendations"), original: `Add Extension to Workspace Folder Ignored Recommendations` },
category: localize('extensions', "Extensions"),
menu: {
id: MenuId.CommandPalette,
when: ContextKeyExpr.and(WorkbenchStateContext.isEqualTo('folder'), ContextKeyExpr.equals('resourceScheme', Schemas.extension)),
},
});
}
run(accessor: ServicesAccessor): Promise<any> {
return accessor.get(ICommandService).executeCommand('workbench.extensions.action.addToWorkspaceIgnoredRecommendations');
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: ConfigureWorkspaceRecommendedExtensionsAction.ID,
title: { value: ConfigureWorkspaceRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace)' },
category: localize('extensions', "Extensions"),
menu: {
id: MenuId.CommandPalette,
when: WorkbenchStateContext.isEqualTo('workspace'),
},
});
}
run(accessor: ServicesAccessor): Promise<any> {
return accessor.get(IInstantiationService).createInstance(ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceRecommendedExtensionsAction.ID, ConfigureWorkspaceRecommendedExtensionsAction.LABEL).run();
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID,
title: { value: ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL, original: 'Configure Recommended Extensions (Workspace Folder)' },
category: localize('extensions', "Extensions"),
menu: {
id: MenuId.CommandPalette,
when: WorkbenchStateContext.notEqualsTo('empty'),
},
});
}
run(accessor: ServicesAccessor): Promise<any> {
return accessor.get(IInstantiationService).createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL).run();
}
});
}
}
......@@ -936,7 +1152,6 @@ const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(Workbench
workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(MaliciousExtensionChecker, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(ConfigureRecommendedExtensionsCommandsContributor, LifecyclePhase.Eventually);
workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase.Restored);
workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting);
workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually);
......
......@@ -44,7 +44,6 @@ import { IProductService } from 'vs/platform/product/common/productService';
import { SeverityIcon } from 'vs/platform/severityIcon/common/severityIcon';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { IMenuService } from 'vs/platform/actions/common/actions';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
......@@ -112,7 +111,6 @@ export class ExtensionsListView extends ViewPane {
@IProductService protected readonly productService: IProductService,
@IContextKeyService contextKeyService: IContextKeyService,
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
@IMenuService private readonly menuService: IMenuService,
@IOpenerService openerService: IOpenerService,
@IPreferencesService private readonly preferencesService: IPreferencesService,
) {
......@@ -251,7 +249,7 @@ export class ExtensionsListView extends ViewPane {
getActions: () => actions.slice(0, actions.length - 1)
});
} else if (e.element) {
const groups = getContextMenuActions(this.menuService, this.contextKeyService.createScoped(), this.instantiationService, e.element);
const groups = getContextMenuActions(e.element, this.instantiationService);
groups.forEach(group => group.forEach(extensionAction => {
if (extensionAction instanceof ExtensionAction) {
extensionAction.extension = e.element!;
......@@ -890,7 +888,6 @@ export class ServerExtensionsView extends ExtensionsListView {
@IWorkbenchExtensioManagementService extensionManagementService: IWorkbenchExtensioManagementService,
@IProductService productService: IProductService,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService menuService: IMenuService,
@IOpenerService openerService: IOpenerService,
@IThemeService themeService: IThemeService,
@IPreferencesService preferencesService: IPreferencesService,
......@@ -898,7 +895,7 @@ export class ServerExtensionsView extends ExtensionsListView {
options.server = server;
super(options, notificationService, keybindingService, contextMenuService, instantiationService, themeService, extensionService, extensionsWorkbenchService, tipsService,
telemetryService, configurationService, contextService, experimentService, extensionManagementServerService, extensionManagementService, productService,
contextKeyService, viewDescriptorService, menuService, openerService, preferencesService);
contextKeyService, viewDescriptorService, openerService, preferencesService);
this._register(onDidChangeTitle(title => this.updateTitle(title)));
}
......
......@@ -7,12 +7,12 @@ import { EXTENSION_IDENTIFIER_PATTERN, IExtensionGalleryService } from 'vs/platf
import { distinct, flatten } from 'vs/base/common/arrays';
import { ExtensionRecommendations, ExtensionRecommendation } from 'vs/workbench/contrib/extensions/browser/extensionRecommendations';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IExtensionsConfigContent, ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { ExtensionRecommendationReason } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { Emitter } from 'vs/base/common/event';
import { IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
import { IExtensionsConfigContent, IWorkpsaceExtensionsConfigService } from 'vs/workbench/services/extensionRecommendations/common/workspaceExtensionsConfig';
export class WorkspaceRecommendations extends ExtensionRecommendations {
......@@ -51,14 +51,18 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
this.notificationService.warn(`The ${invalidRecommendations.length} extension(s) below, in workspace recommendations have issues:\n${message}`);
}
this._recommendations = [];
this._ignoredRecommendations = [];
for (const extensionsConfig of extensionsConfigs) {
if (extensionsConfig.unwantedRecommendations) {
for (const unwantedRecommendation of extensionsConfig.unwantedRecommendations) {
if (invalidRecommendations.indexOf(unwantedRecommendation) === -1) {
this._ignoredRecommendations.push(unwantedRecommendation);
}
}
}
if (extensionsConfig.recommendations) {
for (const extensionId of extensionsConfig.recommendations) {
if (invalidRecommendations.indexOf(extensionId) === -1) {
this._recommendations.push({
......@@ -72,6 +76,7 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
}
}
}
}
private async validateExtensions(contents: IExtensionsConfigContent[]): Promise<{ validRecommendations: string[], invalidRecommendations: string[], message: string }> {
......@@ -114,13 +119,9 @@ export class WorkspaceRecommendations extends ExtensionRecommendations {
}
private async onDidChangeExtensionsConfigs(): Promise<void> {
const oldWorkspaceRecommended = this._recommendations;
await this.fetch();
// Suggest only if at least one of the newly added recommendations was not suggested before
if (this._recommendations.some(current => oldWorkspaceRecommended.every(old => current.extensionId !== old.extensionId))) {
this._onDidChangeRecommendations.fire();
}
}
}
......@@ -304,14 +304,14 @@ suite('ExtensionRecommendationsService Test', () => {
}, null, '\t'));
const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir }));
const fileService = new FileService(new NullLogService());
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()));
instantiationService.stub(IFileService, fileService);
workspaceService = new TestContextService(myWorkspace);
instantiationService.stub(IWorkspaceContextService, workspaceService);
instantiationService.stub(IWorkpsaceExtensionsConfigService, instantiationService.createInstance(WorkspaceExtensionsConfigService));
instantiationService.stub(IExtensionIgnoredRecommendationsService, instantiationService.createInstance(ExtensionIgnoredRecommendationsService));
instantiationService.stub(IExtensionRecommendationNotificationService, instantiationService.createInstance(ExtensionRecommendationNotificationService));
const fileService = new FileService(new NullLogService());
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(new NullLogService()));
instantiationService.stub(IFileService, fileService);
}
function testNoPromptForValidRecommendations(recommendations: string[]) {
......
......@@ -7,11 +7,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { IStringDictionary } from 'vs/base/common/collections';
import { Event } from 'vs/base/common/event';
export interface IExtensionsConfigContent {
recommendations: string[];
unwantedRecommendations: string[];
}
export type DynamicRecommendation = 'dynamic';
export type ConfigRecommendation = 'config';
export type ExecutableRecommendation = 'executable';
......
......@@ -3,20 +3,28 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { coalesce, distinct, flatten } from 'vs/base/common/arrays';
import { distinct, flatten } from 'vs/base/common/arrays';
import { Emitter, Event } from 'vs/base/common/event';
import { parse } from 'vs/base/common/json';
import { Disposable } from 'vs/base/common/lifecycle';
import { IFileService } from 'vs/platform/files/common/files';
import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { FileKind, IFileService } from 'vs/platform/files/common/files';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { localize } from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { IJSONEditingService, IJSONValue } from 'vs/workbench/services/configuration/common/jsonEditing';
import { ResourceMap } from 'vs/base/common/map';
export const EXTENSIONS_CONFIG = '.vscode/extensions.json';
export interface IExtensionsConfigContent {
recommendations: string[];
unwantedRecommendations: string[];
recommendations?: string[];
unwantedRecommendations?: string[];
}
export const IWorkpsaceExtensionsConfigService = createDecorator<IWorkpsaceExtensionsConfigService>('IWorkpsaceExtensionsConfigService');
......@@ -26,8 +34,11 @@ export interface IWorkpsaceExtensionsConfigService {
onDidChangeExtensionsConfigs: Event<void>;
getExtensionsConfigs(): Promise<IExtensionsConfigContent[]>;
getRecommendations(): Promise<string[]>;
getUnwantedRecommendations(): Promise<string[]>;
toggleRecommendation(extensionId: string): Promise<void>;
toggleUnwantedRecommendation(extensionId: string): Promise<void>;
}
export class WorkspaceExtensionsConfigService extends Disposable implements IWorkpsaceExtensionsConfigService {
......@@ -40,54 +51,218 @@ export class WorkspaceExtensionsConfigService extends Disposable implements IWor
constructor(
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IFileService private readonly fileService: IFileService,
@IQuickInputService private readonly quickInputService: IQuickInputService,
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService,
@IJSONEditingService private readonly jsonEditingService: IJSONEditingService,
) {
super();
this._register(this.workspaceContextService.onDidChangeWorkspaceFolders(e => this._onDidChangeExtensionsConfigs.fire()));
this._register(workspaceContextService.onDidChangeWorkspaceFolders(e => this._onDidChangeExtensionsConfigs.fire()));
this._register(fileService.onDidFilesChange(e => {
const workspace = workspaceContextService.getWorkspace();
if ((workspace.configuration && e.affects(workspace.configuration))
|| workspace.folders.some(folder => e.affects(folder.toResource(EXTENSIONS_CONFIG)))
) {
this._onDidChangeExtensionsConfigs.fire();
}
}));
}
async getExtensionsConfigs(): Promise<IExtensionsConfigContent[]> {
const workspace = this.workspaceContextService.getWorkspace();
const result = await Promise.all([
this.resolveWorkspaceExtensionConfig(workspace),
...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder))
]);
return coalesce(result);
const result: IExtensionsConfigContent[] = [];
const workspaceExtensionsConfigContent = workspace.configuration ? await this.resolveWorkspaceExtensionConfig(workspace.configuration) : undefined;
if (workspaceExtensionsConfigContent) {
result.push(workspaceExtensionsConfigContent);
}
result.push(...await Promise.all(workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderExtensionConfig(workspaceFolder))));
return result;
}
async getRecommendations(): Promise<string[]> {
const configs = await this.getExtensionsConfigs();
return distinct(flatten(configs.map(c => c.recommendations ? c.recommendations.map(c => c.toLowerCase()) : [])));
}
async getUnwantedRecommendations(): Promise<string[]> {
const configs = await this.getExtensionsConfigs();
return distinct(flatten(configs.map(c => c.unwantedRecommendations.map(c => c.toLowerCase()))));
return distinct(flatten(configs.map(c => c.unwantedRecommendations ? c.unwantedRecommendations.map(c => c.toLowerCase()) : [])));
}
async toggleRecommendation(extensionId: string): Promise<void> {
const workspace = this.workspaceContextService.getWorkspace();
const workspaceExtensionsConfigContent = workspace.configuration ? await this.resolveWorkspaceExtensionConfig(workspace.configuration) : undefined;
const workspaceFolderExtensionsConfigContents = new ResourceMap<IExtensionsConfigContent>();
await Promise.all(workspace.folders.map(async workspaceFolder => {
const extensionsConfigContent = await this.resolveWorkspaceFolderExtensionConfig(workspaceFolder);
workspaceFolderExtensionsConfigContents.set(workspaceFolder.uri, extensionsConfigContent);
}));
const isWorkspaceRecommended = workspaceExtensionsConfigContent && workspaceExtensionsConfigContent.recommendations?.some(r => r === extensionId);
const recommendedWorksapceFolders = workspace.folders.filter(workspaceFolder => workspaceFolderExtensionsConfigContents.get(workspaceFolder.uri)?.recommendations?.some(r => r === extensionId));
const isRecommended = isWorkspaceRecommended || recommendedWorksapceFolders.length > 0;
const workspaceOrFolders = isRecommended
? await this.pickWorkspaceOrFolders(recommendedWorksapceFolders, isWorkspaceRecommended ? workspace : undefined, localize('select for remove', "Remove extension recommendation from"))
: await this.pickWorkspaceOrFolders(workspace.folders, workspace.configuration ? workspace : undefined, localize('select for add', "Add extension recommendation to"));
for (const workspaceOrWorkspaceFolder of workspaceOrFolders) {
if (IWorkspace.isIWorkspace(workspaceOrWorkspaceFolder)) {
await this.addOrRemoveWorkspaceRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceExtensionsConfigContent, !isRecommended);
} else {
await this.addOrRemoveWorkspaceFolderRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceFolderExtensionsConfigContents.get(workspaceOrWorkspaceFolder.uri)!, !isRecommended);
}
}
}
async toggleUnwantedRecommendation(extensionId: string): Promise<void> {
const workspace = this.workspaceContextService.getWorkspace();
const workspaceExtensionsConfigContent = workspace.configuration ? await this.resolveWorkspaceExtensionConfig(workspace.configuration) : undefined;
const workspaceFolderExtensionsConfigContents = new ResourceMap<IExtensionsConfigContent>();
await Promise.all(workspace.folders.map(async workspaceFolder => {
const extensionsConfigContent = await this.resolveWorkspaceFolderExtensionConfig(workspaceFolder);
workspaceFolderExtensionsConfigContents.set(workspaceFolder.uri, extensionsConfigContent);
}));
const isWorkspaceUnwanted = workspaceExtensionsConfigContent && workspaceExtensionsConfigContent.unwantedRecommendations?.some(r => r === extensionId);
const unWantedWorksapceFolders = workspace.folders.filter(workspaceFolder => workspaceFolderExtensionsConfigContents.get(workspaceFolder.uri)?.unwantedRecommendations?.some(r => r === extensionId));
const isUnwanted = isWorkspaceUnwanted || unWantedWorksapceFolders.length > 0;
const workspaceOrFolders = isUnwanted
? await this.pickWorkspaceOrFolders(unWantedWorksapceFolders, isWorkspaceUnwanted ? workspace : undefined, localize('select for remove', "Remove extension recommendation from"))
: await this.pickWorkspaceOrFolders(workspace.folders, workspace.configuration ? workspace : undefined, localize('select for add', "Add extension recommendation to"));
for (const workspaceOrWorkspaceFolder of workspaceOrFolders) {
if (IWorkspace.isIWorkspace(workspaceOrWorkspaceFolder)) {
await this.addOrRemoveWorkspaceUnwantedRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceExtensionsConfigContent, !isUnwanted);
} else {
await this.addOrRemoveWorkspaceFolderUnwantedRecommendation(extensionId, workspaceOrWorkspaceFolder, workspaceFolderExtensionsConfigContents.get(workspaceOrWorkspaceFolder.uri)!, !isUnwanted);
}
}
}
private async addOrRemoveWorkspaceFolderRecommendation(extensionId: string, workspaceFolder: IWorkspaceFolder, extensionsConfigContent: IExtensionsConfigContent, add: boolean): Promise<void> {
const values: IJSONValue[] = [];
if (add) {
values.push({ path: ['recommendations'], value: [...extensionsConfigContent.recommendations || [], extensionId] });
if (extensionsConfigContent.unwantedRecommendations && extensionsConfigContent.unwantedRecommendations.some(e => e === extensionId)) {
values.push({ path: ['unwantedRecommendations'], value: extensionsConfigContent.unwantedRecommendations.filter(e => e !== extensionId) });
}
} else if (extensionsConfigContent.recommendations) {
values.push({ path: ['recommendations'], value: extensionsConfigContent.recommendations.filter(e => e !== extensionId) });
}
if (values.length) {
return this.jsonEditingService.write(workspaceFolder.toResource(EXTENSIONS_CONFIG), values, true);
}
}
private async addOrRemoveWorkspaceRecommendation(extensionId: string, workspace: IWorkspace, extensionsConfigContent: IExtensionsConfigContent | undefined, add: boolean): Promise<void> {
const values: IJSONValue[] = [];
if (extensionsConfigContent) {
if (add) {
values.push({ path: ['recommendations'], value: [...extensionsConfigContent.recommendations || [], extensionId] });
if (extensionsConfigContent.unwantedRecommendations && extensionsConfigContent.unwantedRecommendations.some(e => e === extensionId)) {
values.push({ path: ['unwantedRecommendations'], value: extensionsConfigContent.unwantedRecommendations.filter(e => e !== extensionId) });
}
} else if (extensionsConfigContent.recommendations) {
values.push({ path: ['recommendations'], value: extensionsConfigContent.recommendations.filter(e => e !== extensionId) });
}
} else if (add) {
values.push({ path: ['extensions'], value: { recommendations: [extensionId] } });
}
if (values.length) {
return this.jsonEditingService.write(workspace.configuration!, values, true);
}
}
private async addOrRemoveWorkspaceFolderUnwantedRecommendation(extensionId: string, workspaceFolder: IWorkspaceFolder, extensionsConfigContent: IExtensionsConfigContent, add: boolean): Promise<void> {
const values: IJSONValue[] = [];
if (add) {
values.push({ path: ['unwantedRecommendations'], value: [...extensionsConfigContent.unwantedRecommendations || [], extensionId] });
if (extensionsConfigContent.recommendations && extensionsConfigContent.recommendations.some(e => e === extensionId)) {
values.push({ path: ['recommendations'], value: extensionsConfigContent.recommendations.filter(e => e !== extensionId) });
}
} else if (extensionsConfigContent.unwantedRecommendations) {
values.push({ path: ['unwantedRecommendations'], value: extensionsConfigContent.unwantedRecommendations.filter(e => e !== extensionId) });
}
if (values.length) {
return this.jsonEditingService.write(workspaceFolder.toResource(EXTENSIONS_CONFIG), values, true);
}
}
private async addOrRemoveWorkspaceUnwantedRecommendation(extensionId: string, workspace: IWorkspace, extensionsConfigContent: IExtensionsConfigContent | undefined, add: boolean): Promise<void> {
const values: IJSONValue[] = [];
if (extensionsConfigContent) {
if (add) {
values.push({ path: ['unwantedRecommendations'], value: [...extensionsConfigContent.unwantedRecommendations || [], extensionId] });
if (extensionsConfigContent.recommendations && extensionsConfigContent.recommendations.some(e => e === extensionId)) {
values.push({ path: ['recommendations'], value: extensionsConfigContent.recommendations.filter(e => e !== extensionId) });
}
} else if (extensionsConfigContent.unwantedRecommendations) {
values.push({ path: ['unwantedRecommendations'], value: extensionsConfigContent.unwantedRecommendations.filter(e => e !== extensionId) });
}
} else if (add) {
values.push({ path: ['extensions'], value: { unwantedRecommendations: [extensionId] } });
}
private async resolveWorkspaceExtensionConfig(workspace: IWorkspace): Promise<IExtensionsConfigContent | null> {
if (values.length) {
return this.jsonEditingService.write(workspace.configuration!, values, true);
}
}
private async pickWorkspaceOrFolders(workspaceFolders: IWorkspaceFolder[], workspace: IWorkspace | undefined, placeHolder: string): Promise<(IWorkspace | IWorkspaceFolder)[]> {
const workspaceOrFolders = workspace ? [...workspaceFolders, workspace] : [...workspaceFolders];
if (workspaceOrFolders.length === 1) {
return workspaceOrFolders;
}
const folderPicks: (IQuickPickItem & { workspaceOrFolder: IWorkspace | IWorkspaceFolder } | IQuickPickSeparator)[] = workspaceFolders.map(workspaceFolder => {
return {
label: workspaceFolder.name,
description: localize('workspace folder', "Workspace Folder"),
workspaceOrFolder: workspaceFolder,
iconClasses: getIconClasses(this.modelService, this.modeService, workspaceFolder.uri, FileKind.ROOT_FOLDER)
};
});
if (workspace) {
folderPicks.push({ type: 'separator' });
folderPicks.push({
label: localize('workspace', "Workspace"),
workspaceOrFolder: workspace,
});
}
const result = await this.quickInputService.pick(folderPicks, { placeHolder, canPickMany: true }) || [];
return result.map(r => r.workspaceOrFolder!);
}
private async resolveWorkspaceExtensionConfig(workspaceConfigurationResource: URI): Promise<IExtensionsConfigContent | undefined> {
try {
if (workspace.configuration) {
const content = await this.fileService.readFile(workspace.configuration);
const content = await this.fileService.readFile(workspaceConfigurationResource);
const extensionsConfigContent = <IExtensionsConfigContent | undefined>parse(content.value.toString())['extensions'];
return this.parseExtensionConfig(extensionsConfigContent);
}
return extensionsConfigContent ? this.parseExtensionConfig(extensionsConfigContent) : undefined;
} catch (e) { /* Ignore */ }
return null;
return undefined;
}
private async resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise<IExtensionsConfigContent | null> {
private async resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise<IExtensionsConfigContent> {
try {
const content = await this.fileService.readFile(workspaceFolder.toResource(EXTENSIONS_CONFIG));
const extensionsConfigContent = <IExtensionsConfigContent>parse(content.value.toString());
return this.parseExtensionConfig(extensionsConfigContent);
} catch (e) { /* ignore */ }
return null;
return {};
}
private parseExtensionConfig(extensionsConfigContent: IExtensionsConfigContent | undefined): IExtensionsConfigContent | null {
if (extensionsConfigContent) {
private parseExtensionConfig(extensionsConfigContent: IExtensionsConfigContent): IExtensionsConfigContent {
return {
recommendations: distinct((extensionsConfigContent.recommendations || []).map(e => e.toLowerCase())),
unwantedRecommendations: distinct((extensionsConfigContent.unwantedRecommendations || []).map(e => e.toLowerCase()))
};
}
return null;
}
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册