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.
/*---------------------------------------------------------------------------------------------
dialogs.ts ×8
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../base/common/cancellation.js';
import { Event } from '../../../base/common/event.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { IMarkdownString } from '../../../base/common/htmlContent.js';
import { basename } from '../../../base/common/resources.js';
import Severity from '../../../base/common/severity.js';
import { URI } from '../../../base/common/uri.js';
import { localize } from '../../../nls.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
import { ITelemetryData } from '../../telemetry/common/telemetry.js';
export interface IDialogArgs {
readonly confirmArgs?: IConfirmDialogArgs;
readonly inputArgs?: IInputDialogArgs;
readonly promptArgs?: IPromptDialogArgs;
}
export interface IBaseDialogOptions {
readonly type?: Severity | DialogType;
readonly title?: string;
readonly message: string;
readonly detail?: string;
readonly checkbox?: ICheckbox;
/**
* Allows to enforce use of custom dialog even in native environments.
*/
readonly custom?: boolean | ICustomDialogOptions;
/**
* An optional cancellation token that can be used to dismiss the dialog
* programmatically for custom dialog implementations.
*
* When cancelled, the custom dialog resolves as if the cancel button was
* pressed. Native dialog handlers cannot currently be dismissed
* programmatically and ignore this option unless a custom dialog is
* explicitly enforced via the {@link custom} option.
*/
readonly token?: CancellationToken;
}
export interface IConfirmDialogArgs {
readonly confirmation: IConfirmation;
}
export interface IConfirmation extends IBaseDialogOptions {
/**
* If not provided, defaults to `Yes`.
*/
readonly primaryButton?: string;
/**
* If not provided, defaults to `Cancel`.
*/
readonly cancelButton?: string;
}
export interface IConfirmationResult extends ICheckboxResult {
/**
* Will be true if the dialog was confirmed with the primary button pressed.
*/
readonly confirmed: boolean;
}
export interface IInputDialogArgs {
readonly input: IInput;
}
export interface IInput extends IConfirmation {
readonly inputs: IInputElement[];
/**
* If not provided, defaults to `Ok`.
*/
readonly primaryButton?: string;
}
export interface IInputElement {
readonly type?: 'text' | 'password';
readonly value?: string;
readonly placeholder?: string;
}
export interface IInputResult extends IConfirmationResult {
/**
* Values for the input fields as provided by the user or `undefined` if none.
*/
readonly values?: string[];
}
export interface IPromptDialogArgs {
readonly prompt: IPrompt<unknown>;
}
export interface IPromptBaseButton<T> {
/**
* @returns the result of the prompt button will be returned
* as result from the `prompt()` call.
*/
run(checkbox: ICheckboxResult): T | Promise<T>;
}
export interface IPromptButton<T> extends IPromptBaseButton<T> {
readonly label: string;
}
export interface IPromptCancelButton<T> extends IPromptBaseButton<T> {
/**
* The cancel button to show in the prompt. Defaults to
* `Cancel` if not provided.
*/
readonly label?: string;
}
export interface IPrompt<T> extends IBaseDialogOptions {
/**
* The buttons to show in the prompt. Defaults to `OK`
* if no buttons or cancel button is provided.
*/
readonly buttons?: IPromptButton<T>[];
/**
* The cancel button to show in the prompt. Defaults to
* `Cancel` if set to `true`.
*/
readonly cancelButton?: IPromptCancelButton<T> | true | string;
}
export interface IPromptWithCustomCancel<T> extends IPrompt<T> {
readonly cancelButton: IPromptCancelButton<T>;
}
export interface IPromptWithDefaultCancel<T> extends IPrompt<T> {
readonly cancelButton: true | string;
}
export interface IPromptResult<T> extends ICheckboxResult {
/**
* The result of the `IPromptButton` that was pressed or `undefined` if none.
*/
readonly result?: T;
}
export interface IPromptResultWithCancel<T> extends IPromptResult<T> {
readonly result: T;
}
export interface IAsyncPromptResult<T> extends ICheckboxResult {
/**
* The result of the `IPromptButton` that was pressed or `undefined` if none.
*/
readonly result?: Promise<T>;
}
export interface IAsyncPromptResultWithCancel<T> extends IAsyncPromptResult<T> {
readonly result: Promise<T>;
}
export type IDialogResult = IConfirmationResult | IInputResult | IAsyncPromptResult<unknown>;
export type DialogType = 'none' | 'info' | 'error' | 'question' | 'warning';
export interface ICheckbox {
readonly label: string;
readonly checked?: boolean;
}
export interface ICheckboxResult {
/**
* This will only be defined if the confirmation was created
* with the checkbox option defined.
*/
readonly checkboxChecked?: boolean;
}
export interface IPickAndOpenOptions {
readonly forceNewWindow?: boolean;
defaultUri?: URI;
readonly telemetryExtraData?: ITelemetryData;
availableFileSystems?: string[];
remoteAuthority?: string | null;
}
export interface FileFilter {
readonly extensions: string[];
readonly name: string;
}
export interface ISaveDialogOptions {
/**
* A human-readable string for the dialog title
*/
title?: string;
/**
* The resource the dialog shows when opened.
*/
defaultUri?: URI;
/**
* A set of file filters that are used by the dialog. Each entry is a human readable label,
* like "TypeScript", and an array of extensions.
*/
filters?: FileFilter[];
/**
* A human-readable string for the ok button
*/
readonly saveLabel?: { readonly withMnemonic: string; readonly withoutMnemonic: string } | string;
/**
* 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,
* the schema of the current window.
*/
availableFileSystems?: readonly string[];
}
export interface IOpenDialogOptions {
/**
* A human-readable string for the dialog title
*/
readonly title?: string;
/**
* The resource the dialog shows when opened.
*/
defaultUri?: URI;
/**
* A human-readable string for the open button.
*/
readonly openLabel?: { readonly withMnemonic: string; readonly withoutMnemonic: string } | string;
/**
* Allow to select files, defaults to `true`.
*/
canSelectFiles?: boolean;
/**
* Allow to select folders, defaults to `false`.
*/
canSelectFolders?: boolean;
/**
* Allow to select many files or folders.
*/
readonly canSelectMany?: boolean;
/**
* A set of file filters that are used by the dialog. Each entry is a human readable label,
* like "TypeScript", and an array of extensions.
*/
filters?: FileFilter[];
/**
* 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,
* the schema of the current window.
*/
availableFileSystems?: readonly string[];
}
export const IDialogService = createDecorator<IDialogService>('dialogService');
export interface ICustomDialogOptions {
readonly buttonDetails?: string[];
readonly markdownDetails?: ICustomDialogMarkdown[];
readonly classes?: string[];
readonly icon?: ThemeIcon;
readonly disableCloseAction?: boolean;
}
export interface ICustomDialogMarkdown {
readonly markdown: IMarkdownString;
readonly classes?: string[];
/** Custom link handler for markdown content, see {@link IContentActionHandler}. Defaults to {@link openLinkFromMarkdown}. */
actionHandler?(link: string): Promise<boolean>;
}
/**
* A handler to bring up modal dialogs.
*/
export interface IDialogHandler {
/**
* Ask the user for confirmation with a modal dialog.
*/
confirm(confirmation: IConfirmation): Promise<IConfirmationResult>;
/**
* Prompt the user with a modal dialog.
*/
prompt<T>(prompt: IPrompt<T>): Promise<IAsyncPromptResult<T>>;
/**
* Present a modal dialog to the user asking for input.
*/
input(input: IInput): Promise<IInputResult>;
/**
* Present the about dialog to the user.
*/
about(title: string, details: string, detailsToCopy: string): Promise<void>;
}
enum DialogKind {
Confirmation = 1,
Prompt,
Input
}
export abstract class AbstractDialogHandler implements IDialogHandler {
protected getConfirmationButtons(dialog: IConfirmation): string[] {
return this.getButtons(dialog, DialogKind.Confirmation);
}
protected getPromptButtons(dialog: IPrompt<unknown>): string[] {
return this.getButtons(dialog, DialogKind.Prompt);
}
protected getInputButtons(dialog: IInput): string[] {
return this.getButtons(dialog, DialogKind.Input);
}
private getButtons(dialog: IConfirmation, kind: DialogKind.Confirmation): string[];
private getButtons(dialog: IPrompt<unknown>, kind: DialogKind.Prompt): string[];
private getButtons(dialog: IInput, kind: DialogKind.Input): string[];
private getButtons(dialog: IConfirmation | IInput | IPrompt<unknown>, kind: DialogKind): string[] {
// We put buttons in the order of "default" button first and "cancel"
// button last. There maybe later processing when presenting the buttons
// based on OS standards.
const buttons: string[] = [];
switch (kind) {
case DialogKind.Confirmation: {
const confirmationDialog = dialog as IConfirmation;
if (confirmationDialog.primaryButton) {
buttons.push(confirmationDialog.primaryButton);
} else {
buttons.push(localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes"));
}
if (confirmationDialog.cancelButton) {
buttons.push(confirmationDialog.cancelButton);
} else {
buttons.push(localize('cancelButton', "Cancel"));
}
break;
}
case DialogKind.Prompt: {
const promptDialog = dialog as IPrompt<unknown>;
if (Array.isArray(promptDialog.buttons) && promptDialog.buttons.length > 0) {
buttons.push(...promptDialog.buttons.map(button => button.label));
}
if (promptDialog.cancelButton) {
if (promptDialog.cancelButton === true) {
buttons.push(localize('cancelButton', "Cancel"));
} else if (typeof promptDialog.cancelButton === 'string') {
buttons.push(promptDialog.cancelButton);
} else {
if (promptDialog.cancelButton.label) {
buttons.push(promptDialog.cancelButton.label);
} else {
buttons.push(localize('cancelButton', "Cancel"));
}
}
}
if (buttons.length === 0) {
buttons.push(localize({ key: 'okButton', comment: ['&& denotes a mnemonic'] }, "&&OK"));
}
break;
}
case DialogKind.Input: {
const inputDialog = dialog as IInput;
if (inputDialog.primaryButton) {
buttons.push(inputDialog.primaryButton);
} else {
buttons.push(localize({ key: 'okButton', comment: ['&& denotes a mnemonic'] }, "&&OK"));
}
if (inputDialog.cancelButton) {
buttons.push(inputDialog.cancelButton);
} else {
buttons.push(localize('cancelButton', "Cancel"));
}
break;
}
}
return buttons;
}
protected getDialogType(type: Severity | DialogType | undefined): DialogType | undefined {
if (typeof type === 'string') {
return type;
}
if (typeof type === 'number') {
return (type === Severity.Info) ? 'info' : (type === Severity.Error) ? 'error' : (type === Severity.Warning) ? 'warning' : 'none';
}
return undefined;
}
protected getPromptResult<T>(prompt: IPrompt<T>, buttonIndex: number, checkboxChecked: boolean | undefined): IAsyncPromptResult<T> {
const promptButtons: IPromptBaseButton<T>[] = [...(prompt.buttons ?? [])];
if (prompt.cancelButton && typeof prompt.cancelButton !== 'string' && typeof prompt.cancelButton !== 'boolean') {
promptButtons.push(prompt.cancelButton);
}
let result = promptButtons[buttonIndex]?.run({ checkboxChecked });
if (!(result instanceof Promise)) {
result = Promise.resolve(result);
}
return { result, checkboxChecked };
}
abstract confirm(confirmation: IConfirmation): Promise<IConfirmationResult>;
abstract input(input: IInput): Promise<IInputResult>;
abstract prompt<T>(prompt: IPrompt<T>): Promise<IAsyncPromptResult<T>>;
abstract about(title: string, details: string, detailsToCopy: string): Promise<void>;
}
/**
* A service to bring up modal dialogs.
*
* Note: use the `INotificationService.prompt()` method for a non-modal way to ask
* the user for input.
*/
export interface IDialogService {
readonly _serviceBrand: undefined;
/**
* An event that fires when a dialog is about to show.
*/
readonly onWillShowDialog: Event<void>;
/**
* An event that fires when a dialog did show (closed).
*/
readonly onDidShowDialog: Event<void>;
/**
* Ask the user for confirmation with a modal dialog.
*/
confirm(confirmation: IConfirmation): Promise<IConfirmationResult>;
/**
* Prompt the user with a modal dialog. Provides a bit
* more control over the dialog compared to the simpler
* `confirm` method. Specifically, allows to show more
* than 2 buttons and makes it easier to just show a
* message to the user.
*
* @returns a promise that resolves to the `T` result
* from the provided `IPromptButton<T>` or `undefined`.
*/
prompt<T>(prompt: IPromptWithCustomCancel<T>): Promise<IPromptResultWithCancel<T>>;
prompt<T>(prompt: IPromptWithDefaultCancel<T>): Promise<IPromptResult<T>>;
prompt<T>(prompt: IPrompt<T>): Promise<IPromptResult<T>>;
/**
* Present a modal dialog to the user asking for input.
*/
input(input: IInput): Promise<IInputResult>;
/**
* Show a modal info dialog.
*/
info(message: string, detail?: string): Promise<void>;
/**
* Show a modal warning dialog.
*/
warn(message: string, detail?: string): Promise<void>;
/**
* Show a modal error dialog.
*/
error(message: string, detail?: string): Promise<void>;
/**
* Present the about dialog to the user.
*/
about(): Promise<void>;
}
export const IFileDialogService = createDecorator<IFileDialogService>('fileDialogService');
/**
* A service to bring up file dialogs.
*/
export interface IFileDialogService {
readonly _serviceBrand: undefined;
/**
* The default path for a new file based on previously used files.
* @param schemeFilter The scheme of the file path. If no filter given, the scheme of the current window is used.
* Falls back to user home in the absence of enough information to find a better URI.
*/
defaultFilePath(schemeFilter?: string): Promise<URI>;
/**
* The default path for a new folder based on previously used folders.
* @param schemeFilter The scheme of the folder path. If no filter given, the scheme of the current window is used.
* Falls back to user home in the absence of enough information to find a better URI.
*/
defaultFolderPath(schemeFilter?: string): Promise<URI>;
/**
* The default path for a new workspace based on previously used workspaces.
* @param schemeFilter The scheme of the workspace path. If no filter given, the scheme of the current window is used.
* Falls back to user home in the absence of enough information to find a better URI.
*/
defaultWorkspacePath(schemeFilter?: string): Promise<URI>;
/**
* Shows a file-folder selection dialog and opens the selected entry.
*/
pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;
/**
* Shows a file selection dialog and opens the selected entry.
*/
pickFileAndOpen(options: IPickAndOpenOptions): Promise<void>;
/**
* Shows a folder selection dialog and opens the selected entry.
*/
pickFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;
/**
* Shows a workspace selection dialog and opens the selected entry.
*/
pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void>;
/**
* Shows a save file dialog and save the file at the chosen file URI.
*/
pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise<URI | undefined>;
/**
* The preferred folder path to open the dialog at.
* @param schemeFilter The scheme of the file path. If no filter given, the scheme of the current window is used.
* Falls back to user home in the absence of a setting.
*/
preferredHome(schemeFilter?: string): Promise<URI>;
/**
* Shows a save file dialog and returns the chosen file URI.
*/
showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined>;
/**
* Shows a confirm dialog for saving 1-N files.
*/
showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult>;
/**
* Shows a open file dialog and returns the chosen file URI.
*/
showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined>;
}
export const enum ConfirmResult {
SAVE,
DONT_SAVE,
CANCEL
}
const MAX_CONFIRM_FILES = 10;
export function getFileNamesMessage(fileNamesOrResources: readonly (string | URI)[]): string {
const message: string[] = [];
message.push(...fileNamesOrResources.slice(0, MAX_CONFIRM_FILES).map(fileNameOrResource => typeof fileNameOrResource === 'string' ? fileNameOrResource : basename(fileNameOrResource)));
if (fileNamesOrResources.length > MAX_CONFIRM_FILES) {
if (fileNamesOrResources.length - MAX_CONFIRM_FILES === 1) {
message.push(localize('moreFile', "...1 additional file not shown"));
} else {
message.push(localize('moreFiles', "...{0} additional files not shown", fileNamesOrResources.length - MAX_CONFIRM_FILES));
}
}
message.push('');
return message.join('\n');
}
export interface INativeOpenDialogOptions {
readonly forceNewWindow?: boolean;
readonly defaultPath?: string;
readonly telemetryEventName?: string;
readonly telemetryExtraData?: ITelemetryData;
}