mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 11:08:51 +01:00
Merge remote-tracking branch 'origin/master' into joao/uri-handler
This commit is contained in:
@@ -335,7 +335,7 @@ export function createApiFactory(
|
||||
return proposedApiFunction(extension, extHostTerminalService.activeTerminal);
|
||||
},
|
||||
get terminals() {
|
||||
return proposedApiFunction(extension, extHostTerminalService.terminals);
|
||||
return extHostTerminalService.terminals;
|
||||
},
|
||||
showTextDocument(documentOrUri: vscode.TextDocument | vscode.Uri, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): TPromise<vscode.TextEditor> {
|
||||
let documentPromise: TPromise<vscode.TextDocument>;
|
||||
@@ -372,9 +372,9 @@ export function createApiFactory(
|
||||
onDidCloseTerminal(listener, thisArg?, disposables?) {
|
||||
return extHostTerminalService.onDidCloseTerminal(listener, thisArg, disposables);
|
||||
},
|
||||
onDidOpenTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
|
||||
onDidOpenTerminal(listener, thisArg?, disposables?) {
|
||||
return extHostTerminalService.onDidOpenTerminal(listener, thisArg, disposables);
|
||||
}),
|
||||
},
|
||||
onDidChangeActiveTerminal: proposedApiFunction(extension, (listener, thisArg?, disposables?) => {
|
||||
return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables);
|
||||
}),
|
||||
@@ -442,6 +442,9 @@ export function createApiFactory(
|
||||
createTreeView(viewId: string, options: { treeDataProvider: vscode.TreeDataProvider<any> }): vscode.TreeView<any> {
|
||||
return extHostTreeViews.createTreeView(viewId, options);
|
||||
},
|
||||
registerWebviewPanelSerializer: (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
|
||||
return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer);
|
||||
},
|
||||
// proposed API
|
||||
sampleFunction: proposedApiFunction(extension, () => {
|
||||
return extHostMessageService.showMessage(extension, Severity.Info, 'Hello Proposed Api!', {}, []);
|
||||
@@ -449,9 +452,6 @@ export function createApiFactory(
|
||||
registerDecorationProvider: proposedApiFunction(extension, (provider: vscode.DecorationProvider) => {
|
||||
return extHostDecorations.registerDecorationProvider(provider, extension.id);
|
||||
}),
|
||||
registerWebviewPanelSerializer: proposedApiFunction(extension, (viewType: string, serializer: vscode.WebviewPanelSerializer) => {
|
||||
return extHostWebviews.registerWebviewPanelSerializer(viewType, serializer);
|
||||
}),
|
||||
registerUriHandler(handler: vscode.UriHandler) {
|
||||
return extHostUrls.registerUriHandler(extension.id, handler);
|
||||
},
|
||||
@@ -500,6 +500,9 @@ export function createApiFactory(
|
||||
findFiles: (include, exclude, maxResults?, token?) => {
|
||||
return extHostWorkspace.findFiles(typeConverters.GlobPattern.from(include), typeConverters.GlobPattern.from(exclude), maxResults, extension.id, token);
|
||||
},
|
||||
findTextInFiles: (query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, token?: vscode.CancellationToken) => {
|
||||
return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.id, token);
|
||||
},
|
||||
saveAll: (includeUntitled?) => {
|
||||
return extHostWorkspace.saveAll(includeUntitled);
|
||||
},
|
||||
@@ -826,7 +829,9 @@ function defineAPI(factory: IExtensionApiFactory, extensionPaths: TernarySearchT
|
||||
|
||||
// fall back to a default implementation
|
||||
if (!defaultApiImpl) {
|
||||
console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}`);
|
||||
let extensionPathsPretty = '';
|
||||
extensionPaths.forEach((value, index) => extensionPathsPretty += `\t${index} -> ${value.id}\n`);
|
||||
console.warn(`Could not identify extension for 'vscode' require call from ${parent.filename}. These are the extension path mappings: \n${extensionPathsPretty}`);
|
||||
defaultApiImpl = factory(nullExtensionDescription);
|
||||
}
|
||||
return defaultApiImpl;
|
||||
|
||||
@@ -4,52 +4,43 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { createMainContextProxyIdentifier as createMainId, createExtHostContextProxyIdentifier as createExtId, ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { SerializedError } from 'vs/base/common/errors';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { IProgressOptions, IProgressStep } from 'vs/workbench/services/progress/common/progress';
|
||||
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
|
||||
import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfig, IAdapterExecutable, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
|
||||
|
||||
import { IQuickPickItem, IPickOptions, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions';
|
||||
import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
|
||||
|
||||
|
||||
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { ISelection, Selection } from 'vs/editor/common/core/selection';
|
||||
|
||||
import { ITreeItem } from 'vs/workbench/common/views';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { SerializedError } from 'vs/base/common/errors';
|
||||
import { IStat, FileChangeType, IWatchOptions, FileSystemProviderCapabilities, FileWriteOptions, FileType, FileOverwriteOptions } from 'vs/platform/files/common/files';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ISingleEditOperation } from 'vs/editor/common/model';
|
||||
import { IPatternInfo, IRawSearchQuery, IRawFileMatch2, ISearchCompleteStats } from 'vs/platform/search/common/search';
|
||||
import { IModelChangedEvent } from 'vs/editor/common/model/mirrorTextModel';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { CharacterPair, CommentRule, EnterAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
|
||||
import { ConfigurationTarget, IConfigurationData, IConfigurationModel } from 'vs/platform/configuration/common/configuration';
|
||||
import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IStat, IWatchOptions } from 'vs/platform/files/common/files';
|
||||
import { LogLevel } from 'vs/platform/log/common/log';
|
||||
import { TaskExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { IPickOptions, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IPatternInfo, IQueryOptions, IRawFileMatch2, IRawSearchQuery, ISearchCompleteStats } from 'vs/platform/search/common/search';
|
||||
import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { EndOfLine, IFileOperationOptions, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { TaskDTO, TaskExecutionDTO, TaskFilterDTO, TaskHandleDTO, TaskProcessEndedDTO, TaskProcessStartedDTO, TaskSystemInfoDTO } from 'vs/workbench/api/shared/tasks';
|
||||
import { ITreeItem } from 'vs/workbench/common/views';
|
||||
import { IAdapterExecutable, IConfig, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug';
|
||||
import { TaskSet } from 'vs/workbench/parts/tasks/common/tasks';
|
||||
import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId, IRPCProtocol, ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
import { IProgressOptions, IProgressStep } from 'vs/workbench/services/progress/common/progress';
|
||||
import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
@@ -411,7 +402,7 @@ export interface TransferInputBox extends BaseTransferQuickInput {
|
||||
}
|
||||
|
||||
export interface MainThreadQuickOpenShape extends IDisposable {
|
||||
$show(options: IPickOptions): TPromise<number | number[]>;
|
||||
$show(options: IPickOptions<TransferQuickPickItems>): TPromise<number | number[]>;
|
||||
$setItems(items: TransferQuickPickItems[]): TPromise<any>;
|
||||
$setError(error: Error): TPromise<any>;
|
||||
$input(options: vscode.InputBoxOptions, validateInput: boolean): TPromise<string>;
|
||||
@@ -471,7 +462,8 @@ export interface ExtHostUrlsShape {
|
||||
}
|
||||
|
||||
export interface MainThreadWorkspaceShape extends IDisposable {
|
||||
$startSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<UriComponents[]>;
|
||||
$startFileSearch(includePattern: string, includeFolder: string, excludePatternOrDisregardExcludes: string | false, maxResults: number, requestId: number): Thenable<UriComponents[]>;
|
||||
$startTextSearch(query: IPatternInfo, options: IQueryOptions, requestId: number): TPromise<void>;
|
||||
$cancelSearch(requestId: number): Thenable<boolean>;
|
||||
$saveAll(includeUntitled?: boolean): Thenable<boolean>;
|
||||
$updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Thenable<void>;
|
||||
@@ -491,7 +483,8 @@ export interface MainThreadFileSystemShape extends IDisposable {
|
||||
export interface MainThreadSearchShape extends IDisposable {
|
||||
$registerSearchProvider(handle: number, scheme: string): void;
|
||||
$unregisterProvider(handle: number): void;
|
||||
$handleFindMatch(handle: number, session: number, data: UriComponents | IRawFileMatch2[]): void;
|
||||
$handleFileMatch(handle: number, session: number, data: UriComponents[]): void;
|
||||
$handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void;
|
||||
$handleTelemetry(eventName: string, data: any): void;
|
||||
}
|
||||
|
||||
@@ -671,6 +664,7 @@ export interface ExtHostTreeViewsShape {
|
||||
|
||||
export interface ExtHostWorkspaceShape {
|
||||
$acceptWorkspaceData(workspace: IWorkspaceData): void;
|
||||
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostFileSystemShape {
|
||||
@@ -681,13 +675,14 @@ export interface ExtHostFileSystemShape {
|
||||
$rename(handle: number, resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): TPromise<void>;
|
||||
$copy(handle: number, resource: UriComponents, target: UriComponents, opts: FileOverwriteOptions): TPromise<void>;
|
||||
$mkdir(handle: number, resource: UriComponents): TPromise<void>;
|
||||
$delete(handle: number, resource: UriComponents): TPromise<void>;
|
||||
$delete(handle: number, resource: UriComponents, opts: FileDeleteOptions): TPromise<void>;
|
||||
$watch(handle: number, session: number, resource: UriComponents, opts: IWatchOptions): void;
|
||||
$unwatch(handle: number, session: number): void;
|
||||
}
|
||||
|
||||
export interface ExtHostSearchShape {
|
||||
$provideFileSearchResults(handle: number, session: number, query: IRawSearchQuery): TPromise<ISearchCompleteStats>;
|
||||
$clearCache(handle: number, cacheKey: string): TPromise<void>;
|
||||
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, query: IRawSearchQuery): TPromise<ISearchCompleteStats>;
|
||||
}
|
||||
|
||||
@@ -774,7 +769,7 @@ export interface WorkspaceSymbolsDto extends IdObject {
|
||||
export interface ResourceFileEditDto {
|
||||
oldUri: UriComponents;
|
||||
newUri: UriComponents;
|
||||
options: { overwrite?: boolean, ignoreIfExists?: boolean };
|
||||
options: IFileOperationOptions;
|
||||
}
|
||||
|
||||
export interface ResourceTextEditDto {
|
||||
@@ -883,7 +878,7 @@ export interface ExtHostSCMShape {
|
||||
}
|
||||
|
||||
export interface ExtHostTaskShape {
|
||||
$provideTasks(handle: number): TPromise<TaskSet>;
|
||||
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): TPromise<TaskSet>;
|
||||
$onDidStartTask(execution: TaskExecutionDTO): void;
|
||||
$onDidStartTaskProcess(value: TaskProcessStartedDTO): void;
|
||||
$onDidEndTaskProcess(value: TaskProcessEndedDTO): void;
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { CustomCodeAction } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import { ICommandsExecutor, PreviewHTMLAPICommand, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand } from './apiCommands';
|
||||
import { EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
|
||||
export class ExtHostApiCommands {
|
||||
|
||||
@@ -411,15 +412,24 @@ export class ExtHostApiCommands {
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentSymbolProvider(resource: URI): Thenable<vscode.DocumentSymbol[]> {
|
||||
private _executeDocumentSymbolProvider(resource: URI): Thenable<vscode.SymbolInformation[]> {
|
||||
const args = {
|
||||
resource
|
||||
};
|
||||
return this._commands.executeCommand<modes.DocumentSymbol[]>('_executeDocumentSymbolProvider', args).then(value => {
|
||||
if (value && Array.isArray(value)) {
|
||||
return value.map(typeConverters.DocumentSymbol.to);
|
||||
if (isFalsyOrEmpty(value)) {
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
let result: vscode.SymbolInformation[] = [];
|
||||
for (const symbol of value) {
|
||||
result.push(new types.SymbolInformation(
|
||||
symbol.name,
|
||||
typeConverters.SymbolKind.to(symbol.kind),
|
||||
symbol.containerName,
|
||||
new types.Location(resource, typeConverters.Range.to(symbol.range))
|
||||
));
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
}
|
||||
}
|
||||
|
||||
public async $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
|
||||
public $runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): TPromise<void> {
|
||||
|
||||
if (args.kind === 'integrated') {
|
||||
|
||||
@@ -136,20 +136,31 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
});
|
||||
}
|
||||
|
||||
let t = this._integratedTerminalInstance;
|
||||
return new TPromise(resolve => {
|
||||
if (this._integratedTerminalInstance) {
|
||||
this._integratedTerminalInstance.processId.then(pid => {
|
||||
resolve(hasChildprocesses(pid));
|
||||
}, err => {
|
||||
resolve(true);
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
}).then(needNewTerminal => {
|
||||
|
||||
if ((t && hasChildprocesses(await t.processId)) || !t) {
|
||||
t = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee"));
|
||||
this._integratedTerminalInstance = t;
|
||||
}
|
||||
t.show();
|
||||
if (needNewTerminal) {
|
||||
this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee"));
|
||||
}
|
||||
|
||||
return new TPromise((resolve, error) => {
|
||||
setTimeout(_ => {
|
||||
const command = prepareCommand(args, config);
|
||||
t.sendText(command, true);
|
||||
resolve(void 0);
|
||||
}, 500);
|
||||
this._integratedTerminalInstance.show();
|
||||
|
||||
return new TPromise((resolve, error) => {
|
||||
setTimeout(_ => {
|
||||
const command = prepareCommand(args, config);
|
||||
this._integratedTerminalInstance.sendText(command, true);
|
||||
resolve(void 0);
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
|
||||
} else if (args.kind === 'external') {
|
||||
@@ -166,15 +177,18 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
if (!this._variableResolver) {
|
||||
this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, this._configurationService);
|
||||
}
|
||||
let ws: IWorkspaceFolder;
|
||||
const folder = this.getFolder(folderUri);
|
||||
let ws: IWorkspaceFolder = {
|
||||
uri: folder.uri,
|
||||
name: folder.name,
|
||||
index: folder.index,
|
||||
toResource: () => {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
};
|
||||
if (folder) {
|
||||
ws = {
|
||||
uri: folder.uri,
|
||||
name: folder.name,
|
||||
index: folder.index,
|
||||
toResource: () => {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
};
|
||||
}
|
||||
return asWinJsPromise(token => this._variableResolver.resolveAny(ws, config));
|
||||
}
|
||||
|
||||
@@ -412,9 +426,9 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
private fireBreakpointChanges(added: vscode.Breakpoint[], removed: vscode.Breakpoint[], changed: vscode.Breakpoint[]) {
|
||||
if (added.length > 0 || removed.length > 0 || changed.length > 0) {
|
||||
this._onDidChangeBreakpoints.fire(Object.freeze({
|
||||
added: Object.freeze<vscode.Breakpoint[]>(added),
|
||||
removed: Object.freeze<vscode.Breakpoint[]>(removed),
|
||||
changed: Object.freeze<vscode.Breakpoint[]>(changed)
|
||||
added,
|
||||
removed,
|
||||
changed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -524,7 +538,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape {
|
||||
this._onDidReceiveDebugSessionCustomEvent.fire(ee);
|
||||
}
|
||||
|
||||
private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder {
|
||||
private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder | undefined {
|
||||
if (_folderUri) {
|
||||
const folderURI = URI.revive(_folderUri);
|
||||
return this._workspaceService.resolveWorkspaceFolder(folderURI);
|
||||
|
||||
@@ -68,7 +68,7 @@ class ExtensionStoragePath {
|
||||
private readonly _workspace: IWorkspaceData;
|
||||
private readonly _environment: IEnvironment;
|
||||
|
||||
private readonly _ready: TPromise<string>;
|
||||
private readonly _ready: Promise<string>;
|
||||
private _value: string;
|
||||
|
||||
constructor(workspace: IWorkspaceData, environment: IEnvironment) {
|
||||
@@ -77,7 +77,7 @@ class ExtensionStoragePath {
|
||||
this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value);
|
||||
}
|
||||
|
||||
get whenReady(): TPromise<any> {
|
||||
get whenReady(): Promise<any> {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ class ExtensionStoragePath {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private async _getOrCreateWorkspaceStoragePath(): TPromise<string> {
|
||||
private async _getOrCreateWorkspaceStoragePath(): Promise<string> {
|
||||
if (!this._workspace) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
@@ -168,8 +168,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), opts));
|
||||
}
|
||||
|
||||
$delete(handle: number, resource: UriComponents): TPromise<void, any> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).delete(URI.revive(resource), { recursive: true }));
|
||||
$delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): TPromise<void, any> {
|
||||
return asWinJsPromise(() => this._fsProvider.get(handle).delete(URI.revive(resource), opts));
|
||||
}
|
||||
|
||||
$rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise<void, any> {
|
||||
|
||||
@@ -146,7 +146,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
const newUri = URI.revive(newUriDto);
|
||||
|
||||
const edits: WorkspaceEdit[] = [];
|
||||
return this._onWillRenameFile.fireAsync((bucket, listener) => {
|
||||
return TPromise.wrap(this._onWillRenameFile.fireAsync((bucket, listener) => {
|
||||
return {
|
||||
oldUri,
|
||||
newUri,
|
||||
@@ -179,6 +179,6 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
|
||||
}
|
||||
}
|
||||
return this._mainThreadTextEditors.$tryApplyWorkspaceEdit({ edits: flatten(allEdits) });
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,14 +237,14 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG
|
||||
return this._resourceStatesMap.get(handle);
|
||||
}
|
||||
|
||||
async $executeResourceCommand(handle: number): TPromise<void> {
|
||||
$executeResourceCommand(handle: number): TPromise<void> {
|
||||
const command = this._resourceStatesCommandsMap.get(handle);
|
||||
|
||||
if (!command) {
|
||||
return;
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
await this._commands.executeCommand(command.command, ...command.arguments);
|
||||
return asWinJsPromise(_ => this._commands.executeCommand(command.command, ...command.arguments));
|
||||
}
|
||||
|
||||
_takeResourceStateSnapshot(): SCMRawResourceSplice[] {
|
||||
@@ -568,25 +568,25 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
async $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise<void> {
|
||||
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise<void> {
|
||||
this.logService.trace('ExtHostSCM#$executeResourceCommand', sourceControlHandle, groupHandle, handle);
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
||||
if (!sourceControl) {
|
||||
return;
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
const group = sourceControl.getResourceGroup(groupHandle);
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
return TPromise.as(null);
|
||||
}
|
||||
|
||||
await group.$executeResourceCommand(handle);
|
||||
return group.$executeResourceCommand(handle);
|
||||
}
|
||||
|
||||
async $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined> {
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined> {
|
||||
this.logService.trace('ExtHostSCM#$validateInput', sourceControlHandle);
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
@@ -599,12 +599,12 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
const result = await sourceControl.inputBox.validateInput(value, cursorPosition);
|
||||
return asWinJsPromise(_ => Promise.resolve(sourceControl.inputBox.validateInput(value, cursorPosition))).then(result => {
|
||||
if (!result) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
return [result.message, result.type];
|
||||
return TPromise.as<[string, number]>([result.message, result.type]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import * as path from 'path';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import * as glob from 'vs/base/common/glob';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { PPromise, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IItemAccessor, ScorerCache, compareItemsByScore, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer';
|
||||
import { ICachedSearchStats, IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchQuery, ISearchCompleteStats, IRawFileMatch2 } from 'vs/platform/search/common/search';
|
||||
import * as extfs from 'vs/base/node/extfs';
|
||||
import * as pfs from 'vs/base/node/pfs';
|
||||
import { IFileMatch, IFolderQuery, IPatternInfo, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
|
||||
type OneOrMore<T> = T | T[];
|
||||
|
||||
export interface ISchemeTransformer {
|
||||
transformOutgoing(scheme: string): string;
|
||||
@@ -36,9 +32,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
|
||||
|
||||
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _extfs = extfs, private _pfs = pfs) {
|
||||
this._proxy = mainContext.getProxy(MainContext.MainThreadSearch);
|
||||
this._fileSearchManager = new FileSearchManager(
|
||||
(eventName: string, data: any) => this._proxy.$handleTelemetry(eventName, data),
|
||||
this._pfs);
|
||||
this._fileSearchManager = new FileSearchManager(this._pfs);
|
||||
}
|
||||
|
||||
private _transformScheme(scheme: string): string {
|
||||
@@ -71,16 +65,20 @@ export class ExtHostSearch implements ExtHostSearchShape {
|
||||
null,
|
||||
null,
|
||||
progress => {
|
||||
if (Array.isArray(progress)) {
|
||||
progress.forEach(p => {
|
||||
this._proxy.$handleFindMatch(handle, session, p.resource);
|
||||
});
|
||||
} else {
|
||||
this._proxy.$handleFindMatch(handle, session, progress.resource);
|
||||
}
|
||||
this._proxy.$handleFileMatch(handle, session, progress.map(p => p.resource));
|
||||
});
|
||||
}
|
||||
|
||||
$clearCache(handle: number, cacheKey: string): TPromise<void> {
|
||||
const provider = this._searchProvider.get(handle);
|
||||
if (!provider.clearCache) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
return TPromise.as(
|
||||
this._fileSearchManager.clearCache(cacheKey, provider));
|
||||
}
|
||||
|
||||
$provideTextSearchResults(handle: number, session: number, pattern: IPatternInfo, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
|
||||
const provider = this._searchProvider.get(handle);
|
||||
if (!provider.provideTextSearchResults) {
|
||||
@@ -93,7 +91,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
|
||||
null,
|
||||
null,
|
||||
progress => {
|
||||
this._proxy.$handleFindMatch(handle, session, progress);
|
||||
this._proxy.$handleTextMatch(handle, session, progress);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -132,29 +130,28 @@ function reviveFolderQuery(rawFolderQuery: IFolderQuery<UriComponents>): IFolder
|
||||
}
|
||||
|
||||
class TextSearchResultsCollector {
|
||||
private _batchedCollector: BatchedCollector<IRawFileMatch2>;
|
||||
private _batchedCollector: BatchedCollector<IFileMatch>;
|
||||
|
||||
private _currentFolderIdx: number;
|
||||
private _currentRelativePath: string;
|
||||
private _currentFileMatch: IRawFileMatch2;
|
||||
private _currentUri: URI;
|
||||
private _currentFileMatch: IFileMatch;
|
||||
|
||||
constructor(private folderQueries: IFolderQuery[], private _onResult: (result: IRawFileMatch2[]) => void) {
|
||||
this._batchedCollector = new BatchedCollector<IRawFileMatch2>(512, items => this.sendItems(items));
|
||||
constructor(private _onResult: (result: IFileMatch[]) => void) {
|
||||
this._batchedCollector = new BatchedCollector<IFileMatch>(512, items => this.sendItems(items));
|
||||
}
|
||||
|
||||
add(data: vscode.TextSearchResult, folderIdx: number): void {
|
||||
// Collects TextSearchResults into IInternalFileMatches and collates using BatchedCollector.
|
||||
// This is efficient for ripgrep which sends results back one file at a time. It wouldn't be efficient for other search
|
||||
// providers that send results in random order. We could do this step afterwards instead.
|
||||
if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || this._currentRelativePath !== data.path)) {
|
||||
if (this._currentFileMatch && (this._currentFolderIdx !== folderIdx || resources.isEqual(this._currentUri, data.uri))) {
|
||||
this.pushToCollector();
|
||||
this._currentFileMatch = null;
|
||||
}
|
||||
|
||||
if (!this._currentFileMatch) {
|
||||
const resource = joinPath(this.folderQueries[folderIdx].folder, data.path);
|
||||
this._currentFileMatch = {
|
||||
resource,
|
||||
resource: data.uri,
|
||||
lineMatches: []
|
||||
};
|
||||
}
|
||||
@@ -180,8 +177,8 @@ class TextSearchResultsCollector {
|
||||
this._batchedCollector.flush();
|
||||
}
|
||||
|
||||
private sendItems(items: IRawFileMatch2 | IRawFileMatch2[]): void {
|
||||
this._onResult(Array.isArray(items) ? items : [items]);
|
||||
private sendItems(items: IFileMatch[]): void {
|
||||
this._onResult(items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +199,7 @@ class BatchedCollector<T> {
|
||||
private batchSize = 0;
|
||||
private timeoutHandle: number;
|
||||
|
||||
constructor(private maxBatchSize: number, private cb: (items: T | T[]) => void) {
|
||||
constructor(private maxBatchSize: number, private cb: (items: T[]) => void) {
|
||||
}
|
||||
|
||||
addItem(item: T, size: number): void {
|
||||
@@ -210,11 +207,7 @@ class BatchedCollector<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.maxBatchSize > 0) {
|
||||
this.addItemToBatch(item, size);
|
||||
} else {
|
||||
this.cb(item);
|
||||
}
|
||||
this.addItemToBatch(item, size);
|
||||
}
|
||||
|
||||
addItems(items: T[], size: number): void {
|
||||
@@ -390,11 +383,11 @@ class TextSearchEngine {
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(): PPromise<{ limitHit: boolean }, IRawFileMatch2[]> {
|
||||
public search(): PPromise<{ limitHit: boolean }, IFileMatch[]> {
|
||||
const folderQueries = this.config.folderQueries;
|
||||
|
||||
return new PPromise<{ limitHit: boolean }, IRawFileMatch2[]>((resolve, reject, _onResult) => {
|
||||
this.collector = new TextSearchResultsCollector(this.config.folderQueries, _onResult);
|
||||
return new PPromise<{ limitHit: boolean }, IFileMatch[]>((resolve, reject, _onResult) => {
|
||||
this.collector = new TextSearchResultsCollector(_onResult);
|
||||
|
||||
const onResult = (match: vscode.TextSearchResult, folderIdx: number) => {
|
||||
if (this.isCanceled) {
|
||||
@@ -437,11 +430,12 @@ class TextSearchEngine {
|
||||
const progress = {
|
||||
report: (result: vscode.TextSearchResult) => {
|
||||
const siblingFn = folderQuery.folder.scheme === 'file' && (() => {
|
||||
return this.readdir(path.dirname(path.join(folderQuery.folder.fsPath, result.path)));
|
||||
return this.readdir(path.dirname(result.uri.fsPath));
|
||||
});
|
||||
|
||||
const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath);
|
||||
testingPs.push(
|
||||
queryTester.includedInQuery(result.path, path.basename(result.path), siblingFn)
|
||||
queryTester.includedInQuery(relativePath, path.basename(relativePath), siblingFn)
|
||||
.then(included => {
|
||||
if (included) {
|
||||
onResult(result);
|
||||
@@ -495,7 +489,8 @@ class TextSearchEngine {
|
||||
useIgnoreFiles: !this.config.disregardIgnoreFiles,
|
||||
followSymlinks: !this.config.ignoreSymlinks,
|
||||
encoding: this.config.fileEncoding,
|
||||
maxFileSize: this.config.maxFileSize
|
||||
maxFileSize: this.config.maxFileSize,
|
||||
maxResults: this.config.maxResults
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -515,16 +510,12 @@ class FileSearchEngine {
|
||||
private includePattern: glob.ParsedExpression;
|
||||
private maxResults: number;
|
||||
private exists: boolean;
|
||||
// private maxFilesize: number;
|
||||
private isLimitHit: boolean;
|
||||
private resultCount: number;
|
||||
private isCanceled: boolean;
|
||||
|
||||
private activeCancellationTokens: Set<CancellationTokenSource>;
|
||||
|
||||
// private filesWalked: number;
|
||||
// private directoriesWalked: number;
|
||||
|
||||
private globalExcludePattern: glob.ParsedExpression;
|
||||
|
||||
constructor(private config: ISearchQuery, private provider: vscode.SearchProvider, private _pfs: typeof pfs) {
|
||||
@@ -532,14 +523,10 @@ class FileSearchEngine {
|
||||
this.includePattern = config.includePattern && glob.parse(config.includePattern);
|
||||
this.maxResults = config.maxResults || null;
|
||||
this.exists = config.exists;
|
||||
// this.maxFilesize = config.maxFileSize || null;
|
||||
this.resultCount = 0;
|
||||
this.isLimitHit = false;
|
||||
this.activeCancellationTokens = new Set<CancellationTokenSource>();
|
||||
|
||||
// this.filesWalked = 0;
|
||||
// this.directoriesWalked = 0;
|
||||
|
||||
if (this.filePattern) {
|
||||
this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase();
|
||||
}
|
||||
@@ -553,87 +540,68 @@ class FileSearchEngine {
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(): PPromise<{ isLimitHit: boolean }, IInternalFileMatch> {
|
||||
public search(): PPromise<IInternalSearchComplete, IInternalFileMatch> {
|
||||
const folderQueries = this.config.folderQueries;
|
||||
|
||||
return new PPromise<{ isLimitHit: boolean }, IInternalFileMatch>((resolve, reject, _onResult) => {
|
||||
return new PPromise((resolve, reject, _onResult) => {
|
||||
const onResult = (match: IInternalFileMatch) => {
|
||||
this.resultCount++;
|
||||
_onResult(match);
|
||||
};
|
||||
|
||||
// Support that the file pattern is a full path to a file that exists
|
||||
this.checkFilePatternAbsoluteMatch().then(({ exists, size }) => {
|
||||
if (this.isCanceled) {
|
||||
return resolve({ isLimitHit: this.isLimitHit });
|
||||
}
|
||||
if (this.isCanceled) {
|
||||
return resolve({ limitHit: this.isLimitHit, cacheKeys: [] });
|
||||
}
|
||||
|
||||
// Report result from file pattern if matching
|
||||
if (exists) {
|
||||
onResult({
|
||||
base: URI.file(this.filePattern),
|
||||
basename: path.basename(this.filePattern),
|
||||
size
|
||||
// For each extra file
|
||||
if (this.config.extraFileResources) {
|
||||
this.config.extraFileResources
|
||||
.forEach(extraFile => {
|
||||
const extraFileStr = extraFile.toString(); // ?
|
||||
const basename = path.basename(extraFileStr);
|
||||
if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) {
|
||||
return; // excluded
|
||||
}
|
||||
|
||||
// File: Check for match on file pattern and include pattern
|
||||
this.matchFile(onResult, { base: extraFile, basename });
|
||||
});
|
||||
}
|
||||
|
||||
// Optimization: a match on an absolute path is a good result and we do not
|
||||
// continue walking the entire root paths array for other matches because
|
||||
// it is very unlikely that another file would match on the full absolute path
|
||||
return resolve({ isLimitHit: this.isLimitHit });
|
||||
}
|
||||
// For each root folder
|
||||
PPromise.join(folderQueries.map(fq => {
|
||||
return this.searchInFolder(fq).then(null, null, onResult);
|
||||
})).then(cacheKeys => {
|
||||
resolve({ limitHit: this.isLimitHit, cacheKeys });
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
.filter(msg => !!msg)[0];
|
||||
|
||||
// For each extra file
|
||||
if (this.config.extraFileResources) {
|
||||
this.config.extraFileResources
|
||||
.forEach(extraFile => {
|
||||
const extraFileStr = extraFile.toString(); // ?
|
||||
const basename = path.basename(extraFileStr);
|
||||
if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) {
|
||||
return; // excluded
|
||||
}
|
||||
|
||||
// File: Check for match on file pattern and include pattern
|
||||
this.matchFile(onResult, { base: extraFile, basename });
|
||||
});
|
||||
}
|
||||
|
||||
// For each root folder
|
||||
PPromise.join(folderQueries.map(fq => {
|
||||
return this.searchInFolder(fq).then(null, null, onResult);
|
||||
})).then(() => {
|
||||
resolve({ isLimitHit: this.isLimitHit });
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
.filter(msg => !!msg)[0];
|
||||
|
||||
reject(new Error(errMsg));
|
||||
});
|
||||
reject(new Error(errMsg));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private searchInFolder(fq: IFolderQuery<URI>): PPromise<void, IInternalFileMatch> {
|
||||
private searchInFolder(fq: IFolderQuery<URI>): PPromise<string, IInternalFileMatch> {
|
||||
let cancellation = new CancellationTokenSource();
|
||||
return new PPromise((resolve, reject, onResult) => {
|
||||
const options = this.getSearchOptionsForFolder(fq);
|
||||
let filePatternSeen = false;
|
||||
const tree = this.initDirectoryTree();
|
||||
|
||||
const queryTester = new QueryGlobTester(this.config, fq);
|
||||
const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses();
|
||||
|
||||
const onProviderResult = (relativePath: string) => {
|
||||
const onProviderResult = (result: URI) => {
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (noSiblingsClauses) {
|
||||
if (relativePath === this.filePattern) {
|
||||
filePatternSeen = true;
|
||||
}
|
||||
const relativePath = path.relative(fq.folder.fsPath, result.fsPath);
|
||||
|
||||
const basename = path.basename(relativePath);
|
||||
if (noSiblingsClauses) {
|
||||
const basename = path.basename(result.fsPath);
|
||||
this.matchFile(onResult, { base: fq.folder, relativePath, basename });
|
||||
|
||||
return;
|
||||
@@ -643,10 +611,21 @@ class FileSearchEngine {
|
||||
this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
|
||||
};
|
||||
|
||||
new TPromise(resolve => process.nextTick(resolve))
|
||||
let folderCacheKey: string;
|
||||
new TPromise(_resolve => process.nextTick(_resolve))
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.add(cancellation);
|
||||
return this.provider.provideFileSearchResults(options, { report: onProviderResult }, cancellation.token);
|
||||
|
||||
folderCacheKey = this.config.cacheKey && (this.config.cacheKey + '_' + fq.folder.fsPath);
|
||||
|
||||
return this.provider.provideFileSearchResults(
|
||||
{
|
||||
pattern: this.config.filePattern || '',
|
||||
cacheKey: folderCacheKey
|
||||
},
|
||||
options,
|
||||
{ report: onProviderResult },
|
||||
cancellation.token);
|
||||
})
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.delete(cancellation);
|
||||
@@ -655,18 +634,16 @@ class FileSearchEngine {
|
||||
}
|
||||
|
||||
if (noSiblingsClauses && this.isLimitHit) {
|
||||
if (!filePatternSeen) {
|
||||
// If the limit was hit, check whether filePattern is an exact relative match because it must be included
|
||||
return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => {
|
||||
if (exists) {
|
||||
onResult({
|
||||
base: fq.folder,
|
||||
relativePath: this.filePattern,
|
||||
basename: path.basename(this.filePattern),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
// If the limit was hit, check whether filePattern is an exact relative match because it must be included
|
||||
return this.checkFilePatternRelativeMatch(fq.folder).then(({ exists, size }) => {
|
||||
if (exists) {
|
||||
onResult({
|
||||
base: fq.folder,
|
||||
relativePath: this.filePattern,
|
||||
basename: path.basename(this.filePattern),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.matchDirectoryTree(tree, queryTester, onResult);
|
||||
@@ -674,7 +651,7 @@ class FileSearchEngine {
|
||||
}).then(
|
||||
() => {
|
||||
cancellation.dispose();
|
||||
resolve(undefined);
|
||||
resolve(folderCacheKey);
|
||||
},
|
||||
err => {
|
||||
cancellation.dispose();
|
||||
@@ -692,7 +669,8 @@ class FileSearchEngine {
|
||||
excludes,
|
||||
includes,
|
||||
useIgnoreFiles: !this.config.disregardIgnoreFiles,
|
||||
followSymlinks: !this.config.ignoreSymlinks
|
||||
followSymlinks: !this.config.ignoreSymlinks,
|
||||
maxResults: this.config.maxResults
|
||||
};
|
||||
}
|
||||
|
||||
@@ -734,7 +712,6 @@ class FileSearchEngine {
|
||||
const self = this;
|
||||
const filePattern = this.filePattern;
|
||||
function matchDirectory(entries: IDirectoryEntry[]) {
|
||||
// self.directoriesWalked++;
|
||||
for (let i = 0, n = entries.length; i < n; i++) {
|
||||
const entry = entries[i];
|
||||
const { relativePath, basename } = entry;
|
||||
@@ -751,7 +728,6 @@ class FileSearchEngine {
|
||||
if (sub) {
|
||||
matchDirectory(sub);
|
||||
} else {
|
||||
// self.filesWalked++;
|
||||
if (relativePath === filePattern) {
|
||||
continue; // ignore file if its path matches with the file pattern because that is already matched above
|
||||
}
|
||||
@@ -767,44 +743,6 @@ class FileSearchEngine {
|
||||
matchDirectory(rootEntries);
|
||||
}
|
||||
|
||||
public getStats(): any {
|
||||
return null;
|
||||
// return {
|
||||
// fromCache: false,
|
||||
// traversal: Traversal[this.traversal],
|
||||
// errors: this.errors,
|
||||
// fileWalkStartTime: this.fileWalkStartTime,
|
||||
// fileWalkResultTime: Date.now(),
|
||||
// directoriesWalked: this.directoriesWalked,
|
||||
// filesWalked: this.filesWalked,
|
||||
// resultCount: this.resultCount,
|
||||
// cmdForkResultTime: this.cmdForkResultTime,
|
||||
// cmdResultCount: this.cmdResultCount
|
||||
// };
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the file pattern is an absolute path to a file that exists.
|
||||
* TODO@roblou should use FS provider?
|
||||
*/
|
||||
private checkFilePatternAbsoluteMatch(): TPromise<{ exists: boolean, size?: number }> {
|
||||
if (!this.filePattern || !path.isAbsolute(this.filePattern)) {
|
||||
return TPromise.wrap({ exists: false });
|
||||
}
|
||||
|
||||
return this._pfs.stat(this.filePattern)
|
||||
.then(stat => {
|
||||
return {
|
||||
exists: !stat.isDirectory(),
|
||||
size: stat.size
|
||||
};
|
||||
}, err => {
|
||||
return {
|
||||
exists: false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private checkFilePatternRelativeMatch(base: URI): TPromise<{ exists: boolean, size?: number }> {
|
||||
if (!this.filePattern || path.isAbsolute(this.filePattern) || base.scheme !== 'file') {
|
||||
return TPromise.wrap({ exists: false });
|
||||
@@ -851,263 +789,69 @@ class FileSearchEngine {
|
||||
}
|
||||
}
|
||||
|
||||
interface IInternalSearchComplete {
|
||||
limitHit: boolean;
|
||||
cacheKeys: string[];
|
||||
}
|
||||
|
||||
class FileSearchManager {
|
||||
|
||||
private static readonly BATCH_SIZE = 512;
|
||||
|
||||
private caches: { [cacheKey: string]: Cache; } = Object.create(null);
|
||||
private readonly expandedCacheKeys = new Map<string, string[]>();
|
||||
|
||||
constructor(private telemetryCallback: (eventName: string, data: any) => void, private _pfs: typeof pfs) { }
|
||||
constructor(private _pfs: typeof pfs) { }
|
||||
|
||||
public fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise<ISearchCompleteStats, OneOrMore<IFileMatch>> {
|
||||
if (config.sortByScore) {
|
||||
let sortedSearch = this.trySortedSearchFromCache(config);
|
||||
if (!sortedSearch) {
|
||||
const engineConfig = config.maxResults ?
|
||||
{
|
||||
...config,
|
||||
...{ maxResults: null }
|
||||
} :
|
||||
config;
|
||||
|
||||
const engine = new FileSearchEngine(engineConfig, provider, this._pfs);
|
||||
sortedSearch = this.doSortedSearch(engine, provider, config);
|
||||
}
|
||||
|
||||
return new PPromise<ISearchCompleteStats, OneOrMore<IFileMatch>>((c, e, p) => {
|
||||
process.nextTick(() => { // allow caller to register progress callback first
|
||||
sortedSearch.then(([result, rawMatches]) => {
|
||||
const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch));
|
||||
this.sendProgress(serializedMatches, p, FileSearchManager.BATCH_SIZE);
|
||||
c(result);
|
||||
}, e, p);
|
||||
});
|
||||
}, () => {
|
||||
sortedSearch.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
let searchPromise: PPromise<void, OneOrMore<IInternalFileMatch>>;
|
||||
return new PPromise<ISearchCompleteStats, OneOrMore<IFileMatch>>((c, e, p) => {
|
||||
fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise<ISearchCompleteStats, IFileMatch[]> {
|
||||
let searchP: PPromise;
|
||||
return new PPromise<ISearchCompleteStats, IFileMatch[]>((c, e, p) => {
|
||||
const engine = new FileSearchEngine(config, provider, this._pfs);
|
||||
searchPromise = this.doSearch(engine, provider, FileSearchManager.BATCH_SIZE)
|
||||
.then(c, e, progress => {
|
||||
if (Array.isArray(progress)) {
|
||||
p(progress.map(m => this.rawMatchToSearchItem(m)));
|
||||
} else if ((<IInternalFileMatch>progress).relativePath) {
|
||||
p(this.rawMatchToSearchItem(<IInternalFileMatch>progress));
|
||||
|
||||
searchP = this.doSearch(engine, FileSearchManager.BATCH_SIZE).then(
|
||||
result => {
|
||||
if (config.cacheKey) {
|
||||
this.expandedCacheKeys.set(config.cacheKey, result.cacheKeys);
|
||||
}
|
||||
|
||||
c({
|
||||
limitHit: result.limitHit
|
||||
});
|
||||
},
|
||||
e,
|
||||
progress => {
|
||||
p(progress.map(m => this.rawMatchToSearchItem(m)));
|
||||
});
|
||||
}, () => {
|
||||
searchPromise.cancel();
|
||||
if (searchP) {
|
||||
searchP.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
clearCache(cacheKey: string, provider: vscode.SearchProvider): void {
|
||||
if (!this.expandedCacheKeys.has(cacheKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.expandedCacheKeys.get(cacheKey).forEach(key => provider.clearCache(key));
|
||||
this.expandedCacheKeys.delete(cacheKey);
|
||||
}
|
||||
|
||||
private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch {
|
||||
return {
|
||||
resource: joinPath(match.base, match.relativePath)
|
||||
resource: resources.joinPath(match.base, match.relativePath)
|
||||
};
|
||||
}
|
||||
|
||||
private doSortedSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, config: IRawSearchQuery): PPromise<[ISearchCompleteStats, IInternalFileMatch[]]> {
|
||||
let searchPromise: PPromise<void, OneOrMore<IInternalFileMatch>>;
|
||||
let allResultsPromise = new PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore<IInternalFileMatch>>((c, e, p) => {
|
||||
let results: IInternalFileMatch[] = [];
|
||||
searchPromise = this.doSearch(engine, provider, -1)
|
||||
.then(result => {
|
||||
c([result, results]);
|
||||
this.telemetryCallback('fileSearch', null);
|
||||
}, e, progress => {
|
||||
if (Array.isArray(progress)) {
|
||||
results = progress;
|
||||
} else {
|
||||
p(progress);
|
||||
}
|
||||
});
|
||||
}, () => {
|
||||
searchPromise.cancel();
|
||||
});
|
||||
|
||||
let cache: Cache;
|
||||
if (config.cacheKey) {
|
||||
cache = this.getOrCreateCache(config.cacheKey);
|
||||
cache.resultsToSearchCache[config.filePattern] = allResultsPromise;
|
||||
allResultsPromise.then(null, err => {
|
||||
delete cache.resultsToSearchCache[config.filePattern];
|
||||
});
|
||||
allResultsPromise = this.preventCancellation(allResultsPromise);
|
||||
}
|
||||
|
||||
let chained: TPromise<void>;
|
||||
return new PPromise<[ISearchCompleteStats, IInternalFileMatch[]]>((c, e, p) => {
|
||||
chained = allResultsPromise.then(([result, results]) => {
|
||||
const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null);
|
||||
const unsortedResultTime = Date.now();
|
||||
return this.sortResults(config, results, scorerCache)
|
||||
.then(sortedResults => {
|
||||
const sortedResultTime = Date.now();
|
||||
|
||||
c([{
|
||||
stats: {
|
||||
...result.stats,
|
||||
...{ unsortedResultTime, sortedResultTime }
|
||||
},
|
||||
limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults
|
||||
}, sortedResults]);
|
||||
});
|
||||
}, e, p);
|
||||
}, () => {
|
||||
chained.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private getOrCreateCache(cacheKey: string): Cache {
|
||||
const existing = this.caches[cacheKey];
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
return this.caches[cacheKey] = new Cache();
|
||||
}
|
||||
|
||||
private trySortedSearchFromCache(config: IRawSearchQuery): TPromise<[ISearchCompleteStats, IInternalFileMatch[]]> {
|
||||
const cache = config.cacheKey && this.caches[config.cacheKey];
|
||||
if (!cache) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const cacheLookupStartTime = Date.now();
|
||||
const cached = this.getResultsFromCache(cache, config.filePattern);
|
||||
if (cached) {
|
||||
let chained: TPromise<void>;
|
||||
return new TPromise<[ISearchCompleteStats, IInternalFileMatch[]]>((c, e) => {
|
||||
chained = cached.then(([result, results, cacheStats]) => {
|
||||
const cacheLookupResultTime = Date.now();
|
||||
return this.sortResults(config, results, cache.scorerCache)
|
||||
.then(sortedResults => {
|
||||
const sortedResultTime = Date.now();
|
||||
|
||||
const stats: ICachedSearchStats = {
|
||||
fromCache: true,
|
||||
cacheLookupStartTime: cacheLookupStartTime,
|
||||
cacheFilterStartTime: cacheStats.cacheFilterStartTime,
|
||||
cacheLookupResultTime: cacheLookupResultTime,
|
||||
cacheEntryCount: cacheStats.cacheFilterResultCount,
|
||||
resultCount: results.length
|
||||
};
|
||||
if (config.sortByScore) {
|
||||
stats.unsortedResultTime = cacheLookupResultTime;
|
||||
stats.sortedResultTime = sortedResultTime;
|
||||
}
|
||||
if (!cacheStats.cacheWasResolved) {
|
||||
stats.joined = result.stats;
|
||||
}
|
||||
c([
|
||||
{
|
||||
limitHit: result.limitHit || typeof config.maxResults === 'number' && results.length > config.maxResults,
|
||||
stats: stats
|
||||
},
|
||||
sortedResults
|
||||
]);
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
chained.cancel();
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private sortResults(config: IRawSearchQuery, results: IInternalFileMatch[], scorerCache: ScorerCache): TPromise<IInternalFileMatch[]> {
|
||||
// we use the same compare function that is used later when showing the results using fuzzy scoring
|
||||
// this is very important because we are also limiting the number of results by config.maxResults
|
||||
// and as such we want the top items to be included in this result set if the number of items
|
||||
// exceeds config.maxResults.
|
||||
const query = prepareQuery(config.filePattern);
|
||||
const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, query, true, FileMatchItemAccessor, scorerCache);
|
||||
|
||||
return arrays.topAsync(results, compare, config.maxResults, 10000);
|
||||
}
|
||||
|
||||
private sendProgress(results: IFileMatch[], progressCb: (batch: IFileMatch[]) => void, batchSize: number) {
|
||||
if (batchSize && batchSize > 0) {
|
||||
for (let i = 0; i < results.length; i += batchSize) {
|
||||
progressCb(results.slice(i, i + batchSize));
|
||||
}
|
||||
} else {
|
||||
progressCb(results);
|
||||
}
|
||||
}
|
||||
|
||||
private getResultsFromCache(cache: Cache, searchValue: string): PPromise<[ISearchCompleteStats, IInternalFileMatch[], CacheStats]> {
|
||||
if (path.isAbsolute(searchValue)) {
|
||||
return null; // bypass cache if user looks up an absolute path where matching goes directly on disk
|
||||
}
|
||||
|
||||
// Find cache entries by prefix of search value
|
||||
const hasPathSep = searchValue.indexOf(path.sep) >= 0;
|
||||
let cached: PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore<IInternalFileMatch>>;
|
||||
let wasResolved: boolean;
|
||||
for (let previousSearch in cache.resultsToSearchCache) {
|
||||
|
||||
// If we narrow down, we might be able to reuse the cached results
|
||||
if (strings.startsWith(searchValue, previousSearch)) {
|
||||
if (hasPathSep && previousSearch.indexOf(path.sep) < 0) {
|
||||
continue; // since a path character widens the search for potential more matches, require it in previous search too
|
||||
}
|
||||
|
||||
const c = cache.resultsToSearchCache[previousSearch];
|
||||
c.then(() => { wasResolved = false; });
|
||||
wasResolved = true;
|
||||
cached = this.preventCancellation(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cached) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new PPromise<[ISearchCompleteStats, IInternalFileMatch[], CacheStats]>((c, e, p) => {
|
||||
cached.then(([complete, cachedEntries]) => {
|
||||
const cacheFilterStartTime = Date.now();
|
||||
|
||||
// Pattern match on results
|
||||
let results: IInternalFileMatch[] = [];
|
||||
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
|
||||
for (let i = 0; i < cachedEntries.length; i++) {
|
||||
let entry = cachedEntries[i];
|
||||
|
||||
// Check if this entry is a match for the search value
|
||||
if (!strings.fuzzyContains(entry.relativePath, normalizedSearchValueLowercase)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
results.push(entry);
|
||||
}
|
||||
|
||||
c([complete, results, {
|
||||
cacheWasResolved: wasResolved,
|
||||
cacheFilterStartTime: cacheFilterStartTime,
|
||||
cacheFilterResultCount: cachedEntries.length
|
||||
}]);
|
||||
}, e, p);
|
||||
}, () => {
|
||||
cached.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private doSearch(engine: FileSearchEngine, provider: vscode.SearchProvider, batchSize?: number): PPromise<ISearchCompleteStats, OneOrMore<IInternalFileMatch>> {
|
||||
return new PPromise<ISearchCompleteStats, OneOrMore<IInternalFileMatch>>((c, e, p) => {
|
||||
private doSearch(engine: FileSearchEngine, batchSize: number): PPromise<IInternalSearchComplete, IInternalFileMatch[]> {
|
||||
return new PPromise((c, e, p) => {
|
||||
let batch: IInternalFileMatch[] = [];
|
||||
engine.search().then(result => {
|
||||
if (batch.length) {
|
||||
p(batch);
|
||||
}
|
||||
|
||||
c({
|
||||
limitHit: result.isLimitHit,
|
||||
stats: engine.getStats() // TODO@roblou
|
||||
});
|
||||
c(result);
|
||||
}, error => {
|
||||
if (batch.length) {
|
||||
p(batch);
|
||||
@@ -1116,14 +860,10 @@ class FileSearchManager {
|
||||
e(error);
|
||||
}, match => {
|
||||
if (match) {
|
||||
if (batchSize) {
|
||||
batch.push(match);
|
||||
if (batchSize > 0 && batch.length >= batchSize) {
|
||||
p(batch);
|
||||
batch = [];
|
||||
}
|
||||
} else {
|
||||
p(match);
|
||||
batch.push(match);
|
||||
if (batchSize > 0 && batch.length >= batchSize) {
|
||||
p(batch);
|
||||
batch = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1131,48 +871,4 @@ class FileSearchManager {
|
||||
engine.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
public clearCache(cacheKey: string): TPromise<void> {
|
||||
delete this.caches[cacheKey];
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
private preventCancellation<C, P>(promise: PPromise<C, P>): PPromise<C, P> {
|
||||
return new PPromise<C, P>((c, e, p) => {
|
||||
// Allow for piled up cancellations to come through first.
|
||||
process.nextTick(() => {
|
||||
promise.then(c, e, p);
|
||||
});
|
||||
}, () => {
|
||||
// Do not propagate.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Cache {
|
||||
|
||||
public resultsToSearchCache: { [searchValue: string]: PPromise<[ISearchCompleteStats, IInternalFileMatch[]], OneOrMore<IInternalFileMatch>>; } = Object.create(null);
|
||||
|
||||
public scorerCache: ScorerCache = Object.create(null);
|
||||
}
|
||||
|
||||
const FileMatchItemAccessor = new class implements IItemAccessor<IInternalFileMatch> {
|
||||
|
||||
public getItemLabel(match: IInternalFileMatch): string {
|
||||
return match.basename; // e.g. myFile.txt
|
||||
}
|
||||
|
||||
public getItemDescription(match: IInternalFileMatch): string {
|
||||
return match.relativePath.substr(0, match.relativePath.length - match.basename.length - 1); // e.g. some/path/to/file
|
||||
}
|
||||
|
||||
public getItemPath(match: IInternalFileMatch): string {
|
||||
return match.relativePath; // e.g. some/path/to/file/myFile.txt
|
||||
}
|
||||
};
|
||||
|
||||
interface CacheStats {
|
||||
cacheWasResolved: boolean;
|
||||
cacheFilterStartTime: number;
|
||||
cacheFilterResultCount: number;
|
||||
}
|
||||
|
||||
@@ -860,15 +860,23 @@ export class ExtHostTask implements ExtHostTaskShape {
|
||||
}
|
||||
}
|
||||
|
||||
public $provideTasks(handle: number): TPromise<tasks.TaskSet> {
|
||||
public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): TPromise<tasks.TaskSet> {
|
||||
let handler = this._handlers.get(handle);
|
||||
if (!handler) {
|
||||
return TPromise.wrapError<tasks.TaskSet>(new Error('no handler found'));
|
||||
}
|
||||
return asWinJsPromise(token => handler.provider.provideTasks(token)).then(value => {
|
||||
let sanitized: vscode.Task[] = [];
|
||||
for (let task of value) {
|
||||
if (task.definition && validTypes[task.definition.type] === true) {
|
||||
sanitized.push(task);
|
||||
} else {
|
||||
console.error(`Dropping task [${task.source}, ${task.name}]. Its type is not known to the system.`);
|
||||
}
|
||||
}
|
||||
let workspaceFolders = this._workspaceService.getWorkspaceFolders();
|
||||
return {
|
||||
tasks: Tasks.from(value, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension),
|
||||
tasks: Tasks.from(sanitized, workspaceFolders && workspaceFolders.length > 0 ? workspaceFolders[0] : undefined, handler.extension),
|
||||
extension: handler.extension
|
||||
};
|
||||
});
|
||||
|
||||
@@ -62,7 +62,7 @@ export class BaseExtHostTerminal {
|
||||
request.run(this._proxy, this._id);
|
||||
}
|
||||
|
||||
protected _runQueuedRequests(id: number): void {
|
||||
public _runQueuedRequests(id: number): void {
|
||||
this._id = id;
|
||||
this._idPromiseComplete(id);
|
||||
this._queuedRequests.forEach((r) => {
|
||||
@@ -77,7 +77,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi
|
||||
private _pidPromiseComplete: (value: number) => any;
|
||||
|
||||
private readonly _onData: Emitter<string> = new Emitter<string>();
|
||||
public get onData(): Event<string> {
|
||||
public get onDidWriteData(): Event<string> {
|
||||
// Tell the main side to start sending data if it's not already
|
||||
this._proxy.$registerOnDataListener(this._id);
|
||||
return this._onData && this._onData.event;
|
||||
@@ -156,7 +156,7 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
|
||||
}
|
||||
|
||||
private readonly _onInput: Emitter<string> = new Emitter<string>();
|
||||
public get onInput(): Event<string> {
|
||||
public get onDidAcceptInput(): Event<string> {
|
||||
this._checkDisposed();
|
||||
this._queueApiRequest(this._proxy.$terminalRendererRegisterOnInputListener, [this._id]);
|
||||
// Tell the main side to start sending data if it's not already
|
||||
@@ -179,7 +179,7 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
|
||||
}
|
||||
return {
|
||||
rows: this._maximumDimensions.rows,
|
||||
cols: this._maximumDimensions.cols
|
||||
columns: this._maximumDimensions.columns
|
||||
};
|
||||
}
|
||||
|
||||
@@ -188,18 +188,19 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
|
||||
return this._onDidChangeMaximumDimensions && this._onDidChangeMaximumDimensions.event;
|
||||
}
|
||||
|
||||
public get terminal(): Promise<ExtHostTerminal> {
|
||||
return this._idPromise.then(id => this._fetchTerminal(id));
|
||||
public get terminal(): ExtHostTerminal {
|
||||
return this._terminal;
|
||||
}
|
||||
|
||||
constructor(
|
||||
proxy: MainThreadTerminalServiceShape,
|
||||
private _name: string,
|
||||
private _fetchTerminal: (id: number) => Promise<ExtHostTerminal>
|
||||
private _terminal: ExtHostTerminal
|
||||
) {
|
||||
super(proxy);
|
||||
this._proxy.$createTerminalRenderer(this._name).then(id => {
|
||||
this._runQueuedRequests(id);
|
||||
(<any>this._terminal)._runQueuedRequests(id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -212,8 +213,11 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
|
||||
this._onInput.fire(data);
|
||||
}
|
||||
|
||||
public _setMaximumDimensions(cols: number, rows: number): void {
|
||||
this._maximumDimensions = { cols, rows };
|
||||
public _setMaximumDimensions(columns: number, rows: number): void {
|
||||
if (this._maximumDimensions && this._maximumDimensions.columns === columns && this._maximumDimensions.rows === rows) {
|
||||
return;
|
||||
}
|
||||
this._maximumDimensions = { columns, rows };
|
||||
this._onDidChangeMaximumDimensions.fire(this.maximumDimensions);
|
||||
}
|
||||
}
|
||||
@@ -258,8 +262,13 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
}
|
||||
|
||||
public createTerminalRenderer(name: string): vscode.TerminalRenderer {
|
||||
const renderer = new ExtHostTerminalRenderer(this._proxy, name, (id) => this._getTerminalByIdEventually(id));
|
||||
const terminal = new ExtHostTerminal(this._proxy, name);
|
||||
terminal._setProcessId(undefined);
|
||||
this._terminals.push(terminal);
|
||||
|
||||
const renderer = new ExtHostTerminalRenderer(this._proxy, name, terminal);
|
||||
this._terminalRenderers.push(renderer);
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@@ -267,18 +276,22 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
const original = this._activeTerminal;
|
||||
if (id === null) {
|
||||
this._activeTerminal = undefined;
|
||||
} else {
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
this._activeTerminal = terminal;
|
||||
if (original !== this._activeTerminal) {
|
||||
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
|
||||
}
|
||||
}
|
||||
if (original !== this._activeTerminal) {
|
||||
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
|
||||
}
|
||||
this._performTerminalIdAction(id, terminal => {
|
||||
if (terminal) {
|
||||
this._activeTerminal = terminal;
|
||||
if (original !== this._activeTerminal) {
|
||||
this._onDidChangeActiveTerminal.fire(this._activeTerminal);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public $acceptTerminalProcessData(id: number, data: string): void {
|
||||
// TODO: Queue requests, currently the first 100ms of data may get missed
|
||||
const terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal._fireOnData(data);
|
||||
@@ -322,16 +335,19 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
}
|
||||
|
||||
public $acceptTerminalProcessId(id: number, processId: number): void {
|
||||
let terminal = this._getTerminalById(id);
|
||||
this._performTerminalIdAction(id, terminal => terminal._setProcessId(processId));
|
||||
}
|
||||
|
||||
private _performTerminalIdAction(id: number, callback: (terminal: ExtHostTerminal) => void): void {
|
||||
let terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal._setProcessId(processId);
|
||||
callback(terminal);
|
||||
} else {
|
||||
// Retry one more time in case the terminal has not yet been initialized.
|
||||
setTimeout(() => {
|
||||
terminal = this._getTerminalById(id);
|
||||
if (terminal) {
|
||||
terminal._setProcessId(processId);
|
||||
callback(terminal);
|
||||
}
|
||||
}, EXT_HOST_CREATION_DELAY);
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
private elements: Map<TreeItemHandle, T> = new Map<TreeItemHandle, T>();
|
||||
private nodes: Map<T, TreeNode> = new Map<T, TreeNode>();
|
||||
|
||||
private _visible: boolean = true;
|
||||
private _visible: boolean = false;
|
||||
get visible(): boolean { return this._visible; }
|
||||
|
||||
private _selectedHandles: TreeItemHandle[] = [];
|
||||
|
||||
@@ -495,11 +495,17 @@ export class TextEdit {
|
||||
}
|
||||
|
||||
|
||||
export interface IFileOperationOptions {
|
||||
overwrite?: boolean;
|
||||
ignoreIfExists?: boolean;
|
||||
recursive?: boolean;
|
||||
}
|
||||
|
||||
export interface IFileOperation {
|
||||
_type: 1;
|
||||
from: URI;
|
||||
to: URI;
|
||||
options?: { overwrite?: boolean, ignoreIfExists?: boolean; };
|
||||
options?: IFileOperationOptions;
|
||||
}
|
||||
|
||||
export interface IFileTextEdit {
|
||||
@@ -520,8 +526,8 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
this._edits.push({ _type: 1, from: undefined, to: uri, options });
|
||||
}
|
||||
|
||||
deleteFile(uri: vscode.Uri): void {
|
||||
this._edits.push({ _type: 1, from: uri, to: undefined });
|
||||
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean }): void {
|
||||
this._edits.push({ _type: 1, from: uri, to: undefined, options });
|
||||
}
|
||||
|
||||
replace(uri: URI, range: Range, newText: string): void {
|
||||
@@ -593,8 +599,8 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
return values(textEdits);
|
||||
}
|
||||
|
||||
_allEntries(): ([URI, TextEdit[]] | [URI, URI, { overwrite?: boolean, ignoreIfExists?: boolean }])[] {
|
||||
let res: ([URI, TextEdit[]] | [URI, URI, { overwrite?: boolean, ignoreIfExists?: boolean }])[] = [];
|
||||
_allEntries(): ([URI, TextEdit[]] | [URI, URI, IFileOperationOptions])[] {
|
||||
let res: ([URI, TextEdit[]] | [URI, URI, IFileOperationOptions])[] = [];
|
||||
for (let edit of this._edits) {
|
||||
if (edit._type === 1) {
|
||||
res.push([edit.from, edit.to, edit.options]);
|
||||
|
||||
@@ -4,22 +4,25 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { posix, relative, join } from 'path';
|
||||
import { delta as arrayDelta } from 'vs/base/common/arrays';
|
||||
import { relative, posix } from 'path';
|
||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IWorkspaceData, ExtHostWorkspaceShape, MainContext, MainThreadWorkspaceShape, IMainContext, MainThreadMessageServiceShape } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
|
||||
import { normalize } from 'vs/base/common/paths';
|
||||
import { isLinux } from 'vs/base/common/platform';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { basenameOrAuthority, isEqual } from 'vs/base/common/resources';
|
||||
import { compare } from 'vs/base/common/strings';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { Severity } from 'vs/platform/notification/common/notification';
|
||||
import { IQueryOptions, IRawFileMatch2 } from 'vs/platform/search/common/search';
|
||||
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { Range } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import * as vscode from 'vscode';
|
||||
import { ExtHostWorkspaceShape, IMainContext, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol';
|
||||
|
||||
function isFolderEqual(folderA: URI, folderB: URI): boolean {
|
||||
return isEqual(folderA, folderB, !isLinux);
|
||||
@@ -145,6 +148,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
|
||||
readonly onDidChangeWorkspace: Event<vscode.WorkspaceFoldersChangeEvent> = this._onDidChangeWorkspace.event;
|
||||
|
||||
private readonly _activeSearchCallbacks = [];
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
data: IWorkspaceData,
|
||||
@@ -324,8 +329,8 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
|
||||
// Events
|
||||
this._onDidChangeWorkspace.fire(Object.freeze({
|
||||
added: Object.freeze<vscode.WorkspaceFolder[]>(added),
|
||||
removed: Object.freeze<vscode.WorkspaceFolder[]>(removed)
|
||||
added,
|
||||
removed,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -358,13 +363,70 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
}
|
||||
}
|
||||
|
||||
const result = this._proxy.$startSearch(includePattern, includeFolder, excludePatternOrDisregardExcludes, maxResults, requestId);
|
||||
const result = this._proxy.$startFileSearch(includePattern, includeFolder, excludePatternOrDisregardExcludes, maxResults, requestId);
|
||||
if (token) {
|
||||
token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId));
|
||||
}
|
||||
return result.then(data => Array.isArray(data) ? data.map(URI.revive) : []);
|
||||
}
|
||||
|
||||
findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: string, token?: vscode.CancellationToken) {
|
||||
this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId}, entryPoint: findTextInFiles`);
|
||||
|
||||
const requestId = ExtHostWorkspace._requestIdPool++;
|
||||
|
||||
const globPatternToString = (pattern: vscode.GlobPattern | string) => {
|
||||
if (typeof pattern === 'string') {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
return join(pattern.base, pattern.pattern);
|
||||
};
|
||||
|
||||
const queryOptions: IQueryOptions = {
|
||||
ignoreSymlinks: typeof options.followSymlinks === 'boolean' ? !options.followSymlinks : undefined,
|
||||
disregardIgnoreFiles: typeof options.useIgnoreFiles === 'boolean' ? !options.useIgnoreFiles : undefined,
|
||||
disregardExcludeSettings: options.exclude === null,
|
||||
fileEncoding: options.encoding,
|
||||
maxResults: options.maxResults,
|
||||
|
||||
includePattern: options.include && globPatternToString(options.include),
|
||||
excludePattern: options.exclude && globPatternToString(options.exclude)
|
||||
};
|
||||
|
||||
this._activeSearchCallbacks[requestId] = p => {
|
||||
p.lineMatches.forEach(lineMatch => {
|
||||
lineMatch.offsetAndLengths.forEach(offsetAndLength => {
|
||||
const range = new Range(lineMatch.lineNumber, offsetAndLength[0], lineMatch.lineNumber, offsetAndLength[0] + offsetAndLength[1]);
|
||||
callback({
|
||||
uri: URI.revive(p.resource),
|
||||
preview: { text: lineMatch.preview, match: range },
|
||||
range
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (token) {
|
||||
token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId));
|
||||
}
|
||||
|
||||
return this._proxy.$startTextSearch(query, queryOptions, requestId).then(
|
||||
() => {
|
||||
delete this._activeSearchCallbacks[requestId];
|
||||
},
|
||||
err => {
|
||||
delete this._activeSearchCallbacks[requestId];
|
||||
return TPromise.wrapError(err);
|
||||
});
|
||||
}
|
||||
|
||||
$handleTextSearchResult(result: IRawFileMatch2, requestId: number): void {
|
||||
if (this._activeSearchCallbacks[requestId]) {
|
||||
this._activeSearchCallbacks[requestId](result);
|
||||
}
|
||||
}
|
||||
|
||||
saveAll(includeUntitled?: boolean): Thenable<boolean> {
|
||||
return this._proxy.$saveAll(includeUntitled);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user