mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 19:18:59 +01:00
Merge branch 'master' into pr/mechatroner/55107-1
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { isMalformedFileUri } from 'vs/base/common/resources';
|
||||
import * as vscode from 'vscode';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands';
|
||||
@@ -48,6 +49,12 @@ export class OpenFolderAPICommand {
|
||||
if (!uri) {
|
||||
return executor.executeCommand('_files.pickFolderAndOpen', forceNewWindow);
|
||||
}
|
||||
let correctedUri = isMalformedFileUri(uri);
|
||||
if (correctedUri) {
|
||||
// workaround for #55916 and #55891, will be removed in 1.28
|
||||
console.warn(`'vscode.openFolder' command invoked with an invalid URI (file:// scheme missing): '${uri}'. Converted to a 'file://' URI: ${correctedUri}`);
|
||||
uri = correctedUri;
|
||||
}
|
||||
|
||||
return executor.executeCommand('_files.windowOpen', [uri], forceNewWindow);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export function createApiFactory(
|
||||
rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
||||
const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol));
|
||||
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, schemeTransformer, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics));
|
||||
const extHostLanguageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, schemeTransformer, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics, extHostLogService));
|
||||
const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures));
|
||||
const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands));
|
||||
@@ -227,7 +227,14 @@ export function createApiFactory(
|
||||
get language() { return platform.language; },
|
||||
get appName() { return product.nameLong; },
|
||||
get appRoot() { return initData.environment.appRoot; },
|
||||
get logLevel() { return extHostLogService.getLevel(); }
|
||||
get logLevel() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLogService.getLevel();
|
||||
},
|
||||
get onDidChangeLogLevel() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostLogService.onDidChangeLogLevel;
|
||||
}
|
||||
});
|
||||
|
||||
// namespace: extensions
|
||||
@@ -266,7 +273,7 @@ export function createApiFactory(
|
||||
return score(typeConverters.LanguageSelector.from(selector), document.uri, document.languageId, true);
|
||||
},
|
||||
registerCodeActionsProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerCodeActionProvider(checkSelector(selector), provider, metadata);
|
||||
return extHostLanguageFeatures.registerCodeActionProvider(checkSelector(selector), provider, extension, metadata);
|
||||
},
|
||||
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
|
||||
return extHostLanguageFeatures.registerCodeLensProvider(checkSelector(selector), provider);
|
||||
@@ -429,7 +436,7 @@ export function createApiFactory(
|
||||
return extHostOutputService.createOutputChannel(name);
|
||||
},
|
||||
createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel {
|
||||
return extHostWebviews.createWebview(viewType, title, showOptions, options, extension.extensionLocation);
|
||||
return extHostWebviews.createWebview(extension.extensionLocation, viewType, title, showOptions, options);
|
||||
},
|
||||
createTerminal(nameOrOptions: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
|
||||
if (typeof nameOrOptions === 'object') {
|
||||
@@ -437,9 +444,9 @@ export function createApiFactory(
|
||||
}
|
||||
return extHostTerminalService.createTerminal(<string>nameOrOptions, shellPath, shellArgs);
|
||||
},
|
||||
createTerminalRenderer(name: string): vscode.TerminalRenderer {
|
||||
createTerminalRenderer: proposedApiFunction(extension, (name: string) => {
|
||||
return extHostTerminalService.createTerminalRenderer(name);
|
||||
},
|
||||
}),
|
||||
registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider<any>): vscode.Disposable {
|
||||
return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider);
|
||||
},
|
||||
@@ -467,11 +474,6 @@ export function createApiFactory(
|
||||
},
|
||||
};
|
||||
|
||||
// namespace: QuickInputButtons
|
||||
const QuickInputButtons: typeof vscode.QuickInputButtons = {
|
||||
Back: extHostQuickOpen.backButton,
|
||||
};
|
||||
|
||||
// namespace: workspace
|
||||
const workspace: typeof vscode.workspace = {
|
||||
get rootPath() {
|
||||
@@ -487,7 +489,7 @@ export function createApiFactory(
|
||||
return extHostWorkspace.getWorkspaceFolders();
|
||||
},
|
||||
get name() {
|
||||
return extHostWorkspace.workspace ? extHostWorkspace.workspace.name : undefined;
|
||||
return extHostWorkspace.name;
|
||||
},
|
||||
set name(value) {
|
||||
throw errors.readonly();
|
||||
@@ -586,8 +588,18 @@ export function createApiFactory(
|
||||
registerFileSystemProvider(scheme, provider, options) {
|
||||
return extHostFileSystem.registerFileSystemProvider(scheme, provider, options);
|
||||
},
|
||||
registerSearchProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostSearch.registerSearchProvider(scheme, provider);
|
||||
registerFileSearchProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostSearch.registerFileSearchProvider(scheme, provider);
|
||||
}),
|
||||
registerSearchProvider: proposedApiFunction(extension, () => {
|
||||
// Temp for live share in Insiders
|
||||
return { dispose: () => { } };
|
||||
}),
|
||||
registerTextSearchProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostSearch.registerTextSearchProvider(scheme, provider);
|
||||
}),
|
||||
registerFileIndexProvider: proposedApiFunction(extension, (scheme, provider) => {
|
||||
return extHostSearch.registerFileIndexProvider(scheme, provider);
|
||||
}),
|
||||
registerDocumentCommentProvider: proposedApiFunction(extension, (provider: vscode.DocumentCommentProvider) => {
|
||||
return exthostCommentProviders.registerDocumentCommentProvider(provider);
|
||||
@@ -684,39 +696,48 @@ export function createApiFactory(
|
||||
version: pkg.version,
|
||||
// namespaces
|
||||
commands,
|
||||
debug,
|
||||
env,
|
||||
extensions,
|
||||
languages,
|
||||
scm,
|
||||
tasks,
|
||||
window,
|
||||
workspace,
|
||||
scm,
|
||||
debug,
|
||||
tasks,
|
||||
// types
|
||||
Breakpoint: extHostTypes.Breakpoint,
|
||||
CancellationTokenSource: CancellationTokenSource,
|
||||
CodeAction: extHostTypes.CodeAction,
|
||||
CodeActionKind: extHostTypes.CodeActionKind,
|
||||
CodeActionTrigger: extHostTypes.CodeActionTrigger,
|
||||
CodeLens: extHostTypes.CodeLens,
|
||||
Color: extHostTypes.Color,
|
||||
ColorPresentation: extHostTypes.ColorPresentation,
|
||||
ColorInformation: extHostTypes.ColorInformation,
|
||||
CodeActionTrigger: extHostTypes.CodeActionTrigger,
|
||||
EndOfLine: extHostTypes.EndOfLine,
|
||||
ColorPresentation: extHostTypes.ColorPresentation,
|
||||
CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState,
|
||||
CompletionItem: extHostTypes.CompletionItem,
|
||||
CompletionItemKind: extHostTypes.CompletionItemKind,
|
||||
CompletionList: extHostTypes.CompletionList,
|
||||
CompletionTriggerKind: extHostTypes.CompletionTriggerKind,
|
||||
ConfigurationTarget: extHostTypes.ConfigurationTarget,
|
||||
DebugAdapterExecutable: extHostTypes.DebugAdapterExecutable,
|
||||
DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior,
|
||||
Diagnostic: extHostTypes.Diagnostic,
|
||||
DiagnosticRelatedInformation: extHostTypes.DiagnosticRelatedInformation,
|
||||
DiagnosticTag: extHostTypes.DiagnosticTag,
|
||||
DiagnosticSeverity: extHostTypes.DiagnosticSeverity,
|
||||
DiagnosticTag: extHostTypes.DiagnosticTag,
|
||||
Disposable: extHostTypes.Disposable,
|
||||
DocumentHighlight: extHostTypes.DocumentHighlight,
|
||||
DocumentHighlightKind: extHostTypes.DocumentHighlightKind,
|
||||
DocumentLink: extHostTypes.DocumentLink,
|
||||
DocumentSymbol: extHostTypes.DocumentSymbol,
|
||||
EndOfLine: extHostTypes.EndOfLine,
|
||||
EventEmitter: Emitter,
|
||||
FileChangeType: extHostTypes.FileChangeType,
|
||||
FileSystemError: extHostTypes.FileSystemError,
|
||||
FileType: files.FileType,
|
||||
FoldingRange: extHostTypes.FoldingRange,
|
||||
FoldingRangeKind: extHostTypes.FoldingRangeKind,
|
||||
FunctionBreakpoint: extHostTypes.FunctionBreakpoint,
|
||||
Hover: extHostTypes.Hover,
|
||||
IndentAction: languageConfiguration.IndentAction,
|
||||
@@ -726,52 +747,41 @@ export function createApiFactory(
|
||||
OverviewRulerLane: OverviewRulerLane,
|
||||
ParameterInformation: extHostTypes.ParameterInformation,
|
||||
Position: extHostTypes.Position,
|
||||
QuickInputButtons,
|
||||
ProcessExecution: extHostTypes.ProcessExecution,
|
||||
ProgressLocation: extHostTypes.ProgressLocation,
|
||||
QuickInputButtons: extHostTypes.QuickInputButtons,
|
||||
Range: extHostTypes.Range,
|
||||
RelativePattern: extHostTypes.RelativePattern,
|
||||
Selection: extHostTypes.Selection,
|
||||
ShellExecution: extHostTypes.ShellExecution,
|
||||
ShellQuoting: extHostTypes.ShellQuoting,
|
||||
SignatureHelp: extHostTypes.SignatureHelp,
|
||||
SignatureInformation: extHostTypes.SignatureInformation,
|
||||
SnippetString: extHostTypes.SnippetString,
|
||||
SourceBreakpoint: extHostTypes.SourceBreakpoint,
|
||||
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
|
||||
StatusBarAlignment: extHostTypes.StatusBarAlignment,
|
||||
SymbolInformation: extHostTypes.SymbolInformation,
|
||||
DocumentSymbol: extHostTypes.DocumentSymbol,
|
||||
SymbolKind: extHostTypes.SymbolKind,
|
||||
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
|
||||
Task: extHostTypes.Task,
|
||||
TaskGroup: extHostTypes.TaskGroup,
|
||||
TaskPanelKind: extHostTypes.TaskPanelKind,
|
||||
TaskRevealKind: extHostTypes.TaskRevealKind,
|
||||
TaskScope: extHostTypes.TaskScope,
|
||||
TextDocumentSaveReason: extHostTypes.TextDocumentSaveReason,
|
||||
TextEdit: extHostTypes.TextEdit,
|
||||
TextEditorCursorStyle: TextEditorCursorStyle,
|
||||
TextEditorLineNumbersStyle: extHostTypes.TextEditorLineNumbersStyle,
|
||||
TextEditorRevealType: extHostTypes.TextEditorRevealType,
|
||||
TextEditorSelectionChangeKind: extHostTypes.TextEditorSelectionChangeKind,
|
||||
DecorationRangeBehavior: extHostTypes.DecorationRangeBehavior,
|
||||
ThemeColor: extHostTypes.ThemeColor,
|
||||
ThemeIcon: extHostTypes.ThemeIcon,
|
||||
TreeItem: extHostTypes.TreeItem,
|
||||
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
|
||||
Uri: URI,
|
||||
ViewColumn: extHostTypes.ViewColumn,
|
||||
WorkspaceEdit: extHostTypes.WorkspaceEdit,
|
||||
ProgressLocation: extHostTypes.ProgressLocation,
|
||||
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
|
||||
ThemeIcon: extHostTypes.ThemeIcon,
|
||||
TreeItem: extHostTypes.TreeItem,
|
||||
ThemeColor: extHostTypes.ThemeColor,
|
||||
// functions
|
||||
TaskRevealKind: extHostTypes.TaskRevealKind,
|
||||
TaskPanelKind: extHostTypes.TaskPanelKind,
|
||||
TaskGroup: extHostTypes.TaskGroup,
|
||||
ProcessExecution: extHostTypes.ProcessExecution,
|
||||
ShellExecution: extHostTypes.ShellExecution,
|
||||
ShellQuoting: extHostTypes.ShellQuoting,
|
||||
TaskScope: extHostTypes.TaskScope,
|
||||
Task: extHostTypes.Task,
|
||||
ConfigurationTarget: extHostTypes.ConfigurationTarget,
|
||||
RelativePattern: extHostTypes.RelativePattern,
|
||||
|
||||
FileChangeType: extHostTypes.FileChangeType,
|
||||
FileType: files.FileType,
|
||||
FileSystemError: extHostTypes.FileSystemError,
|
||||
FoldingRange: extHostTypes.FoldingRange,
|
||||
FoldingRangeKind: extHostTypes.FoldingRangeKind,
|
||||
|
||||
CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,10 +22,11 @@ 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 { LabelRules } from 'vs/platform/label/common/label';
|
||||
import { LogLevel } from 'vs/platform/log/common/log';
|
||||
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 { IPatternInfo, IQueryOptions, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } 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';
|
||||
@@ -46,7 +47,7 @@ export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
appRoot: string;
|
||||
appSettingsHome: string;
|
||||
extensionDevelopmentPath: string;
|
||||
extensionDevelopmentLocationURI: URI;
|
||||
extensionTestsPath: string;
|
||||
}
|
||||
|
||||
@@ -426,11 +427,17 @@ export interface MainThreadTelemetryShape extends IDisposable {
|
||||
|
||||
export type WebviewPanelHandle = string;
|
||||
|
||||
export interface WebviewPanelShowOptions {
|
||||
readonly viewColumn?: EditorViewColumn;
|
||||
readonly preserveFocus?: boolean;
|
||||
}
|
||||
|
||||
export interface MainThreadWebviewsShape extends IDisposable {
|
||||
$createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, viewOptions: { viewColumn: EditorViewColumn, preserveFocus: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions, extensionLocation: UriComponents): void;
|
||||
$createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: vscode.WebviewPanelOptions & vscode.WebviewOptions, extensionLocation: UriComponents): void;
|
||||
$disposeWebview(handle: WebviewPanelHandle): void;
|
||||
$reveal(handle: WebviewPanelHandle, viewColumn: EditorViewColumn | null, preserveFocus: boolean): void;
|
||||
$reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void;
|
||||
$setTitle(handle: WebviewPanelHandle, value: string): void;
|
||||
$setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void;
|
||||
$setHtml(handle: WebviewPanelHandle, value: string): void;
|
||||
$setOptions(handle: WebviewPanelHandle, options: vscode.WebviewOptions): void;
|
||||
$postMessage(handle: WebviewPanelHandle, value: any): Thenable<boolean>;
|
||||
@@ -464,6 +471,7 @@ export interface ExtHostUrlsShape {
|
||||
export interface MainThreadWorkspaceShape extends IDisposable {
|
||||
$startFileSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<UriComponents[]>;
|
||||
$startTextSearch(query: IPatternInfo, options: IQueryOptions, requestId: number): TPromise<void>;
|
||||
$checkExists(query: ISearchQuery, requestId: number): TPromise<boolean>;
|
||||
$cancelSearch(requestId: number): Thenable<boolean>;
|
||||
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
|
||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void>;
|
||||
@@ -477,11 +485,14 @@ export interface IFileChangeDto {
|
||||
export interface MainThreadFileSystemShape extends IDisposable {
|
||||
$registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): void;
|
||||
$unregisterProvider(handle: number): void;
|
||||
$setUriFormatter(scheme: string, formatter: LabelRules): void;
|
||||
$onFileSystemChange(handle: number, resource: IFileChangeDto[]): void;
|
||||
}
|
||||
|
||||
export interface MainThreadSearchShape extends IDisposable {
|
||||
$registerSearchProvider(handle: number, scheme: string): void;
|
||||
$registerFileSearchProvider(handle: number, scheme: string): void;
|
||||
$registerTextSearchProvider(handle: number, scheme: string): void;
|
||||
$registerFileIndexProvider(handle: number, scheme: string): void;
|
||||
$unregisterProvider(handle: number): void;
|
||||
$handleFileMatch(handle: number, session: number, data: UriComponents[]): void;
|
||||
$handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void;
|
||||
@@ -670,8 +681,8 @@ export interface ExtHostWorkspaceShape {
|
||||
export interface ExtHostFileSystemShape {
|
||||
$stat(handle: number, resource: UriComponents): TPromise<IStat>;
|
||||
$readdir(handle: number, resource: UriComponents): TPromise<[string, FileType][]>;
|
||||
$readFile(handle: number, resource: UriComponents): TPromise<string>;
|
||||
$writeFile(handle: number, resource: UriComponents, base64Encoded: string, opts: FileWriteOptions): TPromise<void>;
|
||||
$readFile(handle: number, resource: UriComponents): TPromise<Buffer>;
|
||||
$writeFile(handle: number, resource: UriComponents, content: Buffer, opts: FileWriteOptions): TPromise<void>;
|
||||
$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>;
|
||||
@@ -682,7 +693,7 @@ export interface ExtHostFileSystemShape {
|
||||
|
||||
export interface ExtHostSearchShape {
|
||||
$provideFileSearchResults(handle: number, session: number, query: IRawSearchQuery): TPromise<ISearchCompleteStats>;
|
||||
$clearCache(handle: number, cacheKey: string): TPromise<void>;
|
||||
$clearCache(cacheKey: string): TPromise<void>;
|
||||
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, query: IRawSearchQuery): TPromise<ISearchCompleteStats>;
|
||||
}
|
||||
|
||||
@@ -875,6 +886,7 @@ export interface ExtHostSCMShape {
|
||||
$onInputBoxValueChange(sourceControlHandle: number, value: string): TPromise<void>;
|
||||
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise<void>;
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined>;
|
||||
$setSelectedSourceControls(selectedSourceControlHandles: number[]): TPromise<void>;
|
||||
}
|
||||
|
||||
export interface ExtHostTaskShape {
|
||||
|
||||
@@ -11,21 +11,26 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
|
||||
interface ProviderData {
|
||||
provider: vscode.DecorationProvider;
|
||||
extensionId: string;
|
||||
}
|
||||
|
||||
export class ExtHostDecorations implements ExtHostDecorationsShape {
|
||||
|
||||
private static _handlePool = 0;
|
||||
|
||||
private readonly _provider = new Map<number, vscode.DecorationProvider>();
|
||||
private readonly _provider = new Map<number, ProviderData>();
|
||||
private readonly _proxy: MainThreadDecorationsShape;
|
||||
|
||||
constructor(mainContext: IMainContext) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadDecorations);
|
||||
}
|
||||
|
||||
registerDecorationProvider(provider: vscode.DecorationProvider, label: string): vscode.Disposable {
|
||||
registerDecorationProvider(provider: vscode.DecorationProvider, extensionId: string): vscode.Disposable {
|
||||
const handle = ExtHostDecorations._handlePool++;
|
||||
this._provider.set(handle, provider);
|
||||
this._proxy.$registerDecorationProvider(handle, label);
|
||||
this._provider.set(handle, { provider, extensionId });
|
||||
this._proxy.$registerDecorationProvider(handle, extensionId);
|
||||
|
||||
const listener = provider.onDidChangeDecorations(e => {
|
||||
this._proxy.$onDidChange(handle, !e ? null : Array.isArray(e) ? e : [e]);
|
||||
@@ -42,13 +47,16 @@ export class ExtHostDecorations implements ExtHostDecorationsShape {
|
||||
const result: DecorationReply = Object.create(null);
|
||||
return TPromise.join(requests.map(request => {
|
||||
const { handle, uri, id } = request;
|
||||
const provider = this._provider.get(handle);
|
||||
if (!provider) {
|
||||
if (!this._provider.has(handle)) {
|
||||
// might have been unregistered in the meantime
|
||||
return void 0;
|
||||
}
|
||||
const { provider, extensionId } = this._provider.get(handle);
|
||||
return asWinJsPromise(token => provider.provideDecoration(URI.revive(uri), token)).then(data => {
|
||||
result[id] = data && <DecorationData>[data.priority, data.bubble, data.title, data.abbreviation, data.color, data.source];
|
||||
if (data && data.letter && data.letter.length !== 1) {
|
||||
console.warn(`INVALID decoration from extension '${extensionId}'. The 'letter' must be set and be one character, not '${data.letter}'.`);
|
||||
}
|
||||
result[id] = data && <DecorationData>[data.priority, data.bubble, data.title, data.letter, data.color, data.source];
|
||||
}, err => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ import Severity from 'vs/base/common/severity';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtHostLogger } from 'vs/workbench/api/node/extHostLogService';
|
||||
|
||||
const hasOwnProperty = Object.hasOwnProperty;
|
||||
const NO_OP_VOID_PROMISE = TPromise.wrap<void>(void 0);
|
||||
@@ -27,8 +26,7 @@ export interface IExtensionContext {
|
||||
extensionPath: string;
|
||||
storagePath: string;
|
||||
asAbsolutePath(relativePath: string): string;
|
||||
logger: ExtHostLogger;
|
||||
readonly logDirectory: string;
|
||||
readonly logPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,7 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage';
|
||||
import { createApiFactory, initializeExtensionApi, checkProposedApiEnabled } from 'vs/workbench/api/node/extHost.api.impl';
|
||||
import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl';
|
||||
import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape, IMainContext } from './extHost.protocol';
|
||||
import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes, ExtensionActivationReason, ExtensionActivatedByEvent } from 'vs/workbench/api/node/extHostExtensionActivator';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
@@ -361,14 +361,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape {
|
||||
get extensionPath() { return extensionDescription.extensionLocation.fsPath; },
|
||||
storagePath: this._storagePath.value(extensionDescription),
|
||||
asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionLocation.fsPath, relativePath); },
|
||||
get logger() {
|
||||
checkProposedApiEnabled(extensionDescription);
|
||||
return that._extHostLogService.getExtLogger(extensionDescription.id);
|
||||
},
|
||||
get logDirectory() {
|
||||
checkProposedApiEnabled(extensionDescription);
|
||||
return that._extHostLogService.getLogDirectory(extensionDescription.id);
|
||||
}
|
||||
logPath: that._extHostLogService.getLogDirectory(extensionDescription.id)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { values } from 'vs/base/common/map';
|
||||
import { Range, FileChangeType } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { LabelRules } from 'vs/platform/label/common/label';
|
||||
|
||||
class FsLinkProvider implements vscode.DocumentLinkProvider {
|
||||
|
||||
@@ -141,44 +142,48 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
});
|
||||
}
|
||||
|
||||
setUriFormatter(scheme: string, formatter: LabelRules): void {
|
||||
this._proxy.$setUriFormatter(scheme, formatter);
|
||||
}
|
||||
|
||||
private static _asIStat(stat: vscode.FileStat): files.IStat {
|
||||
const { type, ctime, mtime, size } = stat;
|
||||
return { type, ctime, mtime, size };
|
||||
}
|
||||
|
||||
$stat(handle: number, resource: UriComponents): TPromise<files.IStat, any> {
|
||||
$stat(handle: number, resource: UriComponents): TPromise<files.IStat> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat);
|
||||
}
|
||||
|
||||
$readdir(handle: number, resource: UriComponents): TPromise<[string, files.FileType][], any> {
|
||||
$readdir(handle: number, resource: UriComponents): TPromise<[string, files.FileType][]> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).readDirectory(URI.revive(resource)));
|
||||
}
|
||||
|
||||
$readFile(handle: number, resource: UriComponents): TPromise<string> {
|
||||
$readFile(handle: number, resource: UriComponents): TPromise<Buffer> {
|
||||
return asWinJsPromise(() => {
|
||||
return this._fsProvider.get(handle).readFile(URI.revive(resource));
|
||||
}).then(data => {
|
||||
return Buffer.isBuffer(data) ? data.toString('base64') : Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('base64');
|
||||
return Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
||||
});
|
||||
}
|
||||
|
||||
$writeFile(handle: number, resource: UriComponents, base64Content: string, opts: files.FileWriteOptions): TPromise<void, any> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), opts));
|
||||
$writeFile(handle: number, resource: UriComponents, content: Buffer, opts: files.FileWriteOptions): TPromise<void> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).writeFile(URI.revive(resource), content, opts));
|
||||
}
|
||||
|
||||
$delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): TPromise<void, any> {
|
||||
$delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): TPromise<void> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).delete(URI.revive(resource), opts));
|
||||
}
|
||||
|
||||
$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise<void, any> {
|
||||
$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise<void> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts));
|
||||
}
|
||||
|
||||
$copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise<void, any> {
|
||||
$copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise<void> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).copy(URI.revive(oldUri), URI.revive(newUri), opts));
|
||||
}
|
||||
|
||||
$mkdir(handle: number, resource: UriComponents): TPromise<void, any> {
|
||||
$mkdir(handle: number, resource: UriComponents): TPromise<void> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).createDirectory(URI.revive(resource)));
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
// --- adapter
|
||||
|
||||
@@ -273,7 +274,9 @@ class CodeActionAdapter {
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _commands: CommandsConverter,
|
||||
private readonly _diagnostics: ExtHostDiagnostics,
|
||||
private readonly _provider: vscode.CodeActionProvider
|
||||
private readonly _provider: vscode.CodeActionProvider,
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _extensionId: string
|
||||
) { }
|
||||
|
||||
provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext): TPromise<CodeActionDto[]> {
|
||||
@@ -314,6 +317,14 @@ class CodeActionAdapter {
|
||||
command: this._commands.toInternal(candidate),
|
||||
});
|
||||
} else {
|
||||
if (codeActionContext.only) {
|
||||
if (!candidate.kind) {
|
||||
this._logService.warn(`${this._extensionId} - Code actions of kind '${codeActionContext.only.value} 'requested but returned code action does not have a 'kind'. Code action will be dropped. Please set 'CodeAction.kind'.`);
|
||||
} else if (!codeActionContext.only.contains(candidate.kind)) {
|
||||
this._logService.warn(`${this._extensionId} -Code actions of kind '${codeActionContext.only.value} 'requested but returned code action is of kind '${candidate.kind.value}'. Code action will be dropped. Please check 'CodeActionContext.only' to only return requested code actions.`);
|
||||
}
|
||||
}
|
||||
|
||||
// new school: convert code action
|
||||
result.push({
|
||||
title: candidate.title,
|
||||
@@ -838,6 +849,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
private _heapService: ExtHostHeapService;
|
||||
private _diagnostics: ExtHostDiagnostics;
|
||||
private _adapter = new Map<number, Adapter>();
|
||||
private readonly _logService: ILogService;
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
@@ -845,7 +857,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
documents: ExtHostDocuments,
|
||||
commands: ExtHostCommands,
|
||||
heapMonitor: ExtHostHeapService,
|
||||
diagnostics: ExtHostDiagnostics
|
||||
diagnostics: ExtHostDiagnostics,
|
||||
logService: ILogService
|
||||
) {
|
||||
this._schemeTransformer = schemeTransformer;
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageFeatures);
|
||||
@@ -853,6 +866,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
this._commands = commands;
|
||||
this._heapService = heapMonitor;
|
||||
this._diagnostics = diagnostics;
|
||||
this._logService = logService;
|
||||
}
|
||||
|
||||
private _transformDocumentSelector(selector: vscode.DocumentSelector): ISerializedDocumentFilter[] {
|
||||
@@ -1024,8 +1038,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
|
||||
// --- quick fix
|
||||
|
||||
registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider));
|
||||
registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, extension?: IExtensionDescription, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider, this._logService, extension ? extension.id : ''));
|
||||
this._proxy.$registerQuickFixSupport(handle, this._transformDocumentSelector(selector), metadata && metadata.providedCodeActionKinds ? metadata.providedCodeActionKinds.map(kind => kind.value) : undefined);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { join } from 'vs/base/common/paths';
|
||||
import { LogLevel } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { ILogService, DelegatedLogService } from 'vs/platform/log/common/log';
|
||||
@@ -14,8 +13,6 @@ import { ExtHostLogServiceShape } from 'vs/workbench/api/node/extHost.protocol';
|
||||
|
||||
export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape {
|
||||
|
||||
private _loggers: Map<string, ExtHostLogger> = new Map();
|
||||
|
||||
constructor(
|
||||
private _windowId: number,
|
||||
logLevel: LogLevel,
|
||||
@@ -28,54 +25,7 @@ export class ExtHostLogService extends DelegatedLogService implements ILogServic
|
||||
this.setLevel(level);
|
||||
}
|
||||
|
||||
getExtLogger(extensionID: string): ExtHostLogger {
|
||||
let logger = this._loggers.get(extensionID);
|
||||
if (!logger) {
|
||||
logger = this.createLogger(extensionID);
|
||||
this._loggers.set(extensionID, logger);
|
||||
}
|
||||
return logger;
|
||||
}
|
||||
|
||||
getLogDirectory(extensionID: string): string {
|
||||
return join(this._logsPath, `${extensionID}_${this._windowId}`);
|
||||
}
|
||||
|
||||
private createLogger(extensionID: string): ExtHostLogger {
|
||||
const logsDirPath = this.getLogDirectory(extensionID);
|
||||
const logService = createSpdLogService(extensionID, this.getLevel(), logsDirPath);
|
||||
this._register(this.onDidChangeLogLevel(level => logService.setLevel(level)));
|
||||
return new ExtHostLogger(logService);
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtHostLogger implements vscode.Logger {
|
||||
|
||||
constructor(
|
||||
private readonly _logService: ILogService
|
||||
) { }
|
||||
|
||||
trace(message: string, ...args: any[]): void {
|
||||
return this._logService.trace(message, ...args);
|
||||
}
|
||||
|
||||
debug(message: string, ...args: any[]): void {
|
||||
return this._logService.debug(message, ...args);
|
||||
}
|
||||
|
||||
info(message: string, ...args: any[]): void {
|
||||
return this._logService.info(message, ...args);
|
||||
}
|
||||
|
||||
warn(message: string, ...args: any[]): void {
|
||||
return this._logService.warn(message, ...args);
|
||||
}
|
||||
|
||||
error(message: string | Error, ...args: any[]): void {
|
||||
return this._logService.error(message, ...args);
|
||||
}
|
||||
|
||||
critical(message: string | Error, ...args: any[]): void {
|
||||
return this._logService.critical(message, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,9 +14,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
|
||||
import { InputBox, InputBoxOptions, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode';
|
||||
import { ExtHostQuickOpenShape, IMainContext, MainContext, MainThreadQuickOpenShape, TransferQuickPickItems, TransferQuickInput, TransferQuickInputButton } from './extHost.protocol';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { ThemeIcon } from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
const backButton: QuickInputButton = { iconPath: 'back.svg' };
|
||||
import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
export type Item = string | QuickPickItem;
|
||||
|
||||
@@ -153,8 +151,6 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape {
|
||||
|
||||
// ---- QuickInput
|
||||
|
||||
backButton = backButton;
|
||||
|
||||
createQuickPick<T extends QuickPickItem>(extensionId: string): QuickPick<T> {
|
||||
const session = new ExtHostQuickPick(this._proxy, extensionId, () => this._sessions.delete(session._id));
|
||||
this._sessions.set(session._id, session);
|
||||
@@ -328,14 +324,14 @@ class ExtHostQuickInput implements QuickInput {
|
||||
this._buttons = buttons.slice();
|
||||
this._handlesToButtons.clear();
|
||||
buttons.forEach((button, i) => {
|
||||
const handle = button === backButton ? -1 : i;
|
||||
const handle = button === QuickInputButtons.Back ? -1 : i;
|
||||
this._handlesToButtons.set(handle, button);
|
||||
});
|
||||
this.update({
|
||||
buttons: buttons.map<TransferQuickInputButton>((button, i) => ({
|
||||
iconPath: getIconUris(button.iconPath),
|
||||
tooltip: button.tooltip,
|
||||
handle: button === backButton ? -1 : i,
|
||||
handle: button === QuickInputButtons.Back ? -1 : i,
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
@@ -395,6 +395,15 @@ class ExtHostSourceControl implements vscode.SourceControl {
|
||||
this._proxy.$updateSourceControl(this.handle, { statusBarCommands: internal });
|
||||
}
|
||||
|
||||
private _selected: boolean = false;
|
||||
|
||||
get selected(): boolean {
|
||||
return this._selected;
|
||||
}
|
||||
|
||||
private _onDidChangeSelection = new Emitter<boolean>();
|
||||
readonly onDidChangeSelection = this._onDidChangeSelection.event;
|
||||
|
||||
private handle: number = ExtHostSourceControl._handlePool++;
|
||||
|
||||
constructor(
|
||||
@@ -454,6 +463,11 @@ class ExtHostSourceControl implements vscode.SourceControl {
|
||||
return this._groups.get(handle);
|
||||
}
|
||||
|
||||
setSelectionState(selected: boolean): void {
|
||||
this._selected = selected;
|
||||
this._onDidChangeSelection.fire(selected);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._groups.forEach(group => group.dispose());
|
||||
this._proxy.$unregisterSourceControl(this.handle);
|
||||
@@ -471,6 +485,8 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
private _onDidChangeActiveProvider = new Emitter<vscode.SourceControl>();
|
||||
get onDidChangeActiveProvider(): Event<vscode.SourceControl> { return this._onDidChangeActiveProvider.event; }
|
||||
|
||||
private _selectedSourceControlHandles = new Set<number>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
private _commands: ExtHostCommands,
|
||||
@@ -607,4 +623,41 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
return TPromise.as<[string, number]>([result.message, result.type]);
|
||||
});
|
||||
}
|
||||
|
||||
$setSelectedSourceControls(selectedSourceControlHandles: number[]): TPromise<void> {
|
||||
this.logService.trace('ExtHostSCM#$setSelectedSourceControls', selectedSourceControlHandles);
|
||||
|
||||
const set = new Set<number>();
|
||||
|
||||
for (const handle of selectedSourceControlHandles) {
|
||||
set.add(handle);
|
||||
}
|
||||
|
||||
set.forEach(handle => {
|
||||
if (!this._selectedSourceControlHandles.has(handle)) {
|
||||
const sourceControl = this._sourceControls.get(handle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourceControl.setSelectionState(true);
|
||||
}
|
||||
});
|
||||
|
||||
this._selectedSourceControlHandles.forEach(handle => {
|
||||
if (!set.has(handle)) {
|
||||
const sourceControl = this._sourceControls.get(handle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return;
|
||||
}
|
||||
|
||||
sourceControl.setSelectionState(false);
|
||||
}
|
||||
});
|
||||
|
||||
this._selectedSourceControlHandles = set;
|
||||
return TPromise.as(null);
|
||||
}
|
||||
}
|
||||
|
||||
730
src/vs/workbench/api/node/extHostSearch.fileIndex.ts
Normal file
730
src/vs/workbench/api/node/extHostSearch.fileIndex.ts
Normal file
@@ -0,0 +1,730 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
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 { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
import { ICachedSearchStats, IFileMatch, IFolderQuery, IRawSearchQuery, ISearchCompleteStats, ISearchQuery, IFileSearchStats, IFileIndexProviderStats } from 'vs/platform/search/common/search';
|
||||
import * as vscode from 'vscode';
|
||||
import { canceled } from 'vs/base/common/errors';
|
||||
|
||||
export interface IInternalFileMatch {
|
||||
base: URI;
|
||||
original?: URI;
|
||||
relativePath?: string; // Not present for extraFiles or absolute path matches
|
||||
basename: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the patterns that the provider handles. Discards sibling clauses and 'false' patterns
|
||||
*/
|
||||
export function resolvePatternsForProvider(globalPattern: glob.IExpression, folderPattern: glob.IExpression): string[] {
|
||||
const merged = {
|
||||
...(globalPattern || {}),
|
||||
...(folderPattern || {})
|
||||
};
|
||||
|
||||
return Object.keys(merged)
|
||||
.filter(key => {
|
||||
const value = merged[key];
|
||||
return typeof value === 'boolean' && value;
|
||||
});
|
||||
}
|
||||
|
||||
export class QueryGlobTester {
|
||||
|
||||
private _excludeExpression: glob.IExpression;
|
||||
private _parsedExcludeExpression: glob.ParsedExpression;
|
||||
|
||||
private _parsedIncludeExpression: glob.ParsedExpression;
|
||||
|
||||
constructor(config: ISearchQuery, folderQuery: IFolderQuery) {
|
||||
this._excludeExpression = {
|
||||
...(config.excludePattern || {}),
|
||||
...(folderQuery.excludePattern || {})
|
||||
};
|
||||
this._parsedExcludeExpression = glob.parse(this._excludeExpression);
|
||||
|
||||
// Empty includeExpression means include nothing, so no {} shortcuts
|
||||
let includeExpression: glob.IExpression = config.includePattern;
|
||||
if (folderQuery.includePattern) {
|
||||
if (includeExpression) {
|
||||
includeExpression = {
|
||||
...includeExpression,
|
||||
...folderQuery.includePattern
|
||||
};
|
||||
} else {
|
||||
includeExpression = folderQuery.includePattern;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeExpression) {
|
||||
this._parsedIncludeExpression = glob.parse(includeExpression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guaranteed sync - siblingsFn should not return a promise.
|
||||
*/
|
||||
public includedInQuerySync(testPath: string, basename?: string, hasSibling?: (name: string) => boolean): boolean {
|
||||
if (this._parsedExcludeExpression && this._parsedExcludeExpression(testPath, basename, hasSibling)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._parsedIncludeExpression && !this._parsedIncludeExpression(testPath, basename, hasSibling)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guaranteed async.
|
||||
*/
|
||||
public includedInQuery(testPath: string, basename?: string, hasSibling?: (name: string) => boolean | TPromise<boolean>): TPromise<boolean> {
|
||||
const excludeP = this._parsedExcludeExpression ?
|
||||
TPromise.as(this._parsedExcludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
|
||||
TPromise.wrap(false);
|
||||
|
||||
return excludeP.then(excluded => {
|
||||
if (excluded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._parsedIncludeExpression ?
|
||||
TPromise.as(this._parsedIncludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
|
||||
TPromise.wrap(true);
|
||||
}).then(included => {
|
||||
return included;
|
||||
});
|
||||
}
|
||||
|
||||
public hasSiblingExcludeClauses(): boolean {
|
||||
return hasSiblingClauses(this._excludeExpression);
|
||||
}
|
||||
}
|
||||
|
||||
function hasSiblingClauses(pattern: glob.IExpression): boolean {
|
||||
for (let key in pattern) {
|
||||
if (typeof pattern[key] !== 'boolean') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export interface IDirectoryEntry {
|
||||
base: URI;
|
||||
relativePath: string;
|
||||
basename: string;
|
||||
}
|
||||
|
||||
export interface IDirectoryTree {
|
||||
rootEntries: IDirectoryEntry[];
|
||||
pathToEntries: { [relativePath: string]: IDirectoryEntry[] };
|
||||
}
|
||||
|
||||
interface IInternalSearchComplete<T = IFileSearchStats> {
|
||||
limitHit: boolean;
|
||||
results: IInternalFileMatch[];
|
||||
stats: T;
|
||||
}
|
||||
|
||||
export class FileIndexSearchEngine {
|
||||
private filePattern: string;
|
||||
private normalizedFilePatternLowercase: string;
|
||||
private includePattern: glob.ParsedExpression;
|
||||
private maxResults: number;
|
||||
private exists: boolean;
|
||||
private isLimitHit: boolean;
|
||||
private resultCount: number;
|
||||
private isCanceled: boolean;
|
||||
|
||||
private filesWalked = 0;
|
||||
private dirsWalked = 0;
|
||||
|
||||
private activeCancellationTokens: Set<CancellationTokenSource>;
|
||||
|
||||
private globalExcludePattern: glob.ParsedExpression;
|
||||
|
||||
constructor(private config: ISearchQuery, private provider: vscode.FileIndexProvider) {
|
||||
this.filePattern = config.filePattern;
|
||||
this.includePattern = config.includePattern && glob.parse(config.includePattern);
|
||||
this.maxResults = config.maxResults || null;
|
||||
this.exists = config.exists;
|
||||
this.resultCount = 0;
|
||||
this.isLimitHit = false;
|
||||
this.activeCancellationTokens = new Set<CancellationTokenSource>();
|
||||
|
||||
if (this.filePattern) {
|
||||
this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase();
|
||||
}
|
||||
|
||||
this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.isCanceled = true;
|
||||
this.activeCancellationTokens.forEach(t => t.cancel());
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(_onResult: (match: IInternalFileMatch) => void): TPromise<{ isLimitHit: boolean, stats: IFileIndexProviderStats }> {
|
||||
if (this.config.folderQueries.length !== 1) {
|
||||
throw new Error('Searches just one folder');
|
||||
}
|
||||
|
||||
// Searches a single folder
|
||||
const folderQuery = this.config.folderQueries[0];
|
||||
|
||||
return new TPromise<{ isLimitHit: boolean, stats: IFileIndexProviderStats }>((resolve, reject) => {
|
||||
const onResult = (match: IInternalFileMatch) => {
|
||||
this.resultCount++;
|
||||
_onResult(match);
|
||||
};
|
||||
|
||||
if (this.isCanceled) {
|
||||
throw canceled();
|
||||
}
|
||||
|
||||
// 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 });
|
||||
});
|
||||
}
|
||||
|
||||
return this.searchInFolder(folderQuery, _onResult)
|
||||
.then(stats => {
|
||||
resolve({
|
||||
isLimitHit: this.isLimitHit,
|
||||
stats
|
||||
});
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
.filter(msg => !!msg)[0];
|
||||
|
||||
reject(new Error(errMsg));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private searchInFolder(fq: IFolderQuery<URI>, onResult: (match: IInternalFileMatch) => void): TPromise<IFileIndexProviderStats> {
|
||||
let cancellation = new CancellationTokenSource();
|
||||
return new TPromise((resolve, reject) => {
|
||||
const options = this.getSearchOptionsForFolder(fq);
|
||||
const tree = this.initDirectoryTree();
|
||||
|
||||
const queryTester = new QueryGlobTester(this.config, fq);
|
||||
const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses();
|
||||
|
||||
const onProviderResult = (uri: URI) => {
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO@rob - ???
|
||||
const relativePath = path.relative(fq.folder.path, uri.path);
|
||||
if (noSiblingsClauses) {
|
||||
const basename = path.basename(uri.path);
|
||||
this.matchFile(onResult, { base: fq.folder, relativePath, basename, original: uri });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Optimize siblings clauses with ripgrep here.
|
||||
this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
|
||||
};
|
||||
|
||||
let providerSW: StopWatch;
|
||||
let providerTime: number;
|
||||
let fileWalkTime: number;
|
||||
new TPromise(resolve => process.nextTick(resolve))
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.add(cancellation);
|
||||
providerSW = StopWatch.create();
|
||||
return this.provider.provideFileIndex(options, cancellation.token);
|
||||
})
|
||||
.then(results => {
|
||||
providerTime = providerSW.elapsed();
|
||||
const postProcessSW = StopWatch.create();
|
||||
this.activeCancellationTokens.delete(cancellation);
|
||||
if (this.isCanceled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
results.forEach(onProviderResult);
|
||||
|
||||
this.matchDirectoryTree(tree, queryTester, onResult);
|
||||
fileWalkTime = postProcessSW.elapsed();
|
||||
return null;
|
||||
}).then(
|
||||
() => {
|
||||
cancellation.dispose();
|
||||
resolve(<IFileIndexProviderStats>{
|
||||
providerTime,
|
||||
fileWalkTime,
|
||||
directoriesWalked: this.dirsWalked,
|
||||
filesWalked: this.filesWalked
|
||||
});
|
||||
},
|
||||
err => {
|
||||
cancellation.dispose();
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private getSearchOptionsForFolder(fq: IFolderQuery<URI>): vscode.FileIndexOptions {
|
||||
const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern);
|
||||
const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern);
|
||||
|
||||
return {
|
||||
folder: fq.folder,
|
||||
excludes,
|
||||
includes,
|
||||
useIgnoreFiles: !this.config.disregardIgnoreFiles,
|
||||
followSymlinks: !this.config.ignoreSymlinks
|
||||
};
|
||||
}
|
||||
|
||||
private initDirectoryTree(): IDirectoryTree {
|
||||
const tree: IDirectoryTree = {
|
||||
rootEntries: [],
|
||||
pathToEntries: Object.create(null)
|
||||
};
|
||||
tree.pathToEntries['.'] = tree.rootEntries;
|
||||
return tree;
|
||||
}
|
||||
|
||||
private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: URI, relativeFile: string, onResult: (result: IInternalFileMatch) => void) {
|
||||
// Support relative paths to files from a root resource (ignores excludes)
|
||||
if (relativeFile === this.filePattern) {
|
||||
const basename = path.basename(this.filePattern);
|
||||
this.matchFile(onResult, { base: base, relativePath: this.filePattern, basename });
|
||||
}
|
||||
|
||||
function add(relativePath: string) {
|
||||
const basename = path.basename(relativePath);
|
||||
const dirname = path.dirname(relativePath);
|
||||
let entries = pathToEntries[dirname];
|
||||
if (!entries) {
|
||||
entries = pathToEntries[dirname] = [];
|
||||
add(dirname);
|
||||
}
|
||||
entries.push({
|
||||
base,
|
||||
relativePath,
|
||||
basename
|
||||
});
|
||||
}
|
||||
|
||||
add(relativeFile);
|
||||
}
|
||||
|
||||
private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) {
|
||||
const self = this;
|
||||
const filePattern = this.filePattern;
|
||||
function matchDirectory(entries: IDirectoryEntry[]) {
|
||||
self.dirsWalked++;
|
||||
for (let i = 0, n = entries.length; i < n; i++) {
|
||||
const entry = entries[i];
|
||||
const { relativePath, basename } = entry;
|
||||
|
||||
// Check exclude pattern
|
||||
// If the user searches for the exact file name, we adjust the glob matching
|
||||
// to ignore filtering by siblings because the user seems to know what she
|
||||
// is searching for and we want to include the result in that case anyway
|
||||
const hasSibling = glob.hasSiblingFn(() => entries.map(entry => entry.basename));
|
||||
if (!queryTester.includedInQuerySync(relativePath, basename, filePattern !== basename ? hasSibling : undefined)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sub = pathToEntries[relativePath];
|
||||
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
|
||||
}
|
||||
|
||||
self.matchFile(onResult, entry);
|
||||
}
|
||||
|
||||
if (self.isLimitHit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
matchDirectory(rootEntries);
|
||||
}
|
||||
|
||||
private matchFile(onResult: (result: IInternalFileMatch) => void, candidate: IInternalFileMatch): void {
|
||||
if (this.isFilePatternMatch(candidate.relativePath) && (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename))) {
|
||||
if (this.exists || (this.maxResults && this.resultCount >= this.maxResults)) {
|
||||
this.isLimitHit = true;
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
if (!this.isLimitHit) {
|
||||
onResult(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isFilePatternMatch(path: string): boolean {
|
||||
// Check for search pattern
|
||||
if (this.filePattern) {
|
||||
if (this.filePattern === '*') {
|
||||
return true; // support the all-matching wildcard
|
||||
}
|
||||
|
||||
return strings.fuzzyContains(path, this.normalizedFilePatternLowercase);
|
||||
}
|
||||
|
||||
// No patterns means we match all
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class FileIndexSearchManager {
|
||||
|
||||
private static readonly BATCH_SIZE = 512;
|
||||
|
||||
private caches: { [cacheKey: string]: Cache; } = Object.create(null);
|
||||
|
||||
private readonly folderCacheKeys = new Map<string, Set<string>>();
|
||||
|
||||
public fileSearch(config: ISearchQuery, provider: vscode.FileIndexProvider, onBatch: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> {
|
||||
if (config.sortByScore) {
|
||||
let sortedSearch = this.trySortedSearchFromCache(config);
|
||||
if (!sortedSearch) {
|
||||
const engineConfig = config.maxResults ?
|
||||
{
|
||||
...config,
|
||||
...{ maxResults: null }
|
||||
} :
|
||||
config;
|
||||
|
||||
const engine = new FileIndexSearchEngine(engineConfig, provider);
|
||||
sortedSearch = this.doSortedSearch(engine, config);
|
||||
}
|
||||
|
||||
return new TPromise<ISearchCompleteStats>((c, e) => {
|
||||
sortedSearch.then(complete => {
|
||||
this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE);
|
||||
c(complete);
|
||||
}, e);
|
||||
}, () => {
|
||||
sortedSearch.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
const engine = new FileIndexSearchEngine(config, provider);
|
||||
return this.doSearch(engine)
|
||||
.then(complete => {
|
||||
this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE);
|
||||
return <ISearchCompleteStats>{
|
||||
limitHit: complete.limitHit,
|
||||
stats: {
|
||||
type: 'fileIndexProver',
|
||||
detailStats: complete.stats,
|
||||
fromCache: false,
|
||||
resultCount: complete.results.length
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getFolderCacheKey(config: ISearchQuery): string {
|
||||
const uri = config.folderQueries[0].folder.toString();
|
||||
const folderCacheKey = config.cacheKey && `${uri}_${config.cacheKey}`;
|
||||
if (!this.folderCacheKeys.get(config.cacheKey)) {
|
||||
this.folderCacheKeys.set(config.cacheKey, new Set());
|
||||
}
|
||||
|
||||
this.folderCacheKeys.get(config.cacheKey).add(folderCacheKey);
|
||||
|
||||
return folderCacheKey;
|
||||
}
|
||||
|
||||
private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch {
|
||||
return {
|
||||
resource: match.original || resources.joinPath(match.base, match.relativePath)
|
||||
};
|
||||
}
|
||||
|
||||
private doSortedSearch(engine: FileIndexSearchEngine, config: ISearchQuery): TPromise<IInternalSearchComplete> {
|
||||
let searchPromise: TPromise<void>;
|
||||
let allResultsPromise = new TPromise<IInternalSearchComplete<IFileIndexProviderStats>>((c, e) => {
|
||||
searchPromise = this.doSearch(engine).then(c, e);
|
||||
}, () => {
|
||||
searchPromise.cancel();
|
||||
});
|
||||
|
||||
const folderCacheKey = this.getFolderCacheKey(config);
|
||||
let cache: Cache;
|
||||
if (folderCacheKey) {
|
||||
cache = this.getOrCreateCache(folderCacheKey);
|
||||
const cacheRow: ICacheRow = {
|
||||
promise: allResultsPromise,
|
||||
resolved: false
|
||||
};
|
||||
cache.resultsToSearchCache[config.filePattern] = cacheRow;
|
||||
allResultsPromise.then(() => {
|
||||
cacheRow.resolved = true;
|
||||
}, err => {
|
||||
delete cache.resultsToSearchCache[config.filePattern];
|
||||
});
|
||||
allResultsPromise = this.preventCancellation(allResultsPromise);
|
||||
}
|
||||
|
||||
let chained: TPromise<void>;
|
||||
return new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
chained = allResultsPromise.then(complete => {
|
||||
const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null);
|
||||
const sortSW = (typeof config.maxResults !== 'number' || config.maxResults > 0) && StopWatch.create();
|
||||
return this.sortResults(config, complete.results, scorerCache)
|
||||
.then(sortedResults => {
|
||||
// sortingTime: -1 indicates a "sorted" search that was not sorted, i.e. populating the cache when quickopen is opened.
|
||||
// Contrasting with findFiles which is not sorted and will have sortingTime: undefined
|
||||
const sortingTime = sortSW ? sortSW.elapsed() : -1;
|
||||
c(<IInternalSearchComplete>{
|
||||
limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults, // ??
|
||||
results: sortedResults,
|
||||
stats: {
|
||||
detailStats: complete.stats,
|
||||
fromCache: false,
|
||||
resultCount: sortedResults.length,
|
||||
sortingTime,
|
||||
type: 'fileIndexProver'
|
||||
}
|
||||
});
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
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: ISearchQuery): TPromise<IInternalSearchComplete> {
|
||||
const folderCacheKey = this.getFolderCacheKey(config);
|
||||
const cache = folderCacheKey && this.caches[folderCacheKey];
|
||||
if (!cache) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cached = this.getResultsFromCache(cache, config.filePattern);
|
||||
if (cached) {
|
||||
let chained: TPromise<void>;
|
||||
return new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
chained = cached.then(complete => {
|
||||
const sortSW = StopWatch.create();
|
||||
return this.sortResults(config, complete.results, cache.scorerCache)
|
||||
.then(sortedResults => {
|
||||
c(<IInternalSearchComplete<IFileSearchStats>>{
|
||||
limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults,
|
||||
results: sortedResults,
|
||||
stats: {
|
||||
fromCache: true,
|
||||
detailStats: complete.stats,
|
||||
type: 'fileIndexProver',
|
||||
resultCount: sortedResults.length,
|
||||
sortingTime: sortSW.elapsed()
|
||||
}
|
||||
});
|
||||
});
|
||||
}, 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 sendAsBatches(rawMatches: IInternalFileMatch[], onBatch: (batch: IFileMatch[]) => void, batchSize: number) {
|
||||
const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch));
|
||||
if (batchSize && batchSize > 0) {
|
||||
for (let i = 0; i < serializedMatches.length; i += batchSize) {
|
||||
onBatch(serializedMatches.slice(i, i + batchSize));
|
||||
}
|
||||
} else {
|
||||
onBatch(serializedMatches);
|
||||
}
|
||||
}
|
||||
|
||||
private getResultsFromCache(cache: Cache, searchValue: string): TPromise<IInternalSearchComplete<ICachedSearchStats>> {
|
||||
const cacheLookupSW = StopWatch.create();
|
||||
|
||||
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 cacheRow: ICacheRow;
|
||||
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 row = cache.resultsToSearchCache[previousSearch];
|
||||
cacheRow = {
|
||||
promise: this.preventCancellation(row.promise),
|
||||
resolved: row.resolved
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cacheRow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheLookupTime = cacheLookupSW.elapsed();
|
||||
const cacheFilterSW = StopWatch.create();
|
||||
|
||||
return new TPromise<IInternalSearchComplete<ICachedSearchStats>>((c, e) => {
|
||||
cacheRow.promise.then(complete => {
|
||||
// Pattern match on results
|
||||
let results: IInternalFileMatch[] = [];
|
||||
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
|
||||
for (let i = 0; i < complete.results.length; i++) {
|
||||
let entry = complete.results[i];
|
||||
|
||||
// Check if this entry is a match for the search value
|
||||
if (!strings.fuzzyContains(entry.relativePath, normalizedSearchValueLowercase)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push(entry);
|
||||
}
|
||||
|
||||
c(<IInternalSearchComplete<ICachedSearchStats>>{
|
||||
limitHit: complete.limitHit,
|
||||
results,
|
||||
stats: {
|
||||
cacheWasResolved: cacheRow.resolved,
|
||||
cacheLookupTime,
|
||||
cacheFilterTime: cacheFilterSW.elapsed(),
|
||||
cacheEntryCount: complete.results.length
|
||||
}
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
cacheRow.promise.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private doSearch(engine: FileIndexSearchEngine): TPromise<IInternalSearchComplete<IFileIndexProviderStats>> {
|
||||
const results: IInternalFileMatch[] = [];
|
||||
const onResult = match => results.push(match);
|
||||
return new TPromise<IInternalSearchComplete<IFileIndexProviderStats>>((c, e) => {
|
||||
engine.search(onResult).then(result => {
|
||||
c(<IInternalSearchComplete<IFileIndexProviderStats>>{
|
||||
limitHit: result.isLimitHit,
|
||||
results,
|
||||
stats: result.stats
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
engine.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
public clearCache(cacheKey: string): TPromise<void> {
|
||||
if (!this.folderCacheKeys.has(cacheKey)) {
|
||||
return TPromise.wrap(undefined);
|
||||
}
|
||||
|
||||
const expandedKeys = this.folderCacheKeys.get(cacheKey);
|
||||
expandedKeys.forEach(key => delete this.caches[key]);
|
||||
|
||||
this.folderCacheKeys.delete(cacheKey);
|
||||
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
private preventCancellation<C>(promise: TPromise<C>): TPromise<C> {
|
||||
return new TPromise<C>((c, e) => {
|
||||
// Allow for piled up cancellations to come through first.
|
||||
process.nextTick(() => {
|
||||
promise.then(c, e);
|
||||
});
|
||||
}, () => {
|
||||
// Do not propagate.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface ICacheRow {
|
||||
promise: TPromise<IInternalSearchComplete<IFileIndexProviderStats>>;
|
||||
resolved: boolean;
|
||||
}
|
||||
|
||||
class Cache {
|
||||
|
||||
public resultsToSearchCache: { [searchValue: string]: ICacheRow; } = 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
|
||||
}
|
||||
};
|
||||
@@ -5,17 +5,20 @@
|
||||
'use strict';
|
||||
|
||||
import * as path from 'path';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import { IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search';
|
||||
import { IFileMatch, IFileSearchProviderStats, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery, ITextSearchResult } from 'vs/platform/search/common/search';
|
||||
import { FileIndexSearchManager, IDirectoryEntry, IDirectoryTree, IInternalFileMatch, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/api/node/extHostSearch.fileIndex';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export interface ISchemeTransformer {
|
||||
transformOutgoing(scheme: string): string;
|
||||
@@ -24,14 +27,18 @@ export interface ISchemeTransformer {
|
||||
export class ExtHostSearch implements ExtHostSearchShape {
|
||||
|
||||
private readonly _proxy: MainThreadSearchShape;
|
||||
private readonly _searchProvider = new Map<number, vscode.SearchProvider>();
|
||||
private readonly _fileSearchProvider = new Map<number, vscode.FileSearchProvider>();
|
||||
private readonly _textSearchProvider = new Map<number, vscode.TextSearchProvider>();
|
||||
private readonly _fileIndexProvider = new Map<number, vscode.FileIndexProvider>();
|
||||
private _handlePool: number = 0;
|
||||
|
||||
private _fileSearchManager: FileSearchManager;
|
||||
private _fileIndexSearchManager: FileIndexSearchManager;
|
||||
|
||||
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _extfs = extfs) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadSearch);
|
||||
this._fileSearchManager = new FileSearchManager();
|
||||
this._fileIndexSearchManager = new FileIndexSearchManager();
|
||||
}
|
||||
|
||||
private _transformScheme(scheme: string): string {
|
||||
@@ -41,40 +48,80 @@ export class ExtHostSearch implements ExtHostSearchShape {
|
||||
return scheme;
|
||||
}
|
||||
|
||||
registerSearchProvider(scheme: string, provider: vscode.SearchProvider) {
|
||||
registerFileSearchProvider(scheme: string, provider: vscode.FileSearchProvider) {
|
||||
const handle = this._handlePool++;
|
||||
this._searchProvider.set(handle, provider);
|
||||
this._proxy.$registerSearchProvider(handle, this._transformScheme(scheme));
|
||||
this._fileSearchProvider.set(handle, provider);
|
||||
this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme));
|
||||
return toDisposable(() => {
|
||||
this._searchProvider.delete(handle);
|
||||
this._fileSearchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
$provideFileSearchResults(handle: number, session: number, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
|
||||
const provider = this._searchProvider.get(handle);
|
||||
if (!provider.provideFileSearchResults) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
const query = reviveQuery(rawQuery);
|
||||
return this._fileSearchManager.fileSearch(query, provider, progress => {
|
||||
this._proxy.$handleFileMatch(handle, session, progress.map(p => p.resource));
|
||||
registerTextSearchProvider(scheme: string, provider: vscode.TextSearchProvider) {
|
||||
const handle = this._handlePool++;
|
||||
this._textSearchProvider.set(handle, provider);
|
||||
this._proxy.$registerTextSearchProvider(handle, this._transformScheme(scheme));
|
||||
return toDisposable(() => {
|
||||
this._textSearchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
$clearCache(handle: number, cacheKey: string): TPromise<void> {
|
||||
const provider = this._searchProvider.get(handle);
|
||||
if (!provider.clearCache) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
registerFileIndexProvider(scheme: string, provider: vscode.FileIndexProvider) {
|
||||
const handle = this._handlePool++;
|
||||
this._fileIndexProvider.set(handle, provider);
|
||||
this._proxy.$registerFileIndexProvider(handle, this._transformScheme(scheme));
|
||||
return toDisposable(() => {
|
||||
this._fileSearchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle); // TODO@roblou - unregisterFileIndexProvider
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.as(
|
||||
this._fileSearchManager.clearCache(cacheKey, provider));
|
||||
$provideFileSearchResults(handle: number, session: number, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
|
||||
const provider = this._fileSearchProvider.get(handle);
|
||||
const query = reviveQuery(rawQuery);
|
||||
if (provider) {
|
||||
let cancelSource = new CancellationTokenSource();
|
||||
return new TPromise((c, e) => {
|
||||
this._fileSearchManager.fileSearch(query, provider, batch => {
|
||||
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
|
||||
}, cancelSource.token).then(c, err => {
|
||||
if (!isPromiseCanceledError(err)) {
|
||||
e(err);
|
||||
}
|
||||
});
|
||||
}, () => {
|
||||
// TODO IPC promise cancellation #53526
|
||||
cancelSource.cancel();
|
||||
cancelSource.dispose();
|
||||
});
|
||||
} else {
|
||||
const indexProvider = this._fileIndexProvider.get(handle);
|
||||
if (indexProvider) {
|
||||
return this._fileIndexSearchManager.fileSearch(query, indexProvider, batch => {
|
||||
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
|
||||
}).then(null, err => {
|
||||
if (!isPromiseCanceledError(err)) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
throw new Error('something went wrong');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$clearCache(cacheKey: string): TPromise<void> {
|
||||
// Actually called once per provider.
|
||||
// Only relevant to file index search.
|
||||
return this._fileIndexSearchManager.clearCache(cacheKey);
|
||||
}
|
||||
|
||||
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
|
||||
const provider = this._searchProvider.get(handle);
|
||||
const provider = this._textSearchProvider.get(handle);
|
||||
if (!provider.provideTextSearchResults) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
@@ -85,22 +132,6 @@ export class ExtHostSearch implements ExtHostSearchShape {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the patterns that the provider handles. Discards sibling clauses and 'false' patterns
|
||||
*/
|
||||
function resolvePatternsForProvider(globalPattern: glob.IExpression, folderPattern: glob.IExpression): string[] {
|
||||
const merged = {
|
||||
...(globalPattern || {}),
|
||||
...(folderPattern || {})
|
||||
};
|
||||
|
||||
return Object.keys(merged)
|
||||
.filter(key => {
|
||||
const value = merged[key];
|
||||
return typeof value === 'boolean' && value;
|
||||
});
|
||||
}
|
||||
|
||||
function reviveQuery(rawQuery: IRawSearchQuery): ISearchQuery {
|
||||
return {
|
||||
...rawQuery,
|
||||
@@ -141,22 +172,16 @@ class TextSearchResultsCollector {
|
||||
if (!this._currentFileMatch) {
|
||||
this._currentFileMatch = {
|
||||
resource: data.uri,
|
||||
lineMatches: []
|
||||
matches: []
|
||||
};
|
||||
}
|
||||
|
||||
// TODO@roblou - line text is sent for every match
|
||||
const matchRange = data.preview.match;
|
||||
this._currentFileMatch.lineMatches.push({
|
||||
lineNumber: data.range.start.line,
|
||||
preview: data.preview.text,
|
||||
offsetAndLengths: [[matchRange.start.character, matchRange.end.character - matchRange.start.character]]
|
||||
});
|
||||
this._currentFileMatch.matches.push(extensionResultToFrontendResult(data));
|
||||
}
|
||||
|
||||
private pushToCollector(): void {
|
||||
const size = this._currentFileMatch ?
|
||||
this._currentFileMatch.lineMatches.reduce((acc, match) => acc + match.offsetAndLengths.length, 0) :
|
||||
this._currentFileMatch.matches.length :
|
||||
0;
|
||||
this._batchedCollector.addItem(this._currentFileMatch, size);
|
||||
}
|
||||
@@ -171,6 +196,26 @@ class TextSearchResultsCollector {
|
||||
}
|
||||
}
|
||||
|
||||
function extensionResultToFrontendResult(data: vscode.TextSearchResult): ITextSearchResult {
|
||||
return {
|
||||
preview: {
|
||||
match: {
|
||||
startLineNumber: data.preview.match.start.line,
|
||||
startColumn: data.preview.match.start.character,
|
||||
endLineNumber: data.preview.match.end.line,
|
||||
endColumn: data.preview.match.end.character
|
||||
},
|
||||
text: data.preview.text
|
||||
},
|
||||
range: {
|
||||
startLineNumber: data.range.start.line,
|
||||
startColumn: data.range.start.character,
|
||||
endLineNumber: data.range.end.line,
|
||||
endColumn: data.range.end.character
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects items that have a size - before the cumulative size of collected items reaches START_BATCH_AFTER_COUNT, the callback is called for every
|
||||
* set of items collected.
|
||||
@@ -253,107 +298,6 @@ class BatchedCollector<T> {
|
||||
}
|
||||
}
|
||||
|
||||
interface IDirectoryEntry {
|
||||
base: URI;
|
||||
relativePath: string;
|
||||
basename: string;
|
||||
}
|
||||
|
||||
interface IDirectoryTree {
|
||||
rootEntries: IDirectoryEntry[];
|
||||
pathToEntries: { [relativePath: string]: IDirectoryEntry[] };
|
||||
}
|
||||
|
||||
interface IInternalFileMatch {
|
||||
base: URI;
|
||||
relativePath?: string; // Not present for extraFiles or absolute path matches
|
||||
basename: string;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
class QueryGlobTester {
|
||||
|
||||
private _excludeExpression: glob.IExpression;
|
||||
private _parsedExcludeExpression: glob.ParsedExpression;
|
||||
|
||||
private _parsedIncludeExpression: glob.ParsedExpression;
|
||||
|
||||
constructor(config: ISearchQuery, folderQuery: IFolderQuery) {
|
||||
this._excludeExpression = {
|
||||
...(config.excludePattern || {}),
|
||||
...(folderQuery.excludePattern || {})
|
||||
};
|
||||
this._parsedExcludeExpression = glob.parse(this._excludeExpression);
|
||||
|
||||
// Empty includeExpression means include nothing, so no {} shortcuts
|
||||
let includeExpression: glob.IExpression = config.includePattern;
|
||||
if (folderQuery.includePattern) {
|
||||
if (includeExpression) {
|
||||
includeExpression = {
|
||||
...includeExpression,
|
||||
...folderQuery.includePattern
|
||||
};
|
||||
} else {
|
||||
includeExpression = folderQuery.includePattern;
|
||||
}
|
||||
}
|
||||
|
||||
if (includeExpression) {
|
||||
this._parsedIncludeExpression = glob.parse(includeExpression);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guaranteed sync - siblingsFn should not return a promise.
|
||||
*/
|
||||
public includedInQuerySync(testPath: string, basename?: string, hasSibling?: (name: string) => boolean): boolean {
|
||||
if (this._parsedExcludeExpression && this._parsedExcludeExpression(testPath, basename, hasSibling)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._parsedIncludeExpression && !this._parsedIncludeExpression(testPath, basename, hasSibling)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guaranteed async.
|
||||
*/
|
||||
public includedInQuery(testPath: string, basename?: string, hasSibling?: (name: string) => boolean | TPromise<boolean>): TPromise<boolean> {
|
||||
const excludeP = this._parsedExcludeExpression ?
|
||||
TPromise.as(this._parsedExcludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
|
||||
TPromise.wrap(false);
|
||||
|
||||
return excludeP.then(excluded => {
|
||||
if (excluded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._parsedIncludeExpression ?
|
||||
TPromise.as(this._parsedIncludeExpression(testPath, basename, hasSibling)).then(result => !!result) :
|
||||
TPromise.wrap(true);
|
||||
}).then(included => {
|
||||
return included;
|
||||
});
|
||||
}
|
||||
|
||||
public hasSiblingExcludeClauses(): boolean {
|
||||
return hasSiblingClauses(this._excludeExpression);
|
||||
}
|
||||
}
|
||||
|
||||
function hasSiblingClauses(pattern: glob.IExpression): boolean {
|
||||
for (let key in pattern) {
|
||||
if (typeof pattern[key] !== 'boolean') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class TextSearchEngine {
|
||||
|
||||
private activeCancellationTokens = new Set<CancellationTokenSource>();
|
||||
@@ -363,7 +307,7 @@ class TextSearchEngine {
|
||||
private resultCount = 0;
|
||||
private isCanceled: boolean;
|
||||
|
||||
constructor(private pattern: IPatternInfo, private config: ISearchQuery, private provider: vscode.SearchProvider, private _extfs: typeof extfs) {
|
||||
constructor(private pattern: IPatternInfo, private config: ISearchQuery, private provider: vscode.TextSearchProvider, private _extfs: typeof extfs) {
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
@@ -372,10 +316,10 @@ class TextSearchEngine {
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(onProgress: (matches: IFileMatch[]) => void): TPromise<{ limitHit: boolean }> {
|
||||
public search(onProgress: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> {
|
||||
const folderQueries = this.config.folderQueries;
|
||||
|
||||
return new TPromise<{ limitHit: boolean }>((resolve, reject) => {
|
||||
return new TPromise<ISearchCompleteStats>((resolve, reject) => {
|
||||
this.collector = new TextSearchResultsCollector(onProgress);
|
||||
|
||||
const onResult = (match: vscode.TextSearchResult, folderIdx: number) => {
|
||||
@@ -399,7 +343,12 @@ class TextSearchEngine {
|
||||
return this.searchInFolder(fq, r => onResult(r, i));
|
||||
})).then(() => {
|
||||
this.collector.flush();
|
||||
resolve({ limitHit: this.isLimitHit });
|
||||
resolve({
|
||||
limitHit: this.isLimitHit,
|
||||
stats: {
|
||||
type: 'textSearchProvider'
|
||||
}
|
||||
});
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
@@ -479,7 +428,8 @@ class TextSearchEngine {
|
||||
followSymlinks: !this.config.ignoreSymlinks,
|
||||
encoding: this.config.fileEncoding,
|
||||
maxFileSize: this.config.maxFileSize,
|
||||
maxResults: this.config.maxResults
|
||||
maxResults: this.config.maxResults,
|
||||
previewOptions: this.config.previewOptions
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -506,7 +456,7 @@ class FileSearchEngine {
|
||||
|
||||
private globalExcludePattern: glob.ParsedExpression;
|
||||
|
||||
constructor(private config: ISearchQuery, private provider: vscode.SearchProvider) {
|
||||
constructor(private config: ISearchQuery, private provider: vscode.FileSearchProvider) {
|
||||
this.filePattern = config.filePattern;
|
||||
this.includePattern = config.includePattern && glob.parse(config.includePattern);
|
||||
this.maxResults = config.maxResults || null;
|
||||
@@ -535,7 +485,7 @@ class FileSearchEngine {
|
||||
|
||||
// Support that the file pattern is a full path to a file that exists
|
||||
if (this.isCanceled) {
|
||||
return resolve({ limitHit: this.isLimitHit, cacheKeys: [] });
|
||||
return resolve({ limitHit: this.isLimitHit });
|
||||
}
|
||||
|
||||
// For each extra file
|
||||
@@ -556,8 +506,11 @@ class FileSearchEngine {
|
||||
// For each root folder
|
||||
TPromise.join(folderQueries.map(fq => {
|
||||
return this.searchInFolder(fq, onResult);
|
||||
})).then(cacheKeys => {
|
||||
resolve({ limitHit: this.isLimitHit, cacheKeys });
|
||||
})).then(stats => {
|
||||
resolve({
|
||||
limitHit: this.isLimitHit,
|
||||
stats: stats[0] // Only looking at single-folder workspace stats...
|
||||
});
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
@@ -568,7 +521,7 @@ class FileSearchEngine {
|
||||
});
|
||||
}
|
||||
|
||||
private searchInFolder(fq: IFolderQuery<URI>, onResult: (match: IInternalFileMatch) => void): TPromise<string> {
|
||||
private searchInFolder(fq: IFolderQuery<URI>, onResult: (match: IInternalFileMatch) => void): TPromise<IFileSearchProviderStats> {
|
||||
let cancellation = new CancellationTokenSource();
|
||||
return new TPromise((resolve, reject) => {
|
||||
const options = this.getSearchOptionsForFolder(fq);
|
||||
@@ -577,52 +530,57 @@ class FileSearchEngine {
|
||||
const queryTester = new QueryGlobTester(this.config, fq);
|
||||
const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses();
|
||||
|
||||
const onProviderResult = (result: URI) => {
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const relativePath = path.relative(fq.folder.fsPath, result.fsPath);
|
||||
|
||||
if (noSiblingsClauses) {
|
||||
const basename = path.basename(result.fsPath);
|
||||
this.matchFile(onResult, { base: fq.folder, relativePath, basename });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Optimize siblings clauses with ripgrep here.
|
||||
this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
|
||||
};
|
||||
|
||||
let folderCacheKey: string;
|
||||
let providerSW: StopWatch;
|
||||
new TPromise(_resolve => process.nextTick(_resolve))
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.add(cancellation);
|
||||
|
||||
folderCacheKey = this.config.cacheKey && (this.config.cacheKey + '_' + fq.folder.fsPath);
|
||||
|
||||
providerSW = StopWatch.create();
|
||||
return this.provider.provideFileSearchResults(
|
||||
{
|
||||
pattern: this.config.filePattern || '',
|
||||
cacheKey: folderCacheKey
|
||||
pattern: this.config.filePattern || ''
|
||||
},
|
||||
options,
|
||||
{ report: onProviderResult },
|
||||
cancellation.token);
|
||||
})
|
||||
.then(() => {
|
||||
.then(results => {
|
||||
const providerTime = providerSW.elapsed();
|
||||
const postProcessSW = StopWatch.create();
|
||||
|
||||
if (this.isCanceled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (results) {
|
||||
results.forEach(result => {
|
||||
const relativePath = path.relative(fq.folder.fsPath, result.fsPath);
|
||||
|
||||
if (noSiblingsClauses) {
|
||||
const basename = path.basename(result.fsPath);
|
||||
this.matchFile(onResult, { base: fq.folder, relativePath, basename });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Optimize siblings clauses with ripgrep here.
|
||||
this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
|
||||
});
|
||||
}
|
||||
|
||||
this.activeCancellationTokens.delete(cancellation);
|
||||
if (this.isCanceled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.matchDirectoryTree(tree, queryTester, onResult);
|
||||
return null;
|
||||
return <IFileSearchProviderStats>{
|
||||
providerTime,
|
||||
postProcessTime: postProcessSW.elapsed()
|
||||
};
|
||||
}).then(
|
||||
() => {
|
||||
stats => {
|
||||
cancellation.dispose();
|
||||
resolve(folderCacheKey);
|
||||
resolve(stats);
|
||||
},
|
||||
err => {
|
||||
cancellation.dispose();
|
||||
@@ -731,85 +689,77 @@ class FileSearchEngine {
|
||||
|
||||
interface IInternalSearchComplete {
|
||||
limitHit: boolean;
|
||||
cacheKeys: string[];
|
||||
stats?: IFileSearchProviderStats;
|
||||
}
|
||||
|
||||
class FileSearchManager {
|
||||
|
||||
private static readonly BATCH_SIZE = 512;
|
||||
|
||||
private readonly expandedCacheKeys = new Map<string, string[]>();
|
||||
fileSearch(config: ISearchQuery, provider: vscode.FileSearchProvider, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): TPromise<ISearchCompleteStats> {
|
||||
const engine = new FileSearchEngine(config, provider);
|
||||
|
||||
fileSearch(config: ISearchQuery, provider: vscode.SearchProvider, onResult: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> {
|
||||
let searchP: TPromise;
|
||||
return new TPromise<ISearchCompleteStats>((c, e) => {
|
||||
const engine = new FileSearchEngine(config, provider);
|
||||
let resultCount = 0;
|
||||
const onInternalResult = (batch: IInternalFileMatch[]) => {
|
||||
resultCount += batch.length;
|
||||
onBatch(batch.map(m => this.rawMatchToSearchItem(m)));
|
||||
};
|
||||
|
||||
const onInternalResult = (progress: IInternalFileMatch[]) => {
|
||||
onResult(progress.map(m => this.rawMatchToSearchItem(m)));
|
||||
};
|
||||
|
||||
searchP = this.doSearch(engine, FileSearchManager.BATCH_SIZE, onInternalResult).then(
|
||||
result => {
|
||||
if (config.cacheKey) {
|
||||
this.expandedCacheKeys.set(config.cacheKey, result.cacheKeys);
|
||||
return this.doSearch(engine, FileSearchManager.BATCH_SIZE, onInternalResult, token).then(
|
||||
result => {
|
||||
return <ISearchCompleteStats>{
|
||||
limitHit: result.limitHit,
|
||||
stats: {
|
||||
fromCache: false,
|
||||
type: 'fileSearchProvider',
|
||||
resultCount,
|
||||
detailStats: result.stats
|
||||
}
|
||||
|
||||
c({
|
||||
limitHit: result.limitHit
|
||||
});
|
||||
},
|
||||
e);
|
||||
}, () => {
|
||||
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: resources.joinPath(match.base, match.relativePath)
|
||||
};
|
||||
if (match.relativePath) {
|
||||
return {
|
||||
resource: resources.joinPath(match.base, match.relativePath)
|
||||
};
|
||||
} else {
|
||||
// extraFileResources
|
||||
return {
|
||||
resource: match.base
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private doSearch(engine: FileSearchEngine, batchSize: number, onResultBatch: (matches: IInternalFileMatch[]) => void): TPromise<IInternalSearchComplete> {
|
||||
return new TPromise((c, e) => {
|
||||
const _onResult = match => {
|
||||
if (match) {
|
||||
batch.push(match);
|
||||
if (batchSize > 0 && batch.length >= batchSize) {
|
||||
onResultBatch(batch);
|
||||
batch = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let batch: IInternalFileMatch[] = [];
|
||||
engine.search(_onResult).then(result => {
|
||||
if (batch.length) {
|
||||
onResultBatch(batch);
|
||||
}
|
||||
|
||||
c(result);
|
||||
}, error => {
|
||||
if (batch.length) {
|
||||
onResultBatch(batch);
|
||||
}
|
||||
|
||||
e(error);
|
||||
});
|
||||
}, () => {
|
||||
private doSearch(engine: FileSearchEngine, batchSize: number, onResultBatch: (matches: IInternalFileMatch[]) => void, token: CancellationToken): TPromise<IInternalSearchComplete> {
|
||||
token.onCancellationRequested(() => {
|
||||
engine.cancel();
|
||||
});
|
||||
|
||||
const _onResult = match => {
|
||||
if (match) {
|
||||
batch.push(match);
|
||||
if (batchSize > 0 && batch.length >= batchSize) {
|
||||
onResultBatch(batch);
|
||||
batch = [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let batch: IInternalFileMatch[] = [];
|
||||
return engine.search(_onResult).then(result => {
|
||||
if (batch.length) {
|
||||
onResultBatch(batch);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, error => {
|
||||
if (batch.length) {
|
||||
onResultBatch(batch);
|
||||
}
|
||||
|
||||
return TPromise.wrapError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -497,6 +497,11 @@ export class ExtHostTextEditor implements vscode.TextEditor {
|
||||
private _applyEdit(editBuilder: TextEditorEdit): TPromise<boolean> {
|
||||
let editData = editBuilder.finalize();
|
||||
|
||||
// return when there is nothing to do
|
||||
if (editData.edits.length === 0 && !editData.setEndOfLine) {
|
||||
return TPromise.wrap(true);
|
||||
}
|
||||
|
||||
// check that the edits are not overlapping (i.e. illegal)
|
||||
let editRanges = editData.edits.map(edit => edit.range);
|
||||
|
||||
|
||||
@@ -1956,3 +1956,10 @@ export enum CommentThreadCollapsibleState {
|
||||
*/
|
||||
Expanded = 1
|
||||
}
|
||||
|
||||
export class QuickInputButtons {
|
||||
|
||||
static readonly Back: vscode.QuickInputButton = { iconPath: 'back.svg' };
|
||||
|
||||
private constructor() { }
|
||||
}
|
||||
|
||||
@@ -3,14 +3,16 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainContext, MainThreadWebviewsShape, IMainContext, ExtHostWebviewsShape, WebviewPanelHandle, WebviewPanelViewState } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState } from './extHost.protocol';
|
||||
import { Disposable } from './extHostTypes';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
type IconPath = URI | { light: URI, dark: URI };
|
||||
|
||||
export class ExtHostWebview implements vscode.Webview {
|
||||
private readonly _handle: WebviewPanelHandle;
|
||||
@@ -78,6 +80,7 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private readonly _viewType: string;
|
||||
private _title: string;
|
||||
private _iconPath: IconPath;
|
||||
|
||||
private readonly _options: vscode.WebviewPanelOptions;
|
||||
private readonly _webview: ExtHostWebview;
|
||||
@@ -150,6 +153,20 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
}
|
||||
}
|
||||
|
||||
get iconPath(): IconPath | undefined {
|
||||
this.assertNotDisposed();
|
||||
return this._iconPath;
|
||||
}
|
||||
|
||||
set iconPath(value: IconPath | undefined) {
|
||||
this.assertNotDisposed();
|
||||
if (this._iconPath !== value) {
|
||||
this._iconPath = value;
|
||||
|
||||
this._proxy.$setIconPath(this._handle, URI.isUri(value) ? { light: value, dark: value } : value);
|
||||
}
|
||||
}
|
||||
|
||||
get options() {
|
||||
return this._options;
|
||||
}
|
||||
@@ -191,9 +208,10 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
|
||||
public reveal(viewColumn?: vscode.ViewColumn, preserveFocus?: boolean): void {
|
||||
this.assertNotDisposed();
|
||||
this._proxy.$reveal(this._handle,
|
||||
viewColumn ? typeConverters.ViewColumn.from(viewColumn) : undefined,
|
||||
!!preserveFocus);
|
||||
this._proxy.$reveal(this._handle, {
|
||||
viewColumn: viewColumn ? typeConverters.ViewColumn.from(viewColumn) : undefined,
|
||||
preserveFocus: !!preserveFocus
|
||||
});
|
||||
}
|
||||
|
||||
private assertNotDisposed() {
|
||||
@@ -206,8 +224,11 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel {
|
||||
export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
private static webviewHandlePool = 1;
|
||||
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private static newHandle(): WebviewPanelHandle {
|
||||
return ExtHostWebviews.webviewHandlePool++ + '';
|
||||
}
|
||||
|
||||
private readonly _proxy: MainThreadWebviewsShape;
|
||||
private readonly _webviewPanels = new Map<WebviewPanelHandle, ExtHostWebviewPanel>();
|
||||
private readonly _serializers = new Map<string, vscode.WebviewPanelSerializer>();
|
||||
|
||||
@@ -217,22 +238,20 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews);
|
||||
}
|
||||
|
||||
createWebview(
|
||||
public createWebview(
|
||||
extensionLocation: URI,
|
||||
viewType: string,
|
||||
title: string,
|
||||
showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean },
|
||||
options: (vscode.WebviewPanelOptions & vscode.WebviewOptions) | undefined,
|
||||
extensionLocation: URI
|
||||
options: (vscode.WebviewPanelOptions & vscode.WebviewOptions) = {},
|
||||
): vscode.WebviewPanel {
|
||||
options = options || {};
|
||||
|
||||
const viewColumn = typeof showOptions === 'object' ? showOptions.viewColumn : showOptions;
|
||||
const webviewShowOptions = {
|
||||
viewColumn: typeConverters.ViewColumn.from(viewColumn),
|
||||
preserveFocus: typeof showOptions === 'object' && !!showOptions.preserveFocus
|
||||
};
|
||||
|
||||
const handle = ExtHostWebviews.webviewHandlePool++ + '';
|
||||
const handle = ExtHostWebviews.newHandle();
|
||||
this._proxy.$createWebviewPanel(handle, viewType, title, webviewShowOptions, options, extensionLocation);
|
||||
|
||||
const webview = new ExtHostWebview(handle, this._proxy, options);
|
||||
@@ -241,7 +260,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
return panel;
|
||||
}
|
||||
|
||||
registerWebviewPanelSerializer(
|
||||
public registerWebviewPanelSerializer(
|
||||
viewType: string,
|
||||
serializer: vscode.WebviewPanelSerializer
|
||||
): vscode.Disposable {
|
||||
@@ -258,14 +277,20 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
|
||||
});
|
||||
}
|
||||
|
||||
$onMessage(handle: WebviewPanelHandle, message: any): void {
|
||||
public $onMessage(
|
||||
handle: WebviewPanelHandle,
|
||||
message: any
|
||||
): void {
|
||||
const panel = this.getWebviewPanel(handle);
|
||||
if (panel) {
|
||||
panel.webview._onMessageEmitter.fire(message);
|
||||
}
|
||||
}
|
||||
|
||||
$onDidChangeWebviewPanelViewState(handle: WebviewPanelHandle, newState: WebviewPanelViewState): void {
|
||||
public $onDidChangeWebviewPanelViewState(
|
||||
handle: WebviewPanelHandle,
|
||||
newState: WebviewPanelViewState
|
||||
): void {
|
||||
const panel = this.getWebviewPanel(handle);
|
||||
if (panel) {
|
||||
const viewColumn = typeConverters.ViewColumn.to(newState.position);
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { posix, relative, join } from 'path';
|
||||
import { join, relative } from 'path';
|
||||
import { delta as arrayDelta } from 'vs/base/common/arrays';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { Counter } from 'vs/base/common/numbers';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
|
||||
import { basenameOrAuthority, dirname, 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';
|
||||
@@ -107,8 +108,8 @@ class ExtHostWorkspaceImpl extends Workspace {
|
||||
private readonly _workspaceFolders: vscode.WorkspaceFolder[] = [];
|
||||
private readonly _structure = TernarySearchTree.forPaths<vscode.WorkspaceFolder>();
|
||||
|
||||
private constructor(id: string, name: string, folders: vscode.WorkspaceFolder[]) {
|
||||
super(id, name, folders.map(f => new WorkspaceFolder(f)));
|
||||
private constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[]) {
|
||||
super(id, folders.map(f => new WorkspaceFolder(f)));
|
||||
|
||||
// setup the workspace folder data structure
|
||||
folders.forEach(folder => {
|
||||
@@ -117,6 +118,10 @@ class ExtHostWorkspaceImpl extends Workspace {
|
||||
});
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
get workspaceFolders(): vscode.WorkspaceFolder[] {
|
||||
return this._workspaceFolders.slice(0);
|
||||
}
|
||||
@@ -124,7 +129,7 @@ class ExtHostWorkspaceImpl extends Workspace {
|
||||
getWorkspaceFolder(uri: URI, resolveParent?: boolean): vscode.WorkspaceFolder {
|
||||
if (resolveParent && this._structure.get(uri.toString())) {
|
||||
// `uri` is a workspace folder so we check for its parent
|
||||
uri = uri.with({ path: posix.dirname(uri.path) });
|
||||
uri = dirname(uri);
|
||||
}
|
||||
return this._structure.findSubstr(uri.toString());
|
||||
}
|
||||
@@ -136,8 +141,6 @@ class ExtHostWorkspaceImpl extends Workspace {
|
||||
|
||||
export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
|
||||
private static _requestIdPool = 0;
|
||||
|
||||
private readonly _onDidChangeWorkspace = new Emitter<vscode.WorkspaceFoldersChangeEvent>();
|
||||
private readonly _proxy: MainThreadWorkspaceShape;
|
||||
|
||||
@@ -148,12 +151,13 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
|
||||
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
||||
|
||||
private readonly _activeSearchCallbacks = [];
|
||||
private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = [];
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
data: IWorkspaceData,
|
||||
private _logService: ILogService
|
||||
private _logService: ILogService,
|
||||
private _requestIdProvider: Counter
|
||||
) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace);
|
||||
this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService);
|
||||
@@ -166,6 +170,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
return this._actualWorkspace;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this._actualWorkspace ? this._actualWorkspace.name : undefined;
|
||||
}
|
||||
|
||||
private get _actualWorkspace(): ExtHostWorkspaceImpl {
|
||||
return this._unconfirmedWorkspace || this._confirmedWorkspace;
|
||||
}
|
||||
@@ -340,7 +348,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
findFiles(include: vscode.GlobPattern, exclude: vscode.GlobPattern, maxResults: number, extensionId: string, token?: vscode.CancellationToken): Thenable<vscode.Uri[]> {
|
||||
this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId}, entryPoint: findFiles`);
|
||||
|
||||
const requestId = ExtHostWorkspace._requestIdPool++;
|
||||
const requestId = this._requestIdProvider.getNext();
|
||||
|
||||
let includePattern: string;
|
||||
let includeFolder: string;
|
||||
@@ -374,7 +382,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
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 requestId = this._requestIdProvider.getNext();
|
||||
|
||||
const globPatternToString = (pattern: vscode.GlobPattern | string) => {
|
||||
if (typeof pattern === 'string') {
|
||||
@@ -390,26 +398,36 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
disregardExcludeSettings: options.exclude === null,
|
||||
fileEncoding: options.encoding,
|
||||
maxResults: options.maxResults,
|
||||
previewOptions: options.previewOptions,
|
||||
|
||||
includePattern: options.include && globPatternToString(options.include),
|
||||
excludePattern: options.exclude && globPatternToString(options.exclude)
|
||||
};
|
||||
|
||||
let isCanceled = false;
|
||||
|
||||
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 (isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
p.matches.forEach(match => {
|
||||
callback({
|
||||
uri: URI.revive(p.resource),
|
||||
preview: {
|
||||
text: match.preview.text,
|
||||
match: new Range(match.preview.match.startLineNumber, match.preview.match.startColumn, match.preview.match.endLineNumber, match.preview.match.endColumn)
|
||||
},
|
||||
range: new Range(match.range.startLineNumber, match.range.startColumn, match.range.endLineNumber, match.range.endColumn)
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (token) {
|
||||
token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId));
|
||||
token.onCancellationRequested(() => {
|
||||
isCanceled = true;
|
||||
this._proxy.$cancelSearch(requestId);
|
||||
});
|
||||
}
|
||||
|
||||
return this._proxy.$startTextSearch(query, queryOptions, requestId).then(
|
||||
|
||||
Reference in New Issue
Block a user