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

make command#when be like keybinding#when

上级 0d71e653
......@@ -40,13 +40,6 @@
}
],
"commands": [
{
"command": "markdown.showPreview",
"title": "%markdown.openPreview%",
"category": "%markdown.category%",
"where": ["explorer/context"],
"when": "markdown"
},
{
"command": "markdown.showPreview",
"title": "%markdown.previewMarkdown.title%",
......@@ -55,8 +48,8 @@
"light": "./media/Preview.svg",
"dark": "./media/Preview_inverse.svg"
},
"where": ["editor/primary"],
"when": "markdown"
"when": "resourceLangId == markdown",
"where": "editor/primary"
},
{
"command": "markdown.showSource",
......@@ -66,14 +59,14 @@
"light": "./media/ViewSource.svg",
"dark": "./media/ViewSource_inverse.svg"
},
"where": ["editor/primary"],
"when": { "scheme": "markdown" }
"when": "resourceScheme == markdown",
"where": "editor/primary"
},
{
"command": "markdown.showPreviewToSide",
"title": "%markdown.previewMarkdownSide.title%",
"where": "editor/secondary",
"when": "markdown"
"when": "resourceLangId == markdown",
"where": "editor/secondary"
}
],
"keybindings": [
......
......@@ -8,15 +8,10 @@ import {localize} from 'vs/nls';
import {Action} from 'vs/base/common/actions';
import {join} from 'vs/base/common/paths';
import {IJSONSchema} from 'vs/base/common/jsonSchema';
import {IExtensionService} from 'vs/platform/extensions/common/extensions';
import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService';
import {IExtensionService, IExtensionDescription} from 'vs/platform/extensions/common/extensions';
import {IKeybindingService, KbExpr} from 'vs/platform/keybinding/common/keybindingService';
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';
......@@ -29,42 +24,57 @@ export interface Command {
command: string;
title: string;
category?: string;
where?: Locations | Locations[];
when?: string | string[] | ResourceFilter | ResourceFilter[];
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';
}
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 {
function isValidWhere(where: Locations | Locations[], user: IExtensionPointUser<any>): boolean {
if (Array.isArray<Locations>(where)) {
return where.every(where => isValidWhere(where, user));
} else if (['editor/primary', 'editor/secondary', 'explorer/context'].indexOf(where) < 0) {
function isValidWhere(where: Locations, user: IExtensionPointUser<any>): boolean {
if (where && ['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"));
return false;
}
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 {
if (typeof icon === 'undefined') {
return true;
......@@ -96,63 +106,37 @@ namespace validation {
user.collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'category'));
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)) {
return false;
}
// make icon paths absolute
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);
if (!isValidWhere(candidate.where, user)) {
return false;
}
return true;
}
}
namespace schema {
const filterType: IJSONSchema = {
const commandType: IJSONSchema = {
type: 'object',
properties: {
language: {
description: localize('vscode.extension.contributes.filterType.language', ""),
command: {
description: localize('vscode.extension.contributes.commandType.command', 'Identifier of the command to execute'),
type: 'string'
},
scheme: {
description: localize('vscode.extension.contributes.filterType.scheme', ""),
title: {
description: localize('vscode.extension.contributes.commandType.title', 'Title by which the command is represented in the UI'),
type: 'string'
},
pattern: {
description: localize('vscode.extension.contributes.filterType.pattern', ""),
category: {
description: localize('vscode.extension.contributes.commandType.category', '(Optional) Category string by the command is grouped in the UI'),
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: {
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 {
}
}
]
}
}
};
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: {
description: localize('vscode.extension.contributes.commandType.category', '(Optional) Category string by the command is grouped in the UI'),
when: {
description: localize('vscode.extension.contributes.commandType.context.when', "Condition that must be met in order to show the command."),
type: 'string'
},
context: {
description: localize('vscode.extension.contributes.commandType.context', '(Optional) Define places where the command should show in addition to the Command palette'),
oneOf: [
contextType,
{ type: 'array', items: contextType }
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'
]
}
}
......@@ -213,12 +183,12 @@ namespace schema {
};
}
export const commands: Command[] = [];
export const commands: ParsedCommand[] = [];
function handleCommand(command: Command, user: IExtensionPointUser<any>): void {
if (validation.isValidCommand(command, user)) {
// store command globally
commands.push(command);
commands.push(new ParsedCommand(command, user.description));
}
}
......@@ -240,17 +210,17 @@ ExtensionsRegistry.registerExtensionPoint<Command | Command[]>('commands', schem
export class CommandAction extends Action {
constructor(
public command: Command,
public command: ParsedCommand,
@IExtensionService extensionService: IExtensionService,
@IKeybindingService keybindingService: IKeybindingService
) {
super(command.command, command.title);
super(command.id, command.title);
this.order = Number.MAX_VALUE;
const activationEvent = `onCommand:${command.command}`;
const activationEvent = `onCommand:${command.id}`;
this._actionCallback = (...args: any[]) => {
return extensionService.activateByEvent(activationEvent).then(() => {
return keybindingService.executeCommand(command.command, ...args);
return keybindingService.executeCommand(command.id, ...args);
});
};
}
......
......@@ -8,108 +8,28 @@
import {Registry} from 'vs/platform/platform';
import URI from 'vs/base/common/uri';
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 {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 {IThemeService} from 'vs/workbench/services/themes/common/themeService';
import {isLightTheme} from 'vs/platform/theme/common/themes';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService';
import {commands, CommandAction, Command, Locations} from '../common/commandsExtensionPoint';
import matches from 'vs/editor/common/modes/languageSelector';
import {commands, CommandAction, Locations} from '../common/commandsExtensionPoint';
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 {
private _isReady: boolean = false;
private _contributedActions: ResolvedCommand[];
constructor(
@IExtensionService private _extensionService: IExtensionService,
@IInstantiationService private _instantationService: IInstantiationService
@IInstantiationService private _instantationService: IInstantiationService,
@IKeybindingService private _keybindingService: IKeybindingService
) {
super();
this._extensionService.onReady().then(() => {
this._contributedActions = commands.map(command => _instantationService.createInstance(ResolvedCommand, command));
this._isReady = true;
});
this._extensionService.onReady().then(() => this._isReady = true);
}
protected abstract _wheres(): { primary: Locations; secondary: Locations };
......@@ -133,20 +53,30 @@ abstract class BaseActionBarContributor extends ActionBarContributor {
}
private _getActions(context: any, where: Locations): IAction[] {
const uri = this._getResource(context);
const result: IAction[] = [];
if (uri) {
for (let command of this._contributedActions) {
if (command.matches(where, uri)) {
result.push(command.createAction(uri));
}
for (let command of commands) {
console.log(command.id, command.when,
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;
}
public getActionItem(context: any, action: Action): BaseActionItem {
if (action instanceof ScopedCommandAction) {
if (action instanceof CommandAction) {
return this._instantationService.createInstance(CommandActionItem, action);
}
}
......@@ -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 {
constructor(
action: ScopedCommandAction,
action: CommandAction,
@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);
}
_updateClass(): void {
super._updateClass();
const element = this.$e.getHTMLElement();
const {icon} = <ScopedCommandAction>this._action;
if (icon && element.classList.contains('icon')) {
element.style.backgroundImage = `url("${icon}")`;
const {lightThemeIcon, darkThemeIcon} = (<CommandAction>this._action).command;
if (element.classList.contains('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 {
}
}
Registry.as<IActionBarRegistry>(Extensions.Actionbar).registerActionBarContributor(Scope.EDITOR, EditorContributor);
Registry.as<IActionBarRegistry>(Extensions.Actionbar).registerActionBarContributor(Scope.VIEWER, ContextMenuContributor);
\ No newline at end of file
Registry.as<IActionBarRegistry>(Extensions.Actionbar).registerActionBarContributor(Scope.EDITOR, EditorContributor);
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册