Merge from vscode fc10e26ea50f82cdd84e9141491357922e6f5fba (#4639)

This commit is contained in:
Anthony Dresser
2019-03-21 10:58:16 -07:00
committed by GitHub
parent 8298db7d13
commit b65ee5b42e
149 changed files with 1408 additions and 814 deletions

View 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 {
}
}

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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 {

View 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 { 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,
};
}

View File

@@ -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 { }
}

View File

@@ -273,4 +273,4 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler {
}
}
registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler);
registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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'));

View File

@@ -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);

View File

@@ -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
}
}

View File

@@ -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);
});
});
});

View File

@@ -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);
}
});
});

View 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() { }
}

View 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);

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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> {

View File

@@ -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;