提交 cc8f1034 编写于 作者: J Johannes Rieken

make command#when be like keybinding#when

上级 0d71e653
...@@ -40,13 +40,6 @@ ...@@ -40,13 +40,6 @@
} }
], ],
"commands": [ "commands": [
{
"command": "markdown.showPreview",
"title": "%markdown.openPreview%",
"category": "%markdown.category%",
"where": ["explorer/context"],
"when": "markdown"
},
{ {
"command": "markdown.showPreview", "command": "markdown.showPreview",
"title": "%markdown.previewMarkdown.title%", "title": "%markdown.previewMarkdown.title%",
...@@ -55,8 +48,8 @@ ...@@ -55,8 +48,8 @@
"light": "./media/Preview.svg", "light": "./media/Preview.svg",
"dark": "./media/Preview_inverse.svg" "dark": "./media/Preview_inverse.svg"
}, },
"where": ["editor/primary"], "when": "resourceLangId == markdown",
"when": "markdown" "where": "editor/primary"
}, },
{ {
"command": "markdown.showSource", "command": "markdown.showSource",
...@@ -66,14 +59,14 @@ ...@@ -66,14 +59,14 @@
"light": "./media/ViewSource.svg", "light": "./media/ViewSource.svg",
"dark": "./media/ViewSource_inverse.svg" "dark": "./media/ViewSource_inverse.svg"
}, },
"where": ["editor/primary"], "when": "resourceScheme == markdown",
"when": { "scheme": "markdown" } "where": "editor/primary"
}, },
{ {
"command": "markdown.showPreviewToSide", "command": "markdown.showPreviewToSide",
"title": "%markdown.previewMarkdownSide.title%", "title": "%markdown.previewMarkdownSide.title%",
"where": "editor/secondary", "when": "resourceLangId == markdown",
"when": "markdown" "where": "editor/secondary"
} }
], ],
"keybindings": [ "keybindings": [
......
...@@ -8,15 +8,10 @@ import {localize} from 'vs/nls'; ...@@ -8,15 +8,10 @@ import {localize} from 'vs/nls';
import {Action} from 'vs/base/common/actions'; import {Action} from 'vs/base/common/actions';
import {join} from 'vs/base/common/paths'; import {join} from 'vs/base/common/paths';
import {IJSONSchema} from 'vs/base/common/jsonSchema'; import {IJSONSchema} from 'vs/base/common/jsonSchema';
import {IExtensionService} from 'vs/platform/extensions/common/extensions'; import {IExtensionService, IExtensionDescription} from 'vs/platform/extensions/common/extensions';
import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; import {IKeybindingService, KbExpr} from 'vs/platform/keybinding/common/keybindingService';
import {IExtensionPointUser, ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; import {IExtensionPointUser, ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry';
export interface ResourceFilter {
language?: string;
scheme?: string;
pattern?: string;
}
export type Locations = 'editor/primary' | 'editor/secondary' | 'explorer/context'; export type Locations = 'editor/primary' | 'editor/secondary' | 'explorer/context';
...@@ -29,42 +24,57 @@ export interface Command { ...@@ -29,42 +24,57 @@ export interface Command {
command: string; command: string;
title: string; title: string;
category?: string; category?: string;
where?: Locations | Locations[];
when?: string | string[] | ResourceFilter | ResourceFilter[];
icon?: string | ThemableIcon; icon?: string | ThemableIcon;
when?: string;
where?: Locations;
} }
export function isThemableIcon(thing: any): thing is ThemableIcon { function isThemableIcon(thing: any): thing is ThemableIcon {
return typeof thing === 'object' && thing && typeof (<ThemableIcon>thing).dark === 'string' && typeof (<ThemableIcon>thing).light === 'string'; return typeof thing === 'object' && thing && typeof (<ThemableIcon>thing).dark === 'string' && typeof (<ThemableIcon>thing).light === 'string';
} }
export class ParsedCommand {
id: string;
title: string;
category: string;
lightThemeIcon: string;
darkThemeIcon: string;
when: KbExpr;
where: Locations;
constructor(command: Command, extension: IExtensionDescription) {
this.id = command.command;
this.title = command.title;
this.category = command.category;
this.when = KbExpr.deserialize(command.when);
this.where = command.where;
const {icon} = command;
if (!icon) {
// nothing
} else if (isThemableIcon(icon)) {
this.lightThemeIcon = join(extension.extensionFolderPath, icon.light);
this.darkThemeIcon = join(extension.extensionFolderPath, icon.dark);
} else {
this.lightThemeIcon = this.darkThemeIcon = join(extension.extensionFolderPath, icon);
}
}
}
namespace validation { namespace validation {
function isValidWhere(where: Locations | Locations[], user: IExtensionPointUser<any>): boolean { function isValidWhere(where: Locations, user: IExtensionPointUser<any>): boolean {
if (Array.isArray<Locations>(where)) { if (where && ['editor/primary', 'editor/secondary', 'explorer/context'].indexOf(where) < 0) {
return where.every(where => isValidWhere(where, user));
} else if (['editor/primary', 'editor/secondary', 'explorer/context'].indexOf(where) < 0) {
user.collector.error(localize('optwhere', "property `where` can be omitted or must be a valid enum value")); user.collector.error(localize('optwhere', "property `where` can be omitted or must be a valid enum value"));
return false; return false;
} }
return true; return true;
} }
function isValidWhen(when: string | string[] | ResourceFilter | ResourceFilter[], user: IExtensionPointUser<any>): boolean {
if (Array.isArray<string | ResourceFilter>(when)) {
for (let w of when) {
if (!isValidWhen(w, user)) {
return false;
}
}
} else if (typeof when === 'string' || typeof when === 'object') {
return true;
}
user.collector.error(localize('requirefilter', "property `when` is mandatory and must be a string or like `{language, scheme, pattern}`"));
return false;
}
function isValidIcon(icon: string | ThemableIcon, user: IExtensionPointUser<any>): boolean { function isValidIcon(icon: string | ThemableIcon, user: IExtensionPointUser<any>): boolean {
if (typeof icon === 'undefined') { if (typeof icon === 'undefined') {
return true; return true;
...@@ -96,63 +106,37 @@ namespace validation { ...@@ -96,63 +106,37 @@ namespace validation {
user.collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'category')); user.collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'category'));
return false; return false;
} }
if (candidate.when && typeof candidate.when !== 'string') {
user.collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (!isValidIcon(candidate.icon, user)) { if (!isValidIcon(candidate.icon, user)) {
return false; return false;
} }
if (!isValidWhere(candidate.where, user)) {
// make icon paths absolute return false;
let {icon} = candidate;
if (typeof icon === 'string') {
candidate.icon = join(user.description.extensionFolderPath, icon);
} else if(isThemableIcon(icon)) {
icon.dark = join(user.description.extensionFolderPath, icon.dark);
icon.light = join(user.description.extensionFolderPath, icon.light);
} }
return true; return true;
} }
} }
namespace schema { namespace schema {
const filterType: IJSONSchema = { const commandType: IJSONSchema = {
type: 'object', type: 'object',
properties: { properties: {
language: { command: {
description: localize('vscode.extension.contributes.filterType.language', ""), description: localize('vscode.extension.contributes.commandType.command', 'Identifier of the command to execute'),
type: 'string' type: 'string'
}, },
scheme: { title: {
description: localize('vscode.extension.contributes.filterType.scheme', ""), description: localize('vscode.extension.contributes.commandType.title', 'Title by which the command is represented in the UI'),
type: 'string' type: 'string'
}, },
pattern: { category: {
description: localize('vscode.extension.contributes.filterType.pattern', ""), description: localize('vscode.extension.contributes.commandType.category', '(Optional) Category string by the command is grouped in the UI'),
type: 'string' type: 'string'
}
}
};
const contextType: IJSONSchema = {
type: 'object',
properties: {
where: {
description: localize('vscode.extension.contributes.commandType.context.where', "Menus and tool bars to which commands can be added, e.g. `editor title actions` or `explorer context menu`"),
enum: [
'editor/primary',
'editor/secondary'
]
},
when: {
description: localize('vscode.extension.contributes.commandType.context.when', "Condition that must be met in order to show the command. Can be a language identifier, a glob-pattern, an uri scheme, or a combination of them."),
anyOf: [
'string',
filterType,
{ type: 'array', items: 'string' },
{ type: 'array', items: filterType },
]
}, },
icon: { icon: {
description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path or a themable configuration'), description: localize('vscode.extension.contributes.commandType.icon', '(Optional) Icon which is used to represent the command in the UI. Either a file path or a themable configuration'),
...@@ -172,30 +156,16 @@ namespace schema { ...@@ -172,30 +156,16 @@ namespace schema {
} }
} }
] ]
}
}
};
const commandType: IJSONSchema = {
type: 'object',
properties: {
command: {
description: localize('vscode.extension.contributes.commandType.command', 'Identifier of the command to execute'),
type: 'string'
},
title: {
description: localize('vscode.extension.contributes.commandType.title', 'Title by which the command is represented in the UI'),
type: 'string'
}, },
category: { when: {
description: localize('vscode.extension.contributes.commandType.category', '(Optional) Category string by the command is grouped in the UI'), description: localize('vscode.extension.contributes.commandType.context.when', "Condition that must be met in order to show the command."),
type: 'string' type: 'string'
}, },
context: { where: {
description: localize('vscode.extension.contributes.commandType.context', '(Optional) Define places where the command should show in addition to the Command palette'), description: localize('vscode.extension.contributes.commandType.context.where', "Menus and tool bars to which commands can be added, e.g. `editor title actions` or `explorer context menu`"),
oneOf: [ enum: [
contextType, 'editor/primary',
{ type: 'array', items: contextType } 'editor/secondary'
] ]
} }
} }
...@@ -213,12 +183,12 @@ namespace schema { ...@@ -213,12 +183,12 @@ namespace schema {
}; };
} }
export const commands: Command[] = []; export const commands: ParsedCommand[] = [];
function handleCommand(command: Command, user: IExtensionPointUser<any>): void { function handleCommand(command: Command, user: IExtensionPointUser<any>): void {
if (validation.isValidCommand(command, user)) { if (validation.isValidCommand(command, user)) {
// store command globally // store command globally
commands.push(command); commands.push(new ParsedCommand(command, user.description));
} }
} }
...@@ -240,17 +210,17 @@ ExtensionsRegistry.registerExtensionPoint<Command | Command[]>('commands', schem ...@@ -240,17 +210,17 @@ ExtensionsRegistry.registerExtensionPoint<Command | Command[]>('commands', schem
export class CommandAction extends Action { export class CommandAction extends Action {
constructor( constructor(
public command: Command, public command: ParsedCommand,
@IExtensionService extensionService: IExtensionService, @IExtensionService extensionService: IExtensionService,
@IKeybindingService keybindingService: IKeybindingService @IKeybindingService keybindingService: IKeybindingService
) { ) {
super(command.command, command.title); super(command.id, command.title);
this.order = Number.MAX_VALUE; this.order = Number.MAX_VALUE;
const activationEvent = `onCommand:${command.command}`; const activationEvent = `onCommand:${command.id}`;
this._actionCallback = (...args: any[]) => { this._actionCallback = (...args: any[]) => {
return extensionService.activateByEvent(activationEvent).then(() => { return extensionService.activateByEvent(activationEvent).then(() => {
return keybindingService.executeCommand(command.command, ...args); return keybindingService.executeCommand(command.id, ...args);
}); });
}; };
} }
......
...@@ -8,108 +8,28 @@ ...@@ -8,108 +8,28 @@
import {Registry} from 'vs/platform/platform'; import {Registry} from 'vs/platform/platform';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
import {IAction, Action} from 'vs/base/common/actions'; import {IAction, Action} from 'vs/base/common/actions';
import {IDisposable} from 'vs/base/common/lifecycle';
import {BaseActionItem, ActionItem} from 'vs/base/browser/ui/actionbar/actionbar'; import {BaseActionItem, ActionItem} from 'vs/base/browser/ui/actionbar/actionbar';
import {Scope, IActionBarRegistry, Extensions, ActionBarContributor} from 'vs/workbench/browser/actionBarRegistry'; import {Scope, IActionBarRegistry, Extensions, ActionBarContributor} from 'vs/workbench/browser/actionBarRegistry';
import {IModeService} from 'vs/editor/common/services/modeService';
import {IExtensionService} from 'vs/platform/extensions/common/extensions'; import {IExtensionService} from 'vs/platform/extensions/common/extensions';
import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; import {IThemeService} from 'vs/workbench/services/themes/common/themeService';
import {isLightTheme} from 'vs/platform/theme/common/themes'; import {isLightTheme} from 'vs/platform/theme/common/themes';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService';
import {commands, CommandAction, Command, Locations} from '../common/commandsExtensionPoint'; import {commands, CommandAction, Locations} from '../common/commandsExtensionPoint';
import matches from 'vs/editor/common/modes/languageSelector';
import {EditorInput} from 'vs/workbench/common/editor'; import {EditorInput} from 'vs/workbench/common/editor';
class ResolvedCommand {
constructor(
private _command: Command,
@IInstantiationService private _instantiationService: IInstantiationService,
@IThemeService private _themeService: IThemeService,
@IModeService private _modeService: IModeService
) {
}
matches(location: Locations, resource: URI): boolean {
const {where, when} = this._command;
if (!where || !when) {
return false;
}
// (1) check for location
if (Array.isArray<Locations>(where)) {
if (where.every(where => where !== location)) {
return false;
}
} else if (where !== location) {
return false;
}
// (2) check for resource
if (!matches(when, resource, this._modeService.getModeIdByFilenameOrFirstLine(resource.fsPath))) {
return false;
}
return true;
}
createAction(resource: URI): ScopedCommandAction {
return this._instantiationService.createInstance(ScopedCommandAction, this._command, resource);
}
}
class ScopedCommandAction extends CommandAction {
private _themeListener: IDisposable;
constructor(
command: Command,
private _resource: URI,
@IThemeService private _themeService: IThemeService,
@IExtensionService extensionService: IExtensionService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(command, extensionService, keybindingService);
}
dispose() {
this._themeListener.dispose();
super.dispose();
}
get icon(): string {
const {icon} = this.command;
if (!icon) {
return;
}
if (typeof icon === 'string') {
return icon;
} else {
return isLightTheme(this._themeService.getTheme())
? icon.light
: icon.dark;
}
}
run() {
return super.run(this._resource);
}
}
abstract class BaseActionBarContributor extends ActionBarContributor { abstract class BaseActionBarContributor extends ActionBarContributor {
private _isReady: boolean = false; private _isReady: boolean = false;
private _contributedActions: ResolvedCommand[];
constructor( constructor(
@IExtensionService private _extensionService: IExtensionService, @IExtensionService private _extensionService: IExtensionService,
@IInstantiationService private _instantationService: IInstantiationService @IInstantiationService private _instantationService: IInstantiationService,
@IKeybindingService private _keybindingService: IKeybindingService
) { ) {
super(); super();
this._extensionService.onReady().then(() => { this._extensionService.onReady().then(() => this._isReady = true);
this._contributedActions = commands.map(command => _instantationService.createInstance(ResolvedCommand, command));
this._isReady = true;
});
} }
protected abstract _wheres(): { primary: Locations; secondary: Locations }; protected abstract _wheres(): { primary: Locations; secondary: Locations };
...@@ -133,20 +53,30 @@ abstract class BaseActionBarContributor extends ActionBarContributor { ...@@ -133,20 +53,30 @@ abstract class BaseActionBarContributor extends ActionBarContributor {
} }
private _getActions(context: any, where: Locations): IAction[] { private _getActions(context: any, where: Locations): IAction[] {
const uri = this._getResource(context);
const result: IAction[] = []; const result: IAction[] = [];
if (uri) {
for (let command of this._contributedActions) { for (let command of commands) {
if (command.matches(where, uri)) { console.log(command.id, command.when,
result.push(command.createAction(uri)); this._keybindingService.contextMatchesRules(command.when),
} this._keybindingService.getContextValue('resourceLangId'),
this._keybindingService.getContextValue('resourceScheme')
);
if (command.where === where && this._keybindingService.contextMatchesRules(command.when)) {
let resource = this._keybindingService.getContextValue<URI>('resource');
result.push(this._instantationService.createInstance(class extends CommandAction {
run() {
return super.run(resource);
}
}, command));
} }
} }
return result; return result;
} }
public getActionItem(context: any, action: Action): BaseActionItem { public getActionItem(context: any, action: Action): BaseActionItem {
if (action instanceof ScopedCommandAction) { if (action instanceof CommandAction) {
return this._instantationService.createInstance(CommandActionItem, action); return this._instantationService.createInstance(CommandActionItem, action);
} }
} }
...@@ -170,39 +100,30 @@ class EditorContributor extends BaseActionBarContributor { ...@@ -170,39 +100,30 @@ class EditorContributor extends BaseActionBarContributor {
} }
} }
class ContextMenuContributor extends BaseActionBarContributor {
protected _wheres(): { primary: Locations; secondary: Locations } {
return { secondary: 'explorer/context', primary: undefined };
}
protected _getResource(context: any): URI {
if (context.element) {
if (context.element.resource instanceof URI) {
return <URI> context.element.resource;
}
}
}
}
class CommandActionItem extends ActionItem { class CommandActionItem extends ActionItem {
constructor( constructor(
action: ScopedCommandAction, action: CommandAction,
@IThemeService private _themeService: IThemeService @IThemeService private _themeService: IThemeService
) { ) {
super(undefined, action, { icon: Boolean(action.icon), label: !Boolean(action.icon) }); super(undefined, action, {
icon: !!(action.command.lightThemeIcon || action.command.darkThemeIcon),
label: !(action.command.lightThemeIcon || action.command.darkThemeIcon)
});
this._themeService.onDidThemeChange(this._updateClass, this, this.callOnDispose); this._themeService.onDidThemeChange(this._updateClass, this, this.callOnDispose);
} }
_updateClass(): void { _updateClass(): void {
super._updateClass(); super._updateClass();
const element = this.$e.getHTMLElement(); const element = this.$e.getHTMLElement();
const {icon} = <ScopedCommandAction>this._action; const {lightThemeIcon, darkThemeIcon} = (<CommandAction>this._action).command;
if (icon && element.classList.contains('icon')) { if (element.classList.contains('icon')) {
element.style.backgroundImage = `url("${icon}")`; if (isLightTheme(this._themeService.getTheme())) {
element.style.backgroundImage = `url("${lightThemeIcon}")`;
} else {
element.style.backgroundImage = `url("${darkThemeIcon}")`;
}
} }
} }
...@@ -211,5 +132,4 @@ class CommandActionItem extends ActionItem { ...@@ -211,5 +132,4 @@ class CommandActionItem extends ActionItem {
} }
} }
Registry.as<IActionBarRegistry>(Extensions.Actionbar).registerActionBarContributor(Scope.EDITOR, EditorContributor); Registry.as<IActionBarRegistry>(Extensions.Actionbar).registerActionBarContributor(Scope.EDITOR, EditorContributor);
Registry.as<IActionBarRegistry>(Extensions.Actionbar).registerActionBarContributor(Scope.VIEWER, ContextMenuContributor); \ No newline at end of file
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册