Merge from vscode 3bd60b2ba753e7fe39b42f99184bc6c5881d3551 (#4712)

This commit is contained in:
Anthony Dresser
2019-03-27 11:36:01 -07:00
committed by GitHub
parent eac3420583
commit 46b7afe558
36 changed files with 303 additions and 137 deletions

View File

@@ -22,6 +22,8 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { RemoteFileDialogContext } from 'vs/workbench/common/contextkeys';
interface FileQuickPickItem extends IQuickPickItem {
uri: URI;
@@ -48,6 +50,7 @@ export class RemoteFileDialog {
private scheme: string = REMOTE_HOST_SCHEME;
private shouldOverwriteFile: boolean = false;
private autoComplete: string;
private contextKey: IContextKey<boolean>;
constructor(
@IFileService private readonly fileService: IFileService,
@@ -61,9 +64,11 @@ export class RemoteFileDialog {
@IModeService private readonly modeService: IModeService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService,
@IContextKeyService contextKeyService: IContextKeyService
) {
this.remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
this.contextKey = RemoteFileDialogContext.bindTo(contextKeyService);
}
public async showOpenDialog(options: IOpenDialogOptions = {}): Promise<IURIToOpen[] | undefined> {
@@ -245,10 +250,12 @@ export class RemoteFileDialog {
if (!isResolving) {
resolve(undefined);
}
this.contextKey.set(false);
this.filePickBox.dispose();
});
this.filePickBox.show();
this.contextKey.set(true);
this.updateItems(homedir, trailing);
if (trailing) {
this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - ext.length];

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability } from 'vs/platform/files/common/files';
import { IFileService, IResolveFileOptions, IResourceEncodings, FileChangesEvent, FileOperationEvent, IFileSystemProviderRegistrationEvent, IFileSystemProvider, IFileStat, IResolveFileResult, IResolveContentOptions, IContent, IStreamContent, ITextSnapshot, IUpdateContentOptions, ICreateFileOptions, IFileSystemProviderActivationEvent, FileOperationError, FileOperationResult, FileOperation, FileSystemProviderCapabilities, FileType, toFileSystemProviderErrorCode, FileSystemProviderErrorCode, IStat, IFileStatWithMetadata, IResolveMetadataFileOptions, etag, hasReadWriteCapability, hasFileFolderCopyCapability, hasOpenReadWriteCloseCapability, toFileOperationResult, IFileSystemProviderWithOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadWriteCapability, IResolveFileResultWithMetadata } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation';
@@ -252,7 +252,7 @@ export class FileService2 extends Disposable implements IFileService {
}
async resolveFiles(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]>;
async resolveFiles(toResolve: { resource: URI, options: IResolveMetadataFileOptions }[]): Promise<IResolveFileResult[]>;
async resolveFiles(toResolve: { resource: URI, options: IResolveMetadataFileOptions }[]): Promise<IResolveFileResultWithMetadata[]>;
async resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise<IResolveFileResult[]> {
return Promise.all(toResolve.map(async entry => {
try {
@@ -600,14 +600,17 @@ export class FileService2 extends Disposable implements IFileService {
private async doWriteBuffered(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, resource: URI, buffer: Uint8Array): Promise<void> {
// Open handle
// open handle
const handle = await provider.open(resource, { create: true });
// write into handle until all bytes from buffer have been written
await this.doWriteBuffer(provider, handle, buffer, buffer.byteLength, 0, 0);
// Close handle
return provider.close(handle);
try {
await this.doWriteBuffer(provider, handle, buffer, buffer.byteLength, 0, 0);
} catch (error) {
throw error;
} finally {
await provider.close(handle);
}
}
private async doWriteBuffer(provider: IFileSystemProviderWithOpenReadWriteCloseCapability, handle: number, buffer: Uint8Array, length: number, posInFile: number, posInBuffer: number): Promise<void> {
@@ -623,39 +626,45 @@ export class FileService2 extends Disposable implements IFileService {
}
private async doPipeBuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, target: URI): Promise<void> {
let sourceHandle: number | undefined = undefined;
let targetHandle: number | undefined = undefined;
// Open handles
const sourceHandle = await sourceProvider.open(source, { create: false });
const targetHandle = await targetProvider.open(target, { create: true });
try {
const buffer = new Uint8Array(8 * 1024);
// Open handles
sourceHandle = await sourceProvider.open(source, { create: false });
targetHandle = await targetProvider.open(target, { create: true });
let posInFile = 0;
let posInBuffer = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, posInFile, buffer, posInBuffer, buffer.byteLength - posInBuffer);
const buffer = new Uint8Array(16 * 1024);
// write into target (targetHandle) at current position (posInFile) from buffer (buffer) at
// buffer position (posInBuffer) all bytes we read (bytesRead).
await this.doWriteBuffer(targetProvider, targetHandle, buffer, bytesRead, posInFile, posInBuffer);
let posInFile = 0;
let posInBuffer = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, posInFile, buffer, posInBuffer, buffer.byteLength - posInBuffer);
posInFile += bytesRead;
posInBuffer += bytesRead;
// write into target (targetHandle) at current position (posInFile) from buffer (buffer) at
// buffer position (posInBuffer) all bytes we read (bytesRead).
await this.doWriteBuffer(targetProvider, targetHandle, buffer, bytesRead, posInFile, posInBuffer);
// when buffer full, fill it again from the beginning
if (posInBuffer === buffer.length) {
posInBuffer = 0;
}
} while (bytesRead > 0);
posInFile += bytesRead;
posInBuffer += bytesRead;
// Close handles
return Promise.all([
sourceProvider.close(sourceHandle),
targetProvider.close(targetHandle)
]).then(() => undefined);
// when buffer full, fill it again from the beginning
if (posInBuffer === buffer.length) {
posInBuffer = 0;
}
} while (bytesRead > 0);
} catch (error) {
throw error;
} finally {
await Promise.all([
typeof sourceHandle === 'number' ? sourceProvider.close(sourceHandle) : Promise.resolve(),
typeof targetHandle === 'number' ? targetProvider.close(targetHandle) : Promise.resolve(),
]);
}
}
private async doPipeUnbuffered(sourceProvider: IFileSystemProviderWithFileReadWriteCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI, overwrite: boolean): Promise<void> {
@@ -668,11 +677,14 @@ export class FileService2 extends Disposable implements IFileService {
const targetHandle = await targetProvider.open(target, { create: true });
// Read entire buffer from source and write buffered
const buffer = await sourceProvider.readFile(source);
await this.doWriteBuffer(targetProvider, targetHandle, buffer, buffer.byteLength, 0, 0);
// Close handle
return targetProvider.close(targetHandle);
try {
const buffer = await sourceProvider.readFile(source);
await this.doWriteBuffer(targetProvider, targetHandle, buffer, buffer.byteLength, 0, 0);
} catch (error) {
throw error;
} finally {
await targetProvider.close(targetHandle);
}
}
private async doPipeBufferedToUnbuffered(sourceProvider: IFileSystemProviderWithOpenReadWriteCloseCapability, source: URI, targetProvider: IFileSystemProviderWithFileReadWriteCapability, target: URI, overwrite: boolean): Promise<void> {
@@ -683,23 +695,26 @@ export class FileService2 extends Disposable implements IFileService {
// Open handle
const sourceHandle = await sourceProvider.open(source, { create: false });
const buffer = new Uint8Array(size);
try {
const buffer = new Uint8Array(size);
let pos = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, pos, buffer, pos, buffer.byteLength - pos);
let pos = 0;
let bytesRead = 0;
do {
// read from source (sourceHandle) at current position (posInFile) into buffer (buffer) at
// buffer position (posInBuffer) up to the size of the buffer (buffer.byteLength).
bytesRead = await sourceProvider.read(sourceHandle, pos, buffer, pos, buffer.byteLength - pos);
pos += bytesRead;
} while (bytesRead > 0);
pos += bytesRead;
} while (bytesRead > 0 && pos < size);
// Write buffer into target at once
await this.doWriteUnbuffered(targetProvider, target, buffer, overwrite);
// Close handle
return sourceProvider.close(sourceHandle);
// Write buffer into target at once
await this.doWriteUnbuffered(targetProvider, target, buffer, overwrite);
} catch (error) {
throw error;
} finally {
await sourceProvider.close(sourceHandle);
}
}
private throwIfFileSystemIsReadonly(provider: IFileSystemProvider): IFileSystemProvider {

View File

@@ -9,9 +9,14 @@ import { FileDeleteOptions, FileSystemProviderCapabilities } from 'vs/platform/f
import { isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { basename } from 'vs/base/common/path';
import { ILogService } from 'vs/platform/log/common/log';
export class DiskFileSystemProvider extends NodeDiskFileSystemProvider {
constructor(logService: ILogService) {
super(logService);
}
get capabilities(): FileSystemProviderCapabilities {
if (!this._capabilities) {
this._capabilities = super.capabilities | FileSystemProviderCapabilities.Trash;

View File

@@ -16,9 +16,14 @@ import { normalize } from 'vs/base/common/path';
import { joinPath } from 'vs/base/common/resources';
import { isEqual } from 'vs/base/common/extpath';
import { retry } from 'vs/base/common/async';
import { ILogService } from 'vs/platform/log/common/log';
export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider {
constructor(private logService: ILogService) {
super();
}
//#region File Capabilities
onDidChangeCapabilities: Event<void> = Event.None;
@@ -73,8 +78,12 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
for (let i = 0; i < children.length; i++) {
const child = children[i];
const stat = await this.stat(joinPath(resource, child));
result.push([child, stat.type]);
try {
const stat = await this.stat(joinPath(resource, child));
result.push([child, stat.type]);
} catch (error) {
this.logService.trace(error); // ignore errors for individual entries that can arise from permission denied
}
}
return result;
@@ -112,7 +121,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
if (exists && isWindows) {
try {
// On Windows and if the file exists, we use a different strategy of saving the file
// by first truncating the file and then writing with r+ mode. This helps to save hidden files on Windows
// by first truncating the file and then writing with r+ flag. This helps to save hidden files on Windows
// (see https://github.com/Microsoft/vscode/issues/931) and prevent removing alternate data streams
// (see https://github.com/Microsoft/vscode/issues/6363)
await truncate(filePath, 0);
@@ -123,6 +132,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
// short timeout, assuming that the file is free to write then.
await retry(() => writeFile(filePath, content, { flag: 'r+' }), 100 /* ms delay */, 3 /* retries */);
} catch (error) {
this.logService.trace(error);
// we heard from users that fs.truncate() fails (https://github.com/Microsoft/vscode/issues/59561)
// in that case we simply save the file without truncating first (same as macOS and Linux)
await writeFile(filePath, content);
@@ -142,20 +153,20 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
try {
const filePath = this.toFilePath(resource);
let mode: string;
let flags: string;
if (opts.create) {
// we take this as a hint that the file is opened for writing
// as such we use 'w' to truncate an existing or create the
// file otherwise. we do not allow reading.
mode = 'w';
flags = 'w';
} else {
// otherwise we assume the file is opened for reading
// as such we use 'r' to neither truncate, nor create
// the file.
mode = 'r';
flags = 'r';
}
return await promisify(open)(filePath, mode);
return await promisify(open)(filePath, flags);
} catch (error) {
throw this.toFileSystemProviderError(error);
}
@@ -300,7 +311,7 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
return error; // avoid double conversion
}
let code: FileSystemProviderErrorCode | undefined = undefined;
let code: FileSystemProviderErrorCode;
switch (error.code) {
case 'ENOENT':
code = FileSystemProviderErrorCode.FileNotFound;
@@ -315,6 +326,8 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
case 'EACCESS':
code = FileSystemProviderErrorCode.NoPermissions;
break;
default:
code = FileSystemProviderErrorCode.Unknown;
}
return createFileSystemProviderError(error, code);

View File

@@ -17,8 +17,10 @@ import { URI } from 'vs/base/common/uri';
import { existsSync, statSync, readdirSync, readFileSync } from 'fs';
import { FileOperation, FileOperationEvent, IFileStat, FileOperationResult, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
import { NullLogService } from 'vs/platform/log/common/log';
import { isLinux } from 'vs/base/common/platform';
import { isLinux, isWindows } from 'vs/base/common/platform';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { promisify } from 'util';
import { exec } from 'child_process';
function getByName(root: IFileStat, name: string): IFileStat | null {
if (root.children === undefined) {
@@ -70,13 +72,15 @@ suite('Disk File Service', () => {
let disposables: IDisposable[] = [];
setup(async () => {
service = new FileService2(new NullLogService());
const logService = new NullLogService();
service = new FileService2(logService);
disposables.push(service);
fileProvider = new TestDiskFileSystemProvider();
fileProvider = new TestDiskFileSystemProvider(logService);
service.registerProvider(Schemas.file, fileProvider);
testProvider = new TestDiskFileSystemProvider();
testProvider = new TestDiskFileSystemProvider(logService);
service.registerProvider(testSchema, testProvider);
const id = generateUuid();
@@ -307,6 +311,45 @@ suite('Disk File Service', () => {
assert.equal(r2.name, 'deep');
});
test('resolveFile - folder symbolic link', async () => {
if (isWindows) {
return; // only for unix systems
}
const link = URI.file(join(testDir, 'deep-link'));
await promisify(exec)(`ln -s deep ${basename(link.fsPath)}`, { cwd: testDir });
const resolved = await service.resolveFile(link);
assert.equal(resolved.children!.length, 4);
assert.equal(resolved.isDirectory, true);
assert.equal(resolved.isSymbolicLink, true);
});
test('resolveFile - file symbolic link', async () => {
if (isWindows) {
return; // only for unix systems
}
const link = URI.file(join(testDir, 'lorem.txt-linked'));
await promisify(exec)(`ln -s lorem.txt ${basename(link.fsPath)}`, { cwd: testDir });
const resolved = await service.resolveFile(link);
assert.equal(resolved.isDirectory, false);
assert.equal(resolved.isSymbolicLink, true);
});
test('resolveFile - invalid symbolic link does not break', async () => {
if (isWindows) {
return; // only for unix systems
}
await promisify(exec)('ln -s foo bar', { cwd: testDir });
const resolved = await service.resolveFile(URI.file(testDir));
assert.equal(resolved.isDirectory, true);
assert.equal(resolved.children!.length, 8);
});
test('deleteFile', async () => {
let event: FileOperationEvent;
disposables.push(service.onAfterOperation(e => event = e));

View File

@@ -21,7 +21,7 @@ import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileS
import { ICommandService } from 'vs/platform/commands/common/commands';
import { distinct } from 'vs/base/common/arrays';
import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform';
import { isEqual, basename, isEqualOrParent } from 'vs/base/common/resources';
import { isEqual, basename, isEqualOrParent, getComparisonKey } from 'vs/base/common/resources';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -205,7 +205,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
if (state !== WorkbenchState.WORKSPACE) {
let newWorkspaceFolders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri } as IWorkspaceFolderCreationData));
newWorkspaceFolders.splice(typeof index === 'number' ? index : newWorkspaceFolders.length, 0, ...foldersToAdd);
newWorkspaceFolders = distinct(newWorkspaceFolders, folder => isLinux ? folder.uri.toString() : folder.uri.toString().toLowerCase());
newWorkspaceFolders = distinct(newWorkspaceFolders, folder => getComparisonKey(folder.uri));
if (state === WorkbenchState.EMPTY && newWorkspaceFolders.length === 0 || state === WorkbenchState.FOLDER && newWorkspaceFolders.length === 1) {
return Promise.resolve(); // return if the operation is a no-op for the current state
@@ -245,8 +245,8 @@ export class WorkspaceEditingService implements IWorkspaceEditingService {
if (path && !this.isValidTargetWorkspacePath(path)) {
return Promise.reject(null);
}
const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders);
const remoteAuthority = this.windowService.getConfiguration().remoteAuthority;
const untitledWorkspace = await this.workspaceService.createUntitledWorkspace(folders, remoteAuthority);
if (path) {
await this.saveWorkspaceAs(untitledWorkspace, path);
} else {