mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-11 10:38:31 -05:00
Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c (#8525)
* Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c * remove files we don't want * fix hygiene * update distro * update distro * fix hygiene * fix strict nulls * distro * distro * fix tests * fix tests * add another edit * fix viewlet icon * fix azure dialog * fix some padding * fix more padding issues
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { IWindowOpenable } from 'vs/platform/windows/common/windows';
|
||||
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, FileFilter, IFileDialogService, IDialogService, ConfirmResult, getConfirmMessage } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
@@ -14,14 +14,15 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { IInstantiationService, } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { SimpleFileDialog } from 'vs/workbench/services/dialogs/browser/simpleFileDialog';
|
||||
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { WORKSPACE_EXTENSION, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
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';
|
||||
|
||||
export abstract class AbstractFileDialogService {
|
||||
export abstract class AbstractFileDialogService implements IFileDialogService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
@@ -34,6 +35,7 @@ export abstract class AbstractFileDialogService {
|
||||
@IConfigurationService protected readonly configurationService: IConfigurationService,
|
||||
@IFileService protected readonly fileService: IFileService,
|
||||
@IOpenerService protected readonly openerService: IOpenerService,
|
||||
@IDialogService private readonly dialogService: IDialogService
|
||||
) { }
|
||||
|
||||
defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined {
|
||||
@@ -78,6 +80,40 @@ export abstract class AbstractFileDialogService {
|
||||
return this.defaultFilePath(schemeFilter);
|
||||
}
|
||||
|
||||
async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult> {
|
||||
if (this.environmentService.isExtensionDevelopment) {
|
||||
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
|
||||
}
|
||||
|
||||
if (fileNamesOrResources.length === 0) {
|
||||
return ConfirmResult.DONT_SAVE;
|
||||
}
|
||||
|
||||
let message: string;
|
||||
if (fileNamesOrResources.length === 1) {
|
||||
message = nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", typeof fileNamesOrResources[0] === 'string' ? fileNamesOrResources[0] : resources.basename(fileNamesOrResources[0]));
|
||||
} else {
|
||||
message = getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", fileNamesOrResources.length), fileNamesOrResources);
|
||||
}
|
||||
|
||||
const buttons: string[] = [
|
||||
fileNamesOrResources.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"),
|
||||
nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"),
|
||||
nls.localize('cancel', "Cancel")
|
||||
];
|
||||
|
||||
const { choice } = await this.dialogService.show(Severity.Warning, message, buttons, {
|
||||
cancelId: 2,
|
||||
detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.")
|
||||
});
|
||||
|
||||
switch (choice) {
|
||||
case 0: return ConfirmResult.SAVE;
|
||||
case 1: return ConfirmResult.DONT_SAVE;
|
||||
default: return ConfirmResult.CANCEL;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract addFileSchemaIfNeeded(schema: string): string[];
|
||||
|
||||
protected async pickFileFolderAndOpenSimplified(schema: string, options: IPickAndOpenOptions, preferNewWindow: boolean): Promise<any> {
|
||||
@@ -179,8 +215,12 @@ export abstract class AbstractFileDialogService {
|
||||
protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string {
|
||||
return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow();
|
||||
}
|
||||
}
|
||||
|
||||
function isUntitledWorkspace(path: URI, environmentService: IWorkbenchEnvironmentService): boolean {
|
||||
return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome);
|
||||
abstract pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;
|
||||
abstract pickFileAndOpen(options: IPickAndOpenOptions): Promise<void>;
|
||||
abstract pickFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;
|
||||
abstract pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise<void>;
|
||||
abstract pickFileToSave(options: ISaveDialogOptions): Promise<URI | undefined>;
|
||||
abstract showSaveDialog(options: ISaveDialogOptions): Promise<URI | undefined>;
|
||||
abstract showOpenDialog(options: IOpenDialogOptions): Promise<URI[] | undefined>;
|
||||
}
|
||||
|
||||
@@ -32,10 +32,9 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { createCancelablePromise, CancelablePromise } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { toResource } from 'vs/workbench/common/editor';
|
||||
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
||||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
|
||||
export namespace OpenLocalFileCommand {
|
||||
export const ID = 'workbench.action.files.openLocalFile';
|
||||
@@ -53,13 +52,12 @@ export namespace SaveLocalFileCommand {
|
||||
export const LABEL = nls.localize('saveLocalFile', "Save Local File...");
|
||||
export function handler(): ICommandHandler {
|
||||
return accessor => {
|
||||
const textFileService = accessor.get(ITextFileService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
let resource: URI | undefined = toResource(editorService.activeEditor);
|
||||
const options: ISaveOptions = { force: true, availableFileSystems: [Schemas.file] };
|
||||
if (resource) {
|
||||
return textFileService.saveAs(resource, undefined, options);
|
||||
const activeControl = editorService.activeControl;
|
||||
if (activeControl) {
|
||||
return editorService.save({ groupId: activeControl.group.id, editor: activeControl.input }, { saveAs: true, availableFileSystems: [Schemas.file], reason: SaveReason.EXPLICIT });
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
};
|
||||
}
|
||||
@@ -262,6 +260,7 @@ export class SimpleFileDialog {
|
||||
this.filePickBox = this.quickInputService.createQuickPick<FileQuickPickItem>();
|
||||
this.busy = true;
|
||||
this.filePickBox.matchOnLabel = false;
|
||||
this.filePickBox.sortByLabel = false;
|
||||
this.filePickBox.autoFocusOnList = false;
|
||||
this.filePickBox.ignoreFocusOut = true;
|
||||
this.filePickBox.ok = true;
|
||||
@@ -373,28 +372,7 @@ export class SimpleFileDialog {
|
||||
});
|
||||
|
||||
this.filePickBox.onDidChangeValue(async value => {
|
||||
try {
|
||||
// onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything
|
||||
if (this.isValueChangeFromUser()) {
|
||||
// If the user has just entered more bad path, don't change anything
|
||||
if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) {
|
||||
this.filePickBox.validationMessage = undefined;
|
||||
const filePickBoxUri = this.filePickBoxValue();
|
||||
let updated: UpdateResult = UpdateResult.NotUpdated;
|
||||
if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) {
|
||||
updated = await this.tryUpdateItems(value, filePickBoxUri);
|
||||
}
|
||||
if (updated === UpdateResult.NotUpdated) {
|
||||
this.setActiveItems(value);
|
||||
}
|
||||
} else {
|
||||
this.filePickBox.activeItems = [];
|
||||
this.userEnteredPathSegment = '';
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Since any text can be entered in the input box, there is potential for error causing input. If this happens, do nothing.
|
||||
}
|
||||
return this.handleValueChange(value);
|
||||
});
|
||||
this.filePickBox.onDidHide(() => {
|
||||
this.hidden = true;
|
||||
@@ -415,6 +393,31 @@ export class SimpleFileDialog {
|
||||
});
|
||||
}
|
||||
|
||||
private async handleValueChange(value: string) {
|
||||
try {
|
||||
// onDidChangeValue can also be triggered by the auto complete, so if it looks like the auto complete, don't do anything
|
||||
if (this.isValueChangeFromUser()) {
|
||||
// If the user has just entered more bad path, don't change anything
|
||||
if (!equalsIgnoreCase(value, this.constructFullUserPath()) && !this.isBadSubpath(value)) {
|
||||
this.filePickBox.validationMessage = undefined;
|
||||
const filePickBoxUri = this.filePickBoxValue();
|
||||
let updated: UpdateResult = UpdateResult.NotUpdated;
|
||||
if (!resources.isEqual(this.currentFolder, filePickBoxUri, true)) {
|
||||
updated = await this.tryUpdateItems(value, filePickBoxUri);
|
||||
}
|
||||
if (updated === UpdateResult.NotUpdated) {
|
||||
this.setActiveItems(value);
|
||||
}
|
||||
} else {
|
||||
this.filePickBox.activeItems = [];
|
||||
this.userEnteredPathSegment = '';
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Since any text can be entered in the input box, there is potential for error causing input. If this happens, do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
private isBadSubpath(value: string) {
|
||||
return this.badPath && (value.length > this.badPath.length) && equalsIgnoreCase(value.substring(0, this.badPath.length), this.badPath);
|
||||
}
|
||||
@@ -514,6 +517,16 @@ export class SimpleFileDialog {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private root(value: URI) {
|
||||
let lastDir = value;
|
||||
let dir = resources.dirname(value);
|
||||
while (!resources.isEqual(lastDir, dir)) {
|
||||
lastDir = dir;
|
||||
dir = resources.dirname(dir);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
private async tryUpdateItems(value: string, valueUri: URI): Promise<UpdateResult> {
|
||||
if ((value.length > 0) && ((value[value.length - 1] === '~') || (value[0] === '~'))) {
|
||||
let newDir = this.userHome;
|
||||
@@ -522,6 +535,11 @@ export class SimpleFileDialog {
|
||||
}
|
||||
await this.updateItems(newDir, true);
|
||||
return UpdateResult.Updated;
|
||||
} else if (value === '\\') {
|
||||
valueUri = this.root(this.currentFolder);
|
||||
value = this.pathFromUri(valueUri);
|
||||
await this.updateItems(valueUri, true);
|
||||
return UpdateResult.Updated;
|
||||
} else if (!resources.isEqual(this.currentFolder, valueUri, true) && (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true)))) {
|
||||
let stat: IFileStat | undefined;
|
||||
try {
|
||||
@@ -535,7 +553,7 @@ export class SimpleFileDialog {
|
||||
} else if (this.endsWithSlash(value)) {
|
||||
// The input box contains a path that doesn't exist on the system.
|
||||
this.filePickBox.validationMessage = nls.localize('remoteFileDialog.badPath', 'The path does not exist.');
|
||||
// Save this bad path. It can take too long to to a stat on every user entered character, but once a user enters a bad path they are likely
|
||||
// Save this bad path. It can take too long to a stat on every user entered character, but once a user enters a bad path they are likely
|
||||
// to keep typing more bad path. We can compare against this bad path and see if the user entered path starts with it.
|
||||
this.badPath = value;
|
||||
return UpdateResult.InvalidPath;
|
||||
@@ -602,7 +620,7 @@ export class SimpleFileDialog {
|
||||
this.activeItem = quickPickItem;
|
||||
if (force) {
|
||||
// clear any selected text
|
||||
this.insertText(this.userEnteredPathSegment, '');
|
||||
document.execCommand('insertText', false, '');
|
||||
}
|
||||
return false;
|
||||
} else if (!force && (itemBasename.length >= startingBasename.length) && equalsIgnoreCase(itemBasename.substr(0, startingBasename.length), startingBasename)) {
|
||||
@@ -631,14 +649,19 @@ export class SimpleFileDialog {
|
||||
private insertText(wholeValue: string, insertText: string) {
|
||||
if (this.filePickBox.inputHasFocus()) {
|
||||
document.execCommand('insertText', false, insertText);
|
||||
if (this.filePickBox.value !== wholeValue) {
|
||||
this.filePickBox.value = wholeValue;
|
||||
this.handleValueChange(wholeValue);
|
||||
}
|
||||
} else {
|
||||
this.filePickBox.value = wholeValue;
|
||||
this.handleValueChange(wholeValue);
|
||||
}
|
||||
}
|
||||
|
||||
private addPostfix(uri: URI): URI {
|
||||
let result = uri;
|
||||
if (this.requiresTrailing && this.options.filters && this.options.filters.length > 0) {
|
||||
if (this.requiresTrailing && this.options.filters && this.options.filters.length > 0 && !resources.hasTrailingPathSeparator(uri)) {
|
||||
// Make sure that the suffix is added. If the user deleted it, we automatically add it here
|
||||
let hasExt: boolean = false;
|
||||
const currentExt = resources.extname(uri).substr(1);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { SaveDialogOptions, OpenDialogOptions } from 'electron';
|
||||
import { INativeOpenDialogOptions } from 'vs/platform/dialogs/node/dialogs';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService, IDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
@@ -33,8 +33,11 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@IElectronService private readonly electronService: IElectronService
|
||||
) { super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService); }
|
||||
@IElectronService private readonly electronService: IElectronService,
|
||||
@IDialogService dialogService: IDialogService
|
||||
) {
|
||||
super(hostService, contextService, historyService, environmentService, instantiationService, configurationService, fileService, openerService, dialogService);
|
||||
}
|
||||
|
||||
private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions {
|
||||
return {
|
||||
@@ -180,6 +183,16 @@ export class FileDialogService extends AbstractFileDialogService implements IFil
|
||||
// Don't allow untitled schema through.
|
||||
return schema === Schemas.untitled ? [Schemas.file] : (schema !== Schemas.file ? [schema, Schemas.file] : [schema]);
|
||||
}
|
||||
|
||||
async showSaveConfirm(fileNamesOrResources: (string | URI)[]): Promise<ConfirmResult> {
|
||||
if (this.environmentService.isExtensionDevelopment) {
|
||||
if (!this.environmentService.args['extension-development-confirm-save']) {
|
||||
return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests)
|
||||
}
|
||||
}
|
||||
|
||||
return super.showSaveConfirm(fileNamesOrResources);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IFileDialogService, FileDialogService, true);
|
||||
|
||||
Reference in New Issue
Block a user