Skip to content

Commit

Permalink
Add "Recommend" action to extension viewlet
Browse files Browse the repository at this point in the history
Related to microsoft#13456
  • Loading branch information
reyronald committed Jun 22, 2018
1 parent b7e6e04 commit d4128c6
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 13 deletions.
6 changes: 3 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@
"npm.exclude": "**/extensions/**",
"emmet.excludeLanguages": [],
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.preferences.quoteStyle": "single"

}
"typescript.preferences.quoteStyle": "single",
"editor.formatOnSave": false
}
10 changes: 8 additions & 2 deletions product.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@
"extensionAllowedProposedApi": [
"ms-vscode.node-debug",
"ms-vscode.node-debug2"
]
}
],
"extensionsGallery": {
"serviceUrl":
"https://marketplace.visualstudio.com/_apis/public/gallery",
"cacheUrl": "https://vscode.blob.core.windows.net/gallery/index",
"itemUrl": "https://marketplace.visualstudio.com/items"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { Renderer, DataSource, Controller } from 'vs/workbench/parts/extensions/
import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, MaliciousStatusLabelAction, DisabledStatusLabelAction, MultiServerInstallAction, MultiServerUpdateAction, IgnoreExtensionRecommendationAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, MaliciousStatusLabelAction, DisabledStatusLabelAction, MultiServerInstallAction, MultiServerUpdateAction, IgnoreExtensionRecommendationAction, RecommendToFolderAction, RecommendToWorkspaceAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement';
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
Expand Down Expand Up @@ -375,6 +375,8 @@ export class ExtensionEditor extends BaseEditor {
const enableAction = this.instantiationService.createInstance(EnableAction);
const disableAction = this.instantiationService.createInstance(DisableAction);
const reloadAction = this.instantiationService.createInstance(ReloadAction);
const recommendToFolderAction = this.instantiationService.createInstance(RecommendToFolderAction);
const recommendToWorkspaceAction = this.instantiationService.createInstance(RecommendToWorkspaceAction);

installAction.extension = extension;
maliciousStatusAction.extension = extension;
Expand All @@ -383,9 +385,11 @@ export class ExtensionEditor extends BaseEditor {
enableAction.extension = extension;
disableAction.extension = extension;
reloadAction.extension = extension;
recommendToFolderAction.extension = extension;
recommendToWorkspaceAction.extension = extension;

this.extensionActionBar.clear();
this.extensionActionBar.push([disabledStatusAction, reloadAction, updateAction, enableAction, disableAction, installAction, maliciousStatusAction], { icon: true, label: true });
this.extensionActionBar.push([disabledStatusAction, reloadAction, updateAction, enableAction, disableAction, installAction, recommendToFolderAction, recommendToWorkspaceAction, maliciousStatusAction], { icon: true, label: true });
this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, maliciousStatusAction, disabledStatusAction);

const ignoreAction = this.instantiationService.createInstance(IgnoreExtensionRecommendationAction);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1721,13 +1721,15 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio
constructor(
id: string,
label: string,
cssClass: string,
enabled: boolean,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IFileService private fileService: IFileService,
@IEditorService private editorService: IEditorService,
@IJSONEditingService private jsonEditingService: IJSONEditingService,
@ITextModelService private textModelResolverService: ITextModelService
) {
super(id, label, null);
super(id, label, cssClass, enabled);
}

protected openExtensionsFile(extensionsFileResource: URI): TPromise<any> {
Expand Down Expand Up @@ -1757,6 +1759,75 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio
}));
}

protected getFolderRecommendedExtensions(extensionsFileResource: URI): TPromise<string[]> {
return this.fileService.resolveContent(extensionsFileResource)
.then(content => {
const folderRecommendations = (<IExtensionsContent>json.parse(content.value));
return folderRecommendations.recommendations || [];
});
}

protected getWorkspaceRecommendedExtensions(workspaceConfigurationFile: URI): TPromise<string[]> {
return this.fileService.resolveContent(workspaceConfigurationFile)
.then(content => {
const workspaceRecommendations = <IExtensionsContent>json.parse(content.value)['extensions'];
if (workspaceRecommendations) {
return workspaceRecommendations.recommendations || [];
}
return [];
});
}

protected addRecommendedExtensionToFolder(extensionsFileResource: URI, extensionId: string): TPromise<any> {
return this.getOrCreateExtensionsFile(extensionsFileResource)
.then(({ created, content }) => {
const folderRecommendations: string[] = (<IExtensionsContent>json.parse(content)).recommendations || [];

if (folderRecommendations.indexOf(extensionId) !== -1) {
return TPromise.as(null);
}

// TODO:
// This will actually overwrite the contents of this key in the file,
// removing comments and any additional user modifications.
return this.jsonEditingService.write(extensionsFileResource,
{
key: 'recommendations',
value: [
...folderRecommendations,
extensionId
]
},
true);
});
}

protected addRecommendedExtensionToWorkspace(workspaceConfigurationFile: URI, extensionId: string): TPromise<any> {
return this.getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile)
.then(content => {
const workspaceRecommendations: string[] = (<IExtensionsContent>json.parse(content.value)['extensions']).recommendations;

if (workspaceRecommendations.indexOf(extensionId) !== -1) {
return TPromise.as(null);
}

// TODO:
// This will actually overwrite the contents of this key in the file,
// removing comments and any additional user modifications.
return this.jsonEditingService.write(workspaceConfigurationFile,
{
key: 'extensions',
value: {
recommendations: [
...workspaceRecommendations,
extensionId
]
}
},
true);
});
}

private getOrUpdateWorkspaceConfigurationFile(workspaceConfigurationFile: URI): TPromise<IContent> {
return this.fileService.resolveContent(workspaceConfigurationFile)
.then(content => {
Expand Down Expand Up @@ -1818,7 +1889,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi
@IJSONEditingService jsonEditingService: IJSONEditingService,
@ITextModelService textModelResolverService: ITextModelService
) {
super(id, label, contextService, fileService, editorService, jsonEditingService, textModelResolverService);
super(id, label, null, false, contextService, fileService, editorService, jsonEditingService, textModelResolverService);
this.contextService.onDidChangeWorkbenchState(() => this.update(), this, this.disposables);
this.update();
}
Expand Down Expand Up @@ -1860,7 +1931,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac
@ITextModelService textModelResolverService: ITextModelService,
@ICommandService private commandService: ICommandService
) {
super(id, label, contextService, fileService, editorService, jsonEditingService, textModelResolverService);
super(id, label, null, false, contextService, fileService, editorService, jsonEditingService, textModelResolverService);
this.contextService.onDidChangeWorkspaceFolders(() => this.update(), this, this.disposables);
this.update();
}
Expand Down Expand Up @@ -1954,6 +2025,149 @@ export class DisabledStatusLabelAction extends Action {
}
}

// TODO: test when workspace is folder
// TODO: test when recomendation file already exists
// TODO: test when current extension is already recommended
// TODO: test when current extension is NOT recommended
// TODO: test when recomendation file doesnt exists
// TODO: test when workspace is workspace
// TODO: test when recomendation file already exists
// TODO: test when current extension is already recommended
// TODO: test when current extension is NOT recommended
// TODO: test when recomendation file doesnt exists
// TODO: test when workspace is empty
// TODO: make sure we preserve comments

export class RecommendToFolderAction extends AbstractConfigureRecommendedExtensionsAction {
private static readonly Class = 'extension-action recommend-to-folder';

private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }

private disposables: IDisposable[] = [];

private _configurationFile: URI;
get configurationFile(): URI {
if (!this._configurationFile) {
this._configurationFile = this.contextService.getWorkspace().folders[0].toResource(paths.join('.vscode', 'extensions.json'));
}
return this._configurationFile;
}

constructor(
@IFileService fileService: IFileService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IJSONEditingService jsonEditingService: IJSONEditingService,
@ITextModelService textModelResolverService: ITextModelService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super(
'extensions.install',
localize('recommendToFolder', "Recommend To Folder"),
`${RecommendToFolderAction.Class}`,
false,
contextService,
fileService,
editorService,
jsonEditingService,
textModelResolverService
);
this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update()));
this.update();
}

private update(): void {
if (this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER) {
this.enabled = false;
this.class = this.enabled ? RecommendToFolderAction.Class : `${RecommendToFolderAction.Class} hide`;
} else {
if (this.extension) {
this.getFolderRecommendedExtensions(this.configurationFile).then(recommendedExtensions => {
this.enabled = recommendedExtensions.indexOf(this.extension.id) === -1;
this.class = this.enabled ? RecommendToFolderAction.Class : `${RecommendToFolderAction.Class} hide`;
});
}
}
}

run(): TPromise<any> {
return this.addRecommendedExtensionToFolder(this.configurationFile, this.extension.id)
.then(() => this.update());
}

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

export class RecommendToWorkspaceAction extends AbstractConfigureRecommendedExtensionsAction {
private static readonly Class = 'extension-action recommend-to-workspace';

private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }

private disposables: IDisposable[] = [];

private _configurationFile: URI;
get configurationFile(): URI {
if (!this._configurationFile) {
this._configurationFile = this.contextService.getWorkspace().configuration;
}
return this._configurationFile;
}

constructor(
@IFileService fileService: IFileService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IJSONEditingService jsonEditingService: IJSONEditingService,
@ITextModelService textModelResolverService: ITextModelService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
) {
super(
'extensions.install',
localize('recommendToWorkspace', "Recommend To Workspace"),
`${RecommendToWorkspaceAction.Class}`,
false,
contextService,
fileService,
editorService,
jsonEditingService,
textModelResolverService
);
this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update()));
this.update();
}

private update(): void {
if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) {
this.enabled = false;
this.class = this.enabled ? RecommendToWorkspaceAction.Class : `${RecommendToWorkspaceAction.Class} hide`;
} else {
if (this.extension) {
this.getWorkspaceRecommendedExtensions(this.configurationFile).then(recommendedExtensions => {
this.enabled = recommendedExtensions.indexOf(this.extension.id) === -1;
this.class = this.enabled ? RecommendToWorkspaceAction.Class : `${RecommendToWorkspaceAction.Class} hide`;
});
}
}
}

run(): TPromise<any> {
return this.addRecommendedExtensionToWorkspace(this.configurationFile, this.extension.id)
.then(() => this.update());
}

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

export class DisableAllAction extends Action {

static readonly ID = 'workbench.extensions.action.disableAll';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
.monaco-action-bar .action-item.disabled .action-label.extension-action.disable,
.monaco-action-bar .action-item.disabled .action-label.extension-action.reload,
.monaco-action-bar .action-item.disabled .action-label.disable-status.hide,
.monaco-action-bar .action-item.disabled .action-label.malicious-status.not-malicious {
.monaco-action-bar .action-item.disabled .action-label.malicious-status.not-malicious,
.monaco-action-bar .action-item.disabled .action-label.extension-action.recommend-to-folder,
.monaco-action-bar .action-item.disabled .action-label.extension-action.recommend-to-workspace {
display: none;
}

Expand Down Expand Up @@ -96,4 +98,4 @@

.extension-editor > .header.recommended > .details > .recommendation > .monaco-action-bar .action-item .action-label.extension-action.ignore:hover {
filter: brightness(.8);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1198,6 +1198,10 @@ suite('ExtensionsActions Test', () => {
});
});

test(`RecommendToFolderAction`, () => {
// TODO: Implement test
});

function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension {
const localExtension = <ILocalExtension>Object.create({ manifest: {} });
assign(localExtension, { type: LocalExtensionType.User, manifest: {}, location: URI.file(`pub.${name}`) }, properties);
Expand All @@ -1220,4 +1224,4 @@ suite('ExtensionsActions Test', () => {
return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null };
}

});
});

0 comments on commit d4128c6

Please sign in to comment.