commit
320fffc44a
6 changed files with 239 additions and 2 deletions
|
@ -0,0 +1,119 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { join } from 'path';
|
||||
|
||||
suite('workspace-fs', () => {
|
||||
|
||||
let root: vscode.Uri;
|
||||
|
||||
suiteSetup(function () {
|
||||
root = vscode.workspace.workspaceFolders![0]!.uri;
|
||||
});
|
||||
|
||||
test('fs.stat', async function () {
|
||||
const stat = await vscode.workspace.fs.stat(root);
|
||||
assert.equal(stat.type, vscode.FileType.Directory);
|
||||
|
||||
assert.equal(typeof stat.size, 'number');
|
||||
assert.equal(typeof stat.mtime, 'number');
|
||||
assert.equal(typeof stat.ctime, 'number');
|
||||
|
||||
|
||||
const entries = await vscode.workspace.fs.readDirectory(root);
|
||||
assert.ok(entries.length > 0);
|
||||
|
||||
// find far.js
|
||||
const tuple = entries.find(tuple => tuple[0] === 'far.js')!;
|
||||
assert.ok(tuple);
|
||||
assert.equal(tuple[0], 'far.js');
|
||||
assert.equal(tuple[1], vscode.FileType.File);
|
||||
});
|
||||
|
||||
test('fs.stat - bad scheme', async function () {
|
||||
try {
|
||||
await vscode.workspace.fs.stat(vscode.Uri.parse('foo:/bar/baz/test.txt'));
|
||||
assert.ok(false);
|
||||
} catch {
|
||||
assert.ok(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('fs.stat - missing file', async function () {
|
||||
try {
|
||||
await vscode.workspace.fs.stat(root.with({ path: root.path + '.bad' }));
|
||||
assert.ok(false);
|
||||
} catch (e) {
|
||||
assert.ok(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('fs.write/stat/delete', async function () {
|
||||
|
||||
const uri = root.with({ path: join(root.path, 'new.file') });
|
||||
await vscode.workspace.fs.writeFile(uri, Buffer.from('HELLO'));
|
||||
|
||||
const stat = await vscode.workspace.fs.stat(uri);
|
||||
assert.equal(stat.type, vscode.FileType.File);
|
||||
|
||||
await vscode.workspace.fs.delete(uri);
|
||||
|
||||
try {
|
||||
await vscode.workspace.fs.stat(uri);
|
||||
assert.ok(false);
|
||||
} catch {
|
||||
assert.ok(true);
|
||||
}
|
||||
});
|
||||
|
||||
test('fs.delete folder', async function () {
|
||||
|
||||
const folder = root.with({ path: join(root.path, 'folder') });
|
||||
const file = root.with({ path: join(root.path, 'folder/file') });
|
||||
|
||||
await vscode.workspace.fs.createDirectory(folder);
|
||||
await vscode.workspace.fs.writeFile(file, Buffer.from('FOO'));
|
||||
|
||||
await vscode.workspace.fs.stat(folder);
|
||||
await vscode.workspace.fs.stat(file);
|
||||
|
||||
// ensure non empty folder cannot be deleted
|
||||
try {
|
||||
await vscode.workspace.fs.delete(folder, { recursive: false });
|
||||
assert.ok(false);
|
||||
} catch {
|
||||
await vscode.workspace.fs.stat(folder);
|
||||
await vscode.workspace.fs.stat(file);
|
||||
}
|
||||
|
||||
// ensure non empty folder cannot be deleted is DEFAULT
|
||||
try {
|
||||
await vscode.workspace.fs.delete(folder); // recursive: false as default
|
||||
assert.ok(false);
|
||||
} catch {
|
||||
await vscode.workspace.fs.stat(folder);
|
||||
await vscode.workspace.fs.stat(file);
|
||||
}
|
||||
|
||||
// delete non empty folder with recursive-flag
|
||||
await vscode.workspace.fs.delete(folder, { recursive: true });
|
||||
|
||||
// esnure folder/file are gone
|
||||
try {
|
||||
await vscode.workspace.fs.stat(folder);
|
||||
assert.ok(false);
|
||||
} catch {
|
||||
assert.ok(true);
|
||||
}
|
||||
try {
|
||||
await vscode.workspace.fs.stat(file);
|
||||
assert.ok(false);
|
||||
} catch {
|
||||
assert.ok(true);
|
||||
}
|
||||
});
|
||||
});
|
21
src/vs/vscode.proposed.d.ts
vendored
21
src/vs/vscode.proposed.d.ts
vendored
|
@ -1519,4 +1519,25 @@ declare module 'vscode' {
|
|||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
//#region Joh - read/write files of any scheme
|
||||
|
||||
export interface FileSystem {
|
||||
stat(uri: Uri): Thenable<FileStat>;
|
||||
readDirectory(uri: Uri): Thenable<[string, FileType][]>;
|
||||
createDirectory(uri: Uri): Thenable<void>;
|
||||
readFile(uri: Uri): Thenable<Uint8Array>;
|
||||
writeFile(uri: Uri, content: Uint8Array, options?: { create: boolean, overwrite: boolean }): Thenable<void>;
|
||||
delete(uri: Uri, options?: { recursive: boolean }): Thenable<void>;
|
||||
rename(source: Uri, target: Uri, options?: { overwrite: boolean }): Thenable<void>;
|
||||
copy(source: Uri, target: Uri, options?: { overwrite: boolean }): Thenable<void>;
|
||||
}
|
||||
|
||||
export namespace workspace {
|
||||
|
||||
export const fs: FileSystem;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions } from 'vs/platform/files/common/files';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions, FileOpenOptions, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostFileSystemShape, IExtHostContext, IFileChangeDto, MainContext, MainThreadFileSystemShape } from '../common/extHost.protocol';
|
||||
import { ResourceLabelFormatter, ILabelService } from 'vs/platform/label/common/label';
|
||||
|
@ -60,6 +60,56 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape {
|
|||
}
|
||||
fileProvider.$onFileSystemChange(changes);
|
||||
}
|
||||
|
||||
|
||||
// ---
|
||||
|
||||
async $stat(uri: UriComponents): Promise<IStat> {
|
||||
const stat = await this._fileService.resolve(URI.revive(uri), { resolveMetadata: true });
|
||||
return {
|
||||
ctime: 0,
|
||||
mtime: stat.mtime,
|
||||
size: stat.size,
|
||||
type: MainThreadFileSystem._getFileType(stat)
|
||||
};
|
||||
}
|
||||
|
||||
async $readdir(uri: UriComponents): Promise<[string, FileType][]> {
|
||||
const stat = await this._fileService.resolve(URI.revive(uri), { resolveMetadata: false });
|
||||
if (!stat.children) {
|
||||
throw new Error('not a folder');
|
||||
}
|
||||
return stat.children.map(child => [child.name, MainThreadFileSystem._getFileType(child)]);
|
||||
}
|
||||
|
||||
private static _getFileType(stat: IFileStat): FileType {
|
||||
return (stat.isDirectory ? FileType.Directory : FileType.File) + (stat.isSymbolicLink ? FileType.SymbolicLink : 0);
|
||||
}
|
||||
|
||||
async $readFile(uri: UriComponents): Promise<VSBuffer> {
|
||||
return (await this._fileService.readFile(URI.revive(uri))).value;
|
||||
}
|
||||
|
||||
async $writeFile(uri: UriComponents, content: VSBuffer, opts: FileWriteOptions): Promise<void> {
|
||||
//todo@joh honor opts
|
||||
await this._fileService.writeFile(URI.revive(uri), content, {});
|
||||
}
|
||||
|
||||
async $rename(source: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise<void> {
|
||||
this._fileService.move(URI.revive(source), URI.revive(target), opts.overwrite);
|
||||
}
|
||||
|
||||
async $copy(source: UriComponents, target: UriComponents, opts: FileOverwriteOptions): Promise<void> {
|
||||
this._fileService.copy(URI.revive(source), URI.revive(target), opts.overwrite);
|
||||
}
|
||||
|
||||
async $mkdir(uri: UriComponents): Promise<void> {
|
||||
this._fileService.createFolder(URI.revive(uri));
|
||||
}
|
||||
|
||||
async $delete(uri: UriComponents, opts: FileDeleteOptions): Promise<void> {
|
||||
this._fileService.del(URI.revive(uri), opts);
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteFileSystemProvider implements IFileSystemProvider {
|
||||
|
|
|
@ -594,6 +594,15 @@ export interface MainThreadFileSystemShape extends IDisposable {
|
|||
$registerResourceLabelFormatter(handle: number, formatter: ResourceLabelFormatter): void;
|
||||
$unregisterResourceLabelFormatter(handle: number): void;
|
||||
$onFileSystemChange(handle: number, resource: IFileChangeDto[]): void;
|
||||
|
||||
$stat(uri: UriComponents): Promise<files.IStat>;
|
||||
$readdir(resource: UriComponents): Promise<[string, files.FileType][]>;
|
||||
$readFile(resource: UriComponents): Promise<VSBuffer>;
|
||||
$writeFile(resource: UriComponents, content: VSBuffer, opts: files.FileWriteOptions): Promise<void>;
|
||||
$rename(resource: UriComponents, target: UriComponents, opts: files.FileOverwriteOptions): Promise<void>;
|
||||
$copy(resource: UriComponents, target: UriComponents, opts: files.FileOverwriteOptions): Promise<void>;
|
||||
$mkdir(resource: UriComponents): Promise<void>;
|
||||
$delete(resource: UriComponents, opts: files.FileDeleteOptions): Promise<void>;
|
||||
}
|
||||
|
||||
export interface MainThreadSearchShape extends IDisposable {
|
||||
|
|
|
@ -104,6 +104,36 @@ class FsLinkProvider {
|
|||
}
|
||||
}
|
||||
|
||||
class ConsumerFileSystem implements vscode.FileSystem {
|
||||
|
||||
constructor(private _proxy: MainThreadFileSystemShape) { }
|
||||
|
||||
stat(uri: vscode.Uri): Promise<vscode.FileStat> {
|
||||
return this._proxy.$stat(uri);
|
||||
}
|
||||
readDirectory(uri: vscode.Uri): Promise<[string, vscode.FileType][]> {
|
||||
return this._proxy.$readdir(uri);
|
||||
}
|
||||
createDirectory(uri: vscode.Uri): Promise<void> {
|
||||
return this._proxy.$mkdir(uri);
|
||||
}
|
||||
async readFile(uri: vscode.Uri): Promise<Uint8Array> {
|
||||
return (await this._proxy.$readFile(uri)).buffer;
|
||||
}
|
||||
writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean; overwrite: boolean; } = { create: true, overwrite: true }): Promise<void> {
|
||||
return this._proxy.$writeFile(uri, VSBuffer.wrap(content), options);
|
||||
}
|
||||
delete(uri: vscode.Uri, options: { recursive: boolean; } = { recursive: false }): Promise<void> {
|
||||
return this._proxy.$delete(uri, { ...options, useTrash: true }); //todo@joh useTrash
|
||||
}
|
||||
rename(oldUri: vscode.Uri, newUri: vscode.Uri, options: { overwrite: boolean; } = { overwrite: false }): Promise<void> {
|
||||
return this._proxy.$rename(oldUri, newUri, options);
|
||||
}
|
||||
copy(source: vscode.Uri, destination: vscode.Uri, options: { overwrite: boolean } = { overwrite: false }): Promise<void> {
|
||||
return this._proxy.$copy(source, destination, options);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
|
||||
private readonly _proxy: MainThreadFileSystemShape;
|
||||
|
@ -115,6 +145,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
|||
private _linkProviderRegistration: IDisposable;
|
||||
private _handlePool: number = 0;
|
||||
|
||||
readonly fileSystem: vscode.FileSystem;
|
||||
|
||||
constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadFileSystem);
|
||||
this._usedSchemes.add(Schemas.file);
|
||||
|
@ -127,6 +159,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
|||
this._usedSchemes.add(Schemas.mailto);
|
||||
this._usedSchemes.add(Schemas.data);
|
||||
this._usedSchemes.add(Schemas.command);
|
||||
|
||||
this.fileSystem = new ConsumerFileSystem(this._proxy);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
|
|
@ -678,6 +678,10 @@ export function createApiFactory(
|
|||
registerFileSystemProvider(scheme, provider, options) {
|
||||
return extHostFileSystem.registerFileSystemProvider(scheme, provider, options);
|
||||
},
|
||||
get fs() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostFileSystem.fileSystem;
|
||||
},
|
||||
registerFileSearchProvider: proposedApiFunction(extension, (scheme: string, provider: vscode.FileSearchProvider) => {
|
||||
return extHostSearch.registerFileSearchProvider(scheme, provider);
|
||||
}),
|
||||
|
|
Loading…
Reference in a new issue