This commit is contained in:
Sandeep Somavarapu 2016-09-15 15:17:14 +02:00
parent 7ac6d551d8
commit ef52244d8b
9 changed files with 159 additions and 6 deletions

View file

@ -57,6 +57,10 @@
{
"fileMatch": "%APP_SETTINGS_HOME%/snippets/*.json",
"url": "vscode://schemas/snippets"
},
{
"fileMatch": "/.vscode/extensions.json",
"url": "vscode://schemas/extensions"
}
]
}

View file

@ -202,6 +202,7 @@ export const IExtensionTipsService = createDecorator<IExtensionTipsService>('ext
export interface IExtensionTipsService {
_serviceBrand: any;
getRecommendations(): string[];
getWorkspaceRecommendations(): string[];
}
export const ExtensionsLabel = nls.localize('extensions', "Extensions");

View file

@ -11,6 +11,7 @@ import {TPromise as Promise} from 'vs/base/common/winjs.base';
import {Action} from 'vs/base/common/actions';
import {match} from 'vs/base/common/glob';
import {IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionsConfiguration, EXTENSIONS_CONFIGURAION_NAME } from './extensions';
import {IModelService} from 'vs/editor/common/services/modelService';
import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage';
import product from 'vs/platform/product';
@ -18,6 +19,7 @@ import { IMessageService, CloseAction } from 'vs/platform/message/common/messag
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ShowRecommendedExtensionsAction } from './extensionsActions';
import Severity from 'vs/base/common/severity';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class ExtensionTipsService implements IExtensionTipsService {
@ -35,7 +37,8 @@ export class ExtensionTipsService implements IExtensionTipsService {
@IStorageService private storageService: IStorageService,
@IMessageService private messageService: IMessageService,
@IExtensionManagementService private extensionsService: IExtensionManagementService,
@IInstantiationService private instantiationService: IInstantiationService
@IInstantiationService private instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService
) {
if (!this._galleryService.isEnabled()) {
return;
@ -72,6 +75,11 @@ export class ExtensionTipsService implements IExtensionTipsService {
this._modelService.getModels().forEach(model => this._suggest(model.uri));
}
getWorkspaceRecommendations(): string[] {
let configuration = this.configurationService.getConfiguration<IExtensionsConfiguration>(EXTENSIONS_CONFIGURAION_NAME);
return configuration.recommendations ? configuration.recommendations : [];
}
getRecommendations(): string[] {
return Object.keys(this._recommendations);
}

View file

@ -21,13 +21,15 @@ import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/co
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { VIEWLET_ID, IExtensionsWorkbenchService } from './extensions';
import { ExtensionsWorkbenchService } from './extensionsWorkbenchService';
import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, UpdateAllAction, OpenExtensionsFolderAction } from './extensionsActions';
import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, UpdateAllAction, OpenExtensionsFolderAction, ConfigureWorkspaceRecommendationsAction } from './extensionsActions';
import { ExtensionsInput } from './extensionsInput';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { ExtensionEditor } from './extensionEditor';
import { StatusUpdater } from './extensionsViewlet';
import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import jsonContributionRegistry = require('vs/platform/jsonschemas/common/jsonContributionRegistry');
import { Schema, SchemaId } from 'vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate';
// Singletons
registerSingleton(IExtensionGalleryService, ExtensionGalleryService);
@ -104,6 +106,9 @@ actionRegistry.registerWorkbenchAction(listOutdatedActionDescriptor, 'Extensions
const recommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(recommendationsActionDescriptor, 'Extensions: Show Recommended Extensions', ExtensionsLabel);
const workspaceRecommendationsActionDescriptor = new SyncActionDescriptor(ShowWorkspaceRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction.ID, ShowWorkspaceRecommendedExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(workspaceRecommendationsActionDescriptor, 'Extensions: Show Workspace Recommended Extensions', ExtensionsLabel);
const popularActionDescriptor = new SyncActionDescriptor(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(popularActionDescriptor, 'Extensions: Show Popular Extensions', ExtensionsLabel);
@ -116,6 +121,9 @@ actionRegistry.registerWorkbenchAction(updateAllActionDescriptor, 'Extensions: U
const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL);
actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel);
const openExtensionsFileActionDescriptor = new SyncActionDescriptor(ConfigureWorkspaceRecommendationsAction, ConfigureWorkspaceRecommendationsAction.ID, ConfigureWorkspaceRecommendationsAction.LABEL);
actionRegistry.registerWorkbenchAction(openExtensionsFileActionDescriptor, 'Extensions: Open Extensions File', ExtensionsLabel);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
id: 'extensions',
@ -130,3 +138,6 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
}
}
});
const jsonRegistry = <jsonContributionRegistry.IJSONContributionRegistry>Registry.as(jsonContributionRegistry.Extensions.JSONContribution);
jsonRegistry.registerSchema(SchemaId, Schema);

View file

@ -59,8 +59,12 @@ export interface IExtensionsWorkbenchService {
canInstall(extension: IExtension): boolean;
install(extension: IExtension): TPromise<void>;
uninstall(extension: IExtension): TPromise<void>;
openExtensionsFile(sideBySide?: boolean): TPromise<void>;
}
export const EXTENSIONS_CONFIGURAION_NAME = 'extensions';
export interface IExtensionsConfiguration {
autoUpdate: boolean;
recommendations: string[];
}

View file

@ -458,6 +458,33 @@ export class ShowRecommendedExtensionsAction extends Action {
}
}
export class ShowWorkspaceRecommendedExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showWorkspaceRecommendedExtensions';
static LABEL = localize('showWorkspaceRecommendedExtensions', "Show Workspace Recommended Extensions");
constructor(
id: string,
label: string,
@IViewletService private viewletService: IViewletService
) {
super(id, label, null, true);
}
run(): TPromise<void> {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('@recommended:workspace');
viewlet.focus();
});
}
protected isEnabled(): boolean {
return true;
}
}
export class ChangeSortAction extends Action {
private query: Query;
@ -525,4 +552,17 @@ export class OpenExtensionsFolderAction extends Action {
protected isEnabled(): boolean {
return true;
}
}
export class ConfigureWorkspaceRecommendationsAction extends Action {
static ID = 'workbench.extensions.action.configureWorkspaceRecommendations';
static LABEL = localize('configureWorkspaceRecommendations', "Configure Worksapce Recommendations");
constructor(id: string, label: string, @IExtensionsWorkbenchService private extensionsService: IExtensionsWorkbenchService) {
super(id, label, null, true);
}
public run(event: any): TPromise<any> {
return this.extensionsService.openExtensionsFile();
}
}

View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* 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 { IJSONSchema } from 'vs/base/common/jsonSchema';
export const SchemaId = 'vscode://schemas/extensions';
export const Schema: IJSONSchema = {
id: SchemaId,
type: 'object',
title: localize('app.extensions.json.title', "Extensions"),
properties: {
recommendations: {
type: 'array',
description: localize('app.extensions.json.recommendations', "List of extension recommendations."),
items: {
'type': 'string',
}
}
}
};
export const Content: string = [
'{',
'\t// See https://go.microsoft.com/fwlink/?LinkId=733558',
'\t// for the documentation about the extensions.json format',
'\t"recommendations": [',
'\t\t',
'\t]',
'}'
].join('\n');

View file

@ -30,7 +30,7 @@ import { PagedList } from 'vs/base/browser/ui/list/listPaging';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Delegate, Renderer } from './extensionsList';
import { IExtensionsWorkbenchService, IExtension, IExtensionsViewlet, VIEWLET_ID, ExtensionState } from './extensions';
import { ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction } from './extensionsActions';
import { ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction } from './extensionsActions';
import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, SortBy, SortOrder, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionsInput } from './extensionsInput';
import { Query } from '../common/extensionQuery';
@ -168,6 +168,7 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowWorkspaceRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction.ID, ShowWorkspaceRecommendedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL),
new Separator(),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Sort By: Install Count"), this.onSearchChange, 'installs', undefined),
@ -230,6 +231,10 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
case 'desc': options = assign(options, { sortOrder: SortOrder.Descending }); break;
}
if (/@recommended:workspace/i.test(query.value)) {
return this.queryWorkspaceRecommendations();
}
if (/@recommended/i.test(query.value)) {
const value = query.value.replace(/@recommended/g, '').trim().toLowerCase();
@ -257,6 +262,15 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
.then(result => new PagedModel(result));
}
private queryWorkspaceRecommendations(): TPromise<PagedModel<IExtension>> {
let names = this.tipsService.getWorkspaceRecommendations();
if (!names.length) {
return TPromise.as(new PagedModel([]));
}
return this.extensionsWorkbenchService.queryGallery({ names, pageSize: names.length })
.then(result => new PagedModel(result));
}
private openExtension(extension: IExtension): void {
this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension))
.done(null, err => this.onError(err));

View file

@ -6,6 +6,8 @@
'use strict';
import 'vs/css!./media/extensionsViewlet';
import { localize } from 'vs/nls';
import paths = require('vs/base/common/paths');
import Event, { Emitter } from 'vs/base/common/event';
import { index } from 'vs/base/common/arrays';
import { assign } from 'vs/base/common/objects';
@ -21,15 +23,18 @@ import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IMessageService } from 'vs/platform/message/common/message';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import Severity from 'vs/base/common/severity';
import * as semver from 'semver';
import * as path from 'path';
import URI from 'vs/base/common/uri';
import { readFile } from 'vs/base/node/pfs';
import { asText } from 'vs/base/node/request';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, IExtensionsConfiguration } from './extensions';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, IExtensionsConfiguration, EXTENSIONS_CONFIGURAION_NAME } from './extensions';
import { UpdateAllAction } from './extensionsActions';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Content } from 'vs/workbench/parts/extensions/electron-browser/extensionsFileTemplate';
interface IExtensionStateProvider {
(extension: Extension): ExtensionState;
@ -252,6 +257,9 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IFileService private fileService: IFileService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IExtensionManagementService private extensionService: IExtensionManagementService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IConfigurationService private configurationService: IConfigurationService,
@ -338,7 +346,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
return this.queryGallery({ ids, pageSize: ids.length }).then(() => {
const config = this.configurationService.getConfiguration<IExtensionsConfiguration>('extensions');
const config = this.configurationService.getConfiguration<IExtensionsConfiguration>(EXTENSIONS_CONFIGURAION_NAME);
if (!config.autoUpdate) {
return;
@ -502,6 +510,36 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
this.messageService.show(Severity.Error, err);
}
openExtensionsFile(sideBySide?: boolean): TPromise<any> {
if (!this.contextService.getWorkspace()) {
this.messageService.show(Severity.Info, localize('ConfigureWorkspaceRecommendations.noWorkspace', 'Recommendations are only available on a workspace folder.'));
return TPromise.as(undefined);
}
return this.getOrCreateExtensionsFile().then(value => {
return this.editorService.openEditor({
resource: value.extensionsFileResource,
options: {
forceOpen: true,
pinned: value.created
},
}, sideBySide);
}, (error) => {
throw new Error(localize('OpenExtensionsFile.failed', "Unable to create 'extensions.json' file inside the '.vscode' folder ({0}).", error));
});
}
private getOrCreateExtensionsFile(): TPromise<{ created: boolean, extensionsFileResource: URI }> {
let extensionsFileResource = URI.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '/.vscode/' + EXTENSIONS_CONFIGURAION_NAME + '.json'));
return this.fileService.resolveContent(extensionsFileResource).then(content => {
return { created: false, extensionsFileResource };
}, err => {
return this.fileService.updateContent(extensionsFileResource, Content).then(() => {
return { created: true, extensionsFileResource };
});
});
}
dispose(): void {
this.disposables = dispose(this.disposables);
}