mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-06 01:25:38 -05:00
Merge from vscode fc10e26ea50f82cdd84e9141491357922e6f5fba (#4639)
This commit is contained in:
30
src/vs/workbench/services/broadcast/common/broadcast.ts
Normal file
30
src/vs/workbench/services/broadcast/common/broadcast.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export const IBroadcastService = createDecorator<IBroadcastService>('broadcastService');
|
||||
|
||||
export interface IBroadcast {
|
||||
channel: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export interface IBroadcastService {
|
||||
_serviceBrand: any;
|
||||
|
||||
onBroadcast: Event<IBroadcast>;
|
||||
|
||||
broadcast(b: IBroadcast): void;
|
||||
}
|
||||
|
||||
export class NullBroadcastService implements IBroadcastService {
|
||||
_serviceBrand: any;
|
||||
onBroadcast: Event<IBroadcast> = Event.None;
|
||||
broadcast(_b: IBroadcast): void {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,13 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
export const IBroadcastService = createDecorator<IBroadcastService>('broadcastService');
|
||||
|
||||
export interface IBroadcast {
|
||||
channel: string;
|
||||
payload: any;
|
||||
}
|
||||
|
||||
export interface IBroadcastService {
|
||||
_serviceBrand: any;
|
||||
|
||||
onBroadcast: Event<IBroadcast>;
|
||||
|
||||
broadcast(b: IBroadcast): void;
|
||||
}
|
||||
import { IBroadcastService, IBroadcast } from 'vs/workbench/services/broadcast/common/broadcast';
|
||||
|
||||
export class BroadcastService extends Disposable implements IBroadcastService {
|
||||
_serviceBrand: any;
|
||||
@@ -63,4 +48,4 @@ export class BroadcastService extends Disposable implements IBroadcastService {
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IBroadcastService, BroadcastService, true);
|
||||
registerSingleton(IBroadcastService, BroadcastService, true);
|
||||
|
||||
@@ -487,7 +487,7 @@ export class ConfigurationEditingService {
|
||||
return { key, jsonPath, value: config.value, resource: resource || undefined, target };
|
||||
}
|
||||
|
||||
private isWorkspaceConfigurationResource(resource: URI | null | undefined): boolean {
|
||||
private isWorkspaceConfigurationResource(resource: URI | null): boolean {
|
||||
const workspace = this.contextService.getWorkspace();
|
||||
return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic
|
||||
|
||||
const [type, name] = variable.split(':', 2);
|
||||
|
||||
let result: string | undefined | null;
|
||||
let result: string | undefined;
|
||||
|
||||
switch (type) {
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ class DecorationProviderWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
private _fetchData(uri: URI): IDecorationData | undefined | null {
|
||||
private _fetchData(uri: URI): IDecorationData | null {
|
||||
|
||||
// check for pending request and cancel it
|
||||
const pendingRequest = this.data.get(uri.toString());
|
||||
@@ -319,11 +319,11 @@ class DecorationProviderWrapper {
|
||||
}));
|
||||
|
||||
this.data.set(uri.toString(), request);
|
||||
return undefined;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private _keepItem(uri: URI, data: IDecorationData | null | undefined): IDecorationData | null {
|
||||
private _keepItem(uri: URI, data: IDecorationData | undefined): IDecorationData | null {
|
||||
const deco = data ? data : null;
|
||||
const old = this.data.set(uri.toString(), deco);
|
||||
if (deco || old) {
|
||||
|
||||
@@ -36,7 +36,7 @@ export interface IOpenEditorOverride {
|
||||
* If defined, will prevent the opening of an editor and replace the resulting
|
||||
* promise with the provided promise for the openEditor() call.
|
||||
*/
|
||||
override?: Promise<IEditor | null | undefined>;
|
||||
override?: Promise<IEditor | null>;
|
||||
}
|
||||
|
||||
export interface IVisibleEditor extends IEditor {
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
|
||||
export interface IExtensionDevOptions {
|
||||
readonly isExtensionDevHost: boolean;
|
||||
readonly isExtensionDevDebug: boolean;
|
||||
readonly isExtensionDevDebugBrk: boolean;
|
||||
readonly isExtensionDevTestFromCli: boolean;
|
||||
}
|
||||
|
||||
export function parseExtensionDevOptions(environmentService: IEnvironmentService): IExtensionDevOptions {
|
||||
// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
|
||||
let isExtensionDevHost = environmentService.isExtensionDevelopment;
|
||||
const extDevLoc = environmentService.extensionDevelopmentLocationURI;
|
||||
const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file;
|
||||
let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number';
|
||||
let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break;
|
||||
let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break;
|
||||
return {
|
||||
isExtensionDevHost,
|
||||
isExtensionDevDebug,
|
||||
isExtensionDevDebugBrk,
|
||||
isExtensionDevTestFromCli,
|
||||
};
|
||||
}
|
||||
@@ -218,6 +218,13 @@ export interface IExtensionService extends ICpuProfilerTarget {
|
||||
* Stops the extension host.
|
||||
*/
|
||||
stopExtensionHost(): void;
|
||||
|
||||
_logOrShowMessage(severity: Severity, msg: string): void;
|
||||
_activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise<void>;
|
||||
_onWillActivateExtension(extensionId: ExtensionIdentifier): void;
|
||||
_onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void;
|
||||
_onExtensionRuntimeError(extensionId: ExtensionIdentifier, err: Error): void;
|
||||
_onExtensionHostExit(code: number): void;
|
||||
}
|
||||
|
||||
export interface ICpuProfilerTarget {
|
||||
@@ -278,4 +285,10 @@ export class NullExtensionService implements IExtensionService {
|
||||
stopExtensionHost(): void { }
|
||||
canAddExtension(): boolean { return false; }
|
||||
canRemoveExtension(): boolean { return false; }
|
||||
}
|
||||
_logOrShowMessage(_severity: Severity, _msg: string): void { }
|
||||
_activateById(_extensionId: ExtensionIdentifier, _activationEvent: string): Promise<void> { return Promise.resolve(); }
|
||||
_onWillActivateExtension(_extensionId: ExtensionIdentifier): void { }
|
||||
_onDidActivateExtension(_extensionId: ExtensionIdentifier, _startup: boolean, _codeLoadingTime: number, _activateCallTime: number, _activateResolvedTime: number, _activationEvent: string): void { }
|
||||
_onExtensionRuntimeError(_extensionId: ExtensionIdentifier, _err: Error): void { }
|
||||
_onExtensionHostExit(code: number): void { }
|
||||
}
|
||||
|
||||
@@ -273,4 +273,4 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler);
|
||||
registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler);
|
||||
@@ -12,16 +12,15 @@ import { timeout } from 'vs/base/common/async';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { isEqual } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console';
|
||||
import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console';
|
||||
import { findFreePort, randomPort } from 'vs/base/node/ports';
|
||||
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { PersistentProtocol, generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService';
|
||||
import { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/common/broadcast';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
@@ -36,6 +35,7 @@ import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/node/extensionHostProtocol';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { parseExtensionDevOptions } from '../common/extensionDevOptions';
|
||||
|
||||
export interface IExtensionHostStarter {
|
||||
readonly onCrashed: Event<[number, string | null]>;
|
||||
@@ -44,28 +44,6 @@ export interface IExtensionHostStarter {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface IExtensionDevOptions {
|
||||
readonly isExtensionDevHost: boolean;
|
||||
readonly isExtensionDevDebug: boolean;
|
||||
readonly isExtensionDevDebugBrk: boolean;
|
||||
readonly isExtensionDevTestFromCli: boolean;
|
||||
}
|
||||
export function parseExtensionDevOptions(environmentService: IEnvironmentService): IExtensionDevOptions {
|
||||
// handle extension host lifecycle a bit special when we know we are developing an extension that runs inside
|
||||
let isExtensionDevHost = environmentService.isExtensionDevelopment;
|
||||
const extDevLoc = environmentService.extensionDevelopmentLocationURI;
|
||||
const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file;
|
||||
let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number';
|
||||
let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break;
|
||||
let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break;
|
||||
return {
|
||||
isExtensionDevHost,
|
||||
isExtensionDevDebug,
|
||||
isExtensionDevDebugBrk,
|
||||
isExtensionDevTestFromCli,
|
||||
};
|
||||
}
|
||||
|
||||
export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
private readonly _onCrashed: Emitter<[number, string]> = new Emitter<[number, string]>();
|
||||
@@ -87,7 +65,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
private _inspectPort: number;
|
||||
private _extensionHostProcess: ChildProcess | null;
|
||||
private _extensionHostConnection: Socket | null;
|
||||
private _messageProtocol: Promise<IMessagePassingProtocol> | null;
|
||||
private _messageProtocol: Promise<PersistentProtocol> | null;
|
||||
|
||||
constructor(
|
||||
private readonly _autoStart: boolean,
|
||||
@@ -341,9 +319,9 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
});
|
||||
}
|
||||
|
||||
private _tryExtHostHandshake(): Promise<IMessagePassingProtocol> {
|
||||
private _tryExtHostHandshake(): Promise<PersistentProtocol> {
|
||||
|
||||
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
|
||||
return new Promise<PersistentProtocol>((resolve, reject) => {
|
||||
|
||||
// Wait for the extension host to connect to our named pipe
|
||||
// and wrap the socket in the message passing protocol
|
||||
@@ -373,7 +351,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
|
||||
// 1) wait for the incoming `ready` event and send the initialization data.
|
||||
// 2) wait for the incoming `initialized` event.
|
||||
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
|
||||
return new Promise<PersistentProtocol>((resolve, reject) => {
|
||||
|
||||
let timeoutHandle: NodeJS.Timer;
|
||||
const installTimeoutCheck = () => {
|
||||
@@ -549,6 +527,8 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter {
|
||||
// (graceful termination)
|
||||
protocol.send(createMessageOfType(MessageType.Terminate));
|
||||
|
||||
protocol.dispose();
|
||||
|
||||
// Give the extension host 10s, after which we will
|
||||
// try to kill the process and release any resources
|
||||
setTimeout(() => this._cleanResources(), 10 * 1000);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import { ipcRenderer as ipc } from 'electron';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { Barrier, runWhenIdle } from 'vs/base/common/async';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
@@ -840,6 +841,10 @@ export class ExtensionService extends Disposable implements IExtensionService {
|
||||
this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err);
|
||||
this._onDidChangeExtensionsStatus.fire([extensionId]);
|
||||
}
|
||||
|
||||
public _onExtensionHostExit(code: number): void {
|
||||
ipc.send('vscode:exit', code);
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IExtensionService, ExtensionService);
|
||||
|
||||
@@ -450,7 +450,7 @@ export class ExtensionScannerInput {
|
||||
|
||||
constructor(
|
||||
public readonly ourVersion: string,
|
||||
public readonly commit: string | null | undefined,
|
||||
public readonly commit: string | undefined,
|
||||
public readonly locale: string | undefined,
|
||||
public readonly devMode: boolean,
|
||||
public readonly absoluteFolderPath: string,
|
||||
|
||||
@@ -217,19 +217,6 @@ export class FileService extends Disposable implements ILegacyFileService {
|
||||
return resource.scheme === Schemas.file;
|
||||
}
|
||||
|
||||
resolveFile(resource: uri, options?: IResolveFileOptions): Promise<IFileStat> {
|
||||
return this.resolve(resource, options);
|
||||
}
|
||||
|
||||
resolveFiles(toResolve: { resource: uri, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]> {
|
||||
return Promise.all(toResolve.map(resourceAndOptions => this.resolve(resourceAndOptions.resource, resourceAndOptions.options)
|
||||
.then(stat => ({ stat, success: true }), error => ({ stat: undefined, success: false }))));
|
||||
}
|
||||
|
||||
existsFile(resource: uri): Promise<boolean> {
|
||||
return this.resolveFile(resource).then(() => true, () => false);
|
||||
}
|
||||
|
||||
resolveContent(resource: uri, options?: IResolveContentOptions): Promise<IContent> {
|
||||
return this.resolveStreamContent(resource, options).then(streamContent => {
|
||||
return new Promise<IContent>((resolve, reject) => {
|
||||
@@ -1096,6 +1083,15 @@ export class FileService extends Disposable implements ILegacyFileService {
|
||||
|
||||
// Tests only
|
||||
|
||||
resolveFile(resource: uri, options?: IResolveFileOptions): Promise<IFileStat> {
|
||||
return this.resolve(resource, options);
|
||||
}
|
||||
|
||||
resolveFiles(toResolve: { resource: uri, options?: IResolveFileOptions }[]): Promise<IResolveFileResult[]> {
|
||||
return Promise.all(toResolve.map(resourceAndOptions => this.resolve(resourceAndOptions.resource, resourceAndOptions.options)
|
||||
.then(stat => ({ stat, success: true }), error => ({ stat: undefined, success: false }))));
|
||||
}
|
||||
|
||||
createFolder(resource: uri): Promise<IFileStat> {
|
||||
|
||||
// 1.) Create folder
|
||||
@@ -1112,6 +1108,10 @@ export class FileService extends Disposable implements ILegacyFileService {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
existsFile(resource: uri): Promise<boolean> {
|
||||
return this.resolveFile(resource).then(() => true, () => false);
|
||||
}
|
||||
}
|
||||
|
||||
function etag(stat: fs.Stats): string;
|
||||
|
||||
@@ -223,14 +223,6 @@ export class RemoteFileService extends FileService {
|
||||
});
|
||||
}
|
||||
|
||||
existsFile(resource: URI): Promise<boolean> {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
return super.existsFile(resource);
|
||||
} else {
|
||||
return this.resolveFile(resource).then(_data => true, _err => false);
|
||||
}
|
||||
}
|
||||
|
||||
resolveFile(resource: URI, options?: IResolveFileOptions): Promise<IFileStat> {
|
||||
if (resource.scheme === Schemas.file) {
|
||||
return super.resolveFile(resource, options);
|
||||
|
||||
@@ -12,7 +12,7 @@ export class WatcherChannel implements IServerChannel {
|
||||
|
||||
constructor(private service: IWatcherService) { }
|
||||
|
||||
listen(_, event: string, arg?: any): Event<any> {
|
||||
listen(_: unknown, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
case 'watch': return this.service.watch(arg);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export class WatcherChannel implements IServerChannel {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(_, command: string, arg?: any): Promise<any> {
|
||||
call(_: unknown, command: string, arg?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'setRoots': return this.service.setRoots(arg);
|
||||
case 'setVerboseLogging': return this.service.setVerboseLogging(arg);
|
||||
|
||||
@@ -12,7 +12,7 @@ export class WatcherChannel implements IServerChannel {
|
||||
|
||||
constructor(private service: IWatcherService) { }
|
||||
|
||||
listen(_, event: string, arg?: any): Event<any> {
|
||||
listen(_: unknown, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
case 'watch': return this.service.watch(arg);
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export class WatcherChannel implements IServerChannel {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(_, command: string, arg?: any): Promise<any> {
|
||||
call(_: unknown, command: string, arg?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'setRoots': return this.service.setRoots(arg);
|
||||
case 'setVerboseLogging': return this.service.setVerboseLogging(arg);
|
||||
|
||||
@@ -13,7 +13,6 @@ import { URI as uri } from 'vs/base/common/uri';
|
||||
import * as uuid from 'vs/base/common/uuid';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import * as encodingLib from 'vs/base/node/encoding';
|
||||
import * as utils from 'vs/workbench/services/files/test/electron-browser/utils';
|
||||
import { TestEnvironmentService, TestContextService, TestTextResourceConfigurationService, TestLifecycleService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
@@ -407,92 +406,6 @@ suite('FileService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('deleteFile', () => {
|
||||
let event: FileOperationEvent;
|
||||
const toDispose = service.onAfterOperation(e => {
|
||||
event = e;
|
||||
});
|
||||
|
||||
const resource = uri.file(path.join(testDir, 'deep', 'conway.js'));
|
||||
return service.resolveFile(resource).then(source => {
|
||||
return service.del(source.resource).then(() => {
|
||||
assert.equal(fs.existsSync(source.resource.fsPath), false);
|
||||
|
||||
assert.ok(event);
|
||||
assert.equal(event.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event.operation, FileOperation.DELETE);
|
||||
toDispose.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('deleteFolder (recursive)', function () {
|
||||
let event: FileOperationEvent;
|
||||
const toDispose = service.onAfterOperation(e => {
|
||||
event = e;
|
||||
});
|
||||
|
||||
const resource = uri.file(path.join(testDir, 'deep'));
|
||||
return service.resolveFile(resource).then(source => {
|
||||
return service.del(source.resource, { recursive: true }).then(() => {
|
||||
assert.equal(fs.existsSync(source.resource.fsPath), false);
|
||||
|
||||
assert.ok(event);
|
||||
assert.equal(event.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event.operation, FileOperation.DELETE);
|
||||
toDispose.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('deleteFolder (non recursive)', function () {
|
||||
const resource = uri.file(path.join(testDir, 'deep'));
|
||||
return service.resolveFile(resource).then(source => {
|
||||
return service.del(source.resource).then(() => {
|
||||
return Promise.reject(new Error('Unexpected'));
|
||||
}, error => {
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('resolveFile', () => {
|
||||
return service.resolveFile(uri.file(testDir), { resolveTo: [uri.file(path.join(testDir, 'deep'))] }).then(r => {
|
||||
assert.equal(r.children!.length, 8);
|
||||
|
||||
const deep = utils.getByName(r, 'deep')!;
|
||||
assert.equal(deep.children!.length, 4);
|
||||
});
|
||||
});
|
||||
|
||||
test('resolveFiles', () => {
|
||||
return service.resolveFiles([
|
||||
{ resource: uri.file(testDir), options: { resolveTo: [uri.file(path.join(testDir, 'deep'))] } },
|
||||
{ resource: uri.file(path.join(testDir, 'deep')) }
|
||||
]).then(res => {
|
||||
const r1 = res[0].stat!;
|
||||
|
||||
assert.equal(r1.children!.length, 8);
|
||||
|
||||
const deep = utils.getByName(r1, 'deep')!;
|
||||
assert.equal(deep.children!.length, 4);
|
||||
|
||||
const r2 = res[1].stat!;
|
||||
assert.equal(r2.children!.length, 4);
|
||||
assert.equal(r2.name, 'deep');
|
||||
});
|
||||
});
|
||||
|
||||
test('existsFile', () => {
|
||||
return service.existsFile(uri.file(testDir)).then((exists) => {
|
||||
assert.equal(exists, true);
|
||||
|
||||
return service.existsFile(uri.file(testDir + 'something')).then((exists) => {
|
||||
assert.equal(exists, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('updateContent', () => {
|
||||
const resource = uri.file(path.join(testDir, 'small.txt'));
|
||||
|
||||
|
||||
@@ -4,13 +4,17 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 } 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 } 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';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { isAbsolutePath, dirname, basename, joinPath, isEqual } from 'vs/base/common/resources';
|
||||
import { localize } from 'vs/nls';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||
import { getBaseLabel } from 'vs/base/common/labels';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class FileService2 extends Disposable implements IFileService {
|
||||
|
||||
@@ -29,6 +33,10 @@ export class FileService2 extends Disposable implements IFileService {
|
||||
|
||||
_serviceBrand: ServiceIdentifier<any>;
|
||||
|
||||
constructor(@ILogService private logService: ILogService) {
|
||||
super();
|
||||
}
|
||||
|
||||
//#region File System Provider
|
||||
|
||||
private _onDidChangeFileSystemProviderRegistrations: Emitter<IFileSystemProviderRegistrationEvent> = this._register(new Emitter<IFileSystemProviderRegistrationEvent>());
|
||||
@@ -69,7 +77,7 @@ export class FileService2 extends Disposable implements IFileService {
|
||||
]);
|
||||
}
|
||||
|
||||
activateProvider(scheme: string): Promise<void> {
|
||||
async activateProvider(scheme: string): Promise<void> {
|
||||
|
||||
// Emit an event that we are about to activate a provider with the given scheme.
|
||||
// Listeners can participate in the activation by registering a provider for it.
|
||||
@@ -89,7 +97,7 @@ export class FileService2 extends Disposable implements IFileService {
|
||||
|
||||
// If the provider is not yet there, make sure to join on the listeners assuming
|
||||
// that it takes a bit longer to register the file system provider.
|
||||
return Promise.all(joiners).then(() => undefined);
|
||||
await Promise.all(joiners);
|
||||
}
|
||||
|
||||
canHandleResource(resource: URI): boolean {
|
||||
@@ -129,16 +137,130 @@ export class FileService2 extends Disposable implements IFileService {
|
||||
|
||||
//#region File Metadata Resolving
|
||||
|
||||
resolveFile(resource: URI, options?: IResolveFileOptions): Promise<IFileStat> {
|
||||
return this._impl.resolveFile(resource, options);
|
||||
async resolveFile(resource: URI, options?: IResolveFileOptions): Promise<IFileStat> {
|
||||
try {
|
||||
return await this.doResolveFile(resource, options);
|
||||
} catch (error) {
|
||||
|
||||
// Specially handle file not found case as file operation result
|
||||
if (toFileSystemProviderErrorCode(error) === FileSystemProviderErrorCode.FileNotFound) {
|
||||
throw new FileOperationError(
|
||||
localize('fileNotFoundError', "File not found ({0})", resource.toString(true)),
|
||||
FileOperationResult.FILE_NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
// Bubble up any other error as is
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise<IResolveFileResult[]> {
|
||||
return this._impl.resolveFiles(toResolve);
|
||||
private async doResolveFile(resource: URI, options?: IResolveFileOptions): Promise<IFileStat> {
|
||||
const provider = await this.withProvider(resource);
|
||||
|
||||
// leverage a trie to check for recursive resolving
|
||||
const to = options && options.resolveTo;
|
||||
const trie = TernarySearchTree.forPaths<true>();
|
||||
trie.set(resource.toString(), true);
|
||||
if (isNonEmptyArray(to)) {
|
||||
to.forEach(uri => trie.set(uri.toString(), true));
|
||||
}
|
||||
|
||||
const stat = await provider.stat(resource);
|
||||
|
||||
return await this.toFileStat(provider, resource, stat, undefined, (stat, siblings) => {
|
||||
|
||||
// check for recursive resolving
|
||||
if (Boolean(trie.findSuperstr(stat.resource.toString()) || trie.get(stat.resource.toString()))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check for resolving single child folders
|
||||
if (stat.isDirectory && options && options.resolveSingleChildDescendants) {
|
||||
return siblings === 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
existsFile(resource: URI): Promise<boolean> {
|
||||
return this._impl.existsFile(resource);
|
||||
private async toFileStat(provider: IFileSystemProvider, resource: URI, stat: IStat, siblings: number | undefined, recurse: (stat: IFileStat, siblings?: number) => boolean): Promise<IFileStat> {
|
||||
|
||||
// convert to file stat
|
||||
const fileStat: IFileStat = {
|
||||
resource,
|
||||
name: getBaseLabel(resource),
|
||||
isDirectory: (stat.type & FileType.Directory) !== 0,
|
||||
isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0,
|
||||
isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly),
|
||||
mtime: stat.mtime,
|
||||
size: stat.size,
|
||||
etag: stat.mtime.toString(29) + stat.size.toString(31),
|
||||
};
|
||||
|
||||
// check to recurse for directories
|
||||
if (fileStat.isDirectory && recurse(fileStat, siblings)) {
|
||||
try {
|
||||
const entries = await provider.readdir(resource);
|
||||
|
||||
fileStat.children = await Promise.all(entries.map(async entry => {
|
||||
const childResource = joinPath(resource, entry[0]);
|
||||
const childStat = await provider.stat(childResource);
|
||||
|
||||
return this.toFileStat(provider, childResource, childStat, entries.length, recurse);
|
||||
}));
|
||||
} catch (error) {
|
||||
this.logService.trace(error);
|
||||
|
||||
fileStat.children = []; // gracefully handle errors, we may not have permissions to read
|
||||
}
|
||||
|
||||
return fileStat;
|
||||
}
|
||||
|
||||
return Promise.resolve(fileStat);
|
||||
}
|
||||
|
||||
async resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): Promise<IResolveFileResult[]> {
|
||||
|
||||
// soft-groupBy, keep order, don't rearrange/merge groups
|
||||
const groups: Array<typeof toResolve> = [];
|
||||
let group: typeof toResolve | undefined;
|
||||
for (const request of toResolve) {
|
||||
if (!group || group[0].resource.scheme !== request.resource.scheme) {
|
||||
group = [];
|
||||
groups.push(group);
|
||||
}
|
||||
|
||||
group.push(request);
|
||||
}
|
||||
|
||||
// resolve files
|
||||
const result: IResolveFileResult[] = [];
|
||||
for (const group of groups) {
|
||||
for (const groupEntry of group) {
|
||||
try {
|
||||
const stat = await this.doResolveFile(groupEntry.resource, groupEntry.options);
|
||||
result.push({ stat, success: true });
|
||||
} catch (error) {
|
||||
this.logService.trace(error);
|
||||
|
||||
result.push({ stat: undefined, success: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async existsFile(resource: URI): Promise<boolean> {
|
||||
try {
|
||||
await this.resolveFile(resource);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -200,11 +322,11 @@ export class FileService2 extends Disposable implements IFileService {
|
||||
}
|
||||
|
||||
break; // we have hit a directory that exists -> good
|
||||
} catch (e) {
|
||||
} catch (error) {
|
||||
|
||||
// Bubble up any other error that is not file not found
|
||||
if (toFileSystemProviderErrorCode(e) !== FileSystemProviderErrorCode.FileNotFound) {
|
||||
throw e;
|
||||
if (toFileSystemProviderErrorCode(error) !== FileSystemProviderErrorCode.FileNotFound) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Upon error, remember directories that need to be created
|
||||
@@ -222,8 +344,18 @@ export class FileService2 extends Disposable implements IFileService {
|
||||
}
|
||||
}
|
||||
|
||||
del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise<void> {
|
||||
return this._impl.del(resource, options);
|
||||
async del(resource: URI, options?: { useTrash?: boolean; recursive?: boolean; }): Promise<void> {
|
||||
if (options && options.useTrash) {
|
||||
return this._impl.del(resource, options); //TODO@ben this is https://github.com/Microsoft/vscode/issues/48259
|
||||
}
|
||||
|
||||
const provider = this.throwIfFileSystemIsReadonly(await this.withProvider(resource));
|
||||
|
||||
// Delete through provider
|
||||
await provider.delete(resource, { recursive: !!(options && options.recursive) });
|
||||
|
||||
// Events
|
||||
this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE));
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -256,4 +388,4 @@ export class FileService2 extends Disposable implements IFileService {
|
||||
//#endregion
|
||||
}
|
||||
|
||||
registerSingleton(IFileService, FileService2);
|
||||
registerSingleton(IFileService, FileService2);
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { mkdir } from 'fs';
|
||||
import { tmpdir } from 'os';
|
||||
import { promisify } from 'util';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileSystemProvider, FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, FileType, FileDeleteOptions, FileOverwriteOptions, FileWriteOptions, FileOpenOptions, FileSystemProviderErrorCode, createFileSystemProviderError, FileSystemProviderError } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { statLink } from 'vs/base/node/pfs';
|
||||
import { statLink, readdir, unlink, del } from 'vs/base/node/pfs';
|
||||
import { normalize } from 'vs/base/common/path';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
|
||||
export class DiskFileSystemProvider extends Disposable implements IFileSystemProvider {
|
||||
|
||||
@@ -54,8 +56,22 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
|
||||
}
|
||||
}
|
||||
|
||||
readdir(resource: URI): Promise<[string, FileType][]> {
|
||||
throw new Error('Method not implemented.');
|
||||
async readdir(resource: URI): Promise<[string, FileType][]> {
|
||||
try {
|
||||
const children = await readdir(this.toFilePath(resource));
|
||||
|
||||
const result: [string, FileType][] = [];
|
||||
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]);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -90,12 +106,30 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
|
||||
|
||||
//#region Move/Copy/Delete/Create Folder
|
||||
|
||||
mkdir(resource: URI): Promise<void> {
|
||||
return promisify(mkdir)(resource.fsPath);
|
||||
async mkdir(resource: URI): Promise<void> {
|
||||
try {
|
||||
await promisify(mkdir)(this.toFilePath(resource));
|
||||
} catch (error) {
|
||||
throw this.toFileSystemProviderError(error);
|
||||
}
|
||||
}
|
||||
|
||||
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
|
||||
try {
|
||||
const filePath = this.toFilePath(resource);
|
||||
|
||||
if (opts.recursive) {
|
||||
await del(filePath, tmpdir());
|
||||
} else {
|
||||
await unlink(filePath);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return Promise.resolve(); // tolerate that the file might not exist
|
||||
}
|
||||
|
||||
throw this.toFileSystemProviderError(error);
|
||||
}
|
||||
}
|
||||
|
||||
rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
|
||||
@@ -147,4 +181,4 @@ export class DiskFileSystemProvider extends Disposable implements IFileSystemPro
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { IFileSystemProviderRegistrationEvent } from 'vs/platform/files/common/files';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { NullFileSystemProvider } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
|
||||
suite('File Service 2', () => {
|
||||
|
||||
test('provider registration', async () => {
|
||||
const service = new FileService2();
|
||||
const service = new FileService2(new NullLogService());
|
||||
|
||||
assert.equal(service.canHandleResource(URI.parse('test://foo/bar')), false);
|
||||
|
||||
@@ -56,4 +57,4 @@ suite('File Service 2', () => {
|
||||
assert.equal(registrations[1].scheme, 'test');
|
||||
assert.equal(registrations[1].added, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,17 +10,32 @@ import { Schemas } from 'vs/base/common/network';
|
||||
import { DiskFileSystemProvider } from 'vs/workbench/services/files2/node/diskFileSystemProvider';
|
||||
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { join } from 'vs/base/common/path';
|
||||
import { join, basename } from 'vs/base/common/path';
|
||||
import { getPathFromAmdModule } from 'vs/base/common/amd';
|
||||
import { copy, del } from 'vs/base/node/pfs';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { existsSync } from 'fs';
|
||||
import { FileOperation, FileOperationEvent } from 'vs/platform/files/common/files';
|
||||
import { FileOperation, FileOperationEvent, IFileStat } from 'vs/platform/files/common/files';
|
||||
import { FileService } from 'vs/workbench/services/files/node/fileService';
|
||||
import { TestContextService, TestEnvironmentService, TestTextResourceConfigurationService, TestLifecycleService, TestStorageService } from 'vs/workbench/test/workbenchTestServices';
|
||||
import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
|
||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
|
||||
function getByName(root: IFileStat, name: string): IFileStat | null {
|
||||
if (root.children === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const child of root.children) {
|
||||
if (child.name === name) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
suite('Disk File Service', () => {
|
||||
|
||||
@@ -30,7 +45,7 @@ suite('Disk File Service', () => {
|
||||
let testDir: string;
|
||||
|
||||
setup(async () => {
|
||||
service = new FileService2();
|
||||
service = new FileService2(new NullLogService());
|
||||
service.registerProvider(Schemas.file, new DiskFileSystemProvider());
|
||||
|
||||
const id = generateUuid();
|
||||
@@ -49,50 +64,240 @@ suite('Disk File Service', () => {
|
||||
});
|
||||
|
||||
test('createFolder', async () => {
|
||||
let event: FileOperationEvent;
|
||||
let event: FileOperationEvent | undefined;
|
||||
const toDispose = service.onAfterOperation(e => {
|
||||
event = e;
|
||||
});
|
||||
|
||||
return service.resolveFile(URI.file(testDir)).then(parent => {
|
||||
const resource = URI.file(join(parent.resource.fsPath, 'newFolder'));
|
||||
const parent = await service.resolveFile(URI.file(testDir));
|
||||
|
||||
return service.createFolder(resource).then(f => {
|
||||
assert.equal(f.name, 'newFolder');
|
||||
assert.equal(existsSync(f.resource.fsPath), true);
|
||||
const resource = URI.file(join(parent.resource.fsPath, 'newFolder'));
|
||||
|
||||
assert.ok(event);
|
||||
assert.equal(event.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event.operation, FileOperation.CREATE);
|
||||
assert.equal(event.target!.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event.target!.isDirectory, true);
|
||||
toDispose.dispose();
|
||||
});
|
||||
});
|
||||
const folder = await service.createFolder(resource);
|
||||
|
||||
assert.equal(folder.name, 'newFolder');
|
||||
assert.equal(existsSync(folder.resource.fsPath), true);
|
||||
|
||||
assert.ok(event);
|
||||
assert.equal(event!.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event!.operation, FileOperation.CREATE);
|
||||
assert.equal(event!.target!.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event!.target!.isDirectory, true);
|
||||
|
||||
toDispose.dispose();
|
||||
});
|
||||
|
||||
test('createFolder: creating multiple folders at once', function () {
|
||||
test('createFolder: creating multiple folders at once', async function () {
|
||||
let event: FileOperationEvent;
|
||||
const toDispose = service.onAfterOperation(e => {
|
||||
event = e;
|
||||
});
|
||||
|
||||
const multiFolderPaths = ['a', 'couple', 'of', 'folders'];
|
||||
return service.resolveFile(URI.file(testDir)).then(parent => {
|
||||
const resource = URI.file(join(parent.resource.fsPath, ...multiFolderPaths));
|
||||
const parent = await service.resolveFile(URI.file(testDir));
|
||||
|
||||
return service.createFolder(resource).then(f => {
|
||||
const lastFolderName = multiFolderPaths[multiFolderPaths.length - 1];
|
||||
assert.equal(f.name, lastFolderName);
|
||||
assert.equal(existsSync(f.resource.fsPath), true);
|
||||
const resource = URI.file(join(parent.resource.fsPath, ...multiFolderPaths));
|
||||
|
||||
assert.ok(event);
|
||||
assert.equal(event.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event.operation, FileOperation.CREATE);
|
||||
assert.equal(event.target!.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event.target!.isDirectory, true);
|
||||
toDispose.dispose();
|
||||
const folder = await service.createFolder(resource);
|
||||
|
||||
const lastFolderName = multiFolderPaths[multiFolderPaths.length - 1];
|
||||
assert.equal(folder.name, lastFolderName);
|
||||
assert.equal(existsSync(folder.resource.fsPath), true);
|
||||
|
||||
assert.ok(event!);
|
||||
assert.equal(event!.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event!.operation, FileOperation.CREATE);
|
||||
assert.equal(event!.target!.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event!.target!.isDirectory, true);
|
||||
|
||||
toDispose.dispose();
|
||||
});
|
||||
|
||||
test('existsFile', async () => {
|
||||
let exists = await service.existsFile(URI.file(testDir));
|
||||
assert.equal(exists, true);
|
||||
|
||||
exists = await service.existsFile(URI.file(testDir + 'something'));
|
||||
assert.equal(exists, false);
|
||||
});
|
||||
|
||||
test('resolveFile', async () => {
|
||||
const resolved = await service.resolveFile(URI.file(testDir), { resolveTo: [URI.file(join(testDir, 'deep'))] });
|
||||
assert.equal(resolved.children!.length, 8);
|
||||
|
||||
const deep = (getByName(resolved, 'deep')!);
|
||||
assert.equal(deep.children!.length, 4);
|
||||
});
|
||||
|
||||
test('resolveFile - directory', async () => {
|
||||
const testsElements = ['examples', 'other', 'index.html', 'site.css'];
|
||||
|
||||
const result = await service.resolveFile(URI.file(getPathFromAmdModule(require, './fixtures/resolver')));
|
||||
|
||||
assert.ok(result);
|
||||
assert.ok(result.children);
|
||||
assert.ok(result.children!.length > 0);
|
||||
assert.ok(result!.isDirectory);
|
||||
assert.equal(result.children!.length, testsElements.length);
|
||||
|
||||
assert.ok(result.children!.every((entry) => {
|
||||
return testsElements.some((name) => {
|
||||
return basename(entry.resource.fsPath) === name;
|
||||
});
|
||||
}));
|
||||
|
||||
result.children!.forEach((value) => {
|
||||
assert.ok(basename(value.resource.fsPath));
|
||||
if (['examples', 'other'].indexOf(basename(value.resource.fsPath)) >= 0) {
|
||||
assert.ok(value.isDirectory);
|
||||
} else if (basename(value.resource.fsPath) === 'index.html') {
|
||||
assert.ok(!value.isDirectory);
|
||||
assert.ok(!value.children);
|
||||
} else if (basename(value.resource.fsPath) === 'site.css') {
|
||||
assert.ok(!value.isDirectory);
|
||||
assert.ok(!value.children);
|
||||
} else {
|
||||
assert.ok(!'Unexpected value ' + basename(value.resource.fsPath));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('resolveFile - directory - resolveTo single directory', async () => {
|
||||
const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver');
|
||||
const result = await service.resolveFile(URI.file(resolverFixturesPath), { resolveTo: [URI.file(join(resolverFixturesPath, 'other/deep'))] });
|
||||
|
||||
assert.ok(result);
|
||||
assert.ok(result.children);
|
||||
assert.ok(result.children!.length > 0);
|
||||
assert.ok(result.isDirectory);
|
||||
|
||||
const children = result.children!;
|
||||
assert.equal(children.length, 4);
|
||||
|
||||
const other = getByName(result, 'other');
|
||||
assert.ok(other);
|
||||
assert.ok(other!.children!.length > 0);
|
||||
|
||||
const deep = getByName(other!, 'deep');
|
||||
assert.ok(deep);
|
||||
assert.ok(deep!.children!.length > 0);
|
||||
assert.equal(deep!.children!.length, 4);
|
||||
});
|
||||
|
||||
test('resolve directory - resolveTo multiple directories', async () => {
|
||||
const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver');
|
||||
const result = await service.resolveFile(URI.file(resolverFixturesPath), {
|
||||
resolveTo: [
|
||||
URI.file(join(resolverFixturesPath, 'other/deep')),
|
||||
URI.file(join(resolverFixturesPath, 'examples'))
|
||||
]
|
||||
});
|
||||
|
||||
assert.ok(result);
|
||||
assert.ok(result.children);
|
||||
assert.ok(result.children!.length > 0);
|
||||
assert.ok(result.isDirectory);
|
||||
|
||||
const children = result.children!;
|
||||
assert.equal(children.length, 4);
|
||||
|
||||
const other = getByName(result, 'other');
|
||||
assert.ok(other);
|
||||
assert.ok(other!.children!.length > 0);
|
||||
|
||||
const deep = getByName(other!, 'deep');
|
||||
assert.ok(deep);
|
||||
assert.ok(deep!.children!.length > 0);
|
||||
assert.equal(deep!.children!.length, 4);
|
||||
|
||||
const examples = getByName(result, 'examples');
|
||||
assert.ok(examples);
|
||||
assert.ok(examples!.children!.length > 0);
|
||||
assert.equal(examples!.children!.length, 4);
|
||||
});
|
||||
|
||||
test('resolve directory - resolveSingleChildFolders', async () => {
|
||||
const resolverFixturesPath = getPathFromAmdModule(require, './fixtures/resolver/other');
|
||||
const result = await service.resolveFile(URI.file(resolverFixturesPath), { resolveSingleChildDescendants: true });
|
||||
|
||||
assert.ok(result);
|
||||
assert.ok(result.children);
|
||||
assert.ok(result.children!.length > 0);
|
||||
assert.ok(result.isDirectory);
|
||||
|
||||
const children = result.children!;
|
||||
assert.equal(children.length, 1);
|
||||
|
||||
let deep = getByName(result, 'deep');
|
||||
assert.ok(deep);
|
||||
assert.ok(deep!.children!.length > 0);
|
||||
assert.equal(deep!.children!.length, 4);
|
||||
});
|
||||
|
||||
test('resolveFiles', async () => {
|
||||
const res = await service.resolveFiles([
|
||||
{ resource: URI.file(testDir), options: { resolveTo: [URI.file(join(testDir, 'deep'))] } },
|
||||
{ resource: URI.file(join(testDir, 'deep')) }
|
||||
]);
|
||||
|
||||
const r1 = (res[0].stat!);
|
||||
assert.equal(r1.children!.length, 8);
|
||||
|
||||
const deep = (getByName(r1, 'deep')!);
|
||||
assert.equal(deep.children!.length, 4);
|
||||
|
||||
const r2 = (res[1].stat!);
|
||||
assert.equal(r2.children!.length, 4);
|
||||
assert.equal(r2.name, 'deep');
|
||||
});
|
||||
|
||||
test('deleteFile', async () => {
|
||||
let event: FileOperationEvent;
|
||||
const toDispose = service.onAfterOperation(e => {
|
||||
event = e;
|
||||
});
|
||||
|
||||
const resource = URI.file(join(testDir, 'deep', 'conway.js'));
|
||||
const source = await service.resolveFile(resource);
|
||||
|
||||
await service.del(source.resource);
|
||||
|
||||
assert.equal(existsSync(source.resource.fsPath), false);
|
||||
assert.ok(event!);
|
||||
assert.equal(event!.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event!.operation, FileOperation.DELETE);
|
||||
|
||||
toDispose.dispose();
|
||||
});
|
||||
|
||||
test('deleteFolder (recursive)', async () => {
|
||||
let event: FileOperationEvent;
|
||||
const toDispose = service.onAfterOperation(e => {
|
||||
event = e;
|
||||
});
|
||||
|
||||
const resource = URI.file(join(testDir, 'deep'));
|
||||
const source = await service.resolveFile(resource);
|
||||
|
||||
await service.del(source.resource, { recursive: true });
|
||||
|
||||
assert.equal(existsSync(source.resource.fsPath), false);
|
||||
assert.ok(event!);
|
||||
assert.equal(event!.resource.fsPath, resource.fsPath);
|
||||
assert.equal(event!.operation, FileOperation.DELETE);
|
||||
|
||||
toDispose.dispose();
|
||||
});
|
||||
|
||||
test('deleteFolder (non recursive)', async () => {
|
||||
const resource = URI.file(join(testDir, 'deep'));
|
||||
const source = await service.resolveFile(resource);
|
||||
try {
|
||||
await service.del(source.resource);
|
||||
return Promise.reject(new Error('Unexpected'));
|
||||
}
|
||||
catch (error) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
33
src/vs/workbench/services/heap/common/heap.ts
Normal file
33
src/vs/workbench/services/heap/common/heap.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const IHeapService = createDecorator<IHeapService>('heapService');
|
||||
|
||||
export interface ObjectIdentifier {
|
||||
$ident?: number;
|
||||
}
|
||||
|
||||
export interface IHeapService {
|
||||
_serviceBrand: any;
|
||||
|
||||
readonly onGarbageCollection: Event<number[]>;
|
||||
|
||||
/**
|
||||
* Track gc-collection for the given object
|
||||
*/
|
||||
trackObject(obj: ObjectIdentifier | undefined): void;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class NullHeapService implements IHeapService {
|
||||
_serviceBrand: any;
|
||||
onGarbageCollection = Event.None;
|
||||
trackObject() { }
|
||||
}
|
||||
80
src/vs/workbench/services/heap/node/heap.ts
Normal file
80
src/vs/workbench/services/heap/node/heap.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { GCSignal } from 'gc-signals';
|
||||
import { IHeapService, ObjectIdentifier } from 'vs/workbench/services/heap/common/heap';
|
||||
|
||||
export class HeapService implements IHeapService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly _onGarbageCollection: Emitter<number[]> = new Emitter<number[]>();
|
||||
public readonly onGarbageCollection: Event<number[]> = this._onGarbageCollection.event;
|
||||
|
||||
private _activeSignals = new WeakMap<any, object>();
|
||||
private _activeIds = new Set<number>();
|
||||
|
||||
private _consumeHandle: any;
|
||||
private _ctor: { new(id: number): GCSignal };
|
||||
private _ctorInit: Promise<void>;
|
||||
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
|
||||
dispose() {
|
||||
clearInterval(this._consumeHandle);
|
||||
}
|
||||
|
||||
trackObject(obj: ObjectIdentifier | undefined | null): void {
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ident = obj.$ident;
|
||||
if (typeof ident !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._activeIds.has(ident)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._ctor) {
|
||||
// track and leave
|
||||
this._activeIds.add(ident);
|
||||
this._activeSignals.set(obj, new this._ctor(ident));
|
||||
|
||||
} else {
|
||||
// make sure to load gc-signals, then track and leave
|
||||
if (!this._ctorInit) {
|
||||
this._ctorInit = import('gc-signals').then(({ GCSignal, consumeSignals }) => {
|
||||
this._ctor = GCSignal;
|
||||
this._consumeHandle = setInterval(() => {
|
||||
const ids = consumeSignals();
|
||||
|
||||
if (ids.length > 0) {
|
||||
// local book-keeping
|
||||
for (const id of ids) {
|
||||
this._activeIds.delete(id);
|
||||
}
|
||||
// fire event
|
||||
this._onGarbageCollection.fire(ids);
|
||||
}
|
||||
}, 15 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
this._ctorInit.then(() => {
|
||||
this._activeIds.add(ident);
|
||||
this._activeSignals.set(obj, new this._ctor(ident));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IHeapService, HeapService, true);
|
||||
@@ -577,7 +577,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService {
|
||||
let schemaId = 'vscode://schemas/keybindings';
|
||||
let commandsSchemas: IJSONSchema[] = [];
|
||||
let commandsEnum: string[] = [];
|
||||
let commandsEnumDescriptions: (string | null | undefined)[] = [];
|
||||
let commandsEnumDescriptions: (string | undefined)[] = [];
|
||||
let schema: IJSONSchema = {
|
||||
'id': schemaId,
|
||||
'type': 'array',
|
||||
@@ -636,7 +636,7 @@ function updateSchema() {
|
||||
commandsEnumDescriptions.length = 0;
|
||||
|
||||
const knownCommands = new Set<string>();
|
||||
const addKnownCommand = (commandId: string, description?: string | null) => {
|
||||
const addKnownCommand = (commandId: string, description?: string | undefined) => {
|
||||
if (!/^_/.test(commandId)) {
|
||||
if (!knownCommands.has(commandId)) {
|
||||
knownCommands.add(commandId);
|
||||
@@ -655,7 +655,7 @@ function updateSchema() {
|
||||
for (let commandId in allCommands) {
|
||||
const commandDescription = allCommands[commandId].description;
|
||||
|
||||
addKnownCommand(commandId, commandDescription && commandDescription.description);
|
||||
addKnownCommand(commandId, commandDescription ? commandDescription.description : undefined);
|
||||
|
||||
if (!commandDescription || !commandDescription.args || commandDescription.args.length !== 1 || !commandDescription.args[0].schema) {
|
||||
continue;
|
||||
@@ -684,7 +684,6 @@ function updateSchema() {
|
||||
for (let commandId in menuCommands) {
|
||||
addKnownCommand(commandId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigExtensions.Configuration);
|
||||
|
||||
@@ -11,7 +11,7 @@ export class SearchChannel implements IServerChannel {
|
||||
|
||||
constructor(private service: IRawSearchService) { }
|
||||
|
||||
listen<T>(_, event: string, arg?: any): Event<any> {
|
||||
listen(_: unknown, event: string, arg?: any): Event<any> {
|
||||
switch (event) {
|
||||
case 'fileSearch': return this.service.fileSearch(arg);
|
||||
case 'textSearch': return this.service.textSearch(arg);
|
||||
@@ -19,7 +19,7 @@ export class SearchChannel implements IServerChannel {
|
||||
throw new Error('Event not found');
|
||||
}
|
||||
|
||||
call(_, command: string, arg?: any): Promise<any> {
|
||||
call(_: unknown, command: string, arg?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'clearCache': return this.service.clearCache(arg);
|
||||
}
|
||||
|
||||
@@ -17,19 +17,19 @@ import * as pfs from 'vs/base/node/pfs';
|
||||
import { getNextTickChannel } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IDebugParams, IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, ISearchConfiguration, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from 'vs/workbench/services/search/common/search';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, IRawSearchService, ISearchComplete, ISearchConfiguration, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType } from 'vs/workbench/services/search/common/search';
|
||||
import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { SearchChannelClient } from './searchIpc';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
|
||||
export class SearchService extends Disposable implements ISearchService {
|
||||
_serviceBrand: any;
|
||||
@@ -46,7 +46,8 @@ export class SearchService extends Disposable implements ISearchService {
|
||||
@IEnvironmentService environmentService: IEnvironmentService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IFileService private readonly fileService: IFileService
|
||||
) {
|
||||
super();
|
||||
this.diskSearch = this.instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, environmentService.debugSearch);
|
||||
@@ -376,7 +377,7 @@ export class SearchService extends Disposable implements ISearchService {
|
||||
}
|
||||
|
||||
// Block walkthrough, webview, etc.
|
||||
else if (resource.scheme !== Schemas.file && resource.scheme !== REMOTE_HOST_SCHEME) {
|
||||
else if (!this.fileService.canHandleResource(resource)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -610,8 +610,14 @@ export class TextFileService extends Disposable implements ITextFileService {
|
||||
|
||||
private untitledToAssociatedFileResource(untitled: URI): URI {
|
||||
const authority = this.windowService.getConfiguration().remoteAuthority;
|
||||
|
||||
return authority ? untitled.with({ scheme: REMOTE_HOST_SCHEME, authority }) : untitled.with({ scheme: Schemas.file });
|
||||
if (authority) {
|
||||
let path = untitled.path;
|
||||
if (path && path[0] !== '/') {
|
||||
path = '/' + path;
|
||||
}
|
||||
return untitled.with({ scheme: REMOTE_HOST_SCHEME, authority, path });
|
||||
}
|
||||
return untitled.with({ scheme: Schemas.file });
|
||||
}
|
||||
|
||||
private doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise<ITextFileOperationResult> {
|
||||
|
||||
@@ -104,11 +104,11 @@ class ResourceModelCollection extends ReferenceCollection<Promise<ITextEditorMod
|
||||
private resolveTextModelContent(key: string): Promise<ITextModel> {
|
||||
const resource = URI.parse(key);
|
||||
const providers = this.providers[resource.scheme] || [];
|
||||
const factories = providers.map(p => () => Promise.resolve<ITextModel | undefined | null>(p.provideTextContent(resource)));
|
||||
const factories = providers.map(p => () => Promise.resolve(p.provideTextContent(resource)));
|
||||
|
||||
return first(factories).then(model => {
|
||||
if (!model) {
|
||||
return Promise.reject<any>(new Error('resource is not available'));
|
||||
return Promise.reject(new Error('resource is not available'));
|
||||
}
|
||||
|
||||
return model;
|
||||
|
||||
Reference in New Issue
Block a user