mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 19:18:59 +01:00
Merge branch 'master' into remove-ppromise
This commit is contained in:
@@ -94,11 +94,11 @@ function _generateMarkdown(description: string | ICommandHandlerDescription): st
|
||||
parts.push('\n\n');
|
||||
if (description.args) {
|
||||
for (let arg of description.args) {
|
||||
parts.push(`* _${arg.name}_ ${arg.description || ''}\n`);
|
||||
parts.push(`* _${arg.name}_ - ${arg.description || ''}\n`);
|
||||
}
|
||||
}
|
||||
if (description.returns) {
|
||||
parts.push(`* _(returns)_ ${description.returns}`);
|
||||
parts.push(`* _(returns)_ - ${description.returns}`);
|
||||
}
|
||||
parts.push('\n\n');
|
||||
return parts.join('');
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { FileWriteOptions, FileSystemProviderCapabilities, IFileChange, IFileService, IFileSystemProvider, IStat, IWatchOptions, FileType, FileOverwriteOptions, FileDeleteOptions } from 'vs/platform/files/common/files';
|
||||
@@ -71,11 +71,9 @@ class RemoteFileSystemProvider implements IFileSystemProvider {
|
||||
watch(resource: URI, opts: IWatchOptions) {
|
||||
const session = Math.random();
|
||||
this._proxy.$watch(this._handle, session, resource, opts);
|
||||
return {
|
||||
dispose: () => {
|
||||
this._proxy.$unwatch(this._handle, session);
|
||||
}
|
||||
};
|
||||
return toDisposable(() => {
|
||||
this._proxy.$unwatch(this._handle, session);
|
||||
});
|
||||
}
|
||||
|
||||
$onFileSystemChange(changes: IFileChangeDto[]): void {
|
||||
|
||||
@@ -162,7 +162,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
$registerImplementationSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.ImplementationProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.ImplementationProvider>{
|
||||
provideImplementation: (model, position, token): Thenable<modes.Definition> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto);
|
||||
return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -170,7 +170,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
|
||||
$registerTypeDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void {
|
||||
this._registrations[handle] = modes.TypeDefinitionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), <modes.TypeDefinitionProvider>{
|
||||
provideTypeDefinition: (model, position, token): Thenable<modes.Definition> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto);
|
||||
return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveDefinitionLinkDto);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands
|
||||
import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction';
|
||||
import { ICodeActionsOnSaveOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
export interface ISaveParticipantParticipant extends ISaveParticipant {
|
||||
// progressMessage: string;
|
||||
@@ -215,16 +216,21 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant {
|
||||
const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() });
|
||||
|
||||
return new Promise<ISingleEditOperation[]>((resolve, reject) => {
|
||||
setTimeout(() => reject(localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout)), timeout);
|
||||
getDocumentFormattingEdits(model, { tabSize, insertSpaces })
|
||||
.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits))
|
||||
.then(resolve, err => {
|
||||
if (!(err instanceof Error) || err.name !== NoProviderError.Name) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
let source = new CancellationTokenSource();
|
||||
let request = getDocumentFormattingEdits(model, { tabSize, insertSpaces }, source.token);
|
||||
|
||||
setTimeout(() => {
|
||||
reject(localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout));
|
||||
source.cancel();
|
||||
}, timeout);
|
||||
|
||||
request.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits)).then(resolve, err => {
|
||||
if (!(err instanceof Error) || err.name !== NoProviderError.Name) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
|
||||
}).then(edits => {
|
||||
if (!isFalsyOrEmpty(edits) && versionNow === model.getVersionId()) {
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
'use strict';
|
||||
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { PPromise, TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IFileMatch, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, QueryType, IRawFileMatch2, ISearchCompleteStats } from 'vs/platform/search/common/search';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, QueryType, SearchProviderType } from 'vs/platform/search/common/search';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../node/extHost.protocol';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadSearch)
|
||||
export class MainThreadSearch implements MainThreadSearchShape {
|
||||
@@ -33,8 +33,16 @@ export class MainThreadSearch implements MainThreadSearchShape {
|
||||
this._searchProvider.clear();
|
||||
}
|
||||
|
||||
$registerSearchProvider(handle: number, scheme: string): void {
|
||||
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, scheme, handle, this._proxy));
|
||||
$registerTextSearchProvider(handle: number, scheme: string): void {
|
||||
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.text, scheme, handle, this._proxy));
|
||||
}
|
||||
|
||||
$registerFileSearchProvider(handle: number, scheme: string): void {
|
||||
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.file, scheme, handle, this._proxy));
|
||||
}
|
||||
|
||||
$registerFileIndexProvider(handle: number, scheme: string): void {
|
||||
this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.fileIndex, scheme, handle, this._proxy));
|
||||
}
|
||||
|
||||
$unregisterProvider(handle: number): void {
|
||||
@@ -86,38 +94,29 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
|
||||
|
||||
constructor(
|
||||
searchService: ISearchService,
|
||||
type: SearchProviderType,
|
||||
private readonly _scheme: string,
|
||||
private readonly _handle: number,
|
||||
private readonly _proxy: ExtHostSearchShape
|
||||
) {
|
||||
this._registrations = [searchService.registerSearchResultProvider(this._scheme, this)];
|
||||
this._registrations = [searchService.registerSearchResultProvider(this._scheme, type, this)];
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
dispose(this._registrations);
|
||||
}
|
||||
|
||||
search(query: ISearchQuery): PPromise<ISearchComplete, ISearchProgressItem> {
|
||||
search(query: ISearchQuery, onProgress?: (p: ISearchProgressItem) => void): TPromise<ISearchComplete> {
|
||||
|
||||
if (isFalsyOrEmpty(query.folderQueries)) {
|
||||
return PPromise.as(undefined);
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
const folderQueriesForScheme = query.folderQueries.filter(fq => fq.folder.scheme === this._scheme);
|
||||
if (!folderQueriesForScheme.length) {
|
||||
return TPromise.wrap(null);
|
||||
}
|
||||
|
||||
query = {
|
||||
...query,
|
||||
folderQueries: folderQueriesForScheme
|
||||
};
|
||||
|
||||
let outer: TPromise;
|
||||
|
||||
return new PPromise((resolve, reject, report) => {
|
||||
return new TPromise((resolve, reject) => {
|
||||
|
||||
const search = new SearchOperation(report);
|
||||
const search = new SearchOperation(onProgress);
|
||||
this._searches.set(search.id, search);
|
||||
|
||||
outer = query.type === QueryType.File
|
||||
@@ -139,7 +138,7 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable {
|
||||
}
|
||||
|
||||
clearCache(cacheKey: string): TPromise<void> {
|
||||
return this._proxy.$clearCache(this._handle, cacheKey);
|
||||
return this._proxy.$clearCache(cacheKey);
|
||||
}
|
||||
|
||||
handleFindMatch(session: number, dataOrUri: (UriComponents | IRawFileMatch2)[]): void {
|
||||
|
||||
@@ -16,6 +16,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
private _proxy: ExtHostTerminalServiceShape;
|
||||
private _toDispose: IDisposable[] = [];
|
||||
private _terminalProcesses: { [id: number]: ITerminalProcessExtHostProxy } = {};
|
||||
private _terminalOnDidWriteDataListeners: { [id: number]: IDisposable } = {};
|
||||
private _terminalOnDidAcceptInputListeners: { [id: number]: IDisposable } = {};
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@@ -114,9 +116,18 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
|
||||
public $terminalRendererRegisterOnInputListener(terminalId: number): void {
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.addDisposable(terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data)));
|
||||
if (!terminalInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Listener already registered
|
||||
if (this._terminalOnDidAcceptInputListeners.hasOwnProperty(terminalId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register
|
||||
this._terminalOnDidAcceptInputListeners[terminalId] = terminalInstance.onRendererInput(data => this._onTerminalRendererInput(terminalId, data));
|
||||
terminalInstance.addDisposable(this._terminalOnDidAcceptInputListeners[terminalId]);
|
||||
}
|
||||
|
||||
public $sendText(terminalId: number, text: string, addNewLine: boolean): void {
|
||||
@@ -128,11 +139,20 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
|
||||
public $registerOnDataListener(terminalId: number): void {
|
||||
const terminalInstance = this.terminalService.getInstanceFromId(terminalId);
|
||||
if (terminalInstance) {
|
||||
terminalInstance.addDisposable(terminalInstance.onData(data => {
|
||||
this._onTerminalData(terminalId, data);
|
||||
}));
|
||||
if (!terminalInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Listener already registered
|
||||
if (this._terminalOnDidWriteDataListeners[terminalId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register
|
||||
this._terminalOnDidWriteDataListeners[terminalId] = terminalInstance.onData(data => {
|
||||
this._onTerminalData(terminalId, data);
|
||||
});
|
||||
terminalInstance.addDisposable(this._terminalOnDidWriteDataListeners[terminalId]);
|
||||
}
|
||||
|
||||
private _onActiveTerminalChanged(terminalId: number | undefined): void {
|
||||
@@ -178,7 +198,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
|
||||
};
|
||||
this._proxy.$createProcess(request.proxy.terminalId, shellLaunchConfigDto, request.cols, request.rows);
|
||||
request.proxy.onInput(data => this._proxy.$acceptProcessInput(request.proxy.terminalId, data));
|
||||
request.proxy.onResize((cols, rows) => this._proxy.$acceptProcessResize(request.proxy.terminalId, cols, rows));
|
||||
request.proxy.onResize(dimensions => this._proxy.$acceptProcessResize(request.proxy.terminalId, dimensions.cols, dimensions.rows));
|
||||
request.proxy.onShutdown(() => this._proxy.$acceptProcessShutdown(request.proxy.terminalId));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as map from 'vs/base/common/map';
|
||||
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { EditorViewColumn, viewColumnToEditorGroup, editorGroupToViewColumn } from 'vs/workbench/api/shared/editor';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor';
|
||||
import { WebviewEditor } from 'vs/workbench/parts/webview/electron-browser/webviewEditor';
|
||||
import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInput';
|
||||
import { IWebviewEditorService, WebviewInputOptions, WebviewReviver, ICreateWebViewShowOptions } from 'vs/workbench/parts/webview/electron-browser/webviewEditorService';
|
||||
import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions, WebviewReviver } from 'vs/workbench/parts/webview/electron-browser/webviewEditorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||
import { extHostNamedCustomer } from './extHostCustomers';
|
||||
import * as vscode from 'vscode';
|
||||
import { extHostNamedCustomer } from './extHostCustomers';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWebviews)
|
||||
export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviver {
|
||||
@@ -96,6 +96,11 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
webview.setName(value);
|
||||
}
|
||||
|
||||
public $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.iconPath = reviveWebviewIcon(value);
|
||||
}
|
||||
|
||||
public $setHtml(handle: WebviewPanelHandle, value: string): void {
|
||||
const webview = this.getWebview(handle);
|
||||
webview.html = value;
|
||||
@@ -106,15 +111,15 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
webview.setOptions(reviveWebviewOptions(options));
|
||||
}
|
||||
|
||||
public $reveal(handle: WebviewPanelHandle, viewColumn: EditorViewColumn | null, preserveFocus: boolean): void {
|
||||
public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void {
|
||||
const webview = this.getWebview(handle);
|
||||
if (webview.isDisposed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetGroup = this._editorGroupService.getGroup(viewColumnToEditorGroup(this._editorGroupService, viewColumn));
|
||||
const targetGroup = this._editorGroupService.getGroup(viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn));
|
||||
|
||||
this._webviewService.revealWebview(webview, targetGroup || this._editorGroupService.activeGroup, preserveFocus);
|
||||
this._webviewService.revealWebview(webview, targetGroup || this._editorGroupService.activeGroup, showOptions.preserveFocus);
|
||||
}
|
||||
|
||||
public $postMessage(handle: WebviewPanelHandle, message: any): TPromise<boolean> {
|
||||
@@ -185,9 +190,12 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv
|
||||
onDidClickLink: uri => this.onDidClickLink(handle, uri),
|
||||
onMessage: message => this._proxy.$onMessage(handle, message),
|
||||
onDispose: () => {
|
||||
const cleanUp = () => {
|
||||
this._webviews.delete(handle);
|
||||
};
|
||||
this._proxy.$onDidDisposeWebviewPanel(handle).then(
|
||||
() => this._webviews.delete(handle),
|
||||
() => this._webviews.delete(handle));
|
||||
cleanUp,
|
||||
cleanUp);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -297,3 +305,16 @@ function reviveWebviewOptions(options: WebviewInputOptions): WebviewInputOptions
|
||||
localResourceRoots: Array.isArray(options.localResourceRoots) ? options.localResourceRoots.map(URI.revive) : undefined
|
||||
};
|
||||
}
|
||||
|
||||
function reviveWebviewIcon(
|
||||
value: { light: UriComponents, dark: UriComponents } | undefined
|
||||
): { light: URI, dark: URI } | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
light: URI.revive(value.light),
|
||||
dark: URI.revive(value.dark)
|
||||
};
|
||||
}
|
||||
@@ -10,8 +10,8 @@ import URI, { UriComponents } from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IFileMatch, IFolderQuery, IPatternInfo, IQueryOptions, ISearchConfiguration, ISearchQuery, ISearchService, QueryType } from 'vs/platform/search/common/search';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IFileMatch, IFolderQuery, IPatternInfo, IQueryOptions, ISearchConfiguration, ISearchQuery, ISearchService, QueryType, ISearchProgressItem } from 'vs/platform/search/common/search';
|
||||
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
@@ -19,6 +19,10 @@ import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder';
|
||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
|
||||
import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
|
||||
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
@@ -168,28 +172,29 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
const queryBuilder = this._instantiationService.createInstance(QueryBuilder);
|
||||
const query = queryBuilder.text(pattern, folders, options);
|
||||
|
||||
return new TPromise((resolve, reject) => {
|
||||
const search = this._searchService.search(query).then(
|
||||
() => {
|
||||
delete this._activeSearches[requestId];
|
||||
resolve(null);
|
||||
},
|
||||
err => {
|
||||
delete this._activeSearches[requestId];
|
||||
if (!isPromiseCanceledError(err)) {
|
||||
reject(TPromise.wrapError(err));
|
||||
}
|
||||
const onProgress = (p: ISearchProgressItem) => {
|
||||
if (p.lineMatches) {
|
||||
this._proxy.$handleTextSearchResult(p, requestId);
|
||||
}
|
||||
};
|
||||
|
||||
return undefined;
|
||||
},
|
||||
p => {
|
||||
if (p.lineMatches) {
|
||||
this._proxy.$handleTextSearchResult(p, requestId);
|
||||
}
|
||||
});
|
||||
const search = this._searchService.search(query, onProgress).then(
|
||||
() => {
|
||||
delete this._activeSearches[requestId];
|
||||
return null;
|
||||
},
|
||||
err => {
|
||||
delete this._activeSearches[requestId];
|
||||
if (!isPromiseCanceledError(err)) {
|
||||
return TPromise.wrapError(err);
|
||||
}
|
||||
|
||||
this._activeSearches[requestId] = search;
|
||||
});
|
||||
return undefined;
|
||||
});
|
||||
|
||||
this._activeSearches[requestId] = search;
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
$cancelSearch(requestId: number): Thenable<boolean> {
|
||||
@@ -210,3 +215,19 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CommandsRegistry.registerCommand('_workbench.enterWorkspace', async function (accessor: ServicesAccessor, workspace: URI, disableExtensions: string[]) {
|
||||
const workspaceEditingService = accessor.get(IWorkspaceEditingService);
|
||||
const extensionService = accessor.get(IExtensionService);
|
||||
const windowService = accessor.get(IWindowService);
|
||||
|
||||
if (disableExtensions && disableExtensions.length) {
|
||||
const runningExtensions = await extensionService.getExtensions();
|
||||
// If requested extension to disable is running, then reload window with given workspace
|
||||
if (disableExtensions && runningExtensions.some(runningExtension => disableExtensions.some(id => areSameExtensions({ id }, { id: runningExtension.id })))) {
|
||||
return windowService.openWindow([URI.file(workspace.fsPath)], { args: { _: [], 'disable-extension': disableExtensions } });
|
||||
}
|
||||
}
|
||||
|
||||
return workspaceEditingService.enterWorkspace(workspace.fsPath);
|
||||
});
|
||||
@@ -49,7 +49,7 @@ export class OpenFolderAPICommand {
|
||||
return executor.executeCommand('_files.pickFolderAndOpen', forceNewWindow);
|
||||
}
|
||||
|
||||
return executor.executeCommand('_files.windowOpen', [uri.fsPath], forceNewWindow);
|
||||
return executor.executeCommand('_files.windowOpen', [uri], forceNewWindow);
|
||||
}
|
||||
}
|
||||
CommandsRegistry.registerCommand(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute));
|
||||
|
||||
@@ -110,11 +110,11 @@ export function createApiFactory(
|
||||
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadTextEditors)));
|
||||
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors));
|
||||
const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostHeapService, extHostLogService));
|
||||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands));
|
||||
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
|
||||
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));
|
||||
@@ -262,7 +262,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);
|
||||
@@ -425,7 +425,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') {
|
||||
@@ -455,17 +455,12 @@ export function createApiFactory(
|
||||
registerUriHandler(handler: vscode.UriHandler) {
|
||||
return extHostUrls.registerUriHandler(extension.id, handler);
|
||||
},
|
||||
get quickInputBackButton() {
|
||||
return proposedApiFunction(extension, (): vscode.QuickInputButton => {
|
||||
return extHostQuickOpen.backButton;
|
||||
})();
|
||||
},
|
||||
createQuickPick: proposedApiFunction(extension, <T extends vscode.QuickPickItem>(): vscode.QuickPick<T> => {
|
||||
createQuickPick<T extends vscode.QuickPickItem>(): vscode.QuickPick<T> {
|
||||
return extHostQuickOpen.createQuickPick(extension.id);
|
||||
}),
|
||||
createInputBox: proposedApiFunction(extension, (): vscode.InputBox => {
|
||||
},
|
||||
createInputBox(): vscode.InputBox {
|
||||
return extHostQuickOpen.createInputBox(extension.id);
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
// namespace: workspace
|
||||
@@ -500,7 +495,19 @@ 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) => {
|
||||
findTextInFiles: (query: vscode.TextSearchQuery, optionsOrCallback, callbackOrToken?, token?: vscode.CancellationToken) => {
|
||||
let options: vscode.FindTextInFilesOptions;
|
||||
let callback: (result: vscode.TextSearchResult) => void;
|
||||
|
||||
if (typeof optionsOrCallback === 'object') {
|
||||
options = optionsOrCallback;
|
||||
callback = callbackOrToken;
|
||||
} else {
|
||||
options = {};
|
||||
callback = optionsOrCallback;
|
||||
token = callbackOrToken;
|
||||
}
|
||||
|
||||
return extHostWorkspace.findTextInFiles(query, options || {}, callback, extension.id, token);
|
||||
},
|
||||
saveAll: (includeUntitled?) => {
|
||||
@@ -570,8 +577,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);
|
||||
@@ -710,6 +727,7 @@ export function createApiFactory(
|
||||
OverviewRulerLane: OverviewRulerLane,
|
||||
ParameterInformation: extHostTypes.ParameterInformation,
|
||||
Position: extHostTypes.Position,
|
||||
QuickInputButtons: extHostTypes.QuickInputButtons,
|
||||
Range: extHostTypes.Range,
|
||||
Selection: extHostTypes.Selection,
|
||||
SignatureHelp: extHostTypes.SignatureHelp,
|
||||
|
||||
@@ -46,7 +46,6 @@ export interface IEnvironment {
|
||||
isExtensionDevelopmentDebug: boolean;
|
||||
appRoot: string;
|
||||
appSettingsHome: string;
|
||||
disableExtensions: boolean;
|
||||
extensionDevelopmentPath: string;
|
||||
extensionTestsPath: string;
|
||||
}
|
||||
@@ -426,11 +425,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>;
|
||||
@@ -481,7 +486,9 @@ export interface MainThreadFileSystemShape extends IDisposable {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -682,7 +689,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>;
|
||||
}
|
||||
|
||||
@@ -812,8 +819,8 @@ export interface ExtHostLanguageFeaturesShape {
|
||||
$provideCodeLenses(handle: number, resource: UriComponents): TPromise<modes.ICodeLensSymbol[]>;
|
||||
$resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol): TPromise<modes.ICodeLensSymbol>;
|
||||
$provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<DefinitionLinkDto[]>;
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<LocationDto | LocationDto[]>;
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<LocationDto | LocationDto[]>;
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<DefinitionLinkDto[]>;
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<DefinitionLinkDto[]>;
|
||||
$provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Hover>;
|
||||
$provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DocumentHighlight[]>;
|
||||
$provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext): TPromise<LocationDto[]>;
|
||||
|
||||
@@ -253,7 +253,7 @@ export class ExtHostApiCommands {
|
||||
});
|
||||
|
||||
this._register(SetEditorLayoutAPICommand.ID, adjustHandler(SetEditorLayoutAPICommand.execute), {
|
||||
description: 'Sets the editor layout. The layout is described as object with an initial (optional) orientation (0 = horizontal, 1 = vertical) and an array of editor groups within. Each editor group can have a size and another array of editor groups that will be layed out orthogonal to the orientation. If editor group sizes are provided, their sum must be 1 to be applied per row or column. Example for a 2x2 grid: `{ orientation: 0, groups: [{ groups: [{}, {}], size: 0.5 }, { groups: [{}, {}], size: 0.5 }] }`',
|
||||
description: 'Sets the editor layout. The layout is described as object with an initial (optional) orientation (0 = horizontal, 1 = vertical) and an array of editor groups within. Each editor group can have a size and another array of editor groups that will be laid out orthogonal to the orientation. If editor group sizes are provided, their sum must be 1 to be applied per row or column. Example for a 2x2 grid: `{ orientation: 0, groups: [{ groups: [{}, {}], size: 0.5 }, { groups: [{}, {}], size: 0.5 }] }`',
|
||||
args: [
|
||||
{ name: 'layout', description: 'The editor layout to set.', constraint: (value: EditorGroupLayout) => typeof value === 'object' && Array.isArray(value.groups) }
|
||||
]
|
||||
|
||||
@@ -18,15 +18,17 @@ import { keys } from 'vs/base/common/map';
|
||||
export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
|
||||
private readonly _name: string;
|
||||
private readonly _owner: string;
|
||||
private readonly _maxDiagnosticsPerFile: number;
|
||||
private readonly _onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>;
|
||||
private readonly _proxy: MainThreadDiagnosticsShape;
|
||||
|
||||
private _proxy: MainThreadDiagnosticsShape;
|
||||
private _isDisposed = false;
|
||||
private _data = new Map<string, vscode.Diagnostic[]>();
|
||||
|
||||
constructor(name: string, maxDiagnosticsPerFile: number, proxy: MainThreadDiagnosticsShape, onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>) {
|
||||
constructor(name: string, owner: string, maxDiagnosticsPerFile: number, proxy: MainThreadDiagnosticsShape, onDidChangeDiagnostics: Emitter<(vscode.Uri | string)[]>) {
|
||||
this._name = name;
|
||||
this._owner = owner;
|
||||
this._maxDiagnosticsPerFile = maxDiagnosticsPerFile;
|
||||
this._proxy = proxy;
|
||||
this._onDidChangeDiagnostics = onDidChangeDiagnostics;
|
||||
@@ -35,8 +37,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
dispose(): void {
|
||||
if (!this._isDisposed) {
|
||||
this._onDidChangeDiagnostics.fire(keys(this._data));
|
||||
this._proxy.$clear(this.name);
|
||||
this._proxy = undefined;
|
||||
this._proxy.$clear(this._owner);
|
||||
this._data = undefined;
|
||||
this._isDisposed = true;
|
||||
}
|
||||
@@ -142,21 +143,21 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection {
|
||||
}
|
||||
|
||||
this._onDidChangeDiagnostics.fire(toSync);
|
||||
this._proxy.$changeMany(this.name, entries);
|
||||
this._proxy.$changeMany(this._owner, entries);
|
||||
}
|
||||
|
||||
delete(uri: vscode.Uri): void {
|
||||
this._checkDisposed();
|
||||
this._onDidChangeDiagnostics.fire([uri]);
|
||||
this._data.delete(uri.toString());
|
||||
this._proxy.$changeMany(this.name, [[uri, undefined]]);
|
||||
this._proxy.$changeMany(this._owner, [[uri, undefined]]);
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this._checkDisposed();
|
||||
this._onDidChangeDiagnostics.fire(keys(this._data));
|
||||
this._data.clear();
|
||||
this._proxy.$clear(this.name);
|
||||
this._proxy.$clear(this._owner);
|
||||
}
|
||||
|
||||
forEach(callback: (uri: URI, diagnostics: vscode.Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void {
|
||||
@@ -204,7 +205,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
private static readonly _maxDiagnosticsPerFile: number = 1000;
|
||||
|
||||
private readonly _proxy: MainThreadDiagnosticsShape;
|
||||
private readonly _collections: DiagnosticCollection[] = [];
|
||||
private readonly _collections = new Map<string, DiagnosticCollection>();
|
||||
private readonly _onDidChangeDiagnostics = new Emitter<(vscode.Uri | string)[]>();
|
||||
|
||||
static _debouncer(last: (vscode.Uri | string)[], current: (vscode.Uri | string)[]): (vscode.Uri | string)[] {
|
||||
@@ -242,22 +243,28 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
}
|
||||
|
||||
createDiagnosticCollection(name: string): vscode.DiagnosticCollection {
|
||||
let { _collections, _proxy, _onDidChangeDiagnostics } = this;
|
||||
let owner: string;
|
||||
if (!name) {
|
||||
name = '_generated_diagnostic_collection_name_#' + ExtHostDiagnostics._idPool++;
|
||||
owner = name;
|
||||
} else if (!_collections.has(name)) {
|
||||
owner = name;
|
||||
} else {
|
||||
console.warn(`DiagnosticCollection with name '${name}' does already exist.`);
|
||||
do {
|
||||
owner = name + ExtHostDiagnostics._idPool++;
|
||||
} while (_collections.has(owner));
|
||||
}
|
||||
|
||||
const { _collections, _proxy, _onDidChangeDiagnostics } = this;
|
||||
const result = new class extends DiagnosticCollection {
|
||||
constructor() {
|
||||
super(name, ExtHostDiagnostics._maxDiagnosticsPerFile, _proxy, _onDidChangeDiagnostics);
|
||||
_collections.push(this);
|
||||
super(name, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, _proxy, _onDidChangeDiagnostics);
|
||||
_collections.set(owner, this);
|
||||
}
|
||||
dispose() {
|
||||
super.dispose();
|
||||
let idx = _collections.indexOf(this);
|
||||
if (idx !== -1) {
|
||||
_collections.splice(idx, 1);
|
||||
}
|
||||
_collections.delete(owner);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -272,7 +279,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
} else {
|
||||
let index = new Map<string, number>();
|
||||
let res: [vscode.Uri, vscode.Diagnostic[]][] = [];
|
||||
for (const collection of this._collections) {
|
||||
this._collections.forEach(collection => {
|
||||
collection.forEach((uri, diagnostics) => {
|
||||
let idx = index.get(uri.toString());
|
||||
if (typeof idx === 'undefined') {
|
||||
@@ -282,18 +289,18 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape {
|
||||
}
|
||||
res[idx][1] = res[idx][1].concat(...diagnostics);
|
||||
});
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
private _getDiagnostics(resource: vscode.Uri): vscode.Diagnostic[] {
|
||||
let res: vscode.Diagnostic[] = [];
|
||||
for (const collection of this._collections) {
|
||||
this._collections.forEach(collection => {
|
||||
if (collection.has(resource)) {
|
||||
res = res.concat(collection.get(resource));
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystemShape, IFileChangeDto } from './extHost.protocol';
|
||||
import * as vscode from 'vscode';
|
||||
import * as files from 'vs/platform/files/common/files';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { Range, FileChangeType } from 'vs/workbench/api/node/extHostTypes';
|
||||
@@ -132,15 +132,13 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape {
|
||||
this._proxy.$onFileSystemChange(handle, mapped);
|
||||
});
|
||||
|
||||
return {
|
||||
dispose: () => {
|
||||
subscription.dispose();
|
||||
this._linkProvider.delete(scheme);
|
||||
this._usedSchemes.delete(scheme);
|
||||
this._fsProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
}
|
||||
};
|
||||
return toDisposable(() => {
|
||||
subscription.dispose();
|
||||
this._linkProvider.delete(scheme);
|
||||
this._usedSchemes.delete(scheme);
|
||||
this._fsProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
private static _asIStat(stat: vscode.FileStat): files.IStat {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -143,6 +144,15 @@ class CodeLensAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
function convertToDefinitionLinks(value: vscode.Definition): modes.DefinitionLink[] {
|
||||
if (Array.isArray(value)) {
|
||||
return (value as (vscode.DefinitionLink | vscode.Location)[]).map(typeConvert.DefinitionLink.from);
|
||||
} else if (value) {
|
||||
return [typeConvert.DefinitionLink.from(value)];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
class DefinitionAdapter {
|
||||
|
||||
constructor(
|
||||
@@ -153,29 +163,7 @@ class DefinitionAdapter {
|
||||
provideDefinition(resource: URI, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = typeConvert.Position.to(position);
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideDefinition2 ? this._provider.provideDefinition2(doc, pos, token) : this._provider.provideDefinition(doc, pos, token)).then((value): modes.DefinitionLink[] => {
|
||||
if (Array.isArray(value)) {
|
||||
return (value as (vscode.DefinitionLink | vscode.Location)[]).map(x => DefinitionAdapter.convertDefinitionLink(x));
|
||||
} else if (value) {
|
||||
return [DefinitionAdapter.convertDefinitionLink(value)];
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private static convertDefinitionLink(value: vscode.Location | vscode.DefinitionLink): modes.DefinitionLink {
|
||||
const definitionLink = <vscode.DefinitionLink>value;
|
||||
return {
|
||||
origin: definitionLink.origin
|
||||
? typeConvert.Range.from(definitionLink.origin)
|
||||
: undefined,
|
||||
uri: value.uri,
|
||||
range: typeConvert.Range.from(value.range),
|
||||
selectionRange: definitionLink.selectionRange
|
||||
? typeConvert.Range.from(definitionLink.selectionRange)
|
||||
: undefined,
|
||||
};
|
||||
return asWinJsPromise(token => this._provider.provideDefinition(doc, pos, token)).then(convertToDefinitionLinks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,17 +174,10 @@ class ImplementationAdapter {
|
||||
private readonly _provider: vscode.ImplementationProvider
|
||||
) { }
|
||||
|
||||
provideImplementation(resource: URI, position: IPosition): TPromise<modes.Definition> {
|
||||
provideImplementation(resource: URI, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
let doc = this._documents.getDocumentData(resource).document;
|
||||
let pos = typeConvert.Position.to(position);
|
||||
return asWinJsPromise(token => this._provider.provideImplementation(doc, pos, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConvert.location.from);
|
||||
} else if (value) {
|
||||
return typeConvert.location.from(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return asWinJsPromise(token => this._provider.provideImplementation(doc, pos, token)).then(convertToDefinitionLinks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,17 +188,10 @@ class TypeDefinitionAdapter {
|
||||
private readonly _provider: vscode.TypeDefinitionProvider
|
||||
) { }
|
||||
|
||||
provideTypeDefinition(resource: URI, position: IPosition): TPromise<modes.Definition> {
|
||||
provideTypeDefinition(resource: URI, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const pos = typeConvert.Position.to(position);
|
||||
return asWinJsPromise(token => this._provider.provideTypeDefinition(doc, pos, token)).then(value => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(typeConvert.location.from);
|
||||
} else if (value) {
|
||||
return typeConvert.location.from(value);
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return asWinJsPromise(token => this._provider.provideTypeDefinition(doc, pos, token)).then(convertToDefinitionLinks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,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[]> {
|
||||
@@ -341,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,
|
||||
@@ -865,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,
|
||||
@@ -872,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);
|
||||
@@ -880,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[] {
|
||||
@@ -999,7 +986,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Definition> {
|
||||
$provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
return this._withAdapter(handle, ImplementationAdapter, adapter => adapter.provideImplementation(URI.revive(resource), position));
|
||||
}
|
||||
|
||||
@@ -1009,7 +996,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape {
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.Definition> {
|
||||
$provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise<modes.DefinitionLink[]> {
|
||||
return this._withAdapter(handle, TypeDefinitionAdapter, adapter => adapter.provideTypeDefinition(URI.revive(resource), position));
|
||||
}
|
||||
|
||||
@@ -1051,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);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,11 @@ export class ExtHostProgress implements ExtHostProgressShape {
|
||||
function mergeProgress(result: IProgressStep, currentValue: IProgressStep): IProgressStep {
|
||||
result.message = currentValue.message;
|
||||
if (typeof currentValue.increment === 'number') {
|
||||
result.increment = currentValue.increment;
|
||||
if (typeof result.increment === 'number') {
|
||||
result.increment += currentValue.increment;
|
||||
} else {
|
||||
result.increment = currentValue.increment;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -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,
|
||||
}))
|
||||
});
|
||||
}
|
||||
|
||||
659
src/vs/workbench/api/node/extHostSearch.fileIndex.ts
Normal file
659
src/vs/workbench/api/node/extHostSearch.fileIndex.ts
Normal file
@@ -0,0 +1,659 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * 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 { IFileMatch, IFolderQuery, IRawSearchQuery, ISearchCompleteStats, ISearchQuery } from 'vs/platform/search/common/search';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
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 {
|
||||
limitHit: boolean;
|
||||
results: IInternalFileMatch[];
|
||||
}
|
||||
|
||||
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 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 }> {
|
||||
if (this.config.folderQueries.length !== 1) {
|
||||
throw new Error('Searches just one folder');
|
||||
}
|
||||
|
||||
const folderQuery = this.config.folderQueries[0];
|
||||
|
||||
return new TPromise<{ isLimitHit: boolean }>((resolve, reject) => {
|
||||
const onResult = (match: IInternalFileMatch) => {
|
||||
this.resultCount++;
|
||||
_onResult(match);
|
||||
};
|
||||
|
||||
if (this.isCanceled) {
|
||||
return resolve({ isLimitHit: this.isLimitHit });
|
||||
}
|
||||
|
||||
// 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(() => {
|
||||
resolve({ isLimitHit: this.isLimitHit });
|
||||
}, (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<void> {
|
||||
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);
|
||||
};
|
||||
|
||||
new TPromise(resolve => process.nextTick(resolve))
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.add(cancellation);
|
||||
return this.provider.provideFileIndex(options, cancellation.token);
|
||||
})
|
||||
.then(results => {
|
||||
this.activeCancellationTokens.delete(cancellation);
|
||||
if (this.isCanceled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
results.forEach(onProviderResult);
|
||||
|
||||
this.matchDirectoryTree(tree, queryTester, onResult);
|
||||
return null;
|
||||
}).then(
|
||||
() => {
|
||||
cancellation.dispose();
|
||||
resolve(undefined);
|
||||
},
|
||||
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.directoriesWalked++;
|
||||
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, onBatch);
|
||||
}, () => {
|
||||
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
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
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>((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);
|
||||
cache.resultsToSearchCache[config.filePattern] = allResultsPromise;
|
||||
allResultsPromise.then(null, 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);
|
||||
return this.sortResults(config, complete.results, scorerCache)
|
||||
.then(sortedResults => {
|
||||
|
||||
c({
|
||||
limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults, // ??
|
||||
results: sortedResults
|
||||
});
|
||||
});
|
||||
}, 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 => {
|
||||
return this.sortResults(config, complete.results, cache.scorerCache)
|
||||
.then(sortedResults => {
|
||||
c({
|
||||
limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults,
|
||||
results: 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 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> {
|
||||
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: TPromise<IInternalSearchComplete>;
|
||||
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];
|
||||
cached = this.preventCancellation(c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!cached) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
cached.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({
|
||||
limitHit: complete.limitHit,
|
||||
results
|
||||
});
|
||||
}, e);
|
||||
}, () => {
|
||||
cached.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
private doSearch(engine: FileIndexSearchEngine): TPromise<IInternalSearchComplete> {
|
||||
const results: IInternalFileMatch[] = [];
|
||||
const onResult = match => results.push(match);
|
||||
return new TPromise<IInternalSearchComplete>((c, e) => {
|
||||
engine.search(onResult).then(result => {
|
||||
c({
|
||||
limitHit: result.isLimitHit,
|
||||
results
|
||||
});
|
||||
}, 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.
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Cache {
|
||||
|
||||
public resultsToSearchCache: { [searchValue: string]: TPromise<IInternalSearchComplete>; } = 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
|
||||
}
|
||||
};
|
||||
@@ -9,13 +9,14 @@ 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 { 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 * as vscode from 'vscode';
|
||||
import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol';
|
||||
import { toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IInternalFileMatch, QueryGlobTester, resolvePatternsForProvider, IDirectoryTree, IDirectoryEntry, FileIndexSearchManager } from 'vs/workbench/api/node/extHostSearch.fileIndex';
|
||||
|
||||
export interface ISchemeTransformer {
|
||||
transformOutgoing(scheme: string): string;
|
||||
@@ -24,14 +25,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,76 +46,73 @@ 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));
|
||||
return {
|
||||
dispose: () => {
|
||||
this._searchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
}
|
||||
};
|
||||
this._fileSearchProvider.set(handle, provider);
|
||||
this._proxy.$registerFileSearchProvider(handle, this._transformScheme(scheme));
|
||||
return toDisposable(() => {
|
||||
this._fileSearchProvider.delete(handle);
|
||||
this._proxy.$unregisterProvider(handle);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
$provideFileSearchResults(handle: number, session: number, rawQuery: IRawSearchQuery): TPromise<ISearchCompleteStats> {
|
||||
const provider = this._searchProvider.get(handle);
|
||||
if (!provider.provideFileSearchResults) {
|
||||
return TPromise.as(undefined);
|
||||
}
|
||||
|
||||
const provider = this._fileSearchProvider.get(handle);
|
||||
const query = reviveQuery(rawQuery);
|
||||
return this._fileSearchManager.fileSearch(query, provider).then(
|
||||
null,
|
||||
null,
|
||||
progress => {
|
||||
this._proxy.$handleFileMatch(handle, session, progress.map(p => p.resource));
|
||||
if (provider) {
|
||||
return this._fileSearchManager.fileSearch(query, provider, batch => {
|
||||
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
|
||||
});
|
||||
} 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));
|
||||
});
|
||||
} else {
|
||||
throw new Error('something went wrong');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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));
|
||||
$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);
|
||||
}
|
||||
|
||||
const query = reviveQuery(rawQuery);
|
||||
const engine = new TextSearchEngine(pattern, query, provider, this._extfs);
|
||||
return engine.search().then(
|
||||
null,
|
||||
null,
|
||||
progress => {
|
||||
this._proxy.$handleTextMatch(handle, session, progress);
|
||||
});
|
||||
return engine.search(progress => this._proxy.$handleTextMatch(handle, session, progress));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
@@ -263,107 +265,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, siblingsFn?: () => string[]): boolean {
|
||||
if (this._parsedExcludeExpression && this._parsedExcludeExpression(testPath, basename, siblingsFn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this._parsedIncludeExpression && !this._parsedIncludeExpression(testPath, basename, siblingsFn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guaranteed async.
|
||||
*/
|
||||
public includedInQuery(testPath: string, basename?: string, siblingsFn?: () => string[] | TPromise<string[]>): TPromise<boolean> {
|
||||
const excludeP = this._parsedExcludeExpression ?
|
||||
TPromise.as(this._parsedExcludeExpression(testPath, basename, siblingsFn)).then(result => !!result) :
|
||||
TPromise.wrap(false);
|
||||
|
||||
return excludeP.then(excluded => {
|
||||
if (excluded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._parsedIncludeExpression ?
|
||||
TPromise.as(this._parsedIncludeExpression(testPath, basename, siblingsFn)).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>();
|
||||
@@ -373,7 +274,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 {
|
||||
@@ -382,11 +283,11 @@ class TextSearchEngine {
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(): PPromise<{ limitHit: boolean }, IFileMatch[]> {
|
||||
public search(onProgress: (matches: IFileMatch[]) => void): TPromise<{ limitHit: boolean }> {
|
||||
const folderQueries = this.config.folderQueries;
|
||||
|
||||
return new PPromise<{ limitHit: boolean }, IFileMatch[]>((resolve, reject, _onResult) => {
|
||||
this.collector = new TextSearchResultsCollector(_onResult);
|
||||
return new TPromise<{ limitHit: boolean }>((resolve, reject) => {
|
||||
this.collector = new TextSearchResultsCollector(onProgress);
|
||||
|
||||
const onResult = (match: vscode.TextSearchResult, folderIdx: number) => {
|
||||
if (this.isCanceled) {
|
||||
@@ -405,8 +306,8 @@ class TextSearchEngine {
|
||||
};
|
||||
|
||||
// For each root folder
|
||||
PPromise.join(folderQueries.map((fq, i) => {
|
||||
return this.searchInFolder(fq).then(null, null, r => onResult(r, i));
|
||||
TPromise.join(folderQueries.map((fq, i) => {
|
||||
return this.searchInFolder(fq, r => onResult(r, i));
|
||||
})).then(() => {
|
||||
this.collector.flush();
|
||||
resolve({ limitHit: this.isLimitHit });
|
||||
@@ -420,21 +321,21 @@ class TextSearchEngine {
|
||||
});
|
||||
}
|
||||
|
||||
private searchInFolder(folderQuery: IFolderQuery<URI>): PPromise<void, vscode.TextSearchResult> {
|
||||
private searchInFolder(folderQuery: IFolderQuery<URI>, onResult: (result: vscode.TextSearchResult) => void): TPromise<void> {
|
||||
let cancellation = new CancellationTokenSource();
|
||||
return new PPromise((resolve, reject, onResult) => {
|
||||
return new TPromise((resolve, reject) => {
|
||||
|
||||
const queryTester = new QueryGlobTester(this.config, folderQuery);
|
||||
const testingPs = [];
|
||||
const progress = {
|
||||
report: (result: vscode.TextSearchResult) => {
|
||||
const siblingFn = folderQuery.folder.scheme === 'file' && (() => {
|
||||
const hasSibling = folderQuery.folder.scheme === 'file' && glob.hasSiblingPromiseFn(() => {
|
||||
return this.readdir(path.dirname(result.uri.fsPath));
|
||||
});
|
||||
|
||||
const relativePath = path.relative(folderQuery.folder.fsPath, result.uri.fsPath);
|
||||
testingPs.push(
|
||||
queryTester.includedInQuery(relativePath, path.basename(relativePath), siblingFn)
|
||||
queryTester.includedInQuery(relativePath, path.basename(relativePath), hasSibling)
|
||||
.then(included => {
|
||||
if (included) {
|
||||
onResult(result);
|
||||
@@ -505,7 +406,6 @@ function patternInfoToQuery(patternInfo: IPatternInfo): vscode.TextSearchQuery {
|
||||
|
||||
class FileSearchEngine {
|
||||
private filePattern: string;
|
||||
private normalizedFilePatternLowercase: string;
|
||||
private includePattern: glob.ParsedExpression;
|
||||
private maxResults: number;
|
||||
private exists: boolean;
|
||||
@@ -517,7 +417,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;
|
||||
@@ -526,10 +426,6 @@ class FileSearchEngine {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -539,10 +435,10 @@ class FileSearchEngine {
|
||||
this.activeCancellationTokens = new Set();
|
||||
}
|
||||
|
||||
public search(): PPromise<IInternalSearchComplete, IInternalFileMatch> {
|
||||
public search(_onResult: (match: IInternalFileMatch) => void): TPromise<IInternalSearchComplete> {
|
||||
const folderQueries = this.config.folderQueries;
|
||||
|
||||
return new PPromise((resolve, reject, _onResult) => {
|
||||
return new TPromise((resolve, reject) => {
|
||||
const onResult = (match: IInternalFileMatch) => {
|
||||
this.resultCount++;
|
||||
_onResult(match);
|
||||
@@ -550,7 +446,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
|
||||
@@ -569,10 +465,10 @@ class FileSearchEngine {
|
||||
}
|
||||
|
||||
// For each root folder
|
||||
PPromise.join(folderQueries.map(fq => {
|
||||
return this.searchInFolder(fq).then(null, null, onResult);
|
||||
})).then(cacheKeys => {
|
||||
resolve({ limitHit: this.isLimitHit, cacheKeys });
|
||||
TPromise.join(folderQueries.map(fq => {
|
||||
return this.searchInFolder(fq, onResult);
|
||||
})).then(() => {
|
||||
resolve({ limitHit: this.isLimitHit });
|
||||
}, (errs: Error[]) => {
|
||||
const errMsg = errs
|
||||
.map(err => toErrorMessage(err))
|
||||
@@ -583,50 +479,47 @@ class FileSearchEngine {
|
||||
});
|
||||
}
|
||||
|
||||
private searchInFolder(fq: IFolderQuery<URI>): PPromise<string, IInternalFileMatch> {
|
||||
private searchInFolder(fq: IFolderQuery<URI>, onResult: (match: IInternalFileMatch) => void): TPromise<void> {
|
||||
let cancellation = new CancellationTokenSource();
|
||||
return new PPromise((resolve, reject, onResult) => {
|
||||
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 = (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;
|
||||
new TPromise(_resolve => process.nextTick(_resolve))
|
||||
.then(() => {
|
||||
this.activeCancellationTokens.add(cancellation);
|
||||
|
||||
folderCacheKey = this.config.cacheKey && (this.config.cacheKey + '_' + fq.folder.fsPath);
|
||||
|
||||
return this.provider.provideFileSearchResults(
|
||||
{
|
||||
pattern: this.config.filePattern || '',
|
||||
cacheKey: folderCacheKey
|
||||
pattern: this.config.filePattern || ''
|
||||
},
|
||||
options,
|
||||
{ report: onProviderResult },
|
||||
cancellation.token);
|
||||
})
|
||||
.then(() => {
|
||||
.then(results => {
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -637,7 +530,7 @@ class FileSearchEngine {
|
||||
}).then(
|
||||
() => {
|
||||
cancellation.dispose();
|
||||
resolve(folderCacheKey);
|
||||
resolve(null);
|
||||
},
|
||||
err => {
|
||||
cancellation.dispose();
|
||||
@@ -698,6 +591,7 @@ class FileSearchEngine {
|
||||
const self = this;
|
||||
const filePattern = this.filePattern;
|
||||
function matchDirectory(entries: IDirectoryEntry[]) {
|
||||
const hasSibling = glob.hasSiblingFn(() => entries.map(entry => entry.basename));
|
||||
for (let i = 0, n = entries.length; i < n; i++) {
|
||||
const entry = entries[i];
|
||||
const { relativePath, basename } = entry;
|
||||
@@ -706,7 +600,7 @@ class FileSearchEngine {
|
||||
// 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
|
||||
if (!queryTester.includedInQuerySync(relativePath, basename, () => filePattern !== basename ? entries.map(entry => entry.basename) : [])) {
|
||||
if (!queryTester.includedInQuerySync(relativePath, basename, filePattern !== basename ? hasSibling : undefined)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -730,7 +624,7 @@ class FileSearchEngine {
|
||||
}
|
||||
|
||||
private matchFile(onResult: (result: IInternalFileMatch) => void, candidate: IInternalFileMatch): void {
|
||||
if (this.isFilePatternMatch(candidate.relativePath) && (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename))) {
|
||||
if (!this.includePattern || this.includePattern(candidate.relativePath, candidate.basename)) {
|
||||
if (this.exists || (this.maxResults && this.resultCount >= this.maxResults)) {
|
||||
this.isLimitHit = true;
|
||||
this.cancel();
|
||||
@@ -741,52 +635,32 @@ class FileSearchEngine {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
interface IInternalSearchComplete {
|
||||
limitHit: boolean;
|
||||
cacheKeys: string[];
|
||||
}
|
||||
|
||||
class FileSearchManager {
|
||||
|
||||
private static readonly BATCH_SIZE = 512;
|
||||
|
||||
private readonly expandedCacheKeys = new Map<string, string[]>();
|
||||
|
||||
fileSearch(config: ISearchQuery, provider: vscode.SearchProvider): PPromise<ISearchCompleteStats, IFileMatch[]> {
|
||||
let searchP: PPromise;
|
||||
return new PPromise<ISearchCompleteStats, IFileMatch[]>((c, e, p) => {
|
||||
fileSearch(config: ISearchQuery, provider: vscode.FileSearchProvider, onBatch: (matches: IFileMatch[]) => void): TPromise<ISearchCompleteStats> {
|
||||
let searchP: TPromise;
|
||||
return new TPromise<ISearchCompleteStats>((c, e) => {
|
||||
const engine = new FileSearchEngine(config, provider);
|
||||
|
||||
searchP = this.doSearch(engine, FileSearchManager.BATCH_SIZE).then(
|
||||
result => {
|
||||
if (config.cacheKey) {
|
||||
this.expandedCacheKeys.set(config.cacheKey, result.cacheKeys);
|
||||
}
|
||||
const onInternalResult = (batch: IInternalFileMatch[]) => {
|
||||
onBatch(batch.map(m => this.rawMatchToSearchItem(m)));
|
||||
};
|
||||
|
||||
searchP = this.doSearch(engine, FileSearchManager.BATCH_SIZE, onInternalResult).then(
|
||||
result => {
|
||||
c({
|
||||
limitHit: result.limitHit
|
||||
});
|
||||
},
|
||||
e,
|
||||
progress => {
|
||||
p(progress.map(m => this.rawMatchToSearchItem(m)));
|
||||
});
|
||||
e);
|
||||
}, () => {
|
||||
if (searchP) {
|
||||
searchP.cancel();
|
||||
@@ -794,44 +668,44 @@ class FileSearchManager {
|
||||
});
|
||||
}
|
||||
|
||||
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): PPromise<IInternalSearchComplete, IInternalFileMatch[]> {
|
||||
return new PPromise((c, e, p) => {
|
||||
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().then(result => {
|
||||
engine.search(_onResult).then(result => {
|
||||
if (batch.length) {
|
||||
p(batch);
|
||||
onResultBatch(batch);
|
||||
}
|
||||
|
||||
c(result);
|
||||
}, error => {
|
||||
if (batch.length) {
|
||||
p(batch);
|
||||
onResultBatch(batch);
|
||||
}
|
||||
|
||||
e(error);
|
||||
}, match => {
|
||||
if (match) {
|
||||
batch.push(match);
|
||||
if (batchSize > 0 && batch.length >= batchSize) {
|
||||
p(batch);
|
||||
batch = [];
|
||||
}
|
||||
}
|
||||
});
|
||||
}, () => {
|
||||
engine.cancel();
|
||||
|
||||
@@ -5,17 +5,15 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as cp from 'child_process';
|
||||
import * as os from 'os';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment';
|
||||
import Uri from 'vs/base/common/uri';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IMessageFromTerminalProcess } from 'vs/workbench/parts/terminal/node/terminal';
|
||||
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal';
|
||||
import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess';
|
||||
|
||||
const RENDERER_NO_PROCESS_ID = -1;
|
||||
|
||||
@@ -226,7 +224,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
private _proxy: MainThreadTerminalServiceShape;
|
||||
private _activeTerminal: ExtHostTerminal;
|
||||
private _terminals: ExtHostTerminal[] = [];
|
||||
private _terminalProcesses: { [id: number]: cp.ChildProcess } = {};
|
||||
private _terminalProcesses: { [id: number]: TerminalProcess } = {};
|
||||
private _terminalRenderers: ExtHostTerminalRenderer[] = [];
|
||||
|
||||
public get activeTerminal(): ExtHostTerminal { return this._activeTerminal; }
|
||||
@@ -359,7 +357,6 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
|
||||
const terminalConfig = this._extHostConfiguration.getConfiguration('terminal.integrated');
|
||||
|
||||
const locale = terminalConfig.get('setLocaleVariables') ? platform.locale : undefined;
|
||||
if (!shellLaunchConfig.executable) {
|
||||
// TODO: This duplicates some of TerminalConfigHelper.mergeDefaultShellPathAndArgs and should be merged
|
||||
// this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig);
|
||||
@@ -383,61 +380,48 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
|
||||
// const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux');
|
||||
// const envFromConfig = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot);
|
||||
// const envFromShell = terminalEnvironment.resolveConfigurationVariables(this._configurationResolverService, { ...shellLaunchConfig.env }, lastActiveWorkspaceRoot);
|
||||
// shellLaunchConfig.env = envFromShell;
|
||||
|
||||
// Merge process env with the env from config
|
||||
const parentEnv = { ...process.env };
|
||||
// terminalEnvironment.mergeEnvironments(parentEnv, envFromConfig);
|
||||
const env = { ...process.env };
|
||||
// terminalEnvironment.mergeEnvironments(env, envFromConfig);
|
||||
terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env);
|
||||
|
||||
// Continue env initialization, merging in the env from the launch
|
||||
// config and adding keys that are needed to create the process
|
||||
const env = terminalEnvironment.createTerminalEnv(parentEnv, shellLaunchConfig, initialCwd, locale, cols, rows);
|
||||
const cwd = Uri.parse(require.toUrl('../../parts/terminal/node')).fsPath;
|
||||
const options = { env, cwd, execArgv: [] };
|
||||
const locale = terminalConfig.get('setLocaleVariables') ? platform.locale : undefined;
|
||||
terminalEnvironment.addTerminalEnvironmentKeys(env, locale);
|
||||
|
||||
// Fork the process and listen for messages
|
||||
this._logService.debug(`Terminal process launching on ext host`, options);
|
||||
this._terminalProcesses[id] = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], options);
|
||||
this._terminalProcesses[id].on('message', (message: IMessageFromTerminalProcess) => {
|
||||
switch (message.type) {
|
||||
case 'pid': this._proxy.$sendProcessPid(id, <number>message.content); break;
|
||||
case 'title': this._proxy.$sendProcessTitle(id, <string>message.content); break;
|
||||
case 'data': this._proxy.$sendProcessData(id, <string>message.content); break;
|
||||
}
|
||||
});
|
||||
this._terminalProcesses[id].on('exit', (exitCode) => this._onProcessExit(id, exitCode));
|
||||
this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env);
|
||||
this._terminalProcesses[id] = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env);
|
||||
this._terminalProcesses[id].onProcessIdReady(pid => this._proxy.$sendProcessPid(id, pid));
|
||||
this._terminalProcesses[id].onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title));
|
||||
this._terminalProcesses[id].onProcessData(data => this._proxy.$sendProcessData(id, data));
|
||||
this._terminalProcesses[id].onProcessExit((exitCode) => this._onProcessExit(id, exitCode));
|
||||
}
|
||||
|
||||
public $acceptProcessInput(id: number, data: string): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
this._terminalProcesses[id].send({ event: 'input', data });
|
||||
}
|
||||
this._terminalProcesses[id].input(data);
|
||||
}
|
||||
|
||||
public $acceptProcessResize(id: number, cols: number, rows: number): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
try {
|
||||
this._terminalProcesses[id].send({ event: 'resize', cols, rows });
|
||||
} catch (error) {
|
||||
// We tried to write to a closed pipe / channel.
|
||||
if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') {
|
||||
throw (error);
|
||||
}
|
||||
try {
|
||||
this._terminalProcesses[id].resize(cols, rows);
|
||||
} catch (error) {
|
||||
// We tried to write to a closed pipe / channel.
|
||||
if (error.code !== 'EPIPE' && error.code !== 'ERR_IPC_CHANNEL_CLOSED') {
|
||||
throw (error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public $acceptProcessShutdown(id: number): void {
|
||||
if (this._terminalProcesses[id].connected) {
|
||||
this._terminalProcesses[id].send({ event: 'shutdown' });
|
||||
}
|
||||
this._terminalProcesses[id].shutdown();
|
||||
}
|
||||
|
||||
private _onProcessExit(id: number, exitCode: number): void {
|
||||
// Remove listeners
|
||||
const process = this._terminalProcesses[id];
|
||||
process.removeAllListeners('message');
|
||||
process.removeAllListeners('exit');
|
||||
this._terminalProcesses[id].dispose();
|
||||
|
||||
// Remove process reference
|
||||
delete this._terminalProcesses[id];
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { TreeItemCollapsibleState, ThemeIcon } from 'vs/workbench/api/node/extHostTypes';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
type TreeItemHandle = string;
|
||||
|
||||
@@ -27,7 +28,8 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
|
||||
constructor(
|
||||
private _proxy: MainThreadTreeViewsShape,
|
||||
private commands: ExtHostCommands
|
||||
private commands: ExtHostCommands,
|
||||
private logService: ILogService
|
||||
) {
|
||||
commands.registerArgumentProcessor({
|
||||
processArgument: arg => {
|
||||
@@ -99,7 +101,7 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape {
|
||||
}
|
||||
|
||||
private createExtHostTreeViewer<T>(id: string, dataProvider: vscode.TreeDataProvider<T>): ExtHostTreeView<T> {
|
||||
const treeView = new ExtHostTreeView<T>(id, dataProvider, this._proxy, this.commands.converter);
|
||||
const treeView = new ExtHostTreeView<T>(id, dataProvider, this._proxy, this.commands.converter, this.logService);
|
||||
this.treeViews.set(id, treeView);
|
||||
return treeView;
|
||||
}
|
||||
@@ -145,7 +147,7 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
|
||||
private refreshPromise: TPromise<void> = TPromise.as(null);
|
||||
|
||||
constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter) {
|
||||
constructor(private viewId: string, private dataProvider: vscode.TreeDataProvider<T>, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService) {
|
||||
super();
|
||||
this.proxy.$registerTreeViewDataProvider(viewId);
|
||||
if (this.dataProvider.onDidChangeTreeData) {
|
||||
@@ -192,7 +194,7 @@ class ExtHostTreeView<T> extends Disposable {
|
||||
return this.refreshPromise
|
||||
.then(() => this.resolveUnknownParentChain(element))
|
||||
.then(parentChain => this.resolveTreeNode(element, parentChain[parentChain.length - 1])
|
||||
.then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus })));
|
||||
.then(treeNode => this.proxy.$reveal(this.viewId, treeNode.item, parentChain.map(p => p.item), { select, focus })), error => this.logService.error(error));
|
||||
}
|
||||
|
||||
setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void {
|
||||
|
||||
@@ -413,6 +413,23 @@ export const location = {
|
||||
}
|
||||
};
|
||||
|
||||
export namespace DefinitionLink {
|
||||
export function from(value: vscode.Location | vscode.DefinitionLink): modes.DefinitionLink {
|
||||
const definitionLink = <vscode.DefinitionLink>value;
|
||||
const location = <vscode.Location>value;
|
||||
return {
|
||||
origin: definitionLink.originSelectionRange
|
||||
? Range.from(definitionLink.originSelectionRange)
|
||||
: undefined,
|
||||
uri: definitionLink.targetUri ? definitionLink.targetUri : location.uri,
|
||||
range: Range.from(definitionLink.targetRange ? definitionLink.targetRange : location.range),
|
||||
selectionRange: definitionLink.targetSelectionRange
|
||||
? Range.from(definitionLink.targetSelectionRange)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Hover {
|
||||
export function from(hover: vscode.Hover): modes.Hover {
|
||||
return <modes.Hover>{
|
||||
|
||||
@@ -498,6 +498,7 @@ export class TextEdit {
|
||||
export interface IFileOperationOptions {
|
||||
overwrite?: boolean;
|
||||
ignoreIfExists?: boolean;
|
||||
ignoreIfNotExists?: boolean;
|
||||
recursive?: boolean;
|
||||
}
|
||||
|
||||
@@ -518,7 +519,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
|
||||
private _edits = new Array<IFileOperation | IFileTextEdit>();
|
||||
|
||||
renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean }): void {
|
||||
renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void {
|
||||
this._edits.push({ _type: 1, from, to, options });
|
||||
}
|
||||
|
||||
@@ -526,7 +527,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
|
||||
this._edits.push({ _type: 1, from: undefined, to: uri, options });
|
||||
}
|
||||
|
||||
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean }): void {
|
||||
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }): void {
|
||||
this._edits.push({ _type: 1, from: uri, to: undefined, options });
|
||||
}
|
||||
|
||||
@@ -1955,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);
|
||||
|
||||
@@ -268,6 +268,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
if (folders.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
// #54483 @Joh Why are we still using fsPath?
|
||||
return folders[0].uri.fsPath;
|
||||
}
|
||||
|
||||
@@ -394,7 +395,13 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
excludePattern: options.exclude && globPatternToString(options.exclude)
|
||||
};
|
||||
|
||||
let isCanceled = false;
|
||||
|
||||
this._activeSearchCallbacks[requestId] = p => {
|
||||
if (isCanceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
p.lineMatches.forEach(lineMatch => {
|
||||
lineMatch.offsetAndLengths.forEach(offsetAndLength => {
|
||||
const range = new Range(lineMatch.lineNumber, offsetAndLength[0], lineMatch.lineNumber, offsetAndLength[0] + offsetAndLength[1]);
|
||||
@@ -408,7 +415,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape {
|
||||
};
|
||||
|
||||
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