Merge branch 'master' into ben/electron

This commit is contained in:
Benjamin Pasero 2016-01-06 11:25:40 +01:00
commit d2e85a97f4
13 changed files with 437 additions and 415 deletions

View file

@ -1,6 +1,6 @@
{
"account": "monacobuild",
"container": "debuggers",
"zip": "2985e20/node-debug.zip",
"zip": "b5b96dd/node-debug.zip",
"output": ""
}

View file

@ -417,6 +417,52 @@ export function commonSuffixLength(a: string, b: string): number {
// return 0xDC00 <= chrCode && chrCode <= 0xDFFF;
//}
export function isFullWidthCharacter(charCode:number): boolean {
// Do a cheap trick to better support wrapping of wide characters, treat them as 2 columns
// http://jrgraphix.net/research/unicode_blocks.php
// 2E80 — 2EFF CJK Radicals Supplement
// 2F00 — 2FDF Kangxi Radicals
// 2FF0 — 2FFF Ideographic Description Characters
// 3000 — 303F CJK Symbols and Punctuation
// 3040 — 309F Hiragana
// 30A0 — 30FF Katakana
// 3100 — 312F Bopomofo
// 3130 — 318F Hangul Compatibility Jamo
// 3190 — 319F Kanbun
// 31A0 — 31BF Bopomofo Extended
// 31F0 — 31FF Katakana Phonetic Extensions
// 3200 — 32FF Enclosed CJK Letters and Months
// 3300 — 33FF CJK Compatibility
// 3400 — 4DBF CJK Unified Ideographs Extension A
// 4DC0 — 4DFF Yijing Hexagram Symbols
// 4E00 — 9FFF CJK Unified Ideographs
// A000 — A48F Yi Syllables
// A490 — A4CF Yi Radicals
// AC00 — D7AF Hangul Syllables
// [IGNORE] D800 — DB7F High Surrogates
// [IGNORE] DB80 — DBFF High Private Use Surrogates
// [IGNORE] DC00 — DFFF Low Surrogates
// [IGNORE] E000 — F8FF Private Use Area
// F900 — FAFF CJK Compatibility Ideographs
// [IGNORE] FB00 — FB4F Alphabetic Presentation Forms
// [IGNORE] FB50 — FDFF Arabic Presentation Forms-A
// [IGNORE] FE00 — FE0F Variation Selectors
// [IGNORE] FE20 — FE2F Combining Half Marks
// [IGNORE] FE30 — FE4F CJK Compatibility Forms
// [IGNORE] FE50 — FE6F Small Form Variants
// [IGNORE] FE70 — FEFF Arabic Presentation Forms-B
// FF00 — FFEF Halfwidth and Fullwidth Forms
// [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms]
// of which FF01 - FF5E fullwidth ASCII of 21 to 7E
// [IGNORE] and FF65 - FFDC halfwidth of Katakana and Hangul
// [IGNORE] FFF0 — FFFF Specials
return (
(charCode >= 0x2E80 && charCode <= 0xD7AF)
|| (charCode >= 0xF900 && charCode <= 0xFAFF)
|| (charCode >= 0xFF01 && charCode <= 0xFF5E)
);
}
/**
* Computes the difference score for two strings. More similar strings have a higher score.
* We use largest common subsequence dynamic programming approach but penalize in the end for length differences.

View file

@ -126,51 +126,8 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor
niceBreakVisibleColumn = 0;
}
// Do a cheap trick to better support wrapping of wide characters, treat them as 2 columns
// http://jrgraphix.net/research/unicode_blocks.php
// 2E80 — 2EFF CJK Radicals Supplement
// 2F00 — 2FDF Kangxi Radicals
// 2FF0 — 2FFF Ideographic Description Characters
// 3000 — 303F CJK Symbols and Punctuation
// 3040 — 309F Hiragana
// 30A0 — 30FF Katakana
// 3100 — 312F Bopomofo
// 3130 — 318F Hangul Compatibility Jamo
// 3190 — 319F Kanbun
// 31A0 — 31BF Bopomofo Extended
// 31F0 — 31FF Katakana Phonetic Extensions
// 3200 — 32FF Enclosed CJK Letters and Months
// 3300 — 33FF CJK Compatibility
// 3400 — 4DBF CJK Unified Ideographs Extension A
// 4DC0 — 4DFF Yijing Hexagram Symbols
// 4E00 — 9FFF CJK Unified Ideographs
// A000 — A48F Yi Syllables
// A490 — A4CF Yi Radicals
// AC00 — D7AF Hangul Syllables
// [IGNORE] D800 — DB7F High Surrogates
// [IGNORE] DB80 — DBFF High Private Use Surrogates
// [IGNORE] DC00 — DFFF Low Surrogates
// [IGNORE] E000 — F8FF Private Use Area
// F900 — FAFF CJK Compatibility Ideographs
// [IGNORE] FB00 — FB4F Alphabetic Presentation Forms
// [IGNORE] FB50 — FDFF Arabic Presentation Forms-A
// [IGNORE] FE00 — FE0F Variation Selectors
// [IGNORE] FE20 — FE2F Combining Half Marks
// [IGNORE] FE30 — FE4F CJK Compatibility Forms
// [IGNORE] FE50 — FE6F Small Form Variants
// [IGNORE] FE70 — FEFF Arabic Presentation Forms-B
// FF00 — FFEF Halfwidth and Fullwidth Forms
// [https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms]
// of which FF01 - FF5E fullwidth ASCII of 21 to 7E
// [IGNORE] and FF65 - FFDC halfwidth of Katakana and Hangul
// [IGNORE] FFF0 — FFFF Specials
var charColumnSize = 1;
if (
(charCode >= 0x2E80 && charCode <= 0xD7AF)
|| (charCode >= 0xF900 && charCode <= 0xFAFF)
|| (charCode >= 0xFF01 && charCode <= 0xFF5E)
) {
if (Strings.isFullWidthCharacter(charCode)) {
charColumnSize = columnsForFullWidthChar;
}

View file

@ -130,13 +130,25 @@ export class ElectronIntegration {
ipc.on('vscode:showAutoSaveInfo', () => {
this.messageService.show(
Severity.Info, {
message: nls.localize('autoSaveInfo', "The **File | Auto Save** option moved into settings. Please configure **files.autoSaveDelay: 1** to restore the old behavior."),
message: nls.localize('autoSaveInfo', "The **File | Auto Save** option moved into settings and **files.autoSaveDelay: 1** will be added to preserve it."),
actions: [
CloseAction,
this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL)
]
});
});
});
ipc.on('vscode:showAutoSaveError', () => {
this.messageService.show(
Severity.Warning, {
message: nls.localize('autoSaveError', "Unable to write to settings. Please add **files.autoSaveDelay: 1** to settings.json."),
actions: [
CloseAction,
this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL)
]
});
});
}
private resolveKeybindings(actionIds: string[]): TPromise<{ id: string; binding: number; }[]> {

View file

@ -40,7 +40,7 @@ import {ConfigurationService} from 'vs/workbench/services/configuration/node/con
import {FileService} from 'vs/workbench/services/files/electron-browser/fileService';
import {SearchService} from 'vs/workbench/services/search/node/searchService';
import {LifecycleService} from 'vs/workbench/services/lifecycle/electron-browser/lifecycleService';
import PluginWorkbenchKeybindingService from 'vs/workbench/services/keybinding/electron-browser/pluginKeybindingService';
import {WorkbenchKeybindingService} from 'vs/workbench/services/keybinding/electron-browser/keybindingService';
import {MainThreadService} from 'vs/workbench/services/thread/electron-browser/threadService';
import {MarkerService} from 'vs/platform/markers/common/markerService';
import {IActionsService} from 'vs/platform/actions/common/actions';
@ -164,7 +164,7 @@ export class WorkbenchShell {
private themeService: IThemeService;
private contextService: WorkspaceContextService;
private telemetryService: ElectronTelemetryService;
private keybindingService: PluginWorkbenchKeybindingService;
private keybindingService: WorkbenchKeybindingService;
private container: HTMLElement;
private toUnbind: { (): void; }[];
@ -275,7 +275,7 @@ export class WorkbenchShell {
let enableTelemetry = this.configuration.env.isBuilt && !this.configuration.env.pluginDevelopmentPath ? !!this.configuration.env.enableTelemetry : false;
this.telemetryService = new ElectronTelemetryService(this.storageService, { enableTelemetry: enableTelemetry, version: this.configuration.env.version, commitHash: this.configuration.env.commitHash });
this.keybindingService = new PluginWorkbenchKeybindingService(this.contextService, eventService, this.telemetryService, <any>window);
this.keybindingService = new WorkbenchKeybindingService(this.contextService, eventService, this.telemetryService, <any>window);
this.messageService = new MessageService(this.contextService, this.windowService, this.telemetryService, this.keybindingService);
this.keybindingService.setMessageService(this.messageService);
@ -517,7 +517,7 @@ export class WorkbenchShell {
console.error(errorMsg);
// Show to user if friendly message provided
if (error.friendlyMessage && this.messageService) {
if (error && error.friendlyMessage && this.messageService) {
this.messageService.show(Severity.Error, error.friendlyMessage);
}
}

View file

@ -22,6 +22,7 @@ import window = require('vs/workbench/electron-main/window');
import lifecycle = require('vs/workbench/electron-main/lifecycle');
import nls = require('vs/nls');
import paths = require('vs/base/common/paths');
import json = require('vs/base/common/json');
import arrays = require('vs/base/common/arrays');
import objects = require('vs/base/common/objects');
import storage = require('vs/workbench/electron-main/storage');
@ -174,10 +175,7 @@ export class WindowsManager {
eventEmitter.emit(EventTypes.READY, win);
// TODO@Ben remove me in a couple of versions
if (storage.getItem<number>('autoSaveDelay') === 1000) {
storage.removeItem('autoSaveDelay');
win.send('vscode:showAutoSaveInfo');
}
this.migrateAutoSave(win);
}
});
@ -341,6 +339,46 @@ export class WindowsManager {
});
}
private migrateAutoSave(win: window.VSCodeWindow): void {
if (storage.getItem<number>('autoSaveDelay') === 1000) {
storage.removeItem('autoSaveDelay');
win.send('vscode:showAutoSaveInfo');
try {
// Initial settings file
if (!fs.existsSync(env.appSettingsPath)) {
fs.writeFileSync(env.appSettingsPath, JSON.stringify({ 'files.autoSaveDelay': 1 }, null, ' '));
}
// Update existing settings file
else {
const settingsRaw = fs.readFileSync(env.appSettingsPath).toString();
const lastClosing = settingsRaw.lastIndexOf('}');
const errors = [];
const res = json.parse(settingsRaw, errors);
// We found a closing '}' and the JSON does not contain errors
if (lastClosing > 0 && !errors.length) {
const hasOtherKeys = Object.getOwnPropertyNames(res).length > 0;
const migratedSettings = settingsRaw.substring(0, lastClosing) + '\n // Migrated from previous File | Auto Save setting:\n' + (hasOtherKeys ? ' , "files.autoSaveDelay": 1\n' : ' "files.autoSaveDelay": 1\n') + '}';
fs.writeFileSync(env.appSettingsPath, migratedSettings);
}
// Otherwise inform user that we cannot migrate the settings
else {
win.send('vscode:showAutoSaveError');
}
}
} catch (error) {
env.log(error);
win.send('vscode:showAutoSaveError');
}
}
}
public reload(win: window.VSCodeWindow, cli?: env.ICommandLineArguments): void {
// Only reload when the window has not vetoed this
@ -599,7 +637,7 @@ export class WindowsManager {
recentPaths.unshift(workspacePath);
}
// Clear those dupes
// Clear those dupes
recentPaths = arrays.distinct(recentPaths);
// Make sure it is bounded

View file

@ -376,7 +376,7 @@ export class Model extends ee.EventEmitter implements debug.IModel {
}
public addBreakpoints(rawData: debug.IRawBreakpoint[]): void {
this.breakpoints = this.breakpoints.concat(rawData.map(rawBp => new Breakpoint(Source.fromUri(rawBp.uri), rawBp.lineNumber, rawBp.enabled, rawBp.condition)));
this.breakpoints = this.breakpoints.concat(rawData.map(rawBp => new Breakpoint(new Source(Source.toRawSource(rawBp.uri, this)), rawBp.lineNumber, rawBp.enabled, rawBp.condition)));
this.breakpointsActivated = true;
this.emit(debug.ModelEvents.BREAKPOINTS_UPDATED);
}
@ -610,10 +610,10 @@ export class Model extends ee.EventEmitter implements debug.IModel {
this.threads[data.threadId].callStack = data.callStack.map(
(rsf, level) => {
if (!rsf) {
return new StackFrame(data.threadId, 0, Source.fromUri(uri.parse('unknown')), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
return new StackFrame(data.threadId, 0, new Source({ name: 'unknown' }), nls.localize('unknownStack', "Unknown stack location"), undefined, undefined);
}
return new StackFrame(data.threadId, rsf.id, rsf.source ? new Source(rsf.source) : Source.fromUri(uri.parse('unknown')), rsf.name, rsf.line, rsf.column);
return new StackFrame(data.threadId, rsf.id, rsf.source ? new Source(rsf.source) : new Source({ name: 'unknown' }), rsf.name, rsf.line, rsf.column);
});
this.threads[data.threadId].stoppedReason = data.stoppedReason;

View file

@ -52,13 +52,6 @@ export class Source {
{ path: paths.normalize(uri.fsPath, true) };
}
public static fromUri(uri: uri): Source {
return new Source({
name: Source.getName(uri),
path: uri.fsPath,
});
}
private static getName(uri: uri): string {
const uriStr = uri.toString();
return uriStr.substr(uriStr.lastIndexOf('/') + 1);

View file

@ -110,7 +110,6 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService
private registerListeners(eventService: IEventService, lifecycleService: ILifecycleService): void {
this.toDispose.push(eventService.addListener2(EventType.FILE_CHANGES, (e: FileChangesEvent) => this.onFileChanges(e)));
if (this.taskService) {
this.toDispose.push(this.taskService.addListener2(TaskServiceEvents.Active, (e: TaskEvent) => {
this.lastTaskEvent = e;
@ -312,7 +311,7 @@ export class DebugService extends ee.EventEmitter implements debug.IDebugService
private loadBreakpoints(): debug.IBreakpoint[] {
try {
return JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => {
return new model.Breakpoint(breakpoint.source.raw ? new Source(breakpoint.source.raw) : Source.fromUri(uri.parse(breakpoint.source.uri)),
return new model.Breakpoint(new Source(breakpoint.source.raw ? breakpoint.source.raw : { path: uri.parse(breakpoint.source.uri).fsPath, name: breakpoint.source.name }),
breakpoint.desiredLineNumber || breakpoint.lineNumber, breakpoint.enabled, breakpoint.condition);
});
} catch (e) {

View file

@ -9,16 +9,6 @@ import { Source } from 'vs/workbench/parts/debug/common/debugSource';
suite('Debug - Source', () => {
test('from uri', () => {
const u = uri.file('/a/b/c/d');
const source = Source.fromUri(u);
assert.equal(source.available, true);
assert.equal(source.inMemory, false);
assert.equal(source.uri.toString(), u.toString());
assert.equal(source.name, 'd');
});
test('from raw source', () => {
const rawSource = {
name: 'zz',

View file

@ -1,100 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import {Registry} from 'vs/platform/platform';
import {KeybindingService} from 'vs/platform/keybinding/browser/keybindingServiceImpl';
import {OptionsChangeEvent, EventType} from 'vs/workbench/common/events';
import {IEventService} from 'vs/platform/event/common/event';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {IKeybindingItem, IUserFriendlyKeybinding} from 'vs/platform/keybinding/common/keybindingService';
import {IOSupport} from 'vs/platform/keybinding/common/commonKeybindingResolver';
import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import {IJSONSchema} from 'vs/base/common/jsonSchema';
export abstract class WorkbenchKeybindingService extends KeybindingService {
private contextService: IWorkspaceContextService;
private eventService: IEventService;
private telemetryService: ITelemetryService;
private toDispose: Function;
constructor(contextService: IWorkspaceContextService, eventService: IEventService, telemetryService: ITelemetryService, domNode: HTMLElement) {
this.contextService = contextService;
super(domNode);
this.eventService = eventService;
this.telemetryService = telemetryService;
this.toDispose = this.eventService.addListener(EventType.WORKBENCH_OPTIONS_CHANGED, (e) => this.onOptionsChanged(e));
}
public customKeybindingsCount(): number {
let opts = this.contextService.getOptions();
if (opts.globalSettings && opts.globalSettings.keybindings && Array.isArray(opts.globalSettings.keybindings)) {
return opts.globalSettings.keybindings.length;
}
return 0;
}
protected _getExtraKeybindings(isFirstTime: boolean): IKeybindingItem[] {
let extras: IUserFriendlyKeybinding[] = [];
let opts = this.contextService.getOptions();
if (opts.globalSettings && opts.globalSettings.keybindings) {
if (!isFirstTime) {
let cnt = 0;
if (Array.isArray(opts.globalSettings.keybindings)) {
cnt = opts.globalSettings.keybindings.length;
}
this.telemetryService.publicLog('customKeybindingsChanged', {
keyCount: cnt
});
}
if (Array.isArray(opts.globalSettings.keybindings)) {
extras = opts.globalSettings.keybindings;
}
}
return extras.map((k, i) => IOSupport.readKeybindingItem(k, i));
}
private onOptionsChanged(e: OptionsChangeEvent): void {
if (e.key === 'globalSettings') {
this.updateResolver();
}
}
public dispose(): void {
this.toDispose();
}
}
let schemaId = 'local://schemas/keybindings';
let schema : IJSONSchema = {
'id': schemaId,
'type': 'array',
'title': nls.localize('keybindings.json.title', "Keybindings configuration"),
'items': {
'required': ['key'],
'type': 'object',
'default': { 'key': '{{_}}', 'command': '{{_}}', 'when': '{{_}}' },
'properties': {
'key': {
'type': 'string',
'description': nls.localize('keybindings.json.key', 'Key or key sequence (separated by space)'),
},
'command': {
'description': nls.localize('keybindings.json.command', 'Name of the command to execute'),
},
'when': {
'type': 'string',
'description': nls.localize('keybindings.json.when', 'Condition when the key is active.')
}
}
}
};
let schemaRegistry = <JSONContributionRegistry.IJSONContributionRegistry>Registry.as(JSONContributionRegistry.Extensions.JSONContribution);
schemaRegistry.registerSchema(schemaId, schema);
schemaRegistry.addSchemaFileAssociation('inmemory://defaults/keybindings.json', schemaId);
schemaRegistry.addSchemaFileAssociation('%APP_SETTINGS_HOME%/keybindings.json', schemaId);

View file

@ -0,0 +1,313 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import {TPromise} from 'vs/base/common/winjs.base';
import {IJSONSchema} from 'vs/base/common/jsonSchema';
import {IHTMLContentElement} from 'vs/base/common/htmlContent';
import {Registry} from 'vs/platform/platform';
import {IEventService} from 'vs/platform/event/common/event';
import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {PluginsRegistry, IMessageCollector} from 'vs/platform/plugins/common/pluginsRegistry';
import {IPluginService} from 'vs/platform/plugins/common/plugins';
import {IOSupport} from 'vs/platform/keybinding/common/commonKeybindingResolver';
import {KeybindingService} from 'vs/platform/keybinding/browser/keybindingServiceImpl';
import {IKeybindingItem, IUserFriendlyKeybinding} from 'vs/platform/keybinding/common/keybindingService';
import {ICommandRule, KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry';
import {Keybinding} from 'vs/base/common/keyCodes';
import * as Platform from 'vs/base/common/platform';
import {getNativeLabelProvider} from 'vs/workbench/services/keybinding/electron-browser/nativeKeymap';
import {OptionsChangeEvent, EventType} from 'vs/workbench/common/events';
interface ContributedKeyBinding {
command: string;
key: string;
when?: string;
mac?: string;
linux?: string;
win?: string;
}
function isContributedKeyBindingsArray(thing: ContributedKeyBinding|ContributedKeyBinding[]): thing is ContributedKeyBinding[] {
return Array.isArray(thing);
}
function isValidContributedKeyBinding(keyBinding: ContributedKeyBinding, rejects: string[]): boolean {
if (!keyBinding) {
rejects.push(nls.localize('nonempty', "expected non-empty value."));
return false;
}
if (typeof keyBinding.command !== 'string') {
rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
return false;
}
if (typeof keyBinding.key !== 'string') {
rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'key'));
return false;
}
if (keyBinding.when && typeof keyBinding.when !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (keyBinding.mac && typeof keyBinding.mac !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'mac'));
return false;
}
if (keyBinding.linux && typeof keyBinding.linux !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'linux'));
return false;
}
if (keyBinding.win && typeof keyBinding.win !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'win'));
return false;
}
return true;
}
let keybindingType:IJSONSchema = {
type: 'object',
default: { command: '', key: '' },
properties: {
command: {
description: nls.localize('vscode.extension.contributes.keybindings.command', 'Identifier of the command to run when keybinding is triggered.'),
type: 'string'
},
key: {
description: nls.localize('vscode.extension.contributes.keybindings.key', 'Key or key sequence (separate keys with plus-sign and sequences with space, e.g Ctrl+O and Ctrl+L L for a chord'),
type: 'string'
},
mac: {
description: nls.localize('vscode.extension.contributes.keybindings.mac', 'Mac specific key or key sequence.'),
type: 'string'
},
linux: {
description: nls.localize('vscode.extension.contributes.keybindings.linux', 'Linux specific key or key sequence.'),
type: 'string'
},
win: {
description: nls.localize('vscode.extension.contributes.keybindings.win', 'Windows specific key or key sequence.'),
type: 'string'
},
when: {
description: nls.localize('vscode.extension.contributes.keybindings.when', 'Condition when the key is active.'),
type: 'string'
}
}
};
let keybindingsExtPoint = PluginsRegistry.registerExtensionPoint<ContributedKeyBinding | ContributedKeyBinding[]>('keybindings', {
description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."),
oneOf: [
keybindingType,
{
type: 'array',
items: keybindingType
}
]
});
export class WorkbenchKeybindingService extends KeybindingService {
private contextService: IWorkspaceContextService;
private eventService: IEventService;
private telemetryService: ITelemetryService;
private toDispose: Function;
private _pluginService: IPluginService;
private _eventService: IEventService;
constructor(contextService: IWorkspaceContextService, eventService: IEventService, telemetryService: ITelemetryService, domNode: HTMLElement) {
this.contextService = contextService;
super(domNode);
this.eventService = eventService;
this.telemetryService = telemetryService;
this.toDispose = this.eventService.addListener(EventType.WORKBENCH_OPTIONS_CHANGED, (e) => this.onOptionsChanged(e));
this._eventService = eventService;
keybindingsExtPoint.setHandler((extensions) => {
let commandAdded = false;
for (let extension of extensions) {
commandAdded = this._handleKeybindingsExtensionPointUser(extension.description.isBuiltin, extension.value, extension.collector) || commandAdded;
}
if (commandAdded) {
this.updateResolver();
}
});
}
setPluginService(pluginService: IPluginService): void {
this._pluginService = pluginService;
}
public customKeybindingsCount(): number {
let opts = this.contextService.getOptions();
if (opts.globalSettings && opts.globalSettings.keybindings && Array.isArray(opts.globalSettings.keybindings)) {
return opts.globalSettings.keybindings.length;
}
return 0;
}
protected _getExtraKeybindings(isFirstTime: boolean): IKeybindingItem[] {
let extras: IUserFriendlyKeybinding[] = [];
let opts = this.contextService.getOptions();
if (opts.globalSettings && opts.globalSettings.keybindings) {
if (!isFirstTime) {
let cnt = 0;
if (Array.isArray(opts.globalSettings.keybindings)) {
cnt = opts.globalSettings.keybindings.length;
}
this.telemetryService.publicLog('customKeybindingsChanged', {
keyCount: cnt
});
}
if (Array.isArray(opts.globalSettings.keybindings)) {
extras = opts.globalSettings.keybindings;
}
}
return extras.map((k, i) => IOSupport.readKeybindingItem(k, i));
}
private onOptionsChanged(e: OptionsChangeEvent): void {
if (e.key === 'globalSettings') {
this.updateResolver();
}
}
public dispose(): void {
this.toDispose();
}
public getLabelFor(keybinding:Keybinding): string {
return keybinding.toCustomLabel(getNativeLabelProvider());
}
public getHTMLLabelFor(keybinding:Keybinding): IHTMLContentElement[] {
return keybinding.toCustomHTMLLabel(getNativeLabelProvider());
}
public getElectronAcceleratorFor(keybinding:Keybinding): string {
if (Platform.isWindows) {
// electron menus always do the correct rendering on Windows
return super.getElectronAcceleratorFor(keybinding);
}
let usLabel = keybinding._toUSLabel();
let label = this.getLabelFor(keybinding);
if (usLabel !== label) {
// electron menus are incorrect in rendering (linux) and in rendering and interpreting (mac)
// for non US standard keyboard layouts
return null;
}
return super.getElectronAcceleratorFor(keybinding);
}
private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings:ContributedKeyBinding | ContributedKeyBinding[], collector:IMessageCollector): boolean {
if (isContributedKeyBindingsArray(keybindings)) {
let commandAdded = false;
for (let i = 0, len = keybindings.length; i < len; i++) {
commandAdded = this._handleKeybinding(isBuiltin, i + 1, keybindings[i], collector) || commandAdded;
}
return commandAdded;
} else {
return this._handleKeybinding(isBuiltin, 1, keybindings, collector);
}
}
private _handleKeybinding(isBuiltin: boolean, idx:number, keybindings:ContributedKeyBinding, collector:IMessageCollector): boolean {
let rejects: string[] = [];
let commandAdded = false;
if (isValidContributedKeyBinding(keybindings, rejects)) {
let rule = this._asCommandRule(isBuiltin, idx++, keybindings);
if (rule) {
KeybindingsRegistry.registerCommandRule(rule);
commandAdded = true;
}
}
if (rejects.length > 0) {
collector.error(nls.localize(
'invalid.keybindings',
"Invalid `contributes.{0}`: {1}",
keybindingsExtPoint.name,
rejects.join('\n')
));
}
return commandAdded;
}
protected _invokeHandler(commandId: string, args: any): TPromise<any> {
if (this._pluginService) {
return this._pluginService.activateByEvent('onCommand:' + commandId).then(_ => {
return super._invokeHandler(commandId, args);
});
}
return TPromise.as(null);
}
private _asCommandRule(isBuiltin: boolean, idx:number, binding: ContributedKeyBinding): ICommandRule {
let {command, when, key, mac, linux, win} = binding;
let weight: number;
if (isBuiltin) {
weight = KeybindingsRegistry.WEIGHT.builtinExtension(idx);
} else {
weight = KeybindingsRegistry.WEIGHT.externalExtension(idx);
}
let desc = {
id: command,
context: IOSupport.readKeybindingContexts(when),
weight: weight,
primary: IOSupport.readKeybinding(key),
mac: mac && { primary: IOSupport.readKeybinding(mac) },
linux: linux && { primary: IOSupport.readKeybinding(linux) },
win: win && { primary: IOSupport.readKeybinding(win) }
};
if (!desc.primary && !desc.mac && !desc.linux && !desc.win) {
return;
}
return desc;
}
}
let schemaId = 'local://schemas/keybindings';
let schema : IJSONSchema = {
'id': schemaId,
'type': 'array',
'title': nls.localize('keybindings.json.title', "Keybindings configuration"),
'items': {
'required': ['key'],
'type': 'object',
'default': { 'key': '{{_}}', 'command': '{{_}}', 'when': '{{_}}' },
'properties': {
'key': {
'type': 'string',
'description': nls.localize('keybindings.json.key', 'Key or key sequence (separated by space)'),
},
'command': {
'description': nls.localize('keybindings.json.command', 'Name of the command to execute'),
},
'when': {
'type': 'string',
'description': nls.localize('keybindings.json.when', 'Condition when the key is active.')
}
}
}
};
let schemaRegistry = <JSONContributionRegistry.IJSONContributionRegistry>Registry.as(JSONContributionRegistry.Extensions.JSONContribution);
schemaRegistry.registerSchema(schemaId, schema);
schemaRegistry.addSchemaFileAssociation('inmemory://defaults/keybindings.json', schemaId);
schemaRegistry.addSchemaFileAssociation('%APP_SETTINGS_HOME%/keybindings.json', schemaId);

View file

@ -4,109 +4,10 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import nls = require('vs/nls');
import {TPromise} from 'vs/base/common/winjs.base';
import {IEventService} from 'vs/platform/event/common/event';
import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry';
import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {PluginsRegistry, IMessageCollector} from 'vs/platform/plugins/common/pluginsRegistry';
import {IPluginService} from 'vs/platform/plugins/common/plugins';
import {IOSupport} from 'vs/platform/keybinding/common/commonKeybindingResolver';
import {WorkbenchKeybindingService} from 'vs/workbench/services/keybinding/browser/keybindingService';
import {ICommandRule, KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry';
import {IJSONSchema} from 'vs/base/common/jsonSchema';
import {KeyCode, Keybinding, IKeyBindingLabelProvider, MacUIKeyLabelProvider, ClassicUIKeyLabelProvider} from 'vs/base/common/keyCodes';
import * as nativeKeymap from 'native-keymap';
import Platform = require('vs/base/common/platform');
import {IHTMLContentElement} from 'vs/base/common/htmlContent';
import {KeyCode, Keybinding, IKeyBindingLabelProvider, MacUIKeyLabelProvider, ClassicUIKeyLabelProvider} from 'vs/base/common/keyCodes';
import {lookupKeyCode, setExtractKeyCode} from 'vs/base/browser/keyboardEvent';
interface ContributedKeyBinding {
command: string;
key: string;
when?: string;
mac?: string;
linux?: string;
win?: string;
}
function isContributedKeyBindingsArray(thing: ContributedKeyBinding|ContributedKeyBinding[]): thing is ContributedKeyBinding[] {
return Array.isArray(thing);
}
function isValidContributedKeyBinding(keyBinding: ContributedKeyBinding, rejects: string[]): boolean {
if (!keyBinding) {
rejects.push(nls.localize('nonempty', "expected non-empty value."));
return false;
}
if (typeof keyBinding.command !== 'string') {
rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'command'));
return false;
}
if (typeof keyBinding.key !== 'string') {
rejects.push(nls.localize('requirestring', "property `{0}` is mandatory and must be of type `string`", 'key'));
return false;
}
if (keyBinding.when && typeof keyBinding.when !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when'));
return false;
}
if (keyBinding.mac && typeof keyBinding.mac !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'mac'));
return false;
}
if (keyBinding.linux && typeof keyBinding.linux !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'linux'));
return false;
}
if (keyBinding.win && typeof keyBinding.win !== 'string') {
rejects.push(nls.localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'win'));
return false;
}
return true;
}
let keybindingType:IJSONSchema = {
type: 'object',
default: { command: '', key: '' },
properties: {
command: {
description: nls.localize('vscode.extension.contributes.keybindings.command', 'Identifier of the command to run when keybinding is triggered.'),
type: 'string'
},
key: {
description: nls.localize('vscode.extension.contributes.keybindings.key', 'Key or key sequence (separate keys with plus-sign and sequences with space, e.g Ctrl+O and Ctrl+L L for a chord'),
type: 'string'
},
mac: {
description: nls.localize('vscode.extension.contributes.keybindings.mac', 'Mac specific key or key sequence.'),
type: 'string'
},
linux: {
description: nls.localize('vscode.extension.contributes.keybindings.linux', 'Linux specific key or key sequence.'),
type: 'string'
},
win: {
description: nls.localize('vscode.extension.contributes.keybindings.win', 'Windows specific key or key sequence.'),
type: 'string'
},
when: {
description: nls.localize('vscode.extension.contributes.keybindings.when', 'Condition when the key is active.'),
type: 'string'
}
}
};
let keybindingsExtPoint = PluginsRegistry.registerExtensionPoint<ContributedKeyBinding | ContributedKeyBinding[]>('keybindings', {
description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."),
oneOf: [
keybindingType,
{
type: 'array',
items: keybindingType
}
]
});
import Platform = require('vs/base/common/platform');
let getNativeKeymap = (function() {
let called = false;
@ -122,7 +23,7 @@ let getNativeKeymap = (function() {
})();
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// See https://github.com/alexandrudima/vscode-keyboard/blob/master/deps/chromium/keyboard_codes_win.h
// See https://github.com/Microsoft/node-native-keymap/blob/master/deps/chromium/keyboard_codes_win.h
const NATIVE_KEY_CODE_TO_KEY_CODE: {[nativeKeyCode:string]:KeyCode;} = {
VKEY_BACK: KeyCode.Backspace,
VKEY_TAB: KeyCode.Tab,
@ -430,139 +331,11 @@ setExtractKeyCode((e:KeyboardEvent) => {
return lookupKeyCode(e);
});
export default class PluginWorkbenchKeybindingService extends WorkbenchKeybindingService {
private _pluginService: IPluginService;
private _eventService: IEventService;
constructor(contextService: IWorkspaceContextService, eventService: IEventService, telemetryService: ITelemetryService, domNode: HTMLElement) {
super(contextService, eventService, telemetryService, domNode);
this._eventService = eventService;
keybindingsExtPoint.setHandler((extensions) => {
let commandAdded = false;
for (let extension of extensions) {
commandAdded = this._handleKeybindingsExtensionPointUser(extension.description.isBuiltin, extension.value, extension.collector) || commandAdded;
}
if (commandAdded) {
this.updateResolver();
}
});
}
setPluginService(pluginService: IPluginService): void {
this._pluginService = pluginService;
}
public getLabelFor(keybinding:Keybinding): string {
this._ensureNativeKeymap();
return keybinding.toCustomLabel(this._nativeLabelProvider);
}
public getHTMLLabelFor(keybinding:Keybinding): IHTMLContentElement[] {
this._ensureNativeKeymap();
return keybinding.toCustomHTMLLabel(this._nativeLabelProvider);
}
public getElectronAcceleratorFor(keybinding:Keybinding): string {
if (Platform.isWindows) {
// electron menus always do the correct rendering on Windows
return super.getElectronAcceleratorFor(keybinding);
}
let usLabel = keybinding._toUSLabel();
let label = this.getLabelFor(keybinding);
if (usLabel !== label) {
// electron menus are incorrect in rendering (linux) and in rendering and interpreting (mac)
// for non US standard keyboard layouts
return null;
}
return super.getElectronAcceleratorFor(keybinding);
}
private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings:ContributedKeyBinding | ContributedKeyBinding[], collector:IMessageCollector): boolean {
if (isContributedKeyBindingsArray(keybindings)) {
let commandAdded = false;
for (let i = 0, len = keybindings.length; i < len; i++) {
commandAdded = this._handleKeybinding(isBuiltin, i + 1, keybindings[i], collector) || commandAdded;
}
return commandAdded;
} else {
return this._handleKeybinding(isBuiltin, 1, keybindings, collector);
}
}
private _handleKeybinding(isBuiltin: boolean, idx:number, keybindings:ContributedKeyBinding, collector:IMessageCollector): boolean {
let rejects: string[] = [];
let commandAdded = false;
if (isValidContributedKeyBinding(keybindings, rejects)) {
let rule = this._asCommandRule(isBuiltin, idx++, keybindings);
if (rule) {
KeybindingsRegistry.registerCommandRule(rule);
commandAdded = true;
}
}
if (rejects.length > 0) {
collector.error(nls.localize(
'invalid.keybindings',
"Invalid `contributes.{0}`: {1}",
keybindingsExtPoint.name,
rejects.join('\n')
));
}
return commandAdded;
}
protected _invokeHandler(commandId: string, args: any): TPromise<any> {
return this._pluginService.activateByEvent('onCommand:' + commandId).then(_ => {
return super._invokeHandler(commandId, args);
});
}
private _asCommandRule(isBuiltin: boolean, idx:number, binding: ContributedKeyBinding): ICommandRule {
let {command, when, key, mac, linux, win} = binding;
let weight: number;
if (isBuiltin) {
weight = KeybindingsRegistry.WEIGHT.builtinExtension(idx);
} else {
weight = KeybindingsRegistry.WEIGHT.externalExtension(idx);
}
let desc = {
id: command,
context: IOSupport.readKeybindingContexts(when),
weight: weight,
primary: IOSupport.readKeybinding(key),
mac: mac && { primary: IOSupport.readKeybinding(mac) },
linux: linux && { primary: IOSupport.readKeybinding(linux) },
win: win && { primary: IOSupport.readKeybinding(win) }
};
if (!desc.primary && !desc.mac && !desc.linux && !desc.win) {
return;
}
return desc;
}
private _gotNativeKeymap = false;
private _nativeLabelProvider:IKeyBindingLabelProvider = null;
private _ensureNativeKeymap(): void {
if (this._gotNativeKeymap) {
return;
}
this._gotNativeKeymap = true;
let nativeLabelProvider:IKeyBindingLabelProvider = null;
export function getNativeLabelProvider(): IKeyBindingLabelProvider {
if (!nativeLabelProvider) {
// See https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx
// See https://github.com/alexandrudima/vscode-keyboard/blob/master/deps/chromium/keyboard_codes_win.h
// See https://github.com/Microsoft/node-native-keymap/blob/master/deps/chromium/keyboard_codes_win.h
let interestingKeyCodes:{[vkeyCode:string]:boolean;} = {
VKEY_OEM_1: true,
VKEY_OEM_PLUS: true,
@ -608,11 +381,12 @@ export default class PluginWorkbenchKeybindingService extends WorkbenchKeybindin
}
if (Platform.isMacintosh) {
this._nativeLabelProvider = new NativeMacUIKeyLabelProvider(remaps);
nativeLabelProvider = new NativeMacUIKeyLabelProvider(remaps);
} else {
this._nativeLabelProvider = new NativeClassicUIKeyLabelProvider(remaps);
nativeLabelProvider = new NativeClassicUIKeyLabelProvider(remaps);
}
}
return nativeLabelProvider;
}
class NativeMacUIKeyLabelProvider extends MacUIKeyLabelProvider {