Merge remote-tracking branch 'origin/master' into joao/uri-handler

This commit is contained in:
Joao Moreno
2018-07-06 12:49:16 +02:00
625 changed files with 15064 additions and 7992 deletions

View File

@@ -335,7 +335,7 @@ export function createApiFactory(
return proposedApiFunction(extension, extHostTerminalService.activeTerminal);
},
get terminals() {
return proposedApiFunction(extension, extHostTerminalService.terminals);
return extHostTerminalService.terminals;
},
showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): TPromise<vscode.TextEditor> {
let documentPromise: TPromise<vscode.TextDocument>;
@@ -372,9 +372,9 @@ export function createApiFactory(
onDidCloseTerminal(listener, thisArg?, disposables?) {
return extHostTerminalService.onDidCloseTerminal(listener, thisArg, disposables);
},
onDidOpenTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
onDidOpenTerminal(listener, thisArg?, disposables?) {
return extHostTerminalService.onDidOpenTerminal(listener, thisArg, disposables);
}),
},
onDidChangeActiveTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables);
}),
@@ -442,6 +442,9 @@ export function createApiFactory(
createTreeView(viewId: string, options: { treeDataProvider: vscode.TreeDataProvider<any> }): vscode.TreeView<any> {
return extHostTreeViews.createTreeView(viewId, options);
},
registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer);
},
// proposed API
sampleFunction: proposedApiFunction(extension, () => {
return extHostMessageService.showMessage(extension, Severity.Info, 'Hello Proposed Api!', {}, []);
@@ -449,9 +452,6 @@ export function createApiFactory(
registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => {
return extHostDecorations.registerDecorationProvider(provider, extension.id);
}),
registerWebviewPanelSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer);
}),
registerUriHandler(handler: vscode.UriHandler) {
return extHostUrls.registerUriHandler(extension.id, handler);
},
@@ -500,6 +500,9 @@ export function createApiFactory(
findFiles: (include, exclude, maxResults?, token?) => {
return extHostWorkspace.findFiles(typeConverters.GlobPattern.from(include), typeConverters.GlobPattern.from(exclude), maxResults, extension.id, token);
},
findTextInFiles: (query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, token?: vscode.CancellationToken) => {
return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.id, token);
},
saveAll: (includeUntitled?) => {
return extHostWorkspace.saveAll(includeUntitled);
},
@@ -826,7 +829,9 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT
// fall back to a default implementation
if (!defaultApiImpl) {
console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}`);
let extensionPathsPretty = '';
extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.id}\n`);
console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`);
defaultApiImpl = factory(nullExtensionDescription);
}
return defaultApiImpl;

View File

@@ -4,52 +4,43 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { createMainContextProxyIdentifier as createMainId, createExtHostContextProxyIdentifier as createExtId, ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import * as vscode from 'vscode';
import URI, { UriComponents } from 'vs/base/common/uri';
import { SerializedError } from 'vs/base/common/errors';
import { IDisposable } from 'vs/base/common/lifecycle';
import Severity from 'vs/base/common/severity';
import URI, { UriComponents } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { IProgressOptions, IProgressStep } from 'vs/workbench/services/progress/common/progress';
import * as editorCommon from 'vs/editor/common/editorCommon';
import * as modes from 'vs/editor/common/modes';
import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
import { IConfig, IAdapterExecutable, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
import { IQuickPickItem, IPickOptions, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
import { IPosition } from 'vs/editor/common/core/position';
import { IRange } from 'vs/editor/common/core/range';
import { ISelection, Selection } from 'vs/editor/common/core/selection';
import { ITreeItem } from 'vs/workbench/common/views';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { SerializedError } from 'vs/base/common/errors';
import { IStat, FileChangeType, IWatchOptions, FileSystemProviderCapabilities, FileWriteOptions, FileType, FileOverwriteOptions } from 'vs/platform/files/common/files';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ISingleEditOperation } from 'vs/editor/common/model';
import { IPatternInfo, IRawSearchQuery, IRawFileMatch2, ISearchCompleteStats } from 'vs/platform/search/common/search';
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
import * as modes from 'vs/editor/common/modes';
import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files';
import { LogLevel } from 'vs/platform/log/common/log';
import { TaskExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import { IPickOptions, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IPatternInfo, IQueryOptions, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats } from 'vs/platform/search/common/search';
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
import { ThemeColor } from 'vs/platform/theme/common/themeService';
import { EndOfLine, IFileOperationOptions, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
import { TaskDTO, TaskExecutionDTO, TaskFilterDTO, TaskHandleDTO, TaskProcessEndedDTO, TaskProcessStartedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
import { ITreeItem } from 'vs/workbench/common/views';
import { IAdapterExecutable, IConfig, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol, ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
import { IProgressOptions, IProgressStep } from 'vs/workbench/services/progress/common/progress';
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
import * as vscode from 'vscode';
export interface IEnvironment {
isExtensionDevelopmentDebug: boolean;
@@ -411,7 +402,7 @@ export interface TransferInputBox extends BaseTransferQuickInput {
}
export interface MainThreadQuickOpenShape extends IDisposable {
$show(options: IPickOptions): TPromise<number | number[]>;
$show(options: IPickOptions<TransferQuickPickItems>): TPromise<number | number[]>;
$setItems(items: TransferQuickPickItems[]): TPromise<any>;
$setError(error: Error): TPromise<any>;
$input(options: vscode.InputBoxOptions, validateInput: boolean): TPromise<string>;
@@ -471,7 +462,8 @@ export interface ExtHostUrlsShape {
}
export interface MainThreadWorkspaceShape extends IDisposable {
$startSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<UriComponents[]>;
$startFileSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<UriComponents[]>;
$startTextSearch(query: IPatternInfo, options: IQueryOptions, requestId: number): TPromise<void>;
$cancelSearch(requestId: number): Thenable<boolean>;
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void>;
@@ -491,7 +483,8 @@ export interface MainThreadFileSystemShape extends IDisposable {
export interface MainThreadSearchShape extends IDisposable {
$registerSearchProvider(handle: number, scheme: string): void;
$unregisterProvider(handle: number): void;
$handleFindMatch(handle: number, session: number, data: UriComponents | IRawFileMatch2[]): void;
$handleFileMatch(handle: number, session: number, data: UriComponents[]): void;
$handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void;
$handleTelemetry(eventName: string, data: any): void;
}
@@ -671,6 +664,7 @@ export interface ExtHostTreeViewsShape {
export interface ExtHostWorkspaceShape {
$acceptWorkspaceData(workspace: IWorkspaceData): void;
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void;
}
export interface ExtHostFileSystemShape {
@@ -681,13 +675,14 @@ export interface ExtHostFileSystemShape {
$rename(handle: number, resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): TPromise<void>;
$copy(handle: number, resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): TPromise<void>;
$mkdir(handle: number, resource: UriComponents): TPromise<void>;
$delete(handle: number, resource: UriComponents): TPromise<void>;
$delete(handle: number, resource: UriComponents, opts: FileDeleteOptions): TPromise<void>;
$watch(handle: number, session: number, resource: UriComponents, opts: IWatchOptions): void;
$unwatch(handle: number, session: number): void;
}
export interface ExtHostSearchShape {
$provideFileSearchResults(handle: number, session: number, query: IRawSearchQuery): TPromise<ISearchCompleteStats>;
$clearCache(handle: number, cacheKey: string): TPromise<void>;
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, query: IRawSearchQuery): TPromise<ISearchCompleteStats>;
}
@@ -774,7 +769,7 @@ export interface WorkspaceSymbolsDto extends IdObject {
export interface ResourceFileEditDto {
oldUri: UriComponents;
newUri: UriComponents;
options: { overwrite?: boolean, ignoreIfExists?: boolean };
options: IFileOperationOptions;
}
export interface ResourceTextEditDto {
@@ -883,7 +878,7 @@ export interface ExtHostSCMShape {
}
export interface ExtHostTaskShape {
$provideTasks(handle: number): TPromise<TaskSet>;
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): TPromise<TaskSet>;
$onDidStartTask(execution: TaskExecutionDTO): void;
$onDidStartTaskProcess(value: TaskProcessStartedDTO): void;
$onDidEndTaskProcess(value: TaskProcessEndedDTO): void;

View File

@@ -19,6 +19,7 @@ import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
import { CustomCodeAction } from 'vs/workbench/api/node/extHostLanguageFeatures';
import { ICommandsExecutor, PreviewHTMLAPICommand, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand } from './apiCommands';
import { EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
export class ExtHostApiCommands {
@@ -411,15 +412,24 @@ export class ExtHostApiCommands {
});
}
private _executeDocumentSymbolProvider(resource: URI): Thenable<vscode.DocumentSymbol[]> {
private _executeDocumentSymbolProvider(resource: URI): Thenable<vscode.SymbolInformation[]> {
const args = {
resource
};
return this._commands.executeCommand<modes.DocumentSymbol[]>('_executeDocumentSymbolProvider', args).then(value => {
if (value && Array.isArray(value)) {
return value.map(typeConverters.DocumentSymbol.to);
if (isFalsyOrEmpty(value)) {
return undefined;
}
return undefined;
let result: vscode.SymbolInformation[] = [];
for (const symbol of value) {
result.push(new types.SymbolInformation(
symbol.name,
typeConverters.SymbolKind.to(symbol.kind),
symbol.containerName,
new types.Location(resource, typeConverters.Range.to(symbol.range))
));
}
return result;
});
}

View File

@@ -123,7 +123,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
}
}
public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
public $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
if (args.kind === 'integrated') {
@@ -136,20 +136,31 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
});
}
let t = this._integratedTerminalInstance;
return new TPromise(resolve => {
if (this._integratedTerminalInstance) {
this._integratedTerminalInstance.processId.then(pid => {
resolve(hasChildprocesses(pid));
}, err => {
resolve(true);
});
} else {
resolve(true);
}
}).then(needNewTerminal => {
if ((t && hasChildprocesses(await t.processId)) || !t) {
t = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee"));
this._integratedTerminalInstance = t;
}
t.show();
if (needNewTerminal) {
this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee"));
}
return new TPromise((resolve, error) => {
setTimeout(_ => {
const command = prepareCommand(args, config);
t.sendText(command, true);
resolve(void 0);
}, 500);
this._integratedTerminalInstance.show();
return new TPromise((resolve, error) => {
setTimeout(_ => {
const command = prepareCommand(args, config);
this._integratedTerminalInstance.sendText(command, true);
resolve(void 0);
}, 500);
});
});
} else if (args.kind === 'external') {
@@ -166,15 +177,18 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
if (!this._variableResolver) {
this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, this._configurationService);
}
let ws: IWorkspaceFolder;
const folder = this.getFolder(folderUri);
let ws: IWorkspaceFolder = {
uri: folder.uri,
name: folder.name,
index: folder.index,
toResource: () => {
throw new Error('Not implemented');
}
};
if (folder) {
ws = {
uri: folder.uri,
name: folder.name,
index: folder.index,
toResource: () => {
throw new Error('Not implemented');
}
};
}
return asWinJsPromise(token => this._variableResolver.resolveAny(ws, config));
}
@@ -412,9 +426,9 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) {
if (added.length > 0 || removed.length > 0 || changed.length > 0) {
this._onDidChangeBreakpoints.fire(Object.freeze({
added: Object.freeze<vscode.Breakpoint[]>(added),
removed: Object.freeze<vscode.Breakpoint[]>(removed),
changed: Object.freeze<vscode.Breakpoint[]>(changed)
added,
removed,
changed,
}));
}
}
@@ -524,7 +538,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
this._onDidReceiveDebugSessionCustomEvent.fire(ee);
}
private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder {
private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder | undefined {
if (_folderUri) {
const folderURI = URI.revive(_folderUri);
return this._workspaceService.resolveWorkspaceFolder(folderURI);

View File

@@ -68,7 +68,7 @@ class ExtensionStoragePath {
private readonly _workspace: IWorkspaceData;
private readonly _environment: IEnvironment;
private readonly _ready: TPromise<string>;
private readonly _ready: Promise<string>;
private _value: string;
constructor(workspace: IWorkspaceData, environment: IEnvironment) {
@@ -77,7 +77,7 @@ class ExtensionStoragePath {
this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
}
get whenReady(): TPromise<any> {
get whenReady(): Promise<any> {
return this._ready;
}
@@ -88,7 +88,7 @@ class ExtensionStoragePath {
return undefined;
}
private async _getOrCreateWorkspaceStoragePath(): TPromise<string> {
private async _getOrCreateWorkspaceStoragePath(): Promise<string> {
if (!this._workspace) {
return TPromise.as(undefined);
}

View File

@@ -168,8 +168,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
return asWinJsPromise(() => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), opts));
}
$delete(handle: number, resource: UriComponents): TPromise<void, any> {
return asWinJsPromise(() => this._fsProvider.get(handle).delete(URI.revive(resource), { recursive: true }));
$delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): TPromise<void, any> {
return asWinJsPromise(() => this._fsProvider.get(handle).delete(URI.revive(resource), opts));
}
$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise<void, any> {

View File

@@ -146,7 +146,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
const newUri = URI.revive(newUriDto);
const edits: WorkspaceEdit[] = [];
return this._onWillRenameFile.fireAsync((bucket, listener) => {
return TPromise.wrap(this._onWillRenameFile.fireAsync((bucket, listener) => {
return {
oldUri,
newUri,
@@ -179,6 +179,6 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
}
}
return this._mainThreadTextEditors.$tryApplyWorkspaceEdit({ edits: flatten(allEdits) });
});
}));
}
}

View File

@@ -237,14 +237,14 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
return this._resourceStatesMap.get(handle);
}
async $executeResourceCommand(handle: number): TPromise<void> {
$executeResourceCommand(handle: number): TPromise<void> {
const command = this._resourceStatesCommandsMap.get(handle);
if (!command) {
return;
return TPromise.as(null);
}
await this._commands.executeCommand(command.command, ...command.arguments);
return asWinJsPromise(_ => this._commands.executeCommand(command.command, ...command.arguments));
}
_takeResourceStateSnapshot(): SCMRawResourceSplice[] {
@@ -568,25 +568,25 @@ export class ExtHostSCM implements ExtHostSCMShape {
return TPromise.as(null);
}
async $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise<void> {
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise<void> {
this.logService.trace('ExtHostSCM#$executeResourceCommand', sourceControlHandle, groupHandle, handle);
const sourceControl = this._sourceControls.get(sourceControlHandle);
if (!sourceControl) {
return;
return TPromise.as(null);
}
const group = sourceControl.getResourceGroup(groupHandle);
if (!group) {
return;
return TPromise.as(null);
}
await group.$executeResourceCommand(handle);
return group.$executeResourceCommand(handle);
}
async $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined> {
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined> {
this.logService.trace('ExtHostSCM#$validateInput', sourceControlHandle);
const sourceControl = this._sourceControls.get(sourceControlHandle);
@@ -599,12 +599,12 @@ export class ExtHostSCM implements ExtHostSCMShape {
return TPromise.as(undefined);
}
const result = await sourceControl.inputBox.validateInput(value, cursorPosition);
return asWinJsPromise(_ => Promise.resolve(sourceControl.inputBox.validateInput(value, cursorPosition))).then(result => {
if (!result) {
return TPromise.as(undefined);
}
if (!result) {
return TPromise.as(undefined);
}
return [result.message, result.type];
return TPromise.as<[string, number]>([result.message, result.type]);
});
}
}

View File

@@ -4,23 +4,19 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as pfs from 'vs/base/node/pfs';
import * as extfs from 'vs/base/node/extfs';
import * as path from 'path';
import * as arrays from 'vs/base/common/arrays';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import * as glob from 'vs/base/common/glob';
import * as resources from 'vs/base/common/resources';
import * as strings from 'vs/base/common/strings';
import URI, { UriComponents } from 'vs/base/common/uri';
import { PPromise, TPromise } from 'vs/base/common/winjs.base';
import { IItemAccessor, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { ICachedSearchStats, IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchQuery, ISearchCompleteStats, IRawFileMatch2 } from 'vs/platform/search/common/search';
import * as extfs from 'vs/base/node/extfs';
import * as pfs from 'vs/base/node/pfs';
import { IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search';
import * as vscode from 'vscode';
import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { joinPath } from 'vs/base/common/resources';
type OneOrMore<T> = T | T[];
export interface ISchemeTransformer {
transformOutgoing(scheme: string): string;
@@ -36,9 +32,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _extfs = extfs, private _pfs = pfs) {
this._proxy = mainContext.getProxy(MainContext.MainThreadSearch);
this._fileSearchManager = new FileSearchManager(
(eventName: string, data: any) => this._proxy.$handleTelemetry(eventName, data),
this._pfs);
this._fileSearchManager = new FileSearchManager(this._pfs);
}
private _transformScheme(scheme: string): string {
@@ -71,16 +65,20 @@ export class ExtHostSearch implements ExtHostSearchShape {
null,
null,
progress => {
if (Array.isArray(progress)) {
progress.forEach(p => {
this._proxy.$handleFindMatch(handle, session, p.resource);
});
} else {
this._proxy.$handleFindMatch(handle, session, progress.resource);
}
this._proxy.$handleFileMatch(handle, session, progress.map(p => p.resource));
});
}
$clearCache(handle: number, cacheKey: string): TPromise<void> {
const provider = this._searchProvider.get(handle);
if (!provider.clearCache) {
return TPromise.as(undefined);
}
return TPromise.as(
this._fileSearchManager.clearCache(cacheKey, provider));
}
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
const provider = this._searchProvider.get(handle);
if (!provider.provideTextSearchResults) {
@@ -93,7 +91,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
null,
null,
progress => {
this._proxy.$handleFindMatch(handle, session, progress);
this._proxy.$handleTextMatch(handle, session, progress);
});
}
}
@@ -132,29 +130,28 @@ function reviveFolderQuery(rawFolderQuery: IFolderQuery<UriComponents>): IFolder
}
class TextSearchResultsCollector {
private _batchedCollector: BatchedCollector<IRawFileMatch2>;
private _batchedCollector: BatchedCollector<IFileMatch>;
private _currentFolderIdx: number;
private _currentRelativePath: string;
private _currentFileMatch: IRawFileMatch2;
private _currentUri: URI;
private _currentFileMatch: IFileMatch;
constructor(private folderQueries: IFolderQuery[], private _onResult: (result: IRawFileMatch2[]) => void) {
this._batchedCollector = new BatchedCollector<IRawFileMatch2>(512, items => this.sendItems(items));
constructor(private _onResult: (result: IFileMatch[]) => void) {
this._batchedCollector = new BatchedCollector<IFileMatch>(512, items => this.sendItems(items));
}
add(data: vscode.TextSearchResult, folderIdx: number): void {
// Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector.
// This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search
// providers that send results in random order. We could do this step afterwards instead.
if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || this._currentRelativePath !== data.path)) {
if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || resources.isEqual(this._currentUri, data.uri))) {
this.pushToCollector();
this._currentFileMatch = null;
}
if (!this._currentFileMatch) {
const resource = joinPath(this.folderQueries[folderIdx].folder, data.path);
this._currentFileMatch = {
resource,
resource: data.uri,
lineMatches: []
};
}
@@ -180,8 +177,8 @@ class TextSearchResultsCollector {
this._batchedCollector.flush();
}
private sendItems(items: IRawFileMatch2 | IRawFileMatch2[]): void {
this._onResult(Array.isArray(items) ? items : [items]);
private sendItems(items: IFileMatch[]): void {
this._onResult(items);
}
}
@@ -202,7 +199,7 @@ class BatchedCollector<T> {
private batchSize = 0;
private timeoutHandle: number;
constructor(private maxBatchSize: number, private cb: (items: T | T[]) => void) {
constructor(private maxBatchSize: number, private cb: (items: T[]) => void) {
}
addItem(item: T, size: number): void {
@@ -210,11 +207,7 @@ class BatchedCollector<T> {
return;
}
if (this.maxBatchSize > 0) {
this.addItemToBatch(item, size);
} else {
this.cb(item);
}
this.addItemToBatch(item, size);
}
addItems(items: T[], size: number): void {
@@ -390,11 +383,11 @@ class TextSearchEngine {
this.activeCancellationTokens = new Set();
}
public search(): PPromise<{ limitHit: boolean }, IRawFileMatch2[]> {
public search(): PPromise<{ limitHit: boolean }, IFileMatch[]> {
const folderQueries = this.config.folderQueries;
return new PPromise<{ limitHit: boolean }, IRawFileMatch2[]>((resolve, reject, _onResult) => {
this.collector = new TextSearchResultsCollector(this.config.folderQueries, _onResult);
return new PPromise<{ limitHit: boolean }, IFileMatch[]>((resolve, reject, _onResult) => {
this.collector = new TextSearchResultsCollector(_onResult);
const onResult = (match: vscode.TextSearchResult, folderIdx: number) => {
if (this.isCanceled) {
@@ -437,11 +430,12 @@ class TextSearchEngine {
const progress = {
report: (result: vscode.TextSearchResult) => {
const siblingFn = folderQuery.folder.scheme === 'file' && (() => {
return this.readdir(path.dirname(path.join(folderQuery.folder.fsPath, result.path)));
return this.readdir(path.dirname(result.uri.fsPath));
});
const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath);
testingPs.push(
queryTester.includedInQuery(result.path, path.basename(result.path), siblingFn)
queryTester.includedInQuery(relativePath, path.basename(relativePath), siblingFn)
.then(included => {
if (included) {
onResult(result);
@@ -495,7 +489,8 @@ class TextSearchEngine {
useIgnoreFiles: !this.config.disregardIgnoreFiles,
followSymlinks: !this.config.ignoreSymlinks,
encoding: this.config.fileEncoding,
maxFileSize: this.config.maxFileSize
maxFileSize: this.config.maxFileSize,
maxResults: this.config.maxResults
};
}
}
@@ -515,16 +510,12 @@ class FileSearchEngine {
private includePattern: glob.ParsedExpression;
private maxResults: number;
private exists: boolean;
// private maxFilesize: number;
private isLimitHit: boolean;
private resultCount: number;
private isCanceled: boolean;
private activeCancellationTokens: Set<CancellationTokenSource>;
// private filesWalked: number;
// private directoriesWalked: number;
private globalExcludePattern: glob.ParsedExpression;
constructor(private config: ISearchQuery, private provider: vscode.SearchProvider, private _pfs: typeof pfs) {
@@ -532,14 +523,10 @@ class FileSearchEngine {
this.includePattern = config.includePattern && glob.parse(config.includePattern);
this.maxResults = config.maxResults || null;
this.exists = config.exists;
// this.maxFilesize = config.maxFileSize || null;
this.resultCount = 0;
this.isLimitHit = false;
this.activeCancellationTokens = new Set<CancellationTokenSource>();
// this.filesWalked = 0;
// this.directoriesWalked = 0;
if (this.filePattern) {
this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase();
}
@@ -553,87 +540,68 @@ class FileSearchEngine {
this.activeCancellationTokens = new Set();
}
public search(): PPromise<{ isLimitHit: boolean }, IInternalFileMatch> {
public search(): PPromise<IInternalSearchComplete, IInternalFileMatch> {
const folderQueries = this.config.folderQueries;
return new PPromise<{ isLimitHit: boolean }, IInternalFileMatch>((resolve, reject, _onResult) => {
return new PPromise((resolve, reject, _onResult) => {
const onResult = (match: IInternalFileMatch) => {
this.resultCount++;
_onResult(match);
};
// Support that the file pattern is a full path to a file that exists
this.checkFilePatternAbsoluteMatch().then(({ exists, size }) => {
if (this.isCanceled) {
return resolve({ isLimitHit: this.isLimitHit });
}
if (this.isCanceled) {
return resolve({ limitHit: this.isLimitHit, cacheKeys: [] });
}
// Report result from file pattern if matching
if (exists) {
onResult({
base: URI.file(this.filePattern),
basename: path.basename(this.filePattern),
size
// For each extra file
if (this.config.extraFileResources) {
this.config.extraFileResources
.forEach(extraFile => {
const extraFileStr = extraFile.toString(); // ?
const basename = path.basename(extraFileStr);
if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) {
return; // excluded
}
// File: Check for match on file pattern and include pattern
this.matchFile(onResult, { base: extraFile, basename });
});
}
// Optimization: a match on an absolute path is a good result and we do not
// continue walking the entire root paths array for other matches because
// it is very unlikely that another file would match on the full absolute path
return resolve({ isLimitHit: this.isLimitHit });
}
// For each root folder
PPromise.join(folderQueries.map(fq => {
return this.searchInFolder(fq).then(null, null, onResult);
})).then(cacheKeys => {
resolve({ limitHit: this.isLimitHit, cacheKeys });
}, (errs: Error[]) => {
const errMsg = errs
.map(err => toErrorMessage(err))
.filter(msg => !!msg)[0];
// For each extra file
if (this.config.extraFileResources) {
this.config.extraFileResources
.forEach(extraFile => {
const extraFileStr = extraFile.toString(); // ?
const basename = path.basename(extraFileStr);
if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) {
return; // excluded
}
// File: Check for match on file pattern and include pattern
this.matchFile(onResult, { base: extraFile, basename });
});
}
// For each root folder
PPromise.join(folderQueries.map(fq => {
return this.searchInFolder(fq).then(null, null, onResult);
})).then(() => {
resolve({ isLimitHit: this.isLimitHit });
}, (errs: Error[]) => {
const errMsg = errs
.map(err => toErrorMessage(err))
.filter(msg => !!msg)[0];
reject(new Error(errMsg));
});
reject(new Error(errMsg));
});
});
}
private searchInFolder(fq: IFolderQuery<URI>): PPromise<void, IInternalFileMatch> {
private searchInFolder(fq: IFolderQuery<URI>): PPromise<string, IInternalFileMatch> {
let cancellation = new CancellationTokenSource();
return new PPromise((resolve, reject, onResult) => {
const options = this.getSearchOptionsForFolder(fq);
let filePatternSeen = false;
const tree = this.initDirectoryTree();
const queryTester = new QueryGlobTester(this.config, fq);
const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses();
const onProviderResult = (relativePath: string) => {
const onProviderResult = (result: URI) => {
if (this.isCanceled) {
return;
}
if (noSiblingsClauses) {
if (relativePath === this.filePattern) {
filePatternSeen = true;
}
const relativePath = path.relative(fq.folder.fsPath, result.fsPath);
const basename = path.basename(relativePath);
if (noSiblingsClauses) {
const basename = path.basename(result.fsPath);
this.matchFile(onResult, { base: fq.folder, relativePath, basename });
return;
@@ -643,10 +611,21 @@ class FileSearchEngine {
this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
};
new TPromise(resolve => process.nextTick(resolve))
let folderCacheKey: string;
new TPromise(_resolve => process.nextTick(_resolve))
.then(() => {
this.activeCancellationTokens.add(cancellation);
return this.provider.provideFileSearchResults(options, { report: onProviderResult }, cancellation.token);
folderCacheKey = this.config.cacheKey && (this.config.cacheKey + '_' + fq.folder.fsPath);
return this.provider.provideFileSearchResults(
{
pattern: this.config.filePattern || '',
cacheKey: folderCacheKey
},
options,
{ report: onProviderResult },
cancellation.token);
})
.then(() => {
this.activeCancellationTokens.delete(cancellation);
@@ -655,18 +634,16 @@ class FileSearchEngine {
}
if (noSiblingsClauses && this.isLimitHit) {
if (!filePatternSeen) {
// If the limit was hit, check whether filePattern is an exact relative match because it must be included
return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => {
if (exists) {
onResult({
base: fq.folder,
relativePath: this.filePattern,
basename: path.basename(this.filePattern),
});
}
});
}
// If the limit was hit, check whether filePattern is an exact relative match because it must be included
return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => {
if (exists) {
onResult({
base: fq.folder,
relativePath: this.filePattern,
basename: path.basename(this.filePattern),
});
}
});
}
this.matchDirectoryTree(tree, queryTester, onResult);
@@ -674,7 +651,7 @@ class FileSearchEngine {
}).then(
() => {
cancellation.dispose();
resolve(undefined);
resolve(folderCacheKey);
},
err => {
cancellation.dispose();
@@ -692,7 +669,8 @@ class FileSearchEngine {
excludes,
includes,
useIgnoreFiles: !this.config.disregardIgnoreFiles,
followSymlinks: !this.config.ignoreSymlinks
followSymlinks: !this.config.ignoreSymlinks,
maxResults: this.config.maxResults
};
}
@@ -734,7 +712,6 @@ class FileSearchEngine {
const self = this;
const filePattern = this.filePattern;
function matchDirectory(entries: IDirectoryEntry[]) {
// self.directoriesWalked++;
for (let i = 0, n = entries.length; i < n; i++) {
const entry = entries[i];
const { relativePath, basename } = entry;
@@ -751,7 +728,6 @@ class FileSearchEngine {
if (sub) {
matchDirectory(sub);
} else {
// self.filesWalked++;
if (relativePath === filePattern) {
continue; // ignore file if its path matches with the file pattern because that is already matched above
}
@@ -767,44 +743,6 @@ class FileSearchEngine {
matchDirectory(rootEntries);
}
public getStats(): any {
return null;
// return {
// fromCache: false,
// traversal: Traversal[this.traversal],
// errors: this.errors,
// fileWalkStartTime: this.fileWalkStartTime,
// fileWalkResultTime: Date.now(),
// directoriesWalked: this.directoriesWalked,
// filesWalked: this.filesWalked,
// resultCount: this.resultCount,
// cmdForkResultTime: this.cmdForkResultTime,
// cmdResultCount: this.cmdResultCount
// };
}
/**
* Return whether the file pattern is an absolute path to a file that exists.
* TODO@roblou should use FS provider?
*/
private checkFilePatternAbsoluteMatch(): TPromise<{ exists: boolean, size?: number }> {
if (!this.filePattern || !path.isAbsolute(this.filePattern)) {
return TPromise.wrap({ exists: false });
}
return this._pfs.stat(this.filePattern)
.then(stat => {
return {
exists: !stat.isDirectory(),
size: stat.size
};
}, err => {
return {
exists: false
};
});
}
private checkFilePatternRelativeMatch(base: URI): TPromise<{ exists: boolean, size?: number }> {
if (!this.filePattern || path.isAbsolute(this.filePattern) || base.scheme !== 'file') {
return TPromise.wrap({ exists: false });
@@ -851,263 +789,69 @@ class FileSearchEngine {
}
}
interface IInternalSearchComplete {
limitHit: boolean;
cacheKeys: string[];
}
class FileSearchManager {
private static readonly BATCH_SIZE = 512;
private caches: { [cacheKey: string]: Cache; } = Object.create(null);
private readonly expandedCacheKeys = new Map<string, string[]>();
constructor(private telemetryCallback: (eventName: string, data: any) => void, private _pfs: typeof pfs) { }
constructor(private _pfs: typeof pfs) { }
public fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise<ISearchCompleteStats, OneOrMore<IFileMatch>> {
if (config.sortByScore) {
let sortedSearch = this.trySortedSearchFromCache(config);
if (!sortedSearch) {
const engineConfig = config.maxResults ?
{
...config,
...{ maxResults: null }
} :
config;
const engine = new FileSearchEngine(engineConfig, provider, this._pfs);
sortedSearch = this.doSortedSearch(engine, provider, config);
}
return new PPromise<ISearchCompleteStats, OneOrMore<IFileMatch>>((c, e, p) => {
process.nextTick(() => { // allow caller to register progress callback first
sortedSearch.then(([result, rawMatches]) => {
const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch));
this.sendProgress(serializedMatches, p, FileSearchManager.BATCH_SIZE);
c(result);
}, e, p);
});
}, () => {
sortedSearch.cancel();
});
}
let searchPromise: PPromise<void, OneOrMore<IInternalFileMatch>>;
return new PPromise<ISearchCompleteStats, OneOrMore<IFileMatch>>((c, e, p) => {
fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise<ISearchCompleteStats, IFileMatch[]> {
let searchP: PPromise;
return new PPromise<ISearchCompleteStats, IFileMatch[]>((c, e, p) => {
const engine = new FileSearchEngine(config, provider, this._pfs);
searchPromise = this.doSearch(engine, provider, FileSearchManager.BATCH_SIZE)
.then(c, e, progress => {
if (Array.isArray(progress)) {
p(progress.map(m => this.rawMatchToSearchItem(m)));
} else if ((<IInternalFileMatch>progress).relativePath) {
p(this.rawMatchToSearchItem(<IInternalFileMatch>progress));
searchP = this.doSearch(engine, FileSearchManager.BATCH_SIZE).then(
result => {
if (config.cacheKey) {
this.expandedCacheKeys.set(config.cacheKey, result.cacheKeys);
}
c({
limitHit: result.limitHit
});
},
e,
progress => {
p(progress.map(m => this.rawMatchToSearchItem(m)));
});
}, () => {
searchPromise.cancel();
if (searchP) {
searchP.cancel();
}
});
}
clearCache(cacheKey: string, provider: vscode.SearchProvider): void {
if (!this.expandedCacheKeys.has(cacheKey)) {
return;
}
this.expandedCacheKeys.get(cacheKey).forEach(key => provider.clearCache(key));
this.expandedCacheKeys.delete(cacheKey);
}
private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch {
return {
resource: joinPath(match.base, match.relativePath)
resource: resources.joinPath(match.base, match.relativePath)
};
}
private doSortedSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, config: IRawSearchQuery): PPromise<[ISearchCompleteStats, IInternalFileMatch[]]> {
let searchPromise: PPromise<void, OneOrMore<IInternalFileMatch>>;
let allResultsPromise = new PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore<IInternalFileMatch>>((c, e, p) => {
let results: IInternalFileMatch[] = [];
searchPromise = this.doSearch(engine, provider, -1)
.then(result => {
c([result, results]);
this.telemetryCallback('fileSearch', null);
}, e, progress => {
if (Array.isArray(progress)) {
results = progress;
} else {
p(progress);
}
});
}, () => {
searchPromise.cancel();
});
let cache: Cache;
if (config.cacheKey) {
cache = this.getOrCreateCache(config.cacheKey);
cache.resultsToSearchCache[config.filePattern] = allResultsPromise;
allResultsPromise.then(null, err => {
delete cache.resultsToSearchCache[config.filePattern];
});
allResultsPromise = this.preventCancellation(allResultsPromise);
}
let chained: TPromise<void>;
return new PPromise<[ISearchCompleteStats, IInternalFileMatch[]]>((c, e, p) => {
chained = allResultsPromise.then(([result, results]) => {
const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null);
const unsortedResultTime = Date.now();
return this.sortResults(config, results, scorerCache)
.then(sortedResults => {
const sortedResultTime = Date.now();
c([{
stats: {
...result.stats,
...{ unsortedResultTime, sortedResultTime }
},
limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults
}, sortedResults]);
});
}, e, p);
}, () => {
chained.cancel();
});
}
private getOrCreateCache(cacheKey: string): Cache {
const existing = this.caches[cacheKey];
if (existing) {
return existing;
}
return this.caches[cacheKey] = new Cache();
}
private trySortedSearchFromCache(config: IRawSearchQuery): TPromise<[ISearchCompleteStats, IInternalFileMatch[]]> {
const cache = config.cacheKey && this.caches[config.cacheKey];
if (!cache) {
return undefined;
}
const cacheLookupStartTime = Date.now();
const cached = this.getResultsFromCache(cache, config.filePattern);
if (cached) {
let chained: TPromise<void>;
return new TPromise<[ISearchCompleteStats, IInternalFileMatch[]]>((c, e) => {
chained = cached.then(([result, results, cacheStats]) => {
const cacheLookupResultTime = Date.now();
return this.sortResults(config, results, cache.scorerCache)
.then(sortedResults => {
const sortedResultTime = Date.now();
const stats: ICachedSearchStats = {
fromCache: true,
cacheLookupStartTime: cacheLookupStartTime,
cacheFilterStartTime: cacheStats.cacheFilterStartTime,
cacheLookupResultTime: cacheLookupResultTime,
cacheEntryCount: cacheStats.cacheFilterResultCount,
resultCount: results.length
};
if (config.sortByScore) {
stats.unsortedResultTime = cacheLookupResultTime;
stats.sortedResultTime = sortedResultTime;
}
if (!cacheStats.cacheWasResolved) {
stats.joined = result.stats;
}
c([
{
limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults,
stats: stats
},
sortedResults
]);
});
}, e);
}, () => {
chained.cancel();
});
}
return undefined;
}
private sortResults(config: IRawSearchQuery, results: IInternalFileMatch[], scorerCache: ScorerCache): TPromise<IInternalFileMatch[]> {
// we use the same compare function that is used later when showing the results using fuzzy scoring
// this is very important because we are also limiting the number of results by config.maxResults
// and as such we want the top items to be included in this result set if the number of items
// exceeds config.maxResults.
const query = prepareQuery(config.filePattern);
const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, query, true, FileMatchItemAccessor, scorerCache);
return arrays.topAsync(results, compare, config.maxResults, 10000);
}
private sendProgress(results: IFileMatch[], progressCb: (batch: IFileMatch[]) => void, batchSize: number) {
if (batchSize && batchSize > 0) {
for (let i = 0; i < results.length; i += batchSize) {
progressCb(results.slice(i, i + batchSize));
}
} else {
progressCb(results);
}
}
private getResultsFromCache(cache: Cache, searchValue: string): PPromise<[ISearchCompleteStats, IInternalFileMatch[], CacheStats]> {
if (path.isAbsolute(searchValue)) {
return null; // bypass cache if user looks up an absolute path where matching goes directly on disk
}
// Find cache entries by prefix of search value
const hasPathSep = searchValue.indexOf(path.sep) >= 0;
let cached: PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore<IInternalFileMatch>>;
let wasResolved: boolean;
for (let previousSearch in cache.resultsToSearchCache) {
// If we narrow down, we might be able to reuse the cached results
if (strings.startsWith(searchValue, previousSearch)) {
if (hasPathSep && previousSearch.indexOf(path.sep) < 0) {
continue; // since a path character widens the search for potential more matches, require it in previous search too
}
const c = cache.resultsToSearchCache[previousSearch];
c.then(() => { wasResolved = false; });
wasResolved = true;
cached = this.preventCancellation(c);
break;
}
}
if (!cached) {
return null;
}
return new PPromise<[ISearchCompleteStats, IInternalFileMatch[], CacheStats]>((c, e, p) => {
cached.then(([complete, cachedEntries]) => {
const cacheFilterStartTime = Date.now();
// Pattern match on results
let results: IInternalFileMatch[] = [];
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
for (let i = 0; i < cachedEntries.length; i++) {
let entry = cachedEntries[i];
// Check if this entry is a match for the search value
if (!strings.fuzzyContains(entry.relativePath, normalizedSearchValueLowercase)) {
continue;
}
results.push(entry);
}
c([complete, results, {
cacheWasResolved: wasResolved,
cacheFilterStartTime: cacheFilterStartTime,
cacheFilterResultCount: cachedEntries.length
}]);
}, e, p);
}, () => {
cached.cancel();
});
}
private doSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, batchSize?: number): PPromise<ISearchCompleteStats, OneOrMore<IInternalFileMatch>> {
return new PPromise<ISearchCompleteStats, OneOrMore<IInternalFileMatch>>((c, e, p) => {
private doSearch(engine: FileSearchEngine, batchSize: number): PPromise<IInternalSearchComplete, IInternalFileMatch[]> {
return new PPromise((c, e, p) => {
let batch: IInternalFileMatch[] = [];
engine.search().then(result => {
if (batch.length) {
p(batch);
}
c({
limitHit: result.isLimitHit,
stats: engine.getStats() // TODO@roblou
});
c(result);
}, error => {
if (batch.length) {
p(batch);
@@ -1116,14 +860,10 @@ class FileSearchManager {
e(error);
}, match => {
if (match) {
if (batchSize) {
batch.push(match);
if (batchSize > 0 && batch.length >= batchSize) {
p(batch);
batch = [];
}
} else {
p(match);
batch.push(match);
if (batchSize > 0 && batch.length >= batchSize) {
p(batch);
batch = [];
}
}
});
@@ -1131,48 +871,4 @@ class FileSearchManager {
engine.cancel();
});
}
public clearCache(cacheKey: string): TPromise<void> {
delete this.caches[cacheKey];
return TPromise.as(undefined);
}
private preventCancellation<C, P>(promise: PPromise<C, P>): PPromise<C, P> {
return new PPromise<C, P>((c, e, p) => {
// Allow for piled up cancellations to come through first.
process.nextTick(() => {
promise.then(c, e, p);
});
}, () => {
// Do not propagate.
});
}
}
class Cache {
public resultsToSearchCache: { [searchValue: string]: PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore<IInternalFileMatch>>; } = Object.create(null);
public scorerCache: ScorerCache = Object.create(null);
}
const FileMatchItemAccessor = new class implements IItemAccessor<IInternalFileMatch> {
public getItemLabel(match: IInternalFileMatch): string {
return match.basename; // e.g. myFile.txt
}
public getItemDescription(match: IInternalFileMatch): string {
return match.relativePath.substr(0, match.relativePath.length - match.basename.length - 1); // e.g. some/path/to/file
}
public getItemPath(match: IInternalFileMatch): string {
return match.relativePath; // e.g. some/path/to/file/myFile.txt
}
};
interface CacheStats {
cacheWasResolved: boolean;
cacheFilterStartTime: number;
cacheFilterResultCount: number;
}

View File

@@ -860,15 +860,23 @@ export class ExtHostTask implements ExtHostTaskShape {
}
}
public $provideTasks(handle: number): TPromise<tasks.TaskSet> {
public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): TPromise<tasks.TaskSet> {
let handler = this._handlers.get(handle);
if (!handler) {
return TPromise.wrapError<tasks.TaskSet>(new Error('no handler found'));
}
return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => {
let sanitized: vscode.Task[] = [];
for (let task of value) {
if (task.definition && validTypes[task.definition.type] === true) {
sanitized.push(task);
} else {
console.error(`Dropping task [${task.source}, ${task.name}]. Its type is not known to the system.`);
}
}
let workspaceFolders = this._workspaceService.getWorkspaceFolders();
return {
tasks: Tasks.from(value, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension),
tasks: Tasks.from(sanitized, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension),
extension: handler.extension
};
});

View File

@@ -62,7 +62,7 @@ export class BaseExtHostTerminal {
request.run(this._proxy, this._id);
}
protected _runQueuedRequests(id: number): void {
public _runQueuedRequests(id: number): void {
this._id = id;
this._idPromiseComplete(id);
this._queuedRequests.forEach((r) => {
@@ -77,7 +77,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
private _pidPromiseComplete: (value: number) => any;
private readonly _onData: Emitter<string> = new Emitter<string>();
public get onData(): Event<string> {
public get onDidWriteData(): Event<string> {
// Tell the main side to start sending data if it's not already
this._proxy.$registerOnDataListener(this._id);
return this._onData && this._onData.event;
@@ -156,7 +156,7 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
}
private readonly _onInput: Emitter<string> = new Emitter<string>();
public get onInput(): Event<string> {
public get onDidAcceptInput(): Event<string> {
this._checkDisposed();
this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]);
// Tell the main side to start sending data if it's not already
@@ -179,7 +179,7 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
}
return {
rows: this._maximumDimensions.rows,
cols: this._maximumDimensions.cols
columns: this._maximumDimensions.columns
};
}
@@ -188,18 +188,19 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event;
}
public get terminal(): Promise<ExtHostTerminal> {
return this._idPromise.then(id => this._fetchTerminal(id));
public get terminal(): ExtHostTerminal {
return this._terminal;
}
constructor(
proxy: MainThreadTerminalServiceShape,
private _name: string,
private _fetchTerminal: (id: number) => Promise<ExtHostTerminal>
private _terminal: ExtHostTerminal
) {
super(proxy);
this._proxy.$createTerminalRenderer(this._name).then(id => {
this._runQueuedRequests(id);
(<any>this._terminal)._runQueuedRequests(id);
});
}
@@ -212,8 +213,11 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
this._onInput.fire(data);
}
public _setMaximumDimensions(cols: number, rows: number): void {
this._maximumDimensions = { cols, rows };
public _setMaximumDimensions(columns: number, rows: number): void {
if (this._maximumDimensions && this._maximumDimensions.columns === columns && this._maximumDimensions.rows === rows) {
return;
}
this._maximumDimensions = { columns, rows };
this._onDidChangeMaximumDimensions.fire(this.maximumDimensions);
}
}
@@ -258,8 +262,13 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
public createTerminalRenderer(name: string): vscode.TerminalRenderer {
const renderer = new ExtHostTerminalRenderer(this._proxy, name, (id) => this._getTerminalByIdEventually(id));
const terminal = new ExtHostTerminal(this._proxy, name);
terminal._setProcessId(undefined);
this._terminals.push(terminal);
const renderer = new ExtHostTerminalRenderer(this._proxy, name, terminal);
this._terminalRenderers.push(renderer);
return renderer;
}
@@ -267,18 +276,22 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
const original = this._activeTerminal;
if (id === null) {
this._activeTerminal = undefined;
} else {
const terminal = this._getTerminalById(id);
if (terminal) {
this._activeTerminal = terminal;
if (original !== this._activeTerminal) {
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
}
}
if (original !== this._activeTerminal) {
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
}
this._performTerminalIdAction(id, terminal => {
if (terminal) {
this._activeTerminal = terminal;
if (original !== this._activeTerminal) {
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
}
}
});
}
public $acceptTerminalProcessData(id: number, data: string): void {
// TODO: Queue requests, currently the first 100ms of data may get missed
const terminal = this._getTerminalById(id);
if (terminal) {
terminal._fireOnData(data);
@@ -322,16 +335,19 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
}
public $acceptTerminalProcessId(id: number, processId: number): void {
let terminal = this._getTerminalById(id);
this._performTerminalIdAction(id, terminal => terminal._setProcessId(processId));
}
private _performTerminalIdAction(id: number, callback: (terminal: ExtHostTerminal) => void): void {
let terminal = this._getTerminalById(id);
if (terminal) {
terminal._setProcessId(processId);
callback(terminal);
} else {
// Retry one more time in case the terminal has not yet been initialized.
setTimeout(() => {
terminal = this._getTerminalById(id);
if (terminal) {
terminal._setProcessId(processId);
callback(terminal);
}
}, EXT_HOST_CREATION_DELAY);
}

View File

@@ -125,7 +125,7 @@ class ExtHostTreeView<T> extends Disposable {
private elements: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
private nodes: Map<T, TreeNode> = new Map<T, TreeNode>();
private _visible: boolean = true;
private _visible: boolean = false;
get visible(): boolean { return this._visible; }
private _selectedHandles: TreeItemHandle[] = [];

View File

@@ -495,11 +495,17 @@ export class TextEdit {
}
export interface IFileOperationOptions {
overwrite?: boolean;
ignoreIfExists?: boolean;
recursive?: boolean;
}
export interface IFileOperation {
_type: 1;
from: URI;
to: URI;
options?: { overwrite?: boolean, ignoreIfExists?: boolean; };
options?: IFileOperationOptions;
}
export interface IFileTextEdit {
@@ -520,8 +526,8 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
this._edits.push({ _type: 1, from: undefined, to: uri, options });
}
deleteFile(uri: vscode.Uri): void {
this._edits.push({ _type: 1, from: uri, to: undefined });
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean }): void {
this._edits.push({ _type: 1, from: uri, to: undefined, options });
}
replace(uri: URI, range: Range, newText: string): void {
@@ -593,8 +599,8 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
return values(textEdits);
}
_allEntries(): ([URI, TextEdit[]] | [URI, URI, { overwrite?: boolean, ignoreIfExists?: boolean }])[] {
let res: ([URI, TextEdit[]] | [URI, URI, { overwrite?: boolean, ignoreIfExists?: boolean }])[] = [];
_allEntries(): ([URI, TextEdit[]] | [URI, URI, IFileOperationOptions])[] {
let res: ([URI, TextEdit[]] | [URI, URI, IFileOperationOptions])[] = [];
for (let edit of this._edits) {
if (edit._type === 1) {
res.push([edit.from, edit.to, edit.options]);

View File

@@ -4,22 +4,25 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import { Event, Emitter } from 'vs/base/common/event';
import { normalize } from 'vs/base/common/paths';
import { posix, relative, join } from 'path';
import { delta as arrayDelta } from 'vs/base/common/arrays';
import { relative, posix } from 'path';
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape, IMainContext, MainThreadMessageServiceShape } from './extHost.protocol';
import * as vscode from 'vscode';
import { compare } from 'vs/base/common/strings';
import { Emitter, Event } from 'vs/base/common/event';
import { TernarySearchTree } from 'vs/base/common/map';
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
import { normalize } from 'vs/base/common/paths';
import { isLinux } from 'vs/base/common/platform';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
import { compare } from 'vs/base/common/strings';
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { localize } from 'vs/nls';
import { Severity } from 'vs/platform/notification/common/notification';
import { ILogService } from 'vs/platform/log/common/log';
import { Severity } from 'vs/platform/notification/common/notification';
import { IQueryOptions, IRawFileMatch2 } from 'vs/platform/search/common/search';
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { Range } from 'vs/workbench/api/node/extHostTypes';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import * as vscode from 'vscode';
import { ExtHostWorkspaceShape, IMainContext, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol';
function isFolderEqual(folderA: URI, folderB: URI): boolean {
return isEqual(folderA, folderB, !isLinux);
@@ -145,6 +148,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
private readonly _activeSearchCallbacks = [];
constructor(
mainContext: IMainContext,
data: IWorkspaceData,
@@ -324,8 +329,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
// Events
this._onDidChangeWorkspace.fire(Object.freeze({
added: Object.freeze<vscode.WorkspaceFolder[]>(added),
removed: Object.freeze<vscode.WorkspaceFolder[]>(removed)
added,
removed,
}));
}
@@ -358,13 +363,70 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
}
}
const result = this._proxy.$startSearch(includePattern, includeFolder, excludePatternOrDisregardExcludes, maxResults, requestId);
const result = this._proxy.$startFileSearch(includePattern, includeFolder, excludePatternOrDisregardExcludes, maxResults, requestId);
if (token) {
token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId));
}
return result.then(data => Array.isArray(data) ? data.map(URI.revive) : []);
}
findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: string, token?: vscode.CancellationToken) {
this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId}, entryPoint: findTextInFiles`);
const requestId = ExtHostWorkspace._requestIdPool++;
const globPatternToString = (pattern: vscode.GlobPattern | string) => {
if (typeof pattern === 'string') {
return pattern;
}
return join(pattern.base, pattern.pattern);
};
const queryOptions: IQueryOptions = {
ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined,
disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined,
disregardExcludeSettings: options.exclude === null,
fileEncoding: options.encoding,
maxResults: options.maxResults,
includePattern: options.include && globPatternToString(options.include),
excludePattern: options.exclude && globPatternToString(options.exclude)
};
this._activeSearchCallbacks[requestId] = p => {
p.lineMatches.forEach(lineMatch => {
lineMatch.offsetAndLengths.forEach(offsetAndLength => {
const range = new Range(lineMatch.lineNumber, offsetAndLength[0], lineMatch.lineNumber, offsetAndLength[0] + offsetAndLength[1]);
callback({
uri: URI.revive(p.resource),
preview: { text: lineMatch.preview, match: range },
range
});
});
});
};
if (token) {
token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId));
}
return this._proxy.$startTextSearch(query, queryOptions, requestId).then(
() => {
delete this._activeSearchCallbacks[requestId];
},
err => {
delete this._activeSearchCallbacks[requestId];
return TPromise.wrapError(err);
});
}
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void {
if (this._activeSearchCallbacks[requestId]) {
this._activeSearchCallbacks[requestId](result);
}
}
saveAll(includeUntitled?: boolean): Thenable<boolean> {
return this._proxy.$saveAll(includeUntitled);
}