src/vs/platform/dialogs/common/dialogs.ts

626 LOC · 511 covered · 115 uncovered · 8 ranges · 3693 concepts · 1 introducers · 1990 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- dialogs.ts ×8
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { CancellationToken } from '../../../base/common/cancellation.js';
7 > import { Event } from '../../../base/common/event.js';
8 > import { ThemeIcon } from '../../../base/common/themables.js';
9 > import { IMarkdownString } from '../../../base/common/htmlContent.js';
10 > import { basename } from '../../../base/common/resources.js';
11 > import Severity from '../../../base/common/severity.js';
12 > import { URI } from '../../../base/common/uri.js';
13 > import { localize } from '../../../nls.js';
14 > import { createDecorator } from '../../instantiation/common/instantiation.js';
15 > import { ITelemetryData } from '../../telemetry/common/telemetry.js';
16 >
17 > export interface IDialogArgs {
18 > readonly confirmArgs?: IConfirmDialogArgs;
19 > readonly inputArgs?: IInputDialogArgs;
20 > readonly promptArgs?: IPromptDialogArgs;
21 > }
22 >
23 > export interface IBaseDialogOptions {
24 > readonly type?: Severity | DialogType;
25 >
26 > readonly title?: string;
27 > readonly message: string;
28 > readonly detail?: string;
29 >
30 > readonly checkbox?: ICheckbox;
31 >
32 > /**
33 > * Allows to enforce use of custom dialog even in native environments.
34 > */
35 > readonly custom?: boolean | ICustomDialogOptions;
36 >
37 > /**
38 > * An optional cancellation token that can be used to dismiss the dialog
39 > * programmatically for custom dialog implementations.
40 > *
41 > * When cancelled, the custom dialog resolves as if the cancel button was
42 > * pressed. Native dialog handlers cannot currently be dismissed
43 > * programmatically and ignore this option unless a custom dialog is
44 > * explicitly enforced via the {@link custom} option.
45 > */
46 > readonly token?: CancellationToken;
47 > }
48 >
49 > export interface IConfirmDialogArgs {
50 > readonly confirmation: IConfirmation;
51 > }
52 >
53 > export interface IConfirmation extends IBaseDialogOptions {
54 >
55 > /**
56 > * If not provided, defaults to `Yes`.
57 > */
58 > readonly primaryButton?: string;
59 >
60 > /**
61 > * If not provided, defaults to `Cancel`.
62 > */
63 > readonly cancelButton?: string;
64 > }
65 >
66 > export interface IConfirmationResult extends ICheckboxResult {
67 >
68 > /**
69 > * Will be true if the dialog was confirmed with the primary button pressed.
70 > */
71 > readonly confirmed: boolean;
72 > }
73 >
74 > export interface IInputDialogArgs {
75 > readonly input: IInput;
76 > }
77 >
78 > export interface IInput extends IConfirmation {
79 > readonly inputs: IInputElement[];
80 >
81 > /**
82 > * If not provided, defaults to `Ok`.
83 > */
84 > readonly primaryButton?: string;
85 > }
86 >
87 > export interface IInputElement {
88 > readonly type?: 'text' | 'password';
89 > readonly value?: string;
90 > readonly placeholder?: string;
91 > }
92 >
93 > export interface IInputResult extends IConfirmationResult {
94 >
95 > /**
96 > * Values for the input fields as provided by the user or `undefined` if none.
97 > */
98 > readonly values?: string[];
99 > }
100 >
101 > export interface IPromptDialogArgs {
102 > readonly prompt: IPrompt<unknown>;
103 > }
104 >
105 > export interface IPromptBaseButton<T> {
106 >
107 > /**
108 > * @returns the result of the prompt button will be returned
109 > * as result from the `prompt()` call.
110 > */
111 > run(checkbox: ICheckboxResult): T | Promise<T>;
112 > }
113 >
114 > export interface IPromptButton<T> extends IPromptBaseButton<T> {
115 > readonly label: string;
116 > }
117 >
118 > export interface IPromptCancelButton<T> extends IPromptBaseButton<T> {
119 >
120 > /**
121 > * The cancel button to show in the prompt. Defaults to
122 > * `Cancel` if not provided.
123 > */
124 > readonly label?: string;
125 > }
126 >
127 > export interface IPrompt<T> extends IBaseDialogOptions {
128 >
129 > /**
130 > * The buttons to show in the prompt. Defaults to `OK`
131 > * if no buttons or cancel button is provided.
132 > */
133 > readonly buttons?: IPromptButton<T>[];
134 >
135 > /**
136 > * The cancel button to show in the prompt. Defaults to
137 > * `Cancel` if set to `true`.
138 > */
139 > readonly cancelButton?: IPromptCancelButton<T> | true | string;
140 > }
141 >
142 > export interface IPromptWithCustomCancel<T> extends IPrompt<T> {
143 > readonly cancelButton: IPromptCancelButton<T>;
144 > }
145 >
146 > export interface IPromptWithDefaultCancel<T> extends IPrompt<T> {
147 > readonly cancelButton: true | string;
148 > }
149 >
150 > export interface IPromptResult<T> extends ICheckboxResult {
151 >
152 > /**
153 > * The result of the `IPromptButton` that was pressed or `undefined` if none.
154 > */
155 > readonly result?: T;
156 > }
157 >
158 > export interface IPromptResultWithCancel<T> extends IPromptResult<T> {
159 > readonly result: T;
160 > }
161 >
162 > export interface IAsyncPromptResult<T> extends ICheckboxResult {
163 >
164 > /**
165 > * The result of the `IPromptButton` that was pressed or `undefined` if none.
166 > */
167 > readonly result?: Promise<T>;
168 > }
169 >
170 > export interface IAsyncPromptResultWithCancel<T> extends IAsyncPromptResult<T> {
171 > readonly result: Promise<T>;
172 > }
173 >
174 > export type IDialogResult = IConfirmationResult | IInputResult | IAsyncPromptResult<unknown>;
175 >
176 > export type DialogType = 'none' | 'info' | 'error' | 'question' | 'warning';
177 >
178 > export interface ICheckbox {
179 > readonly label: string;
180 > readonly checked?: boolean;
181 > }
182 >
183 > export interface ICheckboxResult {
184 >
185 > /**
186 > * This will only be defined if the confirmation was created
187 > * with the checkbox option defined.
188 > */
189 > readonly checkboxChecked?: boolean;
190 > }
191 >
192 > export interface IPickAndOpenOptions {
193 > readonly forceNewWindow?: boolean;
194 > defaultUri?: URI;
195 > readonly telemetryExtraData?: ITelemetryData;
196 > availableFileSystems?: string[];
197 > remoteAuthority?: string | null;
198 > }
199 >
200 > export interface FileFilter {
201 > readonly extensions: string[];
202 > readonly name: string;
203 > }
204 >
205 > export interface ISaveDialogOptions {
206 >
207 > /**
208 > * A human-readable string for the dialog title
209 > */
210 > title?: string;
211 >
212 > /**
213 > * The resource the dialog shows when opened.
214 > */
215 > defaultUri?: URI;
216 >
217 > /**
218 > * A set of file filters that are used by the dialog. Each entry is a human readable label,
219 > * like "TypeScript", and an array of extensions.
220 > */
221 > filters?: FileFilter[];
222 >
223 > /**
224 > * A human-readable string for the ok button
225 > */
226 > readonly saveLabel?: { readonly withMnemonic: string; readonly withoutMnemonic: string } | string;
227 >
228 > /**
229 > * Specifies a list of schemas for the file systems the user can save to. If not specified, uses the schema of the defaultURI or, if also not specified,
230 > * the schema of the current window.
231 > */
232 > availableFileSystems?: readonly string[];
233 > }
234 >
235 > export interface IOpenDialogOptions {
236 >
237 > /**
238 > * A human-readable string for the dialog title
239 > */
240 > readonly title?: string;
241 >
242 > /**
243 > * The resource the dialog shows when opened.
244 > */
245 > defaultUri?: URI;
246 >
247 > /**
248 > * A human-readable string for the open button.
249 > */
250 > readonly openLabel?: { readonly withMnemonic: string; readonly withoutMnemonic: string } | string;
251 >
252 > /**
253 > * Allow to select files, defaults to `true`.
254 > */
255 > canSelectFiles?: boolean;
256 >
257 > /**
258 > * Allow to select folders, defaults to `false`.
259 > */
260 > canSelectFolders?: boolean;
261 >
262 > /**
263 > * Allow to select many files or folders.
264 > */
265 > readonly canSelectMany?: boolean;
266 >
267 > /**
268 > * A set of file filters that are used by the dialog. Each entry is a human readable label,
269 > * like "TypeScript", and an array of extensions.
270 > */
271 > filters?: FileFilter[];
272 >
273 > /**
274 > * Specifies a list of schemas for the file systems the user can load from. If not specified, uses the schema of the defaultURI or, if also not available,
275 > * the schema of the current window.
276 > */
277 > availableFileSystems?: readonly string[];
278 > }
279 >
280 > export const IDialogService = createDecorator<IDialogService>('dialogService');
281 >
282 > export interface ICustomDialogOptions {
283 > readonly buttonDetails?: string[];
284 > readonly markdownDetails?: ICustomDialogMarkdown[];
285 > readonly classes?: string[];
286 > readonly icon?: ThemeIcon;
287 > readonly disableCloseAction?: boolean;
288 > }
289 >
290 > export interface ICustomDialogMarkdown {
291 > readonly markdown: IMarkdownString;
292 > readonly classes?: string[];
293 > /** Custom link handler for markdown content, see {@link IContentActionHandler}. Defaults to {@link openLinkFromMarkdown}. */
294 > actionHandler?(link: string): Promise<boolean>;
295 > }
296 >
297 > /**
298 > * A handler to bring up modal dialogs.
299 > */
300 > export interface IDialogHandler {
301 >
302 > /**
303 > * Ask the user for confirmation with a modal dialog.
304 > */
305 > confirm(confirmation: IConfirmation): Promise<IConfirmationResult>;
306 >
307 > /**
308 > * Prompt the user with a modal dialog.
309 > */
310 > prompt<T>(prompt: IPrompt<T>): Promise<IAsyncPromptResult<T>>;
311 >
312 > /**
313 > * Present a modal dialog to the user asking for input.
314 > */
315 > input(input: IInput): Promise<IInputResult>;
316 >
317 > /**
318 > * Present the about dialog to the user.
319 > */
320 > about(title: string, details: string, detailsToCopy: string): Promise<void>;
321 > }
322 >
323 > enum DialogKind {
324 > Confirmation = 1,
325 > Prompt,
326 > Input
327 > }
328 >
329 > export abstract class AbstractDialogHandler implements IDialogHandler {
330 >
331 > protected getConfirmationButtons(dialog: IConfirmation): string[] {
332 return this.getButtons(dialog, DialogKind.Confirmation);
333 }
335 > protected getPromptButtons(dialog: IPrompt<unknown>): string[] {
336 return this.getButtons(dialog, DialogKind.Prompt);
337 }
339 > protected getInputButtons(dialog: IInput): string[] {
340 return this.getButtons(dialog, DialogKind.Input);
341 }
343 > private getButtons(dialog: IConfirmation, kind: DialogKind.Confirmation): string[];
344 > private getButtons(dialog: IPrompt<unknown>, kind: DialogKind.Prompt): string[];
345 > private getButtons(dialog: IInput, kind: DialogKind.Input): string[];
346 > private getButtons(dialog: IConfirmation | IInput | IPrompt<unknown>, kind: DialogKind): string[] {
347
348 // We put buttons in the order of "default" button first and "cancel"
349 // button last. There maybe later processing when presenting the buttons
350 // based on OS standards.
351
352 const buttons: string[] = [];
353
354 switch (kind) {
355 case DialogKind.Confirmation: {
356 const confirmationDialog = dialog as IConfirmation;
357
358 if (confirmationDialog.primaryButton) {
359 buttons.push(confirmationDialog.primaryButton);
360 } else {
361 buttons.push(localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes"));
362 }
363
364 if (confirmationDialog.cancelButton) {
365 buttons.push(confirmationDialog.cancelButton);
366 } else {
367 buttons.push(localize('cancelButton', "Cancel"));
368 }
369
370 break;
371 }
372 case DialogKind.Prompt: {
373 const promptDialog = dialog as IPrompt<unknown>;
374
375 if (Array.isArray(promptDialog.buttons) && promptDialog.buttons.length > 0) {
376 buttons.push(...promptDialog.buttons.map(button => button.label));
377 }
378
379 if (promptDialog.cancelButton) {
380 if (promptDialog.cancelButton === true) {
381 buttons.push(localize('cancelButton', "Cancel"));
382 } else if (typeof promptDialog.cancelButton === 'string') {
383 buttons.push(promptDialog.cancelButton);
384 } else {
385 if (promptDialog.cancelButton.label) {
386 buttons.push(promptDialog.cancelButton.label);
387 } else {
388 buttons.push(localize('cancelButton', "Cancel"));
389 }
390 }
391 }
392
393 if (buttons.length === 0) {
394 buttons.push(localize({ key: 'okButton', comment: ['&& denotes a mnemonic'] }, "&&OK"));
395 }
396
397 break;
398 }
399 case DialogKind.Input: {
400 const inputDialog = dialog as IInput;
401
402 if (inputDialog.primaryButton) {
403 buttons.push(inputDialog.primaryButton);
404 } else {
405 buttons.push(localize({ key: 'okButton', comment: ['&& denotes a mnemonic'] }, "&&OK"));
406 }
407
408 if (inputDialog.cancelButton) {
409 buttons.push(inputDialog.cancelButton);
410 } else {
411 buttons.push(localize('cancelButton', "Cancel"));
412 }
413
414 break;
415 }
416 }
417
418 return buttons;
419 }
421 > protected getDialogType(type: Severity | DialogType | undefined): DialogType | undefined {
422 if (typeof type === 'string') {
423 return type;
424 }
425
426 if (typeof type === 'number') {
427 return (type === Severity.Info) ? 'info' : (type === Severity.Error) ? 'error' : (type === Severity.Warning) ? 'warning' : 'none';
428 }
429
430 return undefined;
431 }
433 > protected getPromptResult<T>(prompt: IPrompt<T>, buttonIndex: number, checkboxChecked: boolean | undefined): IAsyncPromptResult<T> {
434 const promptButtons: IPromptBaseButton<T>[] = [...(prompt.buttons ?? [])];
435 if (prompt.cancelButton && typeof prompt.cancelButton !== 'string' && typeof prompt.cancelButton !== 'boolean') {
436 promptButtons.push(prompt.cancelButton);
437 }
438
439 let result = promptButtons[buttonIndex]?.run({ checkboxChecked });
440 if (!(result instanceof Promise)) {
441 result = Promise.resolve(result);
442 }
443
444 return { result, checkboxChecked };
445 }
447 > abstract confirm(confirmation: IConfirmation): Promise<IConfirmationResult>;
448 > abstract input(input: IInput): Promise<IInputResult>;
449 > abstract prompt<T>(prompt: IPrompt<T>): Promise<IAsyncPromptResult<T>>;
450 > abstract about(title: string, details: string, detailsToCopy: string): Promise<void>;
451 > }
452 >
453 > /**
454 > * A service to bring up modal dialogs.
455 > *
456 > * Note: use the `INotificationService.prompt()` method for a non-modal way to ask
457 > * the user for input.
458 > */
459 > export interface IDialogService {
460 >
461 > readonly _serviceBrand: undefined;
462 >
463 > /**
464 > * An event that fires when a dialog is about to show.
465 > */
466 > readonly onWillShowDialog: Event<void>;
467 >
468 > /**
469 > * An event that fires when a dialog did show (closed).
470 > */
471 > readonly onDidShowDialog: Event<void>;
472 >
473 > /**
474 > * Ask the user for confirmation with a modal dialog.
475 > */
476 > confirm(confirmation: IConfirmation): Promise<IConfirmationResult>;
477 >
478 > /**
479 > * Prompt the user with a modal dialog. Provides a bit
480 > * more control over the dialog compared to the simpler
481 > * `confirm` method. Specifically, allows to show more
482 > * than 2 buttons and makes it easier to just show a
483 > * message to the user.
484 > *
485 > * @returns a promise that resolves to the `T` result
486 > * from the provided `IPromptButton<T>` or `undefined`.
487 > */
488 > prompt<T>(prompt: IPromptWithCustomCancel<T>): Promise<IPromptResultWithCancel<T>>;
489 > prompt<T>(prompt: IPromptWithDefaultCancel<T>): Promise<IPromptResult<T>>;
490 > prompt<T>(prompt: IPrompt<T>): Promise<IPromptResult<T>>;
491 >
492 > /**
493 > * Present a modal dialog to the user asking for input.
494 > */
495 > input(input: IInput): Promise<IInputResult>;
496 >
497 > /**
498 > * Show a modal info dialog.
499 > */
500 > info(message: string, detail?: string): Promise<void>;
501 >
502 > /**
503 > * Show a modal warning dialog.
504 > */
505 > warn(message: string, detail?: string): Promise<void>;
506 >
507 > /**
508 > * Show a modal error dialog.
509 > */
510 > error(message: string, detail?: string): Promise<void>;
511 >
512 > /**
513 > * Present the about dialog to the user.
514 > */
515 > about(): Promise<void>;
516 > }
517 >
518 > export const IFileDialogService = createDecorator<IFileDialogService>('fileDialogService');
519 >
520 > /**
521 > * A service to bring up file dialogs.
522 > */
523 > export interface IFileDialogService {
524 >
525 > readonly _serviceBrand: undefined;
526 >
527 > /**
528 > * The default path for a new file based on previously used files.
529 > * @param schemeFilter The scheme of the file path. If no filter given, the scheme of the current window is used.
530 > * Falls back to user home in the absence of enough information to find a better URI.
531 > */
532 > defaultFilePath(schemeFilter?: string): Promise<URI>;
533 >
534 > /**
535 > * The default path for a new folder based on previously used folders.
536 > * @param schemeFilter The scheme of the folder path. If no filter given, the scheme of the current window is used.
537 > * Falls back to user home in the absence of enough information to find a better URI.
538 > */
539 > defaultFolderPath(schemeFilter?: string): Promise<URI>;
540 >
541 > /**
542 > * The default path for a new workspace based on previously used workspaces.
543 > * @param schemeFilter The scheme of the workspace path. If no filter given, the scheme of the current window is used.
544 > * Falls back to user home in the absence of enough information to find a better URI.
545 > */
546 > defaultWorkspacePath(schemeFilter?: string): Promise<URI>;
547 >
548 > /**
549 > * Shows a file-folder selection dialog and opens the selected entry.
550 > */
551 > pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;
552 >
553 > /**
554 > * Shows a file selection dialog and opens the selected entry.
555 > */
556 > pickFileAndOpen(options: IPickAndOpenOptions): Promise<void>;
557 >
558 > /**
559 > * Shows a folder selection dialog and opens the selected entry.
560 > */
561 > pickFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;
562 >
563 > /**
564 > * Shows a workspace selection dialog and opens the selected entry.
565 > */
566 > pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void>;
567 >
568 > /**
569 > * Shows a save file dialog and save the file at the chosen file URI.
570 > */
571 > pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise<URI | undefined>;
572 >
573 > /**
574 > * The preferred folder path to open the dialog at.
575 > * @param schemeFilter The scheme of the file path. If no filter given, the scheme of the current window is used.
576 > * Falls back to user home in the absence of a setting.
577 > */
578 > preferredHome(schemeFilter?: string): Promise<URI>;
579 >
580 > /**
581 > * Shows a save file dialog and returns the chosen file URI.
582 > */
583 > showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined>;
584 >
585 > /**
586 > * Shows a confirm dialog for saving 1-N files.
587 > */
588 > showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult>;
589 >
590 > /**
591 > * Shows a open file dialog and returns the chosen file URI.
592 > */
593 > showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined>;
594 > }
595 >
596 > export const enum ConfirmResult {
597 > SAVE,
598 > DONT_SAVE,
599 > CANCEL
600 > }
601 >
602 > const MAX_CONFIRM_FILES = 10;
603 > export function getFileNamesMessage(fileNamesOrResources: readonly (string | URI)[]): string {
604 const message: string[] = [];
605 message.push(...fileNamesOrResources.slice(0, MAX_CONFIRM_FILES).map(fileNameOrResource => typeof fileNameOrResource === 'string' ? fileNameOrResource : basename(fileNameOrResource)));
606
607 if (fileNamesOrResources.length > MAX_CONFIRM_FILES) {
608 if (fileNamesOrResources.length - MAX_CONFIRM_FILES === 1) {
609 message.push(localize('moreFile', "...1 additional file not shown"));
610 } else {
611 message.push(localize('moreFiles', "...{0} additional files not shown", fileNamesOrResources.length - MAX_CONFIRM_FILES));
612 }
613 }
614
615 message.push('');
616 return message.join('\n');
617 }
619 > export interface INativeOpenDialogOptions {
620 > readonly forceNewWindow?: boolean;
621 >
622 > readonly defaultPath?: string;
623 >
624 > readonly telemetryEventName?: string;
625 > readonly telemetryExtraData?: ITelemetryData;
626 > }