mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 (#14050)
* Merge from vscode 2c306f762bf9c3db82dc06c7afaa56ef46d72f79 * Fix breaks * Extension management fixes * Fix breaks in windows bundling * Fix/skip failing tests * Update distro * Add clear to nuget.config * Add hygiene task * Bump distro * Fix hygiene issue * Add build to hygiene exclusion * Update distro * Update hygiene * Hygiene exclusions * Update tsconfig * Bump distro for server breaks * Update build config * Update darwin path * Add done calls to notebook tests * Skip failing tests * Disable smoke tests
This commit is contained in:
@@ -19,11 +19,12 @@ import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { coalesce, distinct } from 'vs/base/common/arrays';
|
||||
import { trim } from 'vs/base/common/strings';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IPathService } from 'vs/workbench/services/path/common/pathService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
|
||||
@@ -45,7 +46,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
@IPathService private readonly pathService: IPathService
|
||||
) { }
|
||||
|
||||
defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
|
||||
async defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): Promise<URI> {
|
||||
|
||||
// Check for last active file first...
|
||||
let candidate = this.historyService.getLastActiveFile(schemeFilter);
|
||||
@@ -57,10 +58,14 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
candidate = candidate && resources.dirname(candidate);
|
||||
}
|
||||
|
||||
return candidate || undefined;
|
||||
if (!candidate) {
|
||||
candidate = await this.pathService.userHome({ preferLocal: schemeFilter === Schemas.file });
|
||||
}
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
|
||||
async defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): Promise<URI> {
|
||||
|
||||
// Check for last active file root first...
|
||||
let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter);
|
||||
@@ -70,21 +75,33 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
candidate = this.historyService.getLastActiveFile(schemeFilter);
|
||||
}
|
||||
|
||||
return candidate && resources.dirname(candidate) || undefined;
|
||||
if (!candidate) {
|
||||
return this.pathService.userHome({ preferLocal: schemeFilter === Schemas.file });
|
||||
} else {
|
||||
return resources.dirname(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
|
||||
|
||||
async defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow(), filename?: string): Promise<URI> {
|
||||
let defaultWorkspacePath: URI | undefined;
|
||||
// Check for current workspace config file first...
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
const configuration = this.contextService.getWorkspace().configuration;
|
||||
if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) {
|
||||
return resources.dirname(configuration) || undefined;
|
||||
if (configuration && configuration.scheme === schemeFilter && !isUntitledWorkspace(configuration, this.environmentService)) {
|
||||
defaultWorkspacePath = resources.dirname(configuration) || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// ...then fallback to default file path
|
||||
return this.defaultFilePath(schemeFilter);
|
||||
if (!defaultWorkspacePath) {
|
||||
defaultWorkspacePath = await this.defaultFilePath(schemeFilter);
|
||||
}
|
||||
|
||||
if (defaultWorkspacePath && filename) {
|
||||
defaultWorkspacePath = resources.joinPath(defaultWorkspacePath, filename);
|
||||
}
|
||||
|
||||
return defaultWorkspacePath;
|
||||
}
|
||||
|
||||
async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult> {
|
||||
@@ -147,7 +164,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
if (stat.isDirectory || options.forceNewWindow || preferNewWindow) {
|
||||
return this.hostService.openWindow([toOpen], { forceNewWindow: options.forceNewWindow });
|
||||
} else {
|
||||
return this.openerService.open(uri, { fromUserGesture: true });
|
||||
return this.openerService.open(uri, { fromUserGesture: true, editorOptions: { pinned: true } });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,7 +181,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
if (options.forceNewWindow || preferNewWindow) {
|
||||
return this.hostService.openWindow([{ fileUri: uri }], { forceNewWindow: options.forceNewWindow });
|
||||
} else {
|
||||
return this.openerService.open(uri, { fromUserGesture: true });
|
||||
return this.openerService.open(uri, { fromUserGesture: true, editorOptions: { pinned: true } });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -264,7 +281,7 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) };
|
||||
const filter: IFilter = { name: languageName, extensions: distinct(extensions).slice(0, 10).map(e => trim(e, '.')) };
|
||||
|
||||
if (ext && extensions.indexOf(ext) >= 0) {
|
||||
matchingFilter = filter;
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDialogService, IDialogOptions, IConfirmation, IConfirmationResult, DialogType, IShowResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { Dialog } from 'vs/base/browser/ui/dialog/dialog';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachDialogStyler } from 'vs/platform/theme/common/styler';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { EventHelper } from 'vs/base/browser/dom';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
|
||||
export class DialogService implements IDialogService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private allowableCommands = ['copy', 'cut', 'editor.action.clipboardCopyAction', 'editor.action.clipboardCutAction'];
|
||||
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@ILayoutService private readonly layoutService: ILayoutService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService
|
||||
) { }
|
||||
|
||||
async confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
|
||||
this.logService.trace('DialogService#confirm', confirmation.message);
|
||||
|
||||
const buttons: string[] = [];
|
||||
if (confirmation.primaryButton) {
|
||||
buttons.push(confirmation.primaryButton);
|
||||
} else {
|
||||
buttons.push(nls.localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes"));
|
||||
}
|
||||
|
||||
if (confirmation.secondaryButton) {
|
||||
buttons.push(confirmation.secondaryButton);
|
||||
} else if (typeof confirmation.secondaryButton === 'undefined') {
|
||||
buttons.push(nls.localize('cancelButton', "Cancel"));
|
||||
}
|
||||
|
||||
const dialogDisposables = new DisposableStore();
|
||||
const dialog = new Dialog(
|
||||
this.layoutService.container,
|
||||
confirmation.message,
|
||||
buttons,
|
||||
{
|
||||
detail: confirmation.detail,
|
||||
cancelId: 1,
|
||||
type: confirmation.type,
|
||||
keyEventProcessor: (event: StandardKeyboardEvent) => {
|
||||
const resolved = this.keybindingService.softDispatch(event, this.layoutService.container);
|
||||
if (resolved && resolved.commandId) {
|
||||
if (this.allowableCommands.indexOf(resolved.commandId) === -1) {
|
||||
EventHelper.stop(event, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
checkboxChecked: confirmation.checkbox ? confirmation.checkbox.checked : undefined,
|
||||
checkboxLabel: confirmation.checkbox ? confirmation.checkbox.label : undefined
|
||||
});
|
||||
|
||||
dialogDisposables.add(dialog);
|
||||
dialogDisposables.add(attachDialogStyler(dialog, this.themeService));
|
||||
|
||||
const result = await dialog.show();
|
||||
dialogDisposables.dispose();
|
||||
|
||||
return { confirmed: result.button === 0, checkboxChecked: result.checkboxChecked };
|
||||
}
|
||||
|
||||
private getDialogType(severity: Severity): DialogType {
|
||||
return (severity === Severity.Info) ? 'question' : (severity === Severity.Error) ? 'error' : (severity === Severity.Warning) ? 'warning' : 'none';
|
||||
}
|
||||
|
||||
async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise<IShowResult> {
|
||||
this.logService.trace('DialogService#show', message);
|
||||
|
||||
const dialogDisposables = new DisposableStore();
|
||||
const dialog = new Dialog(
|
||||
this.layoutService.container,
|
||||
message,
|
||||
buttons,
|
||||
{
|
||||
detail: options ? options.detail : undefined,
|
||||
cancelId: options ? options.cancelId : undefined,
|
||||
type: this.getDialogType(severity),
|
||||
keyEventProcessor: (event: StandardKeyboardEvent) => {
|
||||
const resolved = this.keybindingService.softDispatch(event, this.layoutService.container);
|
||||
if (resolved && resolved.commandId) {
|
||||
if (this.allowableCommands.indexOf(resolved.commandId) === -1) {
|
||||
EventHelper.stop(event, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
checkboxLabel: options && options.checkbox ? options.checkbox.label : undefined,
|
||||
checkboxChecked: options && options.checkbox ? options.checkbox.checked : undefined
|
||||
});
|
||||
|
||||
dialogDisposables.add(dialog);
|
||||
dialogDisposables.add(attachDialogStyler(dialog, this.themeService));
|
||||
|
||||
const result = await dialog.show();
|
||||
dialogDisposables.dispose();
|
||||
|
||||
return {
|
||||
choice: result.button,
|
||||
checkboxChecked: result.checkboxChecked
|
||||
};
|
||||
}
|
||||
|
||||
async about(): Promise<void> {
|
||||
const detailString = (useAgo: boolean): string => {
|
||||
return nls.localize('aboutDetail',
|
||||
"Version: {0}\nCommit: {1}\nDate: {2}\nBrowser: {3}",
|
||||
this.productService.version || 'Unknown',
|
||||
this.productService.commit || 'Unknown',
|
||||
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',
|
||||
navigator.userAgent
|
||||
);
|
||||
};
|
||||
|
||||
const detail = detailString(true);
|
||||
const detailToCopy = detailString(false);
|
||||
|
||||
|
||||
const { choice } = await this.show(Severity.Info, this.productService.nameLong, [nls.localize('copy', "Copy"), nls.localize('ok', "OK")], { detail, cancelId: 1 });
|
||||
|
||||
if (choice === 0) {
|
||||
this.clipboardService.writeText(detailToCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IDialogService, DialogService, true);
|
||||
@@ -15,7 +15,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
options.defaultUri = this.defaultFilePath(schema);
|
||||
options.defaultUri = await this.defaultFilePath(schema);
|
||||
}
|
||||
|
||||
return this.pickFileFolderAndOpenSimplified(schema, options, false);
|
||||
@@ -25,7 +25,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
options.defaultUri = this.defaultFilePath(schema);
|
||||
options.defaultUri = await this.defaultFilePath(schema);
|
||||
}
|
||||
|
||||
return this.pickFileAndOpenSimplified(schema, options, false);
|
||||
@@ -35,7 +35,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
options.defaultUri = this.defaultFolderPath(schema);
|
||||
options.defaultUri = await this.defaultFolderPath(schema);
|
||||
}
|
||||
|
||||
return this.pickFolderAndOpenSimplified(schema, options);
|
||||
@@ -45,7 +45,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
options.defaultUri = this.defaultWorkspacePath(schema);
|
||||
options.defaultUri = await this.defaultWorkspacePath(schema);
|
||||
}
|
||||
|
||||
return this.pickWorkspaceAndOpenSimplified(schema, options);
|
||||
|
||||
@@ -139,7 +139,7 @@ export class SimpleFileDialog {
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
this.remoteAuthority = this.environmentService.configuration.remoteAuthority;
|
||||
this.remoteAuthority = this.environmentService.remoteAuthority;
|
||||
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
|
||||
this.scheme = this.pathService.defaultUriScheme;
|
||||
}
|
||||
@@ -212,7 +212,12 @@ export class SimpleFileDialog {
|
||||
path = path.replace(/\\/g, '/');
|
||||
}
|
||||
const uri: URI = this.scheme === Schemas.file ? URI.file(path) : URI.from({ scheme: this.scheme, path });
|
||||
return resources.toLocalResource(uri, uri.scheme === Schemas.file ? undefined : this.remoteAuthority, this.pathService.defaultUriScheme);
|
||||
return resources.toLocalResource(uri,
|
||||
// If the default scheme is file, then we don't care about the remote authority
|
||||
uri.scheme === Schemas.file ? undefined : this.remoteAuthority,
|
||||
// If there is a remote authority, then we should use the system's default URI as the local scheme.
|
||||
// If there is *no* remote authority, then we should use the default scheme for this dialog as that is already local.
|
||||
this.remoteAuthority ? this.pathService.defaultUriScheme : uri.scheme);
|
||||
}
|
||||
|
||||
private getScheme(available: readonly string[] | undefined, defaultUri: URI | undefined): string {
|
||||
@@ -221,6 +226,8 @@ export class SimpleFileDialog {
|
||||
return defaultUri.scheme;
|
||||
}
|
||||
return available[0];
|
||||
} else if (defaultUri) {
|
||||
return defaultUri.scheme;
|
||||
}
|
||||
return Schemas.file;
|
||||
}
|
||||
@@ -582,21 +589,22 @@ export class SimpleFileDialog {
|
||||
|
||||
private setActiveItems(value: string) {
|
||||
const inputBasename = resources.basename(this.remoteUriFrom(value));
|
||||
// Make sure that the folder whose children we are currently viewing matches the path in the input
|
||||
const userPath = this.constructFullUserPath();
|
||||
if (equalsIgnoreCase(userPath, value.substring(0, userPath.length))) {
|
||||
// Make sure that the folder whose children we are currently viewing matches the path in the input
|
||||
const pathsEqual = equalsIgnoreCase(userPath, value.substring(0, userPath.length)) ||
|
||||
equalsIgnoreCase(value, userPath.substring(0, value.length));
|
||||
if (pathsEqual) {
|
||||
let hasMatch = false;
|
||||
if (inputBasename.length > this.userEnteredPathSegment.length) {
|
||||
for (let i = 0; i < this.filePickBox.items.length; i++) {
|
||||
const item = <FileQuickPickItem>this.filePickBox.items[i];
|
||||
if (this.setAutoComplete(value, inputBasename, item)) {
|
||||
hasMatch = true;
|
||||
break;
|
||||
}
|
||||
for (let i = 0; i < this.filePickBox.items.length; i++) {
|
||||
const item = <FileQuickPickItem>this.filePickBox.items[i];
|
||||
if (this.setAutoComplete(value, inputBasename, item)) {
|
||||
hasMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasMatch) {
|
||||
this.userEnteredPathSegment = inputBasename;
|
||||
const userBasename = inputBasename.length >= 2 ? userPath.substring(userPath.length - inputBasename.length + 2) : '';
|
||||
this.userEnteredPathSegment = (userBasename === inputBasename) ? inputBasename : '';
|
||||
this.autoCompletePathSegment = '';
|
||||
this.filePickBox.activeItems = [];
|
||||
}
|
||||
@@ -797,6 +805,7 @@ export class SimpleFileDialog {
|
||||
}
|
||||
|
||||
this.filePickBox.items = items;
|
||||
this.filePickBox.activeItems = [<FileQuickPickItem>this.filePickBox.items[0]];
|
||||
if (this.allowFolderSelection) {
|
||||
this.filePickBox.activeItems = [];
|
||||
}
|
||||
@@ -915,7 +924,8 @@ export class SimpleFileDialog {
|
||||
const ext = resources.extname(file);
|
||||
for (let i = 0; i < this.options.filters.length; i++) {
|
||||
for (let j = 0; j < this.options.filters[i].extensions.length; j++) {
|
||||
if (ext === ('.' + this.options.filters[i].extensions[j])) {
|
||||
const testExt = this.options.filters[i].extensions[j];
|
||||
if ((testExt === '*') || (ext === ('.' + testExt))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
38
src/vs/workbench/services/dialogs/common/dialogService.ts
Normal file
38
src/vs/workbench/services/dialogs/common/dialogService.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInput, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { DialogsModel, IDialogsModel } from 'vs/workbench/common/dialogs';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
export class DialogService extends Disposable implements IDialogService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
readonly model: IDialogsModel = this._register(new DialogsModel());
|
||||
|
||||
async confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
|
||||
const handle = this.model.show({ confirmArgs: { confirmation } });
|
||||
return await handle.result as IConfirmationResult;
|
||||
}
|
||||
|
||||
async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise<IShowResult> {
|
||||
const handle = this.model.show({ showArgs: { severity, message, buttons, options } });
|
||||
return await handle.result as IShowResult;
|
||||
}
|
||||
|
||||
async input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise<IInputResult> {
|
||||
const handle = this.model.show({ inputArgs: { severity, message, buttons, inputs, options } });
|
||||
return await handle.result as IInputResult;
|
||||
}
|
||||
|
||||
async about(): Promise<void> {
|
||||
const handle = this.model.show({});
|
||||
await handle.result;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IDialogService, DialogService, true);
|
||||
@@ -1,261 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
import { mnemonicButtonLabel } from 'vs/base/common/labels';
|
||||
import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, IShowResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { DialogService as HTMLDialogService } from 'vs/workbench/services/dialogs/browser/dialogService';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { MessageBoxOptions } from 'vs/base/parts/sandbox/common/electronTypes';
|
||||
import { fromNow } from 'vs/base/common/date';
|
||||
import { process } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
|
||||
interface IMassagedMessageBoxOptions {
|
||||
|
||||
/**
|
||||
* OS massaged message box options.
|
||||
*/
|
||||
options: MessageBoxOptions;
|
||||
|
||||
/**
|
||||
* Since the massaged result of the message box options potentially
|
||||
* changes the order of buttons, we have to keep a map of these
|
||||
* changes so that we can still return the correct index to the caller.
|
||||
*/
|
||||
buttonIndexMap: number[];
|
||||
}
|
||||
|
||||
export class DialogService implements IDialogService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private nativeImpl: IDialogService;
|
||||
private customImpl: IDialogService;
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@ILogService logService: ILogService,
|
||||
@ILayoutService layoutService: ILayoutService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IProductService productService: IProductService,
|
||||
@IClipboardService clipboardService: IClipboardService,
|
||||
@IElectronService electronService: IElectronService
|
||||
) {
|
||||
this.customImpl = new HTMLDialogService(logService, layoutService, themeService, keybindingService, productService, clipboardService);
|
||||
this.nativeImpl = new NativeDialogService(logService, electronService, productService, clipboardService);
|
||||
}
|
||||
|
||||
private get useCustomDialog(): boolean {
|
||||
return this.configurationService.getValue('workbench.dialogs.customEnabled') === true;
|
||||
}
|
||||
|
||||
confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
|
||||
if (this.useCustomDialog) {
|
||||
return this.customImpl.confirm(confirmation);
|
||||
}
|
||||
|
||||
return this.nativeImpl.confirm(confirmation);
|
||||
}
|
||||
|
||||
show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions | undefined): Promise<IShowResult> {
|
||||
if (this.useCustomDialog) {
|
||||
return this.customImpl.show(severity, message, buttons, options);
|
||||
}
|
||||
|
||||
return this.nativeImpl.show(severity, message, buttons, options);
|
||||
}
|
||||
|
||||
about(): Promise<void> {
|
||||
return this.nativeImpl.about();
|
||||
}
|
||||
}
|
||||
|
||||
class NativeDialogService implements IDialogService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IElectronService private readonly electronService: IElectronService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService
|
||||
) {
|
||||
}
|
||||
|
||||
async confirm(confirmation: IConfirmation): Promise<IConfirmationResult> {
|
||||
this.logService.trace('DialogService#confirm', confirmation.message);
|
||||
|
||||
const { options, buttonIndexMap } = this.massageMessageBoxOptions(this.getConfirmOptions(confirmation));
|
||||
|
||||
const result = await this.electronService.showMessageBox(options);
|
||||
return {
|
||||
confirmed: buttonIndexMap[result.response] === 0 ? true : false,
|
||||
checkboxChecked: result.checkboxChecked
|
||||
};
|
||||
}
|
||||
|
||||
private getConfirmOptions(confirmation: IConfirmation): MessageBoxOptions {
|
||||
const buttons: string[] = [];
|
||||
if (confirmation.primaryButton) {
|
||||
buttons.push(confirmation.primaryButton);
|
||||
} else {
|
||||
buttons.push(nls.localize({ key: 'yesButton', comment: ['&& denotes a mnemonic'] }, "&&Yes"));
|
||||
}
|
||||
|
||||
if (confirmation.secondaryButton) {
|
||||
buttons.push(confirmation.secondaryButton);
|
||||
} else if (typeof confirmation.secondaryButton === 'undefined') {
|
||||
buttons.push(nls.localize('cancelButton', "Cancel"));
|
||||
}
|
||||
|
||||
const opts: MessageBoxOptions = {
|
||||
title: confirmation.title,
|
||||
message: confirmation.message,
|
||||
buttons,
|
||||
cancelId: 1
|
||||
};
|
||||
|
||||
if (confirmation.detail) {
|
||||
opts.detail = confirmation.detail;
|
||||
}
|
||||
|
||||
if (confirmation.type) {
|
||||
opts.type = confirmation.type;
|
||||
}
|
||||
|
||||
if (confirmation.checkbox) {
|
||||
opts.checkboxLabel = confirmation.checkbox.label;
|
||||
opts.checkboxChecked = confirmation.checkbox.checked;
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
async show(severity: Severity, message: string, buttons: string[], dialogOptions?: IDialogOptions): Promise<IShowResult> {
|
||||
this.logService.trace('DialogService#show', message);
|
||||
|
||||
const { options, buttonIndexMap } = this.massageMessageBoxOptions({
|
||||
message,
|
||||
buttons,
|
||||
type: (severity === Severity.Info) ? 'question' : (severity === Severity.Error) ? 'error' : (severity === Severity.Warning) ? 'warning' : 'none',
|
||||
cancelId: dialogOptions ? dialogOptions.cancelId : undefined,
|
||||
detail: dialogOptions ? dialogOptions.detail : undefined,
|
||||
checkboxLabel: dialogOptions && dialogOptions.checkbox ? dialogOptions.checkbox.label : undefined,
|
||||
checkboxChecked: dialogOptions && dialogOptions.checkbox ? dialogOptions.checkbox.checked : undefined
|
||||
});
|
||||
|
||||
const result = await this.electronService.showMessageBox(options);
|
||||
return { choice: buttonIndexMap[result.response], checkboxChecked: result.checkboxChecked };
|
||||
}
|
||||
|
||||
private massageMessageBoxOptions(options: MessageBoxOptions): IMassagedMessageBoxOptions {
|
||||
let buttonIndexMap = (options.buttons || []).map((button, index) => index);
|
||||
let buttons = (options.buttons || []).map(button => mnemonicButtonLabel(button));
|
||||
let cancelId = options.cancelId;
|
||||
|
||||
// Linux: order of buttons is reverse
|
||||
// macOS: also reverse, but the OS handles this for us!
|
||||
if (isLinux) {
|
||||
buttons = buttons.reverse();
|
||||
buttonIndexMap = buttonIndexMap.reverse();
|
||||
}
|
||||
|
||||
// Default Button (always first one)
|
||||
options.defaultId = buttonIndexMap[0];
|
||||
|
||||
// Cancel Button
|
||||
if (typeof cancelId === 'number') {
|
||||
|
||||
// Ensure the cancelId is the correct one from our mapping
|
||||
cancelId = buttonIndexMap[cancelId];
|
||||
|
||||
// macOS/Linux: the cancel button should always be to the left of the primary action
|
||||
// if we see more than 2 buttons, move the cancel one to the left of the primary
|
||||
if (!isWindows && buttons.length > 2 && cancelId !== 1) {
|
||||
const cancelButton = buttons[cancelId];
|
||||
buttons.splice(cancelId, 1);
|
||||
buttons.splice(1, 0, cancelButton);
|
||||
|
||||
const cancelButtonIndex = buttonIndexMap[cancelId];
|
||||
buttonIndexMap.splice(cancelId, 1);
|
||||
buttonIndexMap.splice(1, 0, cancelButtonIndex);
|
||||
|
||||
cancelId = 1;
|
||||
}
|
||||
}
|
||||
|
||||
options.buttons = buttons;
|
||||
options.cancelId = cancelId;
|
||||
options.noLink = true;
|
||||
options.title = options.title || this.productService.nameLong;
|
||||
|
||||
return { options, buttonIndexMap };
|
||||
}
|
||||
|
||||
async about(): Promise<void> {
|
||||
let version = this.productService.version;
|
||||
if (this.productService.target) {
|
||||
version = `${version} (${this.productService.target} setup)`;
|
||||
}
|
||||
|
||||
const isSnap = process.platform === 'linux' && process.env.SNAP && process.env.SNAP_REVISION;
|
||||
const os = await this.electronService.getOS();
|
||||
|
||||
const detailString = (useAgo: boolean): string => {
|
||||
return nls.localize('aboutDetail',
|
||||
"Version: {0}\nCommit: {1}\nDate: {2}\nVS Code: {8}\nElectron: {3}\nChrome: {4}\nNode.js: {5}\nV8: {6}\nOS: {7}",
|
||||
version,
|
||||
this.productService.commit || 'Unknown',
|
||||
this.productService.date ? `${this.productService.date}${useAgo ? ' (' + fromNow(new Date(this.productService.date), true) + ')' : ''}` : 'Unknown',
|
||||
process.versions['electron'],
|
||||
process.versions['chrome'],
|
||||
process.versions['node'],
|
||||
process.versions['v8'],
|
||||
`${os.type} ${os.arch} ${os.release}${isSnap ? ' snap' : ''}`,
|
||||
this.productService.vscodeVersion
|
||||
);
|
||||
};
|
||||
|
||||
const detail = detailString(true);
|
||||
const detailToCopy = detailString(false);
|
||||
|
||||
const ok = nls.localize('okButton', "OK");
|
||||
const copy = mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy"));
|
||||
let buttons: string[];
|
||||
if (isLinux) {
|
||||
buttons = [copy, ok];
|
||||
} else {
|
||||
buttons = [ok, copy];
|
||||
}
|
||||
|
||||
const result = await this.electronService.showMessageBox({
|
||||
title: this.productService.nameLong,
|
||||
type: 'info',
|
||||
message: this.productService.nameLong,
|
||||
detail: `\n${detail}`,
|
||||
buttons,
|
||||
noLink: true,
|
||||
defaultId: buttons.indexOf(ok),
|
||||
cancelId: buttons.indexOf(ok)
|
||||
});
|
||||
|
||||
if (buttons[result.response] === copy) {
|
||||
this.clipboardService.writeText(detailToCopy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IDialogService, DialogService, true);
|
||||
@@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
|
||||
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { AbstractFileDialogService } from 'vs/workbench/services/dialogs/browser/abstractFileDialogService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
@@ -36,7 +36,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IElectronService private readonly electronService: IElectronService,
|
||||
@INativeHostService private readonly nativeHostService: INativeHostService,
|
||||
@IDialogService dialogService: IDialogService,
|
||||
@IModeService modeService: IModeService,
|
||||
@IWorkspacesService workspacesService: IWorkspacesService,
|
||||
@@ -58,61 +58,64 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
private shouldUseSimplified(schema: string): { useSimplified: boolean, isSetting: boolean } {
|
||||
const setting = (this.configurationService.getValue('files.simpleDialog.enable') === true);
|
||||
const newWindowSetting = (this.configurationService.getValue('window.openFilesInNewWindow') === 'on');
|
||||
return { useSimplified: (schema !== Schemas.file) || setting, isSetting: newWindowSetting };
|
||||
return {
|
||||
useSimplified: ((schema !== Schemas.file) && (schema !== Schemas.userData)) || setting,
|
||||
isSetting: newWindowSetting
|
||||
};
|
||||
}
|
||||
|
||||
async pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
options.defaultUri = this.defaultFilePath(schema);
|
||||
options.defaultUri = await this.defaultFilePath(schema);
|
||||
}
|
||||
|
||||
const shouldUseSimplified = this.shouldUseSimplified(schema);
|
||||
if (shouldUseSimplified.useSimplified) {
|
||||
return this.pickFileFolderAndOpenSimplified(schema, options, shouldUseSimplified.isSetting);
|
||||
}
|
||||
return this.electronService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
return this.nativeHostService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
async pickFileAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
options.defaultUri = this.defaultFilePath(schema);
|
||||
options.defaultUri = await this.defaultFilePath(schema);
|
||||
}
|
||||
|
||||
const shouldUseSimplified = this.shouldUseSimplified(schema);
|
||||
if (shouldUseSimplified.useSimplified) {
|
||||
return this.pickFileAndOpenSimplified(schema, options, shouldUseSimplified.isSetting);
|
||||
}
|
||||
return this.electronService.pickFileAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
return this.nativeHostService.pickFileAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
async pickFolderAndOpen(options: IPickAndOpenOptions): Promise<any> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
options.defaultUri = this.defaultFolderPath(schema);
|
||||
options.defaultUri = await this.defaultFolderPath(schema);
|
||||
}
|
||||
|
||||
if (this.shouldUseSimplified(schema).useSimplified) {
|
||||
return this.pickFolderAndOpenSimplified(schema, options);
|
||||
}
|
||||
return this.electronService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
return this.nativeHostService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
async pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void> {
|
||||
const schema = this.getFileSystemSchema(options);
|
||||
|
||||
if (!options.defaultUri) {
|
||||
options.defaultUri = this.defaultWorkspacePath(schema);
|
||||
options.defaultUri = await this.defaultWorkspacePath(schema);
|
||||
}
|
||||
|
||||
if (this.shouldUseSimplified(schema).useSimplified) {
|
||||
return this.pickWorkspaceAndOpenSimplified(schema, options);
|
||||
}
|
||||
return this.electronService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
return this.nativeHostService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options));
|
||||
}
|
||||
|
||||
async pickFileToSave(defaultUri: URI, availableFileSystems?: string[]): Promise<URI | undefined> {
|
||||
@@ -121,7 +124,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
if (this.shouldUseSimplified(schema).useSimplified) {
|
||||
return this.pickFileToSaveSimplified(schema, options);
|
||||
} else {
|
||||
const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options));
|
||||
const result = await this.nativeHostService.showSaveDialog(this.toNativeSaveDialogOptions(options));
|
||||
if (result && !result.canceled && result.filePath) {
|
||||
return URI.file(result.filePath);
|
||||
}
|
||||
@@ -145,7 +148,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
return this.showSaveDialogSimplified(schema, options);
|
||||
}
|
||||
|
||||
const result = await this.electronService.showSaveDialog(this.toNativeSaveDialogOptions(options));
|
||||
const result = await this.nativeHostService.showSaveDialog(this.toNativeSaveDialogOptions(options));
|
||||
if (result && !result.canceled && result.filePath) {
|
||||
return URI.file(result.filePath);
|
||||
}
|
||||
@@ -183,7 +186,7 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
newOptions.properties.push('multiSelections');
|
||||
}
|
||||
|
||||
const result = await this.electronService.showOpenDialog(newOptions);
|
||||
const result = await this.nativeHostService.showOpenDialog(newOptions);
|
||||
return result && Array.isArray(result.filePaths) && result.filePaths.length > 0 ? result.filePaths.map(URI.file) : undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user