提交 9c51f1a8 编写于 作者: J Joao Moreno

remove extension tips status bar contribution

fixes #3633
上级 9f881a08
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'vs/text!vs/workbench/parts/extensions/electron-browser/extensionTips.json'; import 'vs/text!vs/workbench/parts/extensions/electron-browser/extensionTips.json';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import {toObject} from 'vs/base/common/objects';
import {values, forEach} from 'vs/base/common/collections'; import {values, forEach} from 'vs/base/common/collections';
import {IDisposable, disposeAll} from 'vs/base/common/lifecycle'; import {IDisposable, disposeAll} from 'vs/base/common/lifecycle';
import {TPromise as Promise} from 'vs/base/common/winjs.base'; import {TPromise as Promise} from 'vs/base/common/winjs.base';
...@@ -30,8 +31,8 @@ enum ExtensionTipReasons { ...@@ -30,8 +31,8 @@ enum ExtensionTipReasons {
class ExtensionTip { class ExtensionTip {
private _resources: { [uri: string]: ExtensionTipReasons } = Object.create(null); private resources: { [uri: string]: ExtensionTipReasons } = Object.create(null);
private _touched = Date.now(); private touched = Date.now();
private _score = -1; private _score = -1;
constructor(public extension: IExtension) { constructor(public extension: IExtension) {
...@@ -39,9 +40,9 @@ class ExtensionTip { ...@@ -39,9 +40,9 @@ class ExtensionTip {
} }
resource(uri: URI, reason: ExtensionTipReasons): boolean { resource(uri: URI, reason: ExtensionTipReasons): boolean {
if (reason !== this._resources[uri.toString()]) { if (reason !== this.resources[uri.toString()]) {
this._touched = Date.now(); this.touched = Date.now();
this._resources[uri.toString()] = Math.max((this._resources[uri.toString()] || 0), reason); this.resources[uri.toString()] = Math.max((this.resources[uri.toString()] || 0), reason);
this._score = - 1; this._score = - 1;
return true; return true;
} }
...@@ -49,7 +50,7 @@ class ExtensionTip { ...@@ -49,7 +50,7 @@ class ExtensionTip {
get score() { get score() {
if (this._score === -1) { if (this._score === -1) {
forEach(this._resources, entry => this._score += entry.value); forEach(this.resources, entry => this._score += entry.value);
} }
return this._score; return this._score;
} }
...@@ -58,7 +59,7 @@ class ExtensionTip { ...@@ -58,7 +59,7 @@ class ExtensionTip {
if (this === tip) { if (this === tip) {
return 0; return 0;
} }
let result = tip._touched - this._touched; let result = tip.touched - this.touched;
if (result === 0) { if (result === 0) {
result = tip.score - this.score; result = tip.score - this.score;
} }
...@@ -72,20 +73,20 @@ export class ExtensionTipsService implements IExtensionTipsService { ...@@ -72,20 +73,20 @@ export class ExtensionTipsService implements IExtensionTipsService {
private _onDidChangeTips: Emitter<IExtension[]> = new Emitter<IExtension[]>(); private _onDidChangeTips: Emitter<IExtension[]> = new Emitter<IExtension[]>();
private _tips: { [id: string]: ExtensionTip } = Object.create(null); private _tips: { [id: string]: ExtensionTip } = Object.create(null);
private _disposables: IDisposable[] = []; private disposables: IDisposable[] = [];
private _availableExtensions: Promise<ExtensionMap>; private availableExtensions: Promise<ExtensionMap>;
private _extensionData: Promise<ExtensionData>; private extensionData: Promise<ExtensionData>;
constructor( constructor(
@IExtensionsService private _extensionService: IExtensionsService, @IExtensionsService private extensionService: IExtensionsService,
@IGalleryService private _galleryService: IGalleryService, @IGalleryService private galleryService: IGalleryService,
@IModelService private _modelService: IModelService @IModelService private modelService: IModelService
) { ) {
this._init(); this.init();
} }
dispose() { dispose() {
this._disposables = disposeAll(this._disposables); this.disposables = disposeAll(this.disposables);
} }
get onDidChangeTips(): Event<IExtension[]> { get onDidChangeTips(): Event<IExtension[]> {
...@@ -100,48 +101,35 @@ export class ExtensionTipsService implements IExtensionTipsService { ...@@ -100,48 +101,35 @@ export class ExtensionTipsService implements IExtensionTipsService {
// --- internals // --- internals
private _init():void { private init():void {
if (!this._galleryService.isEnabled()) { if (!this.galleryService.isEnabled()) {
return; return;
} }
this._extensionData = new Promise((resolve, reject) => { this.extensionData = new Promise((resolve, reject) => {
require(['vs/text!vs/workbench/parts/extensions/electron-browser/extensionTips.json'], require(['vs/text!vs/workbench/parts/extensions/electron-browser/extensionTips.json'],
data => resolve(JSON.parse(data)), data => resolve(JSON.parse(data)),
reject); reject);
}); });
this._availableExtensions = this._getAvailableExtensions(); this.availableExtensions = this.getAvailableExtensions();
// don't suggest what got installed
this._disposables.push(this._extensionService.onDidInstallExtension(ext => {
const id = `${ext.publisher}.${ext.name}`;
let change = false;
if (delete this._tips[id]) {
change = true;
}
if (change) {
this._onDidChangeTips.fire(this.tips);
}
this._availableExtensions = this._getAvailableExtensions();
}));
// we listen for editor models being added and changed // we listen for editor models being added and changed
// when a model is added it gives 2 points, a change gives 3 points // when a model is added it gives 2 points, a change gives 3 points
// such that files you type have bigger impact on the suggest // such that files you type have bigger impact on the suggest
// order than those you only look at // order than those you only look at
const modelListener: { [uri: string]: IDisposable } = Object.create(null); const modelListener: { [uri: string]: IDisposable } = Object.create(null);
this._disposables.push({ dispose() { disposeAll(values(modelListener)); } }); this.disposables.push({ dispose() { disposeAll(values(modelListener)); } });
this._disposables.push(this._modelService.onModelAdded(model => { this.disposables.push(this.modelService.onModelAdded(model => {
const uri = model.getAssociatedResource(); const uri = model.getAssociatedResource();
this._suggestByResource(uri, ExtensionTipReasons.FileOpened); this.suggestByResource(uri, ExtensionTipReasons.FileOpened);
modelListener[uri.toString()] = model.addListener2(EventType.ModelContentChanged2, modelListener[uri.toString()] = model.addListener2(EventType.ModelContentChanged2,
() => this._suggestByResource(uri, ExtensionTipReasons.FileEdited)); () => this.suggestByResource(uri, ExtensionTipReasons.FileEdited));
})); }));
this._disposables.push(this._modelService.onModelRemoved(model => { this.disposables.push(this.modelService.onModelRemoved(model => {
const subscription = modelListener[model.getAssociatedResource().toString()]; const subscription = modelListener[model.getAssociatedResource().toString()];
if (subscription) { if (subscription) {
subscription.dispose(); subscription.dispose();
...@@ -149,38 +137,25 @@ export class ExtensionTipsService implements IExtensionTipsService { ...@@ -149,38 +137,25 @@ export class ExtensionTipsService implements IExtensionTipsService {
} }
})); }));
for (let model of this._modelService.getModels()) { for (let model of this.modelService.getModels()) {
this._suggestByResource(model.getAssociatedResource(), ExtensionTipReasons.FileOpened); this.suggestByResource(model.getAssociatedResource(), ExtensionTipReasons.FileOpened);
} }
} }
private _getAvailableExtensions(): Promise<ExtensionMap> { private getAvailableExtensions(): Promise<ExtensionMap> {
return this._galleryService.query().then(extensions => { return this.galleryService.query()
let map: ExtensionMap = Object.create(null); .then(null, () => [])
for (let ext of extensions) { .then(extensions => toObject(extensions, ext => `${ext.publisher}.${ext.name}`));
map[`${ext.publisher}.${ext.name}`] = ext;
}
return this._extensionService.getInstalled().then(installed => {
for (let ext of installed) {
delete map[`${ext.publisher}.${ext.name}`];
}
return map;
});
}, () => {
return Object.create(null);
});
} }
// --- suggest logic // --- suggest logic
private _suggestByResource(uri: URI, reason: ExtensionTipReasons): Promise<any> { private suggestByResource(uri: URI, reason: ExtensionTipReasons): Promise<any> {
if (!uri) { if (!uri) {
return; return;
} }
Promise.join<any>([this._availableExtensions, this._extensionData]).then(all => { Promise.join<any>([this.availableExtensions, this.extensionData]).then(all => {
let extensions = <ExtensionMap>all[0]; let extensions = <ExtensionMap>all[0];
let data = <ExtensionData>all[1]; let data = <ExtensionData>all[1];
......
...@@ -3,16 +3,14 @@ ...@@ -3,16 +3,14 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import nls = require('vs/nls');
import platform = require('vs/platform/platform'); import platform = require('vs/platform/platform');
import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import statusbar = require('vs/workbench/browser/parts/statusbar/statusbar'); import statusbar = require('vs/workbench/browser/parts/statusbar/statusbar');
import { ExtensionsStatusbarItem, ExtensionTipsStatusbarItem } from 'vs/workbench/parts/extensions/electron-browser/extensionsWidgets'; import { ExtensionsStatusbarItem } from 'vs/workbench/parts/extensions/electron-browser/extensionsWidgets';
import { IGalleryService } from 'vs/workbench/parts/extensions/common/extensions'; import { IGalleryService } from 'vs/workbench/parts/extensions/common/extensions';
import { GalleryService } from 'vs/workbench/parts/extensions/node/vsoGalleryService'; import { GalleryService } from 'vs/workbench/parts/extensions/node/vsoGalleryService';
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
import { ExtensionsWorkbenchExtension } from 'vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension'; import { ExtensionsWorkbenchExtension } from 'vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchExtension';
import ConfigurationRegistry = require('vs/platform/configuration/common/configurationRegistry');
// Register Gallery Service // Register Gallery Service
registerSingleton(IGalleryService, GalleryService); registerSingleton(IGalleryService, GalleryService);
...@@ -28,23 +26,3 @@ registerSingleton(IGalleryService, GalleryService); ...@@ -28,23 +26,3 @@ registerSingleton(IGalleryService, GalleryService);
statusbar.StatusbarAlignment.LEFT, statusbar.StatusbarAlignment.LEFT,
10 /* Low Priority */ 10 /* Low Priority */
)); ));
// Register Statusbar item
(<statusbar.IStatusbarRegistry>platform.Registry.as(statusbar.Extensions.Statusbar)).registerStatusbarItem(new statusbar.StatusbarItemDescriptor(
ExtensionTipsStatusbarItem,
statusbar.StatusbarAlignment.LEFT,
9 /* Low Priority */
));
(<ConfigurationRegistry.IConfigurationRegistry>platform.Registry.as(ConfigurationRegistry.Extensions.Configuration)).registerConfiguration({
id: 'extensions',
type: 'object',
properties: {
'extensions.showTips': {
type: 'boolean',
default: true,
description: nls.localize('extConfig', "Suggest extensions based on changed and open files."),
}
}
});
...@@ -86,7 +86,7 @@ export class ListOutdatedExtensionsAction extends Action { ...@@ -86,7 +86,7 @@ export class ListOutdatedExtensionsAction extends Action {
export class ListSuggestedExtensionsAction extends Action { export class ListSuggestedExtensionsAction extends Action {
static ID = 'workbench.extensions.action.listSuggestedExtensions'; static ID = 'workbench.extensions.action.listSuggestedExtensions';
static LABEL = nls.localize('showExtensionTips', "Show Extension Tips"); static LABEL = nls.localize('showExtensionRecommendations', "Show Extension Recommendations");
constructor( constructor(
id: string, id: string,
......
...@@ -18,7 +18,6 @@ import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; ...@@ -18,7 +18,6 @@ import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { IExtensionsService, IGalleryService, IExtensionTipsService, IExtension } from 'vs/workbench/parts/extensions/common/extensions'; import { IExtensionsService, IGalleryService, IExtensionTipsService, IExtension } from 'vs/workbench/parts/extensions/common/extensions';
import { InstallAction, UninstallAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; import { InstallAction, UninstallAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IMessageService } from 'vs/platform/message/common/message'; import { IMessageService } from 'vs/platform/message/common/message';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
...@@ -559,6 +558,7 @@ class SuggestedExtensionsModel implements IModel<IExtensionEntry> { ...@@ -559,6 +558,7 @@ class SuggestedExtensionsModel implements IModel<IExtensionEntry> {
constructor( constructor(
private suggestedExtensions: IExtension[], private suggestedExtensions: IExtension[],
private localExtensions: IExtension[],
@IInstantiationService instantiationService: IInstantiationService @IInstantiationService instantiationService: IInstantiationService
) { ) {
this.renderer = instantiationService.createInstance(Renderer); this.renderer = instantiationService.createInstance(Renderer);
...@@ -569,9 +569,11 @@ class SuggestedExtensionsModel implements IModel<IExtensionEntry> { ...@@ -569,9 +569,11 @@ class SuggestedExtensionsModel implements IModel<IExtensionEntry> {
public set input(input: string) { public set input(input: string) {
this.entries = this.suggestedExtensions this.entries = this.suggestedExtensions
.map(extension => ({ extension, highlights: getHighlights(input.trim(), extension) })) .map(extension => ({ extension, highlights: getHighlights(input.trim(), extension) }))
.filter(({ highlights }) => !!highlights) .filter(({ extension, highlights }) => {
const local = this.localExtensions.filter(local => extensionEquals(local, extension))[0];
return !local && !!highlights;
})
.map(({ extension, highlights }: { extension: IExtension, highlights: IHighlights }) => { .map(({ extension, highlights }: { extension: IExtension, highlights: IHighlights }) => {
return { return {
extension, extension,
highlights, highlights,
...@@ -590,30 +592,22 @@ export class SuggestedExtensionHandler extends QuickOpenHandler { ...@@ -590,30 +592,22 @@ export class SuggestedExtensionHandler extends QuickOpenHandler {
@IExtensionTipsService private extensionTipsService: IExtensionTipsService, @IExtensionTipsService private extensionTipsService: IExtensionTipsService,
@IInstantiationService private instantiationService: IInstantiationService, @IInstantiationService private instantiationService: IInstantiationService,
@ITelemetryService private telemetryService: ITelemetryService, @ITelemetryService private telemetryService: ITelemetryService,
@IExtensionsService extensionService: IExtensionsService, @IExtensionsService private extensionsService: IExtensionsService
@ILifecycleService lifecycleService:ILifecycleService
) { ) {
super(); super();
const subscription = extensionService.onInstallExtension(manifest => {
if (this.model) { // indicates that tips currently show
this.telemetryService.publicLog('extensionGallery:tips', { installed: true });
}
});
lifecycleService.onShutdown(() => subscription.dispose());
} }
getResults(input: string): TPromise<IModel<IExtensionEntry>> { getResults(input: string): TPromise<IModel<IExtensionEntry>> {
if (!this.model) { return this.extensionsService.getInstalled().then(localExtensions => {
const {tips} = this.extensionTipsService; const model = this.instantiationService.createInstance(
this.telemetryService.publicLog('extensionGallery:tips', { count: tips.length });
this.model = this.instantiationService.createInstance(
SuggestedExtensionsModel, SuggestedExtensionsModel,
tips); this.extensionTipsService.tips,
} localExtensions
this.model.input = input; );
return TPromise.as(this.model);
model.input = input;
return model;
});
} }
onClose(canceled: boolean): void { onClose(canceled: boolean): void {
...@@ -621,7 +615,7 @@ export class SuggestedExtensionHandler extends QuickOpenHandler { ...@@ -621,7 +615,7 @@ export class SuggestedExtensionHandler extends QuickOpenHandler {
} }
getEmptyLabel(input: string): string { getEmptyLabel(input: string): string {
return nls.localize('noSuggestedExtensions', "No suggested extensions"); return nls.localize('noRecommendedExtensions', "No recommended extensions");
} }
getAutoFocus(searchValue: string): IAutoFocus { getAutoFocus(searchValue: string): IAutoFocus {
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
import nls = require('vs/nls'); import nls = require('vs/nls');
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import {forEach} from 'vs/base/common/collections';
import dom = require('vs/base/browser/dom'); import dom = require('vs/base/browser/dom');
import lifecycle = require('vs/base/common/lifecycle'); import lifecycle = require('vs/base/common/lifecycle');
import {onUnexpectedError} from 'vs/base/common/errors'; import {onUnexpectedError} from 'vs/base/common/errors';
...@@ -13,14 +12,9 @@ import { Action } from 'vs/base/common/actions'; ...@@ -13,14 +12,9 @@ import { Action } from 'vs/base/common/actions';
import statusbar = require('vs/workbench/browser/parts/statusbar/statusbar'); import statusbar = require('vs/workbench/browser/parts/statusbar/statusbar');
import { IExtensionService, IExtensionsStatus } from 'vs/platform/extensions/common/extensions'; import { IExtensionService, IExtensionsStatus } from 'vs/platform/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import { IMessageService, CloseAction } from 'vs/platform/message/common/message'; import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { UninstallAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; import { UninstallAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { IQuickOpenService } from 'vs/workbench/services/quickopen/common/quickOpenService'; import { IExtensionsService } from 'vs/workbench/parts/extensions/common/extensions';
import { IExtensionsService, IExtension, IExtensionTipsService } from 'vs/workbench/parts/extensions/common/extensions';
import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel';
var $ = dom.emmet; var $ = dom.emmet;
...@@ -88,90 +82,3 @@ export class ExtensionsStatusbarItem implements statusbar.IStatusbarItem { ...@@ -88,90 +82,3 @@ export class ExtensionsStatusbarItem implements statusbar.IStatusbarItem {
}; };
} }
} }
\ No newline at end of file
export class ExtensionTipsStatusbarItem implements statusbar.IStatusbarItem {
private static _dontSuggestAgainTimeout = 1000 * 60 * 60 * 24 * 28; // 4 wks
private _domNode: HTMLElement;
private _label: OcticonLabel;
private _previousTips: { [id: string]: number };
constructor(
@IQuickOpenService private _quickOpenService: IQuickOpenService,
@IExtensionTipsService private _extensionTipsService: IExtensionTipsService,
@IStorageService private _storageService: IStorageService,
@IConfigurationService private _configurationService: IConfigurationService,
@ITelemetryService private _telemetryService: ITelemetryService
) {
// previously shown tips, not older than 28 days
this._previousTips = JSON.parse(this._storageService.get('extensionsAssistant/tips', StorageScope.GLOBAL, '{}'));
const now = Date.now();
forEach(this._previousTips, (entry, rm) => {
if (now - entry.value > ExtensionTipsStatusbarItem._dontSuggestAgainTimeout) {
rm();
}
});
// show/hide tips depending on configuration
let localDispose: lifecycle.Disposable[] = [];
let update = () => {
localDispose = lifecycle.disposeAll(localDispose);
this._configurationService.loadConfiguration('extensions').then(value => {
if (value && value.showTips === true) {
this._extensionTipsService.onDidChangeTips(this._onTips, this, localDispose);
this._onTips(this._extensionTipsService.tips);
} else {
this._onTips([]);
}
}, onUnexpectedError);
this._configurationService.onDidUpdateConfiguration(update, this, localDispose);
};
update();
}
private _onTips(tips: IExtension[]): void {
if (!this._domNode) {
return;
}
if (tips.length === 0) {
dom.addClass(this._domNode, 'disabled');
return;
}
function extid(ext: IExtension): string {
return `${ext.publisher}.${ext.name}@${ext.version}`;
}
// check for new tips
let hasNewTips = false;
for (let tip of tips) {
const id = extid(tip);
if (!this._previousTips[id]) {
this._previousTips[id] = Date.now();
hasNewTips = true;
}
}
if (hasNewTips) {
dom.removeClass(this._domNode, 'disabled');
this._telemetryService.publicLog('extensionGallery:tips', { hintingTips: true });
}
}
public render(container: HTMLElement): lifecycle.IDisposable {
this._domNode = document.createElement('a');
this._domNode.className = 'extensions-suggestions disabled';
this._label = new OcticonLabel(this._domNode);
this._label.text = '$(light-bulb) extension tips';
container.appendChild(this._domNode);
return dom.addDisposableListener(this._domNode, 'click', event => this._onClick(event));
}
private _onClick(event: MouseEvent): void {
this._storageService.store('extensionsAssistant/tips', JSON.stringify(this._previousTips), StorageScope.GLOBAL);
this._telemetryService.publicLog('extensionGallery:tips', { revealingTips: true });
this._quickOpenService.show('ext tips ').then(() => dom.addClass(this._domNode, 'disabled'));
}
}
\ No newline at end of file
...@@ -92,7 +92,7 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution { ...@@ -92,7 +92,7 @@ export class ExtensionsWorkbenchExtension implements IWorkbenchContribution {
'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen', 'vs/workbench/parts/extensions/electron-browser/extensionsQuickOpen',
'SuggestedExtensionHandler', 'SuggestedExtensionHandler',
'ext tips ', 'ext tips ',
nls.localize('suggestedExtensionsCommands', "Show Extension Tips") nls.localize('suggestedExtensionsCommands', "Show Extension Recommendations")
) )
); );
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册