提交 bf7a2338 编写于 作者: C Christof Marti

Add keymaps the top-level Preferences menu (fixes #15159)

上级 03d9bb3a
......@@ -388,6 +388,7 @@ export class VSCodeMenu {
const userSettings = this.createMenuItem(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&User Settings"), 'workbench.action.openGlobalSettings');
const workspaceSettings = this.createMenuItem(nls.localize({ key: 'miOpenWorkspaceSettings', comment: ['&& denotes a mnemonic'] }, "&&Workspace Settings"), 'workbench.action.openWorkspaceSettings');
const kebindingSettings = this.createMenuItem(nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts"), 'workbench.action.openGlobalKeybindings');
const keymapExtensions = this.createMenuItem(nls.localize({ key: 'miOpenKeymapExtensions', comment: ['&& denotes a mnemonic'] }, "&&Keymaps"), 'workbench.extensions.action.showRecommendedKeymapExtensions');
const snippetsSettings = this.createMenuItem(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), 'workbench.action.openSnippets');
const colorThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme"), 'workbench.action.selectTheme');
const iconThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme"), 'workbench.action.selectIconTheme');
......@@ -397,6 +398,7 @@ export class VSCodeMenu {
preferencesMenu.append(workspaceSettings);
preferencesMenu.append(__separator__());
preferencesMenu.append(kebindingSettings);
preferencesMenu.append(keymapExtensions);
preferencesMenu.append(__separator__());
preferencesMenu.append(snippetsSettings);
preferencesMenu.append(__separator__());
......
......@@ -93,12 +93,12 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return TPromise.wrap(false);
}
private enableExtension(identifier: string, scope: StorageScope): TPromise<boolean> {
private enableExtension(identifier: string, scope: StorageScope, fireEvent = true): TPromise<boolean> {
let disabledExtensions = this.getDisabledExtensions(scope);
const index = disabledExtensions.indexOf(identifier);
if (index !== -1) {
disabledExtensions.splice(index, 1);
this.setDisabledExtensions(disabledExtensions, scope, identifier);
this.setDisabledExtensions(disabledExtensions, scope, identifier, fireEvent);
return TPromise.wrap(true);
}
return TPromise.wrap(false);
......@@ -112,20 +112,22 @@ export class ExtensionEnablementService implements IExtensionEnablementService {
return value ? distinct(value.split(',')) : [];
}
private setDisabledExtensions(disabledExtensions: string[], scope: StorageScope, extension: string): void {
private setDisabledExtensions(disabledExtensions: string[], scope: StorageScope, extension: string, fireEvent = true): void {
if (disabledExtensions.length) {
this.storageService.store(DISABLED_EXTENSIONS_STORAGE_PATH, disabledExtensions.join(','), scope);
} else {
this.storageService.remove(DISABLED_EXTENSIONS_STORAGE_PATH, scope);
}
if (fireEvent) {
this._onEnablementChanged.fire(extension);
}
}
private onDidUninstallExtension({id, error}: DidUninstallExtensionEvent): void {
if (!error) {
id = stripVersion(id);
this.enableExtension(id, StorageScope.WORKSPACE);
this.enableExtension(id, StorageScope.GLOBAL);
this.enableExtension(id, StorageScope.WORKSPACE, false);
this.enableExtension(id, StorageScope.GLOBAL, false);
}
}
......
......@@ -286,7 +286,9 @@ export interface IExtensionTipsService {
_serviceBrand: any;
getRecommendations(): string[];
getWorkspaceRecommendations(): TPromise<string[]>;
getKeymapRecommendations(): string[];
}
export const ExtensionsLabel = localize('extensions', "Extensions");
export const ExtensionsChannelId = 'extensions';
export const PreferencesLabel = localize('preferences', "Preferences");
\ No newline at end of file
......@@ -26,6 +26,7 @@ export interface IProductConfiguration {
};
extensionTips: { [id: string]: string; };
extensionImportantTips: { [id: string]: { name: string; pattern: string; }; };
keymapExtensionTips: string[];
crashReporter: Electron.CrashReporterStartOptions;
welcomePage: string;
enableTelemetry: boolean;
......
......@@ -75,6 +75,10 @@ export class ExtensionTipsService implements IExtensionTipsService {
return Object.keys(this._recommendations);
}
getKeymapRecommendations(): string[] {
return product.keymapExtensionTips || [];
}
private _suggestTips() {
const extensionTips = product.extensionTips;
if (!extensionTips) {
......
......@@ -1009,6 +1009,34 @@ export class ShowWorkspaceRecommendedExtensionsAction extends Action {
}
}
export class ShowRecommendedKeymapExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showRecommendedKeymapExtensions';
static LABEL = localize('showRecommendedKeymapExtensions', "Show Recommended Keymaps");
static SHORT_LABEL = localize('showRecommendedKeymapExtensionsShort', "Keymaps");
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:keymaps ');
viewlet.focus();
});
}
protected isEnabled(): boolean {
return true;
}
}
export class ChangeSortAction extends Action {
private query: Query;
......
......@@ -9,7 +9,7 @@ import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { Registry } from 'vs/platform/platform';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionGalleryService, IExtensionTipsService, ExtensionsLabel, ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionTipsService, ExtensionsLabel, ExtensionsChannelId, PreferencesLabel } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actionRegistry';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/browser/extensionTipsService';
......@@ -21,7 +21,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { VIEWLET_ID, IExtensionsWorkbenchService } from '../common/extensions';
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import {
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction,
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction,
ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, UpdateAllAction, ConfigureWorkspaceRecommendedExtensionsAction,
EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction
} from 'vs/workbench/parts/extensions/browser/extensionsActions';
......@@ -106,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 keymapRecommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.SHORT_LABEL);
actionRegistry.registerWorkbenchAction(keymapRecommendationsActionDescriptor, 'Preferences: Keymaps', PreferencesLabel);
const workspaceRecommendationsActionDescriptor = new SyncActionDescriptor(ShowWorkspaceRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction.ID, ShowWorkspaceRecommendedExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(workspaceRecommendationsActionDescriptor, 'Extensions: Show Workspace Recommended Extensions', ExtensionsLabel);
......
......@@ -31,7 +31,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { Delegate, Renderer } from 'vs/workbench/parts/extensions/browser/extensionsList';
import { IExtensionsWorkbenchService, IExtension, IExtensionsViewlet, VIEWLET_ID, ExtensionState } from '../common/extensions';
import {
ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction,
ShowRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction,
ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction
} from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { InstallVSIXAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
......@@ -170,6 +170,7 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowWorkspaceRecommendedExtensionsAction, ShowWorkspaceRecommendedExtensionsAction.ID, ShowWorkspaceRecommendedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowRecommendedKeymapExtensionsAction, ShowRecommendedKeymapExtensionsAction.ID, ShowRecommendedKeymapExtensionsAction.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),
......@@ -263,6 +264,8 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
if (/@recommended:workspace/i.test(query.value)) {
return this.getWorkspaceRecommendationsModel(query, options);
} else if (/@recommended:keymaps/i.test(query.value)) {
return this.getKeymapRecommendationsModel(query, options);
} else if (/@recommended/i.test(query.value)) {
return this.getRecommendationsModel(query, options);
}
......@@ -316,6 +319,20 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet {
});
}
private getKeymapRecommendationsModel(query: Query, options: IQueryOptions): TPromise<IPagedModel<IExtension>> {
const value = query.value.replace(/@recommended:keymaps/g, '').trim().toLowerCase();
const names = this.tipsService.getKeymapRecommendations()
.filter(name => name.toLowerCase().indexOf(value) > -1);
this.telemetryService.publicLog('extensionKeymapRecommendations:open', { count: names.length });
if (!names.length) {
return TPromise.as(new PagedModel([]));
}
return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length }))
.then(result => new PagedModel(result));
}
private openExtension(extension: IExtension): void {
this.extensionsWorkbenchService.open(extension).done(null, err => this.onError(err));
}
......
......@@ -5,6 +5,7 @@
'use strict';
import nls = require('vs/nls');
import { readFile } from 'vs/base/node/pfs';
import * as semver from 'semver';
import * as path from 'path';
......@@ -14,19 +15,19 @@ import { LinkedMap as Map } from 'vs/base/common/map';
import { assign } from 'vs/base/common/objects';
import { isUUID } from 'vs/base/common/uuid';
import { ThrottledDelayer } from 'vs/base/common/async';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { isPromiseCanceledError, onUnexpectedError, canceled } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest,
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionTipsService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionTelemetry';
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 { IChoiceService, IMessageService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import URI from 'vs/base/common/uri';
import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, IExtensionsConfiguration, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions';
......@@ -314,8 +315,10 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
@IConfigurationService private configurationService: IConfigurationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IMessageService private messageService: IMessageService,
@IChoiceService private choiceService: IChoiceService,
@IURLService urlService: IURLService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
@IExtensionTipsService private tipsService: IExtensionTipsService,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
) {
this.stateProvider = ext => this.getExtensionState(ext);
......@@ -565,6 +568,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
installed.local = local;
} else {
this.installed.push(extension);
this.checkForOtherKeymaps(extension)
.then(null, onUnexpectedError);
}
}
}
......@@ -576,6 +581,39 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
this._onChange.fire();
}
private checkForOtherKeymaps(extension: Extension): TPromise<void> {
if (!extension.disabledGlobally && this.isKeymapExtension(extension)) {
const otherKeymaps = this.installed.filter(ext => ext.identifier !== extension.identifier &&
!ext.disabledGlobally &&
this.isKeymapExtension(ext));
if (otherKeymaps.length) {
return this.promptForDisablingOtherKeymaps(otherKeymaps);
}
}
return TPromise.as(undefined);
}
private isKeymapExtension(extension: Extension): boolean {
const cats = extension.local.manifest.categories;
return cats && cats.indexOf('Keymaps') !== -1 || this.tipsService.getKeymapRecommendations().indexOf(extension.identifier) !== -1;
}
private promptForDisablingOtherKeymaps(keymaps: Extension[]): TPromise<void> {
const message = nls.localize('disableOtherKeymapsConfirmation', "Disable other keymaps to avoid conflicts between keybindings?");
const options = [
nls.localize('yes', "Yes"),
nls.localize('no', "No")
];
return this.choiceService.choose(Severity.Info, message, options, false)
.then<void>(value => {
if (value === 0) {
return TPromise.join(keymaps.map(keymap => {
return this.setEnablement(keymap, false);
}));
}
}, error => TPromise.wrapError(canceled()));
}
private onUninstallExtension(id: string): void {
const extension = this.installed.filter(e => e.local.id === id)[0];
const newLength = this.installed.filter(e => e.local.id !== id).length;
......@@ -618,6 +656,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
extension.disabledGlobally = globallyDisabledExtensions.indexOf(extension.identifier) !== -1;
extension.disabledForWorkspace = workspaceDisabledExtensions.indexOf(extension.identifier) !== -1;
this._onChange.fire();
this.checkForOtherKeymaps(<Extension>extension)
.then(null, onUnexpectedError);
}
}
......
......@@ -13,10 +13,11 @@ import { generateUuid } from 'vs/base/common/uuid';
import { IExtensionsWorkbenchService, ExtensionState } from 'vs/workbench/parts/extensions/common/extensions';
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import {
IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, ILocalExtension, LocalExtensionType, IGalleryExtension,
IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, LocalExtensionType, IGalleryExtension,
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/browser/extensionTipsService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
......@@ -54,6 +55,9 @@ suite('ExtensionsWorkbenchService Test', () => {
instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
instantiationService.stub(IExtensionTipsService, ExtensionTipsService);
instantiationService.stub(IExtensionTipsService, 'getKeymapRecommendations', () => []);
});
setup(() => {
......
......@@ -13,10 +13,11 @@ import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/commo
import * as ExtensionsActions from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import {
IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, ILocalExtension, LocalExtensionType, IGalleryExtension,
IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, LocalExtensionType, IGalleryExtension,
DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/browser/extensionTipsService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { IURLService } from 'vs/platform/url/common/url';
......@@ -55,6 +56,9 @@ suite('ExtensionsActions Test', () => {
instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event);
instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService));
instantiationService.stub(IExtensionTipsService, ExtensionTipsService);
instantiationService.stub(IExtensionTipsService, 'getKeymapRecommendations', () => []);
});
setup(() => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册