diff --git a/extensions/node-debug/node-debug.azure.json b/extensions/node-debug/node-debug.azure.json index 45dbe718e6e..229c05461c8 100644 --- a/extensions/node-debug/node-debug.azure.json +++ b/extensions/node-debug/node-debug.azure.json @@ -1,6 +1,6 @@ { "account": "monacobuild", "container": "debuggers", - "zip": "2985e20/node-debug.zip", + "zip": "b5b96dd/node-debug.zip", "output": "" } diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index b3a9c91bd5e..b914a83c85f 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -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. diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 258426bc5b9..14cc6c8a224 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -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; } diff --git a/src/vs/workbench/electron-browser/integration.ts b/src/vs/workbench/electron-browser/integration.ts index 2df5a20e315..0678c6ee6e4 100644 --- a/src/vs/workbench/electron-browser/integration.ts +++ b/src/vs/workbench/electron-browser/integration.ts @@ -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; }[]> { diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index bdcbe6b01fd..0bfba0af255 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -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, window); + this.keybindingService = new WorkbenchKeybindingService(this.contextService, eventService, this.telemetryService, 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); } } diff --git a/src/vs/workbench/electron-main/windows.ts b/src/vs/workbench/electron-main/windows.ts index 65f8572705e..d4a3d048d7a 100644 --- a/src/vs/workbench/electron-main/windows.ts +++ b/src/vs/workbench/electron-main/windows.ts @@ -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('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('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 diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 31658b83a62..824c5f3d3db 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -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; diff --git a/src/vs/workbench/parts/debug/common/debugSource.ts b/src/vs/workbench/parts/debug/common/debugSource.ts index e3ab15902fd..6347f2eb49b 100644 --- a/src/vs/workbench/parts/debug/common/debugSource.ts +++ b/src/vs/workbench/parts/debug/common/debugSource.ts @@ -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); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index b6608db1fb4..8105d9bb691 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -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) { diff --git a/src/vs/workbench/parts/debug/test/common/debugSource.test.ts b/src/vs/workbench/parts/debug/test/common/debugSource.test.ts index 089e04af7e2..592b6b51dcf 100644 --- a/src/vs/workbench/parts/debug/test/common/debugSource.test.ts +++ b/src/vs/workbench/parts/debug/test/common/debugSource.test.ts @@ -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', diff --git a/src/vs/workbench/services/keybinding/browser/keybindingService.ts b/src/vs/workbench/services/keybinding/browser/keybindingService.ts deleted file mode 100644 index 5de1bb2f1a5..00000000000 --- a/src/vs/workbench/services/keybinding/browser/keybindingService.ts +++ /dev/null @@ -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 = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); -schemaRegistry.registerSchema(schemaId, schema); -schemaRegistry.addSchemaFileAssociation('inmemory://defaults/keybindings.json', schemaId); -schemaRegistry.addSchemaFileAssociation('%APP_SETTINGS_HOME%/keybindings.json', schemaId); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts new file mode 100644 index 00000000000..4a4da378adf --- /dev/null +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -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('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 { + 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 = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); +schemaRegistry.registerSchema(schemaId, schema); +schemaRegistry.addSchemaFileAssociation('inmemory://defaults/keybindings.json', schemaId); +schemaRegistry.addSchemaFileAssociation('%APP_SETTINGS_HOME%/keybindings.json', schemaId); diff --git a/src/vs/workbench/services/keybinding/electron-browser/pluginKeybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymap.ts similarity index 62% rename from src/vs/workbench/services/keybinding/electron-browser/pluginKeybindingService.ts rename to src/vs/workbench/services/keybinding/electron-browser/nativeKeymap.ts index 9a8e9eafb47..208dbddd709 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/pluginKeybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/nativeKeymap.ts @@ -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('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 { - 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 {