From b7d8b1127b20a0954747e608d0030e870c7ab843 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Tue, 15 Dec 2015 14:20:35 +0100 Subject: [PATCH] Add information messages in keybindings.json when the current keyboard layout is not US standard --- src/vs/base/common/keyCodes.ts | 99 ++++++++++++++++ .../browser/defineKeybinding.css | 27 ++++- .../browser/defineKeybinding.ts | 112 +++++++++++++++++- .../contrib/defineKeybinding/browser/info.svg | 1 + .../browser/keybindingServiceImpl.ts | 53 ++++----- .../keybinding/browser/keybindings.css | 21 ++++ .../keybinding/common/keybindingService.ts | 2 + .../pluginKeybindingService.ts | 6 + .../test/browser/servicesTestUtils.ts | 5 + 9 files changed, 292 insertions(+), 34 deletions(-) create mode 100644 src/vs/editor/contrib/defineKeybinding/browser/info.svg create mode 100644 src/vs/platform/keybinding/browser/keybindings.css diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index f3c933f0d8b..207a7bd5841 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -7,6 +7,7 @@ import nls = require('vs/nls'); import Platform = require('vs/base/common/platform'); +import {IHTMLContentElement} from 'vs/base/common/htmlContent'; /** * Virtual Key Codes, the value does not hold any inherent meaning. @@ -419,6 +420,13 @@ export class Keybinding { return _asString(value, (Platform.isMacintosh ? MacUIKeyLabelProvider.INSTANCE : ClassicUIKeyLabelProvider.INSTANCE)); } + /** + * Format the binding to a format appropiate for rendering in the UI + */ + private static _toUSHTMLLabel(value:number): IHTMLContentElement[] { + return _asHTML(value, (Platform.isMacintosh ? MacUIKeyLabelProvider.INSTANCE : ClassicUIKeyLabelProvider.INSTANCE)); + } + /** * Format the binding to a format appropiate for rendering in the UI */ @@ -426,6 +434,13 @@ export class Keybinding { return _asString(value, labelProvider); } + /** + * Format the binding to a format appropiate for rendering in the UI + */ + private static _toCustomHTMLLabel(value:number, labelProvider:IKeyBindingLabelProvider): IHTMLContentElement[] { + return _asHTML(value, labelProvider); + } + /** * This prints the binding in a format suitable for electron's accelerators. * See https://github.com/atom/electron/blob/master/docs/api/accelerator.md @@ -487,6 +502,13 @@ export class Keybinding { return Keybinding._toUSLabel(this.value); } + /** + * Format the binding to a format appropiate for rendering in the UI + */ + public _toUSHTMLLabel(): IHTMLContentElement[] { + return Keybinding._toUSHTMLLabel(this.value); + } + /** * Format the binding to a format appropiate for rendering in the UI */ @@ -494,6 +516,13 @@ export class Keybinding { return Keybinding._toCustomLabel(this.value, labelProvider); } + /** + * Format the binding to a format appropiate for rendering in the UI + */ + public toCustomHTMLLabel(labelProvider:IKeyBindingLabelProvider): IHTMLContentElement[] { + return Keybinding._toCustomHTMLLabel(this.value, labelProvider); + } + /** * This prints the binding in a format suitable for electron's accelerators. * See https://github.com/atom/electron/blob/master/docs/api/accelerator.md @@ -661,3 +690,73 @@ function _asString(keybinding:number, labelProvider:IKeyBindingLabelProvider): s return actualResult; } + +function _pushKey(result:IHTMLContentElement[], str:string): void { + if (result.length > 0) { + result.push({ + tagName: 'span', + text: '+' + }); + } + result.push({ + tagName: 'span', + className: 'monaco-kbkey', + text: str + }); +} + +function _asHTML(keybinding:number, labelProvider:IKeyBindingLabelProvider, isChord:boolean = false): IHTMLContentElement[] { + let result:IHTMLContentElement[] = [], + ctrlCmd = BinaryKeybindings.hasCtrlCmd(keybinding), + shift = BinaryKeybindings.hasShift(keybinding), + alt = BinaryKeybindings.hasAlt(keybinding), + winCtrl = BinaryKeybindings.hasWinCtrl(keybinding), + keyCode = BinaryKeybindings.extractKeyCode(keybinding); + + // translate modifier keys: Ctrl-Shift-Alt-Meta + if ((ctrlCmd && !Platform.isMacintosh) || (winCtrl && Platform.isMacintosh)) { + _pushKey(result, labelProvider.ctrlKeyLabel); + } + + if (shift) { + _pushKey(result, labelProvider.shiftKeyLabel); + } + + if (alt) { + _pushKey(result, labelProvider.altKeyLabel); + } + + if (ctrlCmd && Platform.isMacintosh) { + _pushKey(result, labelProvider.cmdKeyLabel); + } + + if (winCtrl && !Platform.isMacintosh) { + _pushKey(result, labelProvider.windowsKeyLabel); + } + + // the actual key + _pushKey(result, labelProvider.getLabelForKey(keyCode)); + + let chordTo: IHTMLContentElement[] = null; + + if (BinaryKeybindings.hasChord(keybinding)) { + chordTo = _asHTML(BinaryKeybindings.extractChordPart(keybinding), labelProvider, true); + result.push({ + tagName: 'span', + text: ' ' + }); + result = result.concat(chordTo); + } + + if (isChord) { + return result; + } + + return [{ + tagName: 'span', + className: 'monaco-kb', + children: result + }] + + return result; +} diff --git a/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.css b/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.css index 5c8e9edb53e..08390cb45aa 100644 --- a/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.css +++ b/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.css @@ -39,4 +39,29 @@ .monaco-editor.vs-dark .defineKeybindingWidget { background-color: #2D2D30; box-shadow: 0 2px 8px #000; -} \ No newline at end of file +} + +.monaco-editor .inlineKeybindingInfo:before { + margin: 0.2em 0.1em 0 0.1em; + content:" "; + display:inline-block; + height:0.8em; + width:1em; + background: url(info.svg) 0px -0.1em no-repeat; + background-size: 0.9em; +} + +/*.monaco-editor .inlineKeybindingError:before { + margin: 0.1em 0.1em 0 0.1em; + content:" "; + display:inline-block; + height:0.8em; + width:1em; + background: url(status-error.svg) 0px -0.1em no-repeat; + background-size: 1em; +}*/ + +.monaco-editor .keybindingInfo { + box-shadow: inset 0 0 0 1px #B9B9B9; + background-color: rgba(100, 100, 250, 0.2); +} diff --git a/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.ts b/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.ts index 8df557eb7de..c5ae88cfa1d 100644 --- a/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.ts +++ b/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.ts @@ -20,10 +20,16 @@ import {EditorAction, Behaviour} from 'vs/editor/common/editorAction'; import {CommonEditorRegistry, ContextKey, EditorActionDescriptor} from 'vs/editor/common/editorCommonExtensions'; import {TPromise} from 'vs/base/common/winjs.base'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybindingService'; +import {RunOnceScheduler} from 'vs/base/common/async'; +import {IOSupport} from 'vs/platform/keybinding/common/commonKeybindingResolver'; +import {IHTMLContentElement} from 'vs/base/common/htmlContent'; const NLS_LAUNCH_MESSAGE = nls.localize('defineKeybinding.start', "Define Keybinding"); const NLS_DEFINE_MESSAGE = nls.localize('defineKeybinding.initial', "Press desired key combination and ENTER"); const NLS_DEFINE_ACTION_LABEL = nls.localize('DefineKeybindingAction',"Define Keybinding"); +const NLS_KB_LAYOUT_INFO_MESSAGE = nls.localize('defineKeybinding.kbLayoutMessage', "For your current keyboard layout press "); + +const INTERESTING_FILE = /keybindings\.json$/; export class DefineKeybindingController implements EditorCommon.IEditorContribution { @@ -34,15 +40,19 @@ export class DefineKeybindingController implements EditorCommon.IEditorContribut } private _editor: EditorBrowser.ICodeEditor; + private _keybindingService:IKeybindingService; private _launchWidget: DefineKeybindingLauncherWidget; private _defineWidget: DefineKeybindingWidget; private _toDispose: IDisposable[]; + private _modelToDispose: IDisposable[]; + private _updateDecorations: RunOnceScheduler; constructor( editor:EditorBrowser.ICodeEditor, @IKeybindingService keybindingService:IKeybindingService ) { this._editor = editor; + this._keybindingService = keybindingService; this._toDispose = []; this._launchWidget = new DefineKeybindingLauncherWidget(this._editor, keybindingService, () => this.launch()); this._defineWidget = new DefineKeybindingWidget(this._editor, (keybinding) => this._onAccepted(keybinding)); @@ -53,7 +63,14 @@ export class DefineKeybindingController implements EditorCommon.IEditorContribut } else { this._launchWidget.hide(); } + this._onModel(); })); + + this._updateDecorations = new RunOnceScheduler(() => this._updateDecorationsNow(), 500); + this._toDispose.push(this._updateDecorations); + + this._modelToDispose = []; + this._onModel(); } public getId(): string { @@ -61,6 +78,7 @@ export class DefineKeybindingController implements EditorCommon.IEditorContribut } public dispose(): void { + this._modelToDispose = disposeAll(this._modelToDispose); this._toDispose = disposeAll(this._toDispose); this._launchWidget.dispose(); this._launchWidget = null; @@ -85,6 +103,99 @@ export class DefineKeybindingController implements EditorCommon.IEditorContribut Snippet.get(this._editor).run(new Snippet.CodeSnippet(snippetText), 0, 0); } + + private _onModel(): void { + this._modelToDispose = disposeAll(this._modelToDispose); + + let model = this._editor.getModel(); + if (!model) { + return; + } + + let url = model.getAssociatedResource().toString(); + if (!INTERESTING_FILE.test(url)) { + return; + } + + this._modelToDispose.push(model.addListener2(EditorCommon.EventType.ModelContentChanged2, (e) => this._updateDecorations.schedule())); + this._modelToDispose.push({ + dispose: () => { + this._dec = this._editor.deltaDecorations(this._dec, []); + this._updateDecorations.cancel(); + } + }); + this._updateDecorations.schedule(); + } + + private static _cachedKeybindingRegex: string = null; + private static _getKeybindingRegex(): string { + if (!this._cachedKeybindingRegex) { + let numpadKey = "numpad(0|1|2|3|4|5|6|7|8|9|_multiply|_add|_subtract|_decimal|_divide)"; + let punctKey = "`|\\-|=|\\[|\\]|\\\\\\\\|;|'|,|\\.|\\/"; + let specialKey = "left|up|right|down|pageup|pagedown|end|home|tab|enter|escape|space|backspace|delete|pausebreak|capslock|insert"; + let casualKey = "[a-z]|[0-9]|f(1|2|3|4|5|6|7|8|9|10|11|12|13|14|15)"; + let key = '((' + [numpadKey, punctKey, specialKey, casualKey].join(')|(') + '))'; + let mod = '((ctrl|shift|alt|cmd|win|meta)\\+)*'; + let keybinding = '(' + mod + key + ')'; + + this._cachedKeybindingRegex = '"\\s*(' + keybinding + '(\\s+' + keybinding +')?' + ')\\s*"'; + } + return this._cachedKeybindingRegex; + } + + private _dec:string[] = []; + private _updateDecorationsNow(): void { + let model = this._editor.getModel(); + let regex = DefineKeybindingController._getKeybindingRegex(); + + var m = model.findMatches(regex, false, true, false, false); + + let data = m.map((range) => { + let text = model.getValueInRange(range); + + let strKeybinding = text.substring(1, text.length - 1); + strKeybinding = strKeybinding.replace(/\\\\/g, '\\'); + + let numKeybinding = IOSupport.readKeybinding(strKeybinding); + + let keybinding = new Keybinding(numKeybinding); + + return { + strKeybinding: strKeybinding, + keybinding: keybinding, + usLabel: keybinding._toUSLabel(), + label: this._keybindingService.getLabelFor(keybinding), + range: range + }; + }); + + data = data.filter((entry) => { + return (entry.usLabel !== entry.label); + }); + + this._dec = this._editor.deltaDecorations(this._dec, data.map((m) : EditorCommon.IModelDeltaDecoration => { + let label = m.label; + let msg:IHTMLContentElement[] = [{ + tagName: 'span', + text: NLS_KB_LAYOUT_INFO_MESSAGE + }]; + msg = msg.concat(this._keybindingService.getHTMLLabelFor(m.keybinding)); + return { + range: m.range, + options: { + stickiness: EditorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + className: 'keybindingInfo', + htmlMessage: msg, + inlineClassName: 'inlineKeybindingInfo', + overviewRuler: { + color: 'rgba(100, 100, 250, 0.6)', + darkColor: 'rgba(100, 100, 250, 0.6)', + position: EditorCommon.OverviewRulerLane.Right + } + } + } + })) + } } class DefineKeybindingLauncherWidget implements EditorBrowser.IOverlayWidget { @@ -277,7 +388,6 @@ export class DefineKeybindingAction extends EditorAction { } -const INTERESTING_FILE = /keybindings\.json$/; function isInterestingEditorModel(editor:EditorCommon.ICommonCodeEditor): boolean { if (editor.getConfiguration().readOnly) { return false; diff --git a/src/vs/editor/contrib/defineKeybinding/browser/info.svg b/src/vs/editor/contrib/defineKeybinding/browser/info.svg new file mode 100644 index 00000000000..6578b81ea3f --- /dev/null +++ b/src/vs/editor/contrib/defineKeybinding/browser/info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts b/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts index 34c98b4e2fd..5aba623f5fb 100644 --- a/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts +++ b/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import 'vs/css!./keybindings'; + import Severity from 'vs/base/common/severity'; import {TPromise} from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); @@ -19,6 +21,7 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IMessageService} from 'vs/platform/message/common/message'; import {IResolveResult, CommonKeybindingResolver} from 'vs/platform/keybinding/common/commonKeybindingResolver'; import {Keybinding, KeyCode} from 'vs/base/common/keyCodes'; +import {IHTMLContentElement} from 'vs/base/common/htmlContent'; var KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context'; @@ -82,7 +85,7 @@ class KeybindingContextKey implements IKeybindingContextKey { } -export class AbstractKeybindingService { +export abstract class AbstractKeybindingService { public serviceId = IKeybindingService; protected _myContextId: number; protected _instantiationService: IInstantiationService; @@ -118,37 +121,15 @@ export class AbstractKeybindingService { this.getContext(this._myContextId).removeValue(key); } - public getLabelFor(keybinding:Keybinding): string { - throw new Error('Not implemented'); - } - - public customKeybindingsCount(): number { - throw new Error('Not implemented'); - } - - public getContext(contextId: number): KeybindingContext { - throw new Error('Not implemented'); - } - - public createChildContext(parentContextId?: number): number { - throw new Error('Not implemented'); - } - - public disposeContext(contextId: number): void { - throw new Error('Not implemented'); - } - - public getDefaultKeybindings(): string { - throw new Error('Not implemented'); - } - - public lookupKeybindings(commandId: string): Keybinding[]{ - throw new Error('Not implemented'); - } - - public executeCommand(commandId: string, args:any): TPromise { - throw new Error('Not implemented'); - } + public abstract getLabelFor(keybinding:Keybinding): string; + public abstract getHTMLLabelFor(keybinding:Keybinding): IHTMLContentElement[]; + public abstract customKeybindingsCount(): number; + public abstract getContext(contextId: number): KeybindingContext; + public abstract createChildContext(parentContextId?: number): number; + public abstract disposeContext(contextId: number): void; + public abstract getDefaultKeybindings(): string; + public abstract lookupKeybindings(commandId: string): Keybinding[]; + public abstract executeCommand(commandId: string, args:any): TPromise; } export class KeybindingService extends AbstractKeybindingService implements IKeybindingService { @@ -189,6 +170,10 @@ export class KeybindingService extends AbstractKeybindingService implements IKey return keybinding._toUSLabel(); } + public getHTMLLabelFor(keybinding:Keybinding): IHTMLContentElement[] { + return keybinding._toUSHTMLLabel(); + } + protected updateResolver(): void { this._createOrUpdateResolver(false); } @@ -345,6 +330,10 @@ class ScopedKeybindingService extends AbstractKeybindingService { return this._parent.getLabelFor(keybinding); } + public getHTMLLabelFor(keybinding:Keybinding): IHTMLContentElement[] { + return this._parent.getHTMLLabelFor(keybinding); + } + public getDefaultKeybindings(): string { return this._parent.getDefaultKeybindings(); } diff --git a/src/vs/platform/keybinding/browser/keybindings.css b/src/vs/platform/keybinding/browser/keybindings.css new file mode 100644 index 00000000000..cdec888394f --- /dev/null +++ b/src/vs/platform/keybinding/browser/keybindings.css @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-kb { + white-space: nowrap; +} +.monaco-kbkey { + display: inline-block; + border: solid 1px #ccc; + border-bottom-color: #bbb; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #bbb; + background-color: #fcfcfc; + vertical-align: middle; + color: #555; + line-height: 10px; + font-size: 11px; + padding: 3px 5px; +} \ No newline at end of file diff --git a/src/vs/platform/keybinding/common/keybindingService.ts b/src/vs/platform/keybinding/common/keybindingService.ts index 45caf230232..c880dc07611 100644 --- a/src/vs/platform/keybinding/common/keybindingService.ts +++ b/src/vs/platform/keybinding/common/keybindingService.ts @@ -8,6 +8,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {TypeConstraint} from 'vs/base/common/types'; import {createDecorator, IInstantiationService, ServiceIdentifier, ServicesAccessor} from 'vs/platform/instantiation/common/instantiation'; import {Keybinding} from 'vs/base/common/keyCodes'; +import {IHTMLContentElement} from 'vs/base/common/htmlContent'; export interface IUserFriendlyKeybinding { key: string; @@ -92,6 +93,7 @@ export interface IKeybindingService { customKeybindingsCount(): number; getLabelFor(keybinding:Keybinding): string; + getHTMLLabelFor(keybinding:Keybinding): IHTMLContentElement[]; executeCommand(commandId: string, args?: any): TPromise; executeCommand(commandId: string, args?: any): TPromise; diff --git a/src/vs/workbench/services/keybinding/electron-browser/pluginKeybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/pluginKeybindingService.ts index 73a328eb181..14e453a04c8 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/pluginKeybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/pluginKeybindingService.ts @@ -20,6 +20,7 @@ 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'; interface ContributedKeyBinding { command: string; @@ -138,6 +139,11 @@ export default class PluginWorkbenchKeybindingService extends WorkbenchKeybindin return keybinding.toCustomLabel(this._nativeLabelProvider); } + public getHTMLLabelFor(keybinding:Keybinding): IHTMLContentElement[] { + this._ensureNativeKeymap(); + return keybinding.toCustomHTMLLabel(this._nativeLabelProvider); + } + private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings:ContributedKeyBinding | ContributedKeyBinding[], collector:IMessageCollector): boolean { if (isContributedKeyBindingsArray(keybindings)) { let commandAdded = false; diff --git a/src/vs/workbench/test/browser/servicesTestUtils.ts b/src/vs/workbench/test/browser/servicesTestUtils.ts index 75c6e8324ec..0b4651edef2 100644 --- a/src/vs/workbench/test/browser/servicesTestUtils.ts +++ b/src/vs/workbench/test/browser/servicesTestUtils.ts @@ -49,6 +49,7 @@ import {ITelemetryService, ITelemetryInfo} from 'vs/platform/telemetry/common/te import {IWorkspaceContextService, IWorkspace, IConfiguration} from 'vs/platform/workspace/common/workspace'; import {IKeybindingService, IKeybindingContextKey, IKeybindingItem} from 'vs/platform/keybinding/common/keybindingService'; import {Keybinding} from 'vs/base/common/keyCodes'; +import {IHTMLContentElement} from 'vs/base/common/htmlContent'; export const TestWorkspace: IWorkspace = { resource: URI.file('C:\\testWorkspace'), @@ -168,6 +169,10 @@ export class TestKeybindingService implements IKeybindingService { return keybinding._toUSLabel(); } + public getHTMLLabelFor(keybinding:Keybinding): IHTMLContentElement[] { + return keybinding._toUSHTMLLabel(); + } + public createScoped(domNode: HTMLElement): IKeybindingService { return this; } -- GitLab