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", "fileMatch": "%APP_SETTINGS_HOME%/snippets/*.json",
"url": "vscode://schemas/snippets" "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 { export interface IExtensionTipsService {
_serviceBrand: any; _serviceBrand: any;
getRecommendations(): string[]; getRecommendations(): string[];
getWorkspaceRecommendations(): string[];
} }
export const ExtensionsLabel = nls.localize('extensions', "Extensions"); 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 {Action} from 'vs/base/common/actions';
import {match} from 'vs/base/common/glob'; import {match} from 'vs/base/common/glob';
import {IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService} from 'vs/platform/extensionManagement/common/extensionManagement'; 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 {IModelService} from 'vs/editor/common/services/modelService';
import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage';
import product from 'vs/platform/product'; 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ShowRecommendedExtensionsAction } from './extensionsActions'; import { ShowRecommendedExtensionsAction } from './extensionsActions';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class ExtensionTipsService implements IExtensionTipsService { export class ExtensionTipsService implements IExtensionTipsService {
@ -35,7 +37,8 @@ export class ExtensionTipsService implements IExtensionTipsService {
@IStorageService private storageService: IStorageService, @IStorageService private storageService: IStorageService,
@IMessageService private messageService: IMessageService, @IMessageService private messageService: IMessageService,
@IExtensionManagementService private extensionsService: IExtensionManagementService, @IExtensionManagementService private extensionsService: IExtensionManagementService,
@IInstantiationService private instantiationService: IInstantiationService @IInstantiationService private instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService
) { ) {
if (!this._galleryService.isEnabled()) { if (!this._galleryService.isEnabled()) {
return; return;
@ -72,6 +75,11 @@ export class ExtensionTipsService implements IExtensionTipsService {
this._modelService.getModels().forEach(model => this._suggest(model.uri)); 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[] { getRecommendations(): string[] {
return Object.keys(this._recommendations); 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 { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { VIEWLET_ID, IExtensionsWorkbenchService } from './extensions'; import { VIEWLET_ID, IExtensionsWorkbenchService } from './extensions';
import { ExtensionsWorkbenchService } from './extensionsWorkbenchService'; 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 { ExtensionsInput } from './extensionsInput';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { ExtensionEditor } from './extensionEditor'; import { ExtensionEditor } from './extensionEditor';
import { StatusUpdater } from './extensionsViewlet'; import { StatusUpdater } from './extensionsViewlet';
import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; 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 // Singletons
registerSingleton(IExtensionGalleryService, ExtensionGalleryService); registerSingleton(IExtensionGalleryService, ExtensionGalleryService);
@ -104,6 +106,9 @@ actionRegistry.registerWorkbenchAction(listOutdatedActionDescriptor, 'Extensions
const recommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL); const recommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(recommendationsActionDescriptor, 'Extensions: Show Recommended Extensions', ExtensionsLabel); 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); const popularActionDescriptor = new SyncActionDescriptor(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(popularActionDescriptor, 'Extensions: Show Popular Extensions', ExtensionsLabel); 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); const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL);
actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); 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) Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({ .registerConfiguration({
id: 'extensions', 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; canInstall(extension: IExtension): boolean;
install(extension: IExtension): TPromise<void>; install(extension: IExtension): TPromise<void>;
uninstall(extension: IExtension): TPromise<void>; uninstall(extension: IExtension): TPromise<void>;
openExtensionsFile(sideBySide?: boolean): TPromise<void>;
} }
export const EXTENSIONS_CONFIGURAION_NAME = 'extensions';
export interface IExtensionsConfiguration { export interface IExtensionsConfiguration {
autoUpdate: boolean; 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 { export class ChangeSortAction extends Action {
private query: Query; private query: Query;
@ -525,4 +552,17 @@ export class OpenExtensionsFolderAction extends Action {
protected isEnabled(): boolean { protected isEnabled(): boolean {
return true; 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 { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Delegate, Renderer } from './extensionsList'; import { Delegate, Renderer } from './extensionsList';
import { IExtensionsWorkbenchService, IExtension, IExtensionsViewlet, VIEWLET_ID, ExtensionState } from './extensions'; 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 { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, SortBy, SortOrder, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionsInput } from './extensionsInput'; import { ExtensionsInput } from './extensionsInput';
import { Query } from '../common/extensionQuery'; 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(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL), this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.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), this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL),
new Separator(), new Separator(),
this.instantiationService.createInstance(ChangeSortAction, 'extensions.sort.install', localize('sort by installs', "Sort By: Install Count"), this.onSearchChange, 'installs', undefined), 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; 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)) { if (/@recommended/i.test(query.value)) {
const value = query.value.replace(/@recommended/g, '').trim().toLowerCase(); 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)); .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 { private openExtension(extension: IExtension): void {
this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension)) this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension))
.done(null, err => this.onError(err)); .done(null, err => this.onError(err));

View file

@ -6,6 +6,8 @@
'use strict'; 'use strict';
import 'vs/css!./media/extensionsViewlet'; 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 Event, { Emitter } from 'vs/base/common/event';
import { index } from 'vs/base/common/arrays'; import { index } from 'vs/base/common/arrays';
import { assign } from 'vs/base/common/objects'; import { assign } from 'vs/base/common/objects';
@ -21,15 +23,18 @@ import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IMessageService } from 'vs/platform/message/common/message'; import { IMessageService } from 'vs/platform/message/common/message';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import * as semver from 'semver'; import * as semver from 'semver';
import * as path from 'path'; import * as path from 'path';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import { readFile } from 'vs/base/node/pfs'; import { readFile } from 'vs/base/node/pfs';
import { asText } from 'vs/base/node/request'; 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 { 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 { interface IExtensionStateProvider {
(extension: Extension): ExtensionState; (extension: Extension): ExtensionState;
@ -252,6 +257,9 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
constructor( constructor(
@IInstantiationService private instantiationService: IInstantiationService, @IInstantiationService private instantiationService: IInstantiationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IFileService private fileService: IFileService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IExtensionManagementService private extensionService: IExtensionManagementService, @IExtensionManagementService private extensionService: IExtensionManagementService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService, @IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IConfigurationService private configurationService: IConfigurationService, @IConfigurationService private configurationService: IConfigurationService,
@ -338,7 +346,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
} }
return this.queryGallery({ ids, pageSize: ids.length }).then(() => { 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) { if (!config.autoUpdate) {
return; return;
@ -502,6 +510,36 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
this.messageService.show(Severity.Error, err); 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 { dispose(): void {
this.disposables = dispose(this.disposables); this.disposables = dispose(this.disposables);
} }