Skip to content

Commit e54c774

Browse files
authored
Add Folder to Workspace button displaying as "&Add" (#240185)
* Add Folder to Workspace button displaying as "&Add" Fixes #240105 * Change mnemonicButtonLabel to return both with and without mnemonic * PR feedback * Remove save label changes for release branch * Fix tests * Fix silly mistake * Fix one more test * Fix for mac Unit tests on main for mac caught a bug
1 parent 0a5a4d6 commit e54c774

File tree

8 files changed

+31
-23
lines changed

8 files changed

+31
-23
lines changed

Diff for: src/vs/base/common/labels.ts

+16-6
Original file line numberDiff line numberDiff line change
@@ -417,17 +417,27 @@ export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean
417417
* - Windows: Supported via & character (replace && with & and & with && for escaping)
418418
* - Linux: Supported via _ character (replace && with _)
419419
* - macOS: Unsupported (replace && with empty string)
420+
* When forceDisableMnemonics is set, returns just the label without mnemonics.
420421
*/
421-
export function mnemonicButtonLabel(label: string, forceDisableMnemonics?: boolean): string {
422-
if (isMacintosh || forceDisableMnemonics) {
423-
return label.replace(/\(&&\w\)|&&/g, '');
422+
export function mnemonicButtonLabel(label: string, forceDisableMnemonics: true): string;
423+
export function mnemonicButtonLabel(label: string, forceDisableMnemonics?: false): { readonly withMnemonic: string; readonly withoutMnemonic: string };
424+
export function mnemonicButtonLabel(label: string, forceDisableMnemonics?: boolean): { readonly withMnemonic: string; readonly withoutMnemonic: string } | string {
425+
const withoutMnemonic = label.replace(/\(&&\w\)|&&/g, '');
426+
427+
if (forceDisableMnemonics) {
428+
return withoutMnemonic;
429+
}
430+
if (isMacintosh) {
431+
return { withMnemonic: withoutMnemonic, withoutMnemonic };
424432
}
425433

434+
let withMnemonic: string;
426435
if (isWindows) {
427-
return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&');
436+
withMnemonic = label.replace(/&&|&/g, m => m === '&' ? '&&' : '&');
437+
} else {
438+
withMnemonic = label.replace(/&&/g, '_');
428439
}
429-
430-
return label.replace(/&&/g, '_');
440+
return { withMnemonic, withoutMnemonic };
431441
}
432442

433443
export function unmnemonicLabel(label: string): string {

Diff for: src/vs/base/test/common/labels.test.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,17 @@ suite('Labels', () => {
145145
});
146146

147147
test('mnemonicButtonLabel', () => {
148-
assert.strictEqual(labels.mnemonicButtonLabel('Hello World'), 'Hello World');
149-
assert.strictEqual(labels.mnemonicButtonLabel(''), '');
148+
assert.strictEqual(labels.mnemonicButtonLabel('Hello World').withMnemonic, 'Hello World');
149+
assert.strictEqual(labels.mnemonicButtonLabel('').withMnemonic, '');
150150
if (isWindows) {
151-
assert.strictEqual(labels.mnemonicButtonLabel('Hello & World'), 'Hello && World');
152-
assert.strictEqual(labels.mnemonicButtonLabel('Do &&not Save & Continue'), 'Do &not Save && Continue');
151+
assert.strictEqual(labels.mnemonicButtonLabel('Hello & World').withMnemonic, 'Hello && World');
152+
assert.strictEqual(labels.mnemonicButtonLabel('Do &&not Save & Continue').withMnemonic, 'Do &not Save && Continue');
153153
} else if (isMacintosh) {
154-
assert.strictEqual(labels.mnemonicButtonLabel('Hello & World'), 'Hello & World');
155-
assert.strictEqual(labels.mnemonicButtonLabel('Do &&not Save & Continue'), 'Do not Save & Continue');
154+
assert.strictEqual(labels.mnemonicButtonLabel('Hello & World').withMnemonic, 'Hello & World');
155+
assert.strictEqual(labels.mnemonicButtonLabel('Do &&not Save & Continue').withMnemonic, 'Do not Save & Continue');
156156
} else {
157-
assert.strictEqual(labels.mnemonicButtonLabel('Hello & World'), 'Hello & World');
158-
assert.strictEqual(labels.mnemonicButtonLabel('Do &&not Save & Continue'), 'Do _not Save & Continue');
157+
assert.strictEqual(labels.mnemonicButtonLabel('Hello & World').withMnemonic, 'Hello & World');
158+
assert.strictEqual(labels.mnemonicButtonLabel('Do &&not Save & Continue').withMnemonic, 'Do _not Save & Continue');
159159
}
160160
});
161161

Diff for: src/vs/platform/dialogs/common/dialogs.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export interface IOpenDialogOptions {
240240
/**
241241
* A human-readable string for the open button.
242242
*/
243-
readonly openLabel?: string;
243+
readonly openLabel?: { readonly withMnemonic: string; readonly withoutMnemonic: string } | string;
244244

245245
/**
246246
* Allow to select files, defaults to `true`.
@@ -640,7 +640,7 @@ export interface IMassagedMessageBoxOptions {
640640
export function massageMessageBoxOptions(options: MessageBoxOptions, productService: IProductService): IMassagedMessageBoxOptions {
641641
const massagedOptions = deepClone(options);
642642

643-
let buttons = (massagedOptions.buttons ?? []).map(button => mnemonicButtonLabel(button));
643+
let buttons = (massagedOptions.buttons ?? []).map(button => mnemonicButtonLabel(button).withMnemonic);
644644
let buttonIndeces = (options.buttons || []).map((button, index) => index);
645645

646646
let defaultId = 0; // by default the first button is default button

Diff for: src/vs/platform/dialogs/electron-main/dialogMainService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export class DialogMainService implements IDialogMainService {
7171

7272
pickWorkspace(options: INativeOpenDialogOptions, window?: electron.BrowserWindow): Promise<string[] | undefined> {
7373
const title = localize('openWorkspaceTitle', "Open Workspace from File");
74-
const buttonLabel = mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open"));
74+
const buttonLabel = mnemonicButtonLabel(localize({ key: 'openWorkspace', comment: ['&& denotes a mnemonic'] }, "&&Open")).withMnemonic;
7575
const filters = WORKSPACE_FILTER;
7676

7777
return this.doPick({ ...options, pickFiles: true, title, filters, buttonLabel }, window);

Diff for: src/vs/workbench/contrib/files/browser/fileImportExport.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import { isWeb } from '../../../../base/common/platform.js';
2626
import { getActiveWindow, isDragEvent, triggerDownload } from '../../../../base/browser/dom.js';
2727
import { ILogService } from '../../../../platform/log/common/log.js';
2828
import { FileAccess, Schemas } from '../../../../base/common/network.js';
29-
import { mnemonicButtonLabel } from '../../../../base/common/labels.js';
3029
import { listenStream } from '../../../../base/common/stream.js';
3130
import { DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
3231
import { createSingleCallFunction } from '../../../../base/common/functional.js';
@@ -828,7 +827,7 @@ export class FileDownload {
828827

829828
const destination = await this.fileDialogService.showSaveDialog({
830829
availableFileSystems: [Schemas.file],
831-
saveLabel: mnemonicButtonLabel(localize('downloadButton', "Download")),
830+
saveLabel: localize('downloadButton', "Download"),
832831
title: localize('chooseWhereToDownload', "Choose Where to Download"),
833832
defaultUri
834833
});

Diff for: src/vs/workbench/services/dialogs/browser/simpleFileDialog.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ export class SimpleFileDialog extends Disposable implements ISimpleFileDialog {
283283
this.filePickBox.sortByLabel = false;
284284
this.filePickBox.ignoreFocusOut = true;
285285
this.filePickBox.ok = true;
286-
this.filePickBox.okLabel = this.options.openLabel;
286+
this.filePickBox.okLabel = typeof this.options.openLabel === 'string' ? this.options.openLabel : this.options.openLabel?.withoutMnemonic;
287287
if ((this.scheme !== Schemas.file) && this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1) && (this.options.availableFileSystems.indexOf(Schemas.file) > -1)) {
288288
this.filePickBox.customButton = true;
289289
this.filePickBox.customLabel = nls.localize('remoteFileDialog.local', 'Show Local');

Diff for: src/vs/workbench/services/dialogs/electron-sandbox/fileDialogService.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
178178
const newOptions: OpenDialogOptions & { properties: string[] } & INativeHostOptions = {
179179
title: options.title,
180180
defaultPath: options.defaultUri?.fsPath,
181-
buttonLabel: options.openLabel,
181+
buttonLabel: typeof options.openLabel === 'string' ? options.openLabel : options.openLabel?.withMnemonic,
182182
filters: options.filters,
183183
properties: [],
184184
targetWindowId: getActiveWindow().vscodeWindowId

Diff for: src/vs/workbench/services/workspaces/browser/abstractWorkspaceEditingService.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { INotificationService, Severity } from '../../../../platform/notificatio
1919
import { IFileService } from '../../../../platform/files/common/files.js';
2020
import { IWorkbenchEnvironmentService } from '../../environment/common/environmentService.js';
2121
import { IFileDialogService, IDialogService } from '../../../../platform/dialogs/common/dialogs.js';
22-
import { mnemonicButtonLabel } from '../../../../base/common/labels.js';
2322
import { ITextFileService } from '../../textfile/common/textfiles.js';
2423
import { IHostService } from '../../host/browser/host.js';
2524
import { Schemas } from '../../../../base/common/network.js';
@@ -62,7 +61,7 @@ export abstract class AbstractWorkspaceEditingService extends Disposable impleme
6261
availableFileSystems.unshift(Schemas.vscodeRemote);
6362
}
6463
let workspacePath = await this.fileDialogService.showSaveDialog({
65-
saveLabel: mnemonicButtonLabel(localize('save', "Save")),
64+
saveLabel: localize('save', "Save"),
6665
title: localize('saveWorkspace', "Save Workspace"),
6766
filters: WORKSPACE_FILTER,
6867
defaultUri: joinPath(await this.fileDialogService.defaultWorkspacePath(), this.getNewWorkspaceName()),

0 commit comments

Comments
 (0)