Add information messages in keybindings.json when the current keyboard layout is not US standard

This commit is contained in:
Alex Dima 2015-12-15 14:20:35 +01:00
parent 3eb71362bf
commit b7d8b1127b
9 changed files with 292 additions and 34 deletions

View file

@ -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;
}

View file

@ -39,4 +39,29 @@
.monaco-editor.vs-dark .defineKeybindingWidget {
background-color: #2D2D30;
box-shadow: 0 2px 8px #000;
}
}
.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);
}

View file

@ -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;

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M8 1c-3.865 0-7 3.135-7 7s3.135 7 7 7 7-3.135 7-7-3.135-7-7-7zm1 12h-2v-7h2v7zm0-8h-2v-2h2v2z" fill="#1BA1E2"/><path d="M7 6h2v7h-2v-7zm0-1h2v-2h-2v2z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 243 B

View file

@ -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<T> implements IKeybindingContextKey<T> {
}
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<any> {
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<any>;
}
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();
}

View file

@ -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;
}

View file

@ -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<T>(commandId: string, args?: any): TPromise<T>;
executeCommand(commandId: string, args?: any): TPromise<any>;

View file

@ -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;

View file

@ -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;
}