Skip to content

Commit e5b1616

Browse files
authored
Handle mnemonic escaping (microsoft#68196)
* handle mnemonic escapes * Update src/vs/workbench/electron-browser/main.contribution.ts * update custom menu and recent items list to support escaped mnemonics * remove need for &&& * qfix
1 parent ba304fe commit e5b1616

4 files changed

Lines changed: 18 additions & 12 deletions

File tree

src/vs/base/browser/ui/menu/menu.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import { Event, Emitter } from 'vs/base/common/event';
2020
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
2121
import { isLinux } from 'vs/base/common/platform';
2222

23-
export const MENU_MNEMONIC_REGEX: RegExp = /\(&{1,2}(.)\)|&{1,2}(.)/;
24-
export const MENU_ESCAPED_MNEMONIC_REGEX: RegExp = /(?:&){1,2}(.)/;
23+
export const MENU_MNEMONIC_REGEX: RegExp = /\(&(\w)\)|(?<!&)&(\w)/;
24+
export const MENU_ESCAPED_MNEMONIC_REGEX: RegExp = /(?<!&amp;)(?:&amp;)(\w)/;
2525

2626
export interface IMenuOptions {
2727
context?: any;
@@ -440,13 +440,16 @@ class MenuActionItem extends BaseActionItem {
440440
label = cleanLabel;
441441
}
442442

443-
this.label.setAttribute('aria-label', cleanLabel);
443+
this.label.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&'));
444444

445445
const matches = MENU_MNEMONIC_REGEX.exec(label);
446446

447447
if (matches) {
448448
label = strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '<u aria-hidden="true">$1</u>');
449+
label = label.replace(/&amp;&amp;/g, '&amp;');
449450
this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[2]).toLocaleLowerCase());
451+
} else {
452+
label = label.replace(/&&/g, '&');
450453
}
451454
}
452455

src/vs/base/browser/ui/menu/menubar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,8 @@ export class MenuBar extends Disposable {
457457

458458
// Update the button label to reflect mnemonics
459459
titleElement.innerHTML = this.options.enableMnemonics ?
460-
strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '<mnemonic aria-hidden="true">$1</mnemonic>') :
461-
cleanMenuLabel;
460+
strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '<mnemonic aria-hidden="true">$1</mnemonic>').replace(/&amp;&amp;/g, '&amp;') :
461+
cleanMenuLabel.replace(/&&/g, '&');
462462

463463
let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label);
464464

src/vs/base/common/labels.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,10 @@ export function template(template: string, values: { [key: string]: string | ISe
356356
*/
357357
export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean): string {
358358
if (isMacintosh || forceDisableMnemonics) {
359-
return label.replace(/\(&&\w\)|&&/g, '');
359+
return label.replace(/\(&&\w\)|&&/g, '').replace(/&/g, isMacintosh ? '&' : '&&');
360360
}
361361

362-
return label.replace(/&&/g, '&');
362+
return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&');
363363
}
364364

365365
/**

src/vs/workbench/browser/parts/titlebar/menubarControl.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { MenuBar } from 'vs/base/browser/ui/menu/menubar';
3232
import { SubmenuAction } from 'vs/base/browser/ui/menu/menu';
3333
import { attachMenuStyler } from 'vs/platform/theme/common/styler';
3434
import { assign } from 'vs/base/common/objects';
35+
import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels';
3536
import { getAccessibilitySupport } from 'vs/base/browser/browser';
3637

3738
export class MenubarControl extends Disposable {
@@ -375,6 +376,8 @@ export class MenubarControl extends Disposable {
375376
typeHint = 'file';
376377
}
377378

379+
label = unmnemonicLabel(label);
380+
378381
const ret: IAction = new Action(commandId, label, undefined, undefined, (event) => {
379382
const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey)));
380383

@@ -517,10 +520,10 @@ export class MenubarControl extends Disposable {
517520
const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService);
518521
const submenuActions: SubmenuAction[] = [];
519522
updateActions(submenu, submenuActions);
520-
target.push(new SubmenuAction(action.label, submenuActions));
523+
target.push(new SubmenuAction(mnemonicMenuLabel(action.label), submenuActions));
521524
submenu.dispose();
522525
} else {
523-
action.label = this.calculateActionLabel(action);
526+
action.label = mnemonicMenuLabel(this.calculateActionLabel(action));
524527
target.push(action);
525528
}
526529
}
@@ -537,17 +540,17 @@ export class MenubarControl extends Disposable {
537540
this._register(menu.onDidChange(() => {
538541
const actions = [];
539542
updateActions(menu, actions);
540-
this.menubar.updateMenu({ actions: actions, label: this.topLevelTitles[title] });
543+
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
541544
}));
542545
}
543546

544547
const actions = [];
545548
updateActions(menu, actions);
546549

547550
if (!firstTime) {
548-
this.menubar.updateMenu({ actions: actions, label: this.topLevelTitles[title] });
551+
this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
549552
} else {
550-
this.menubar.push({ actions: actions, label: this.topLevelTitles[title] });
553+
this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) });
551554
}
552555
}
553556
}

0 commit comments

Comments
 (0)