Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding suggestion to search for extension for files are of unknown mime type #40269

Merged
merged 4 commits into from
Jan 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,18 @@ import * as pfs from 'vs/base/node/pfs';
import * as os from 'os';
import { flatten, distinct } from 'vs/base/common/arrays';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime';
import { ShowLanguageExtensionsAction } from 'vs/workbench/browser/parts/editor/editorStatus';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';

interface IExtensionsContent {
recommendations: string[];
}

const empty: { [key: string]: any; } = Object.create(null);
const milliSecondsInADay = 1000 * 60 * 60 * 24;
const choiceNever = localize('neverShowAgain', "Don't show again");
const choiceClose = localize('close', "Close");

export class ExtensionTipsService extends Disposable implements IExtensionTipsService {

Expand All @@ -61,15 +66,19 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
@IConfigurationService private configurationService: IConfigurationService,
@IMessageService private messageService: IMessageService,
@ITelemetryService private telemetryService: ITelemetryService,
@IEnvironmentService private environmentService: IEnvironmentService
@IEnvironmentService private environmentService: IEnvironmentService,
@ILifecycleService private lifecycleService: ILifecycleService
) {
super();

if (!this._galleryService.isEnabled() || this.environmentService.extensionDevelopmentPath) {
return;
}

this._suggestFileBasedRecommendations();
this.lifecycleService.when(LifecyclePhase.Eventually).then(() => {
this._suggestFileBasedRecommendations();
});

this.promptWorkspaceRecommendationsPromise = this._suggestWorkspaceRecommendations();

// Executable based recommendations carry out a lot of file stats, so run them after 10 secs
Expand Down Expand Up @@ -256,6 +265,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe

private _suggest(model: ITextModel): void {
const uri = model.uri;
let hasSuggestion = false;

if (!uri || uri.scheme !== Schemas.file) {
return;
Expand All @@ -282,21 +292,25 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
);

const config = this.configurationService.getValue<IExtensionsConfiguration>(ConfigurationKey);
if (config.ignoreRecommendations) {
return;
}

const importantRecommendationsIgnoreList = <string[]>JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]'));
let recommendationsToSuggest = Object.keys(product.extensionImportantTips || [])
.filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1 && match(product.extensionImportantTips[id]['pattern'], uri.fsPath));
if (config.ignoreRecommendations || !recommendationsToSuggest.length) {
return;
}

this.extensionsService.getInstalled(LocalExtensionType.User).done(local => {
const importantTipsPromise = recommendationsToSuggest.length === 0 ? TPromise.as(null) : this.extensionsService.getInstalled(LocalExtensionType.User).then(local => {
recommendationsToSuggest = recommendationsToSuggest.filter(id => local.every(local => `${local.manifest.publisher}.${local.manifest.name}` !== id));
if (!recommendationsToSuggest.length) {
return;
}
const id = recommendationsToSuggest[0];
const name = product.extensionImportantTips[id]['name'];

// Indicates we have a suggested extension via the whitelist
hasSuggestion = true;

let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", name);
// Temporary fix for the only extension pack we recommend. See https://github.com/Microsoft/vscode/issues/35364
if (id === 'vscjava.vscode-java-pack') {
Expand All @@ -308,8 +322,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
const options = [
localize('install', 'Install'),
recommendationsAction.label,
localize('neverShowAgain', "Don't Show Again"),
localize('close', "Close")
choiceNever,
choiceClose
];

this.choiceService.choose(Severity.Info, message, options, 3).done(choice => {
Expand Down Expand Up @@ -364,7 +378,82 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name });
});
});

importantTipsPromise.then(() => {
const fileExtensionSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get
('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]'));
let mimeTypes = guessMimeTypes(uri.fsPath);
let fileExtension = paths.extname(uri.fsPath);
if (fileExtension) {
fileExtension = fileExtension.substr(1); // Strip the dot
}

if (hasSuggestion ||
!fileExtension ||
mimeTypes.length !== 1 ||
mimeTypes[0] !== MIME_UNKNOWN ||
fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1
) {
return;
}

const keywords = this.getKeywordsForExtension(fileExtension);
this._galleryService.query({ text: `tag:"__ext_${fileExtension}" ${keywords.map(tag => `tag:"${tag}"`)}` }).then(pager => {
if (!pager || !pager.firstPage || !pager.firstPage.length) {
return;
}

// Suggest the search only once as this is not a strong recommendation
fileExtensionSuggestionIgnoreList.push(fileExtension);
this.storageService.store(
'extensionsAssistant/fileExtensionsSuggestionIgnore',
JSON.stringify(fileExtensionSuggestionIgnoreList),
StorageScope.GLOBAL
);


const message = localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension);

const searchMarketplaceAction = this.instantiationService.createInstance(ShowLanguageExtensionsAction, fileExtension);

const options = [
localize('searchMarketplace', "Search Marketplace"),
choiceClose
];

this.choiceService.choose(Severity.Info, message, options, 1).done(choice => {
switch (choice) {
case 0:
/* __GDPR__
"fileExtensionSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension: fileExtension });
searchMarketplaceAction.run();
break;
case 1:
/* __GDPR__
"fileExtensionSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'close', fileExtension: fileExtension });
break;
}
}, () => {
/* __GDPR__
"fileExtensionSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension: fileExtension });
});
});
});
});
}
Expand Down Expand Up @@ -393,8 +482,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
const options = [
installAllAction.label,
showAction.label,
localize('neverShowAgain', "Don't Show Again"),
localize('close', "Close")
choiceNever,
choiceClose
];

return this.choiceService.choose(Severity.Info, message, options, 3).done(choice => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { IChoiceService } from 'vs/platform/message/common/message';
import product from 'vs/platform/node/product';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';

const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {
Expand Down Expand Up @@ -179,7 +180,7 @@ suite('ExtensionsTipsService Test', () => {
uninstallEvent = new Emitter<IExtensionIdentifier>();
didUninstallEvent = new Emitter<DidUninstallExtensionEvent>();
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);

instantiationService.stub(ILifecycleService, new TestLifecycleService());
testConfigurationService = new TestConfigurationService();
instantiationService.stub(IConfigurationService, testConfigurationService);
instantiationService.stub(IExtensionManagementService, ExtensionManagementService);
Expand Down