Merge branch 'master' into aeschli/recentWithLabel

This commit is contained in:
Martin Aeschlimann
2019-03-07 22:53:44 +01:00
360 changed files with 5856 additions and 5499 deletions

View File

@@ -119,7 +119,7 @@ export function createApiFactory(
const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService));
const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments));
const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, schemeTransformer, extHostLogService));
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration));
const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService));
const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol));
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)));
@@ -527,7 +527,7 @@ export function createApiFactory(
onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) {
return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables);
},
asRelativePath: (pathOrUri, includeWorkspace) => {
asRelativePath: (pathOrUri, includeWorkspace?) => {
return extHostWorkspace.getRelativePath(pathOrUri, includeWorkspace);
},
findFiles: (include, exclude, maxResults?, token?) => {
@@ -624,9 +624,6 @@ export function createApiFactory(
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 extHostComment.registerDocumentCommentProvider(extension.identifier, provider);
}),
@@ -658,8 +655,8 @@ export function createApiFactory(
};
const comment: typeof vscode.comment = {
createCommentControl(id: string, label: string) {
return extHostComment.createCommentControl(extension, id, label);
createCommentController(id: string, label: string) {
return extHostComment.createCommentController(extension, id, label);
}
};
@@ -780,6 +777,7 @@ export function createApiFactory(
DocumentSymbol: extHostTypes.DocumentSymbol,
EndOfLine: extHostTypes.EndOfLine,
EventEmitter: Emitter,
CustomExecution: extHostTypes.CustomExecution,
FileChangeType: extHostTypes.FileChangeType,
FileSystemError: extHostTypes.FileSystemError,
FileType: files.FileType,

View File

@@ -119,16 +119,16 @@ export interface CommentProviderFeatures {
}
export interface MainThreadCommentsShape extends IDisposable {
$registerCommentControl(handle: number, id: string, label: string): void;
$createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], commands: modes.Command[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread2 | undefined;
$registerCommentController(handle: number, id: string, label: string): void;
$createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], acceptInputCommand: modes.Command, additionalCommands: modes.Command[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread2 | undefined;
$deleteCommentThread(handle: number, commentThreadHandle: number): void;
$updateComments(handle: number, commentThreadHandle: number, comments: modes.Comment[]): void;
$createCommentingRanges(handle: number, commentingRangesHandle: number, resource: UriComponents, ranges: IRange[], commands: modes.Command): void;
$deleteCommentingRanges(handle: number, commentingRangesHandle: number): void;
$updateCommentingRanges(handle: number, commentingRangesHandle: number, newRanges: IRange[]): void;
$updateCommentingRangesCommands(handle: number, commentingRangesHandle: number, command: modes.Command): void;
$setInputValue(handle: number, commentThreadHandle: number, input: string): void;
$updateCommentThreadCommands(handle: number, commentThreadHandle: number, acceptInputCommands: modes.Command[]): void;
$setInputValue(handle: number, input: string): void;
$updateCommentThreadAcceptInputCommand(handle: number, commentThreadHandle: number, acceptInputCommand: modes.Command): void;
$updateCommentThreadAdditionalCommands(handle: number, commentThreadHandle: number, additionalCommands: modes.Command[]): void;
$updateCommentThreadCollapsibleState(handle: number, commentThreadHandle: number, collapseState: modes.CommentThreadCollapsibleState): void;
$updateCommentThreadRange(handle: number, commentThreadHandle: number, range: IRange): void;
$updateCommentThreadLabel(handle: number, commentThreadHandle: number, label: string): void;
$registerDocumentCommentProvider(handle: number, features: CommentProviderFeatures): void;
$unregisterDocumentCommentProvider(handle: number): void;
$registerWorkspaceCommentProvider(handle: number, extensionId: ExtensionIdentifier): void;
@@ -224,7 +224,7 @@ export interface ITextDocumentShowOptions {
}
export interface MainThreadTextEditorsShape extends IDisposable {
$tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise<string>;
$tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise<string | undefined>;
$registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void;
$removeTextEditorDecorationType(key: string): void;
$tryShowEditor(id: string, position: EditorViewColumn): Promise<void>;
@@ -551,7 +551,6 @@ export interface MainThreadFileSystemShape extends IDisposable {
export interface MainThreadSearchShape extends IDisposable {
$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;
@@ -559,12 +558,14 @@ export interface MainThreadSearchShape extends IDisposable {
}
export interface MainThreadTaskShape extends IDisposable {
$createTaskId(task: TaskDTO): Promise<string>;
$registerTaskProvider(handle: number): Promise<void>;
$unregisterTaskProvider(handle: number): Promise<void>;
$fetchTasks(filter?: TaskFilterDTO): Promise<TaskDTO[]>;
$executeTask(task: TaskHandleDTO | TaskDTO): Promise<TaskExecutionDTO>;
$terminateTask(id: string): Promise<void>;
$registerTaskSystem(scheme: string, info: TaskSystemInfoDTO): void;
$customExecutionComplete(id: string, result?: number): Promise<void>;
}
export interface MainThreadExtensionServiceShape extends IDisposable {
@@ -996,7 +997,7 @@ export interface ExtHostSCMShape {
export interface ExtHostTaskShape {
$provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable<TaskSetDTO>;
$onDidStartTask(execution: TaskExecutionDTO): void;
$onDidStartTask(execution: TaskExecutionDTO, terminalId: number): void;
$onDidStartTaskProcess(value: TaskProcessStartedDTO): void;
$onDidEndTaskProcess(value: TaskProcessEndedDTO): void;
$OnDidEndTask(execution: TaskExecutionDTO): void;
@@ -1104,8 +1105,9 @@ export interface ExtHostProgressShape {
export interface ExtHostCommentsShape {
$provideDocumentComments(handle: number, document: UriComponents): Promise<modes.CommentInfo>;
$createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise<modes.CommentThread>;
$onActiveCommentWidgetChange(commentControlhandle: number, commentThread: modes.CommentThread2, comment: modes.Comment | undefined, input: string): Promise<number | undefined>;
$onActiveCommentingRangeChange(commentControlhandle: number, range: IRange): void;
$onCommentWidgetInputChange(commentControllerHandle: number, input: string): Promise<number | undefined>;
$provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<IRange[] | undefined>;
$createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): void;
$replyToCommentThread(handle: number, document: UriComponents, range: IRange, commentThread: modes.CommentThread, text: string): Promise<modes.CommentThread>;
$editComment(handle: number, document: UriComponents, comment: modes.Comment, text: string): Promise<void>;
$deleteComment(handle: number, document: UriComponents, comment: modes.Comment): Promise<void>;

View File

@@ -8,6 +8,7 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import * as modes from 'vs/editor/common/modes';
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters';
import * as types from 'vs/workbench/api/node/extHostTypes';
import * as vscode from 'vscode';
import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from './extHost.protocol';
import { CommandsConverter, ExtHostCommands } from './extHostCommands';
@@ -30,9 +31,9 @@ export class ExtHostComments implements ExtHostCommentsShape {
private _proxy: MainThreadCommentsShape;
private _commentControls: Map<ProviderHandle, ExtHostCommentControl> = new Map<ProviderHandle, ExtHostCommentControl>();
private _commentControllers: Map<ProviderHandle, ExtHostCommentController> = new Map<ProviderHandle, ExtHostCommentController>();
private _commentControlsByExtension: Map<string, ExtHostCommentControl[]> = new Map<string, ExtHostCommentControl[]>();
private _commentControllersByExtension: Map<string, ExtHostCommentController[]> = new Map<string, ExtHostCommentController[]>();
private _documentProviders = new Map<number, HandlerData<vscode.DocumentCommentProvider>>();
private _workspaceProviders = new Map<number, HandlerData<vscode.WorkspaceCommentProvider>>();
@@ -47,21 +48,21 @@ export class ExtHostComments implements ExtHostCommentsShape {
_commands.registerArgumentProcessor({
processArgument: arg => {
if (arg && arg.$mid === 6) {
const commentControl = this._commentControls.get(arg.handle);
const commentController = this._commentControllers.get(arg.handle);
if (!commentControl) {
if (!commentController) {
return arg;
}
return commentControl;
return commentController;
} else if (arg && arg.$mid === 7) {
const commentControl = this._commentControls.get(arg.commentControlHandle);
const commentController = this._commentControllers.get(arg.commentControlHandle);
if (!commentControl) {
if (!commentController) {
return arg;
}
const commentThread = commentControl.getCommentThread(arg.commentThreadHandle);
const commentThread = commentController.getCommentThread(arg.commentThreadHandle);
if (!commentThread) {
return arg;
@@ -75,48 +76,51 @@ export class ExtHostComments implements ExtHostCommentsShape {
});
}
createCommentControl(extension: IExtensionDescription, id: string, label: string): vscode.CommentControl {
createCommentController(extension: IExtensionDescription, id: string, label: string): vscode.CommentController {
const handle = ExtHostComments.handlePool++;
const commentControl = new ExtHostCommentControl(extension, handle, this._commands.converter, this._proxy, id, label);
this._commentControls.set(commentControl.handle, commentControl);
const commentController = new ExtHostCommentController(extension, handle, this._commands.converter, this._proxy, id, label);
this._commentControllers.set(commentController.handle, commentController);
const commentControls = this._commentControlsByExtension.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
commentControls.push(commentControl);
this._commentControlsByExtension.set(ExtensionIdentifier.toKey(extension.identifier), commentControls);
const commentControllers = this._commentControllersByExtension.get(ExtensionIdentifier.toKey(extension.identifier)) || [];
commentControllers.push(commentController);
this._commentControllersByExtension.set(ExtensionIdentifier.toKey(extension.identifier), commentControllers);
return commentControl;
return commentController;
}
$onActiveCommentWidgetChange(commentControlhandle: number, commentThread: modes.CommentThread2, comment: modes.Comment | undefined, input: string): Promise<number | undefined> {
const commentControl = this._commentControls.get(commentControlhandle);
$onCommentWidgetInputChange(commentControllerHandle: number, input: string): Promise<number | undefined> {
const commentController = this._commentControllers.get(commentControllerHandle);
if (!commentControl) {
if (!commentController) {
return Promise.resolve(undefined);
}
commentControl.$onActiveCommentWidgetChange(commentThread, comment, input);
return Promise.resolve(commentControlhandle);
commentController.$onCommentWidgetInputChange(input);
return Promise.resolve(commentControllerHandle);
}
$onCommentWidgetInputChange(commentControlhandle: number, value: string): Promise<void> {
const commentControl = this._commentControls.get(commentControlhandle);
$provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<IRange[] | undefined> {
const commentController = this._commentControllers.get(commentControllerHandle);
if (!commentControl || !commentControl.widget) {
if (!commentController || !commentController.commentingRangeProvider) {
return Promise.resolve(undefined);
}
commentControl.widget.input = value;
return Promise.resolve(undefined);
const document = this._documents.getDocument(URI.revive(uriComponents));
return asPromise(() => {
return commentController.commentingRangeProvider.provider(document, token);
}).then(ranges => ranges ? ranges.map(extHostTypeConverter.Range.from) : undefined);
}
$onActiveCommentingRangeChange(commentControlhandle: number, range: IRange) {
const commentControl = this._commentControls.get(commentControlhandle);
$createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): void {
const commentController = this._commentControllers.get(commentControllerHandle);
if (!commentControl) {
if (!commentController || !commentController.commentingRangeProvider) {
return;
}
commentControl.setActiveCommentingRange(extHostTypeConverter.Range.to(range));
const document = this._documents.getDocument(URI.revive(uriComponents));
commentController.commentingRangeProvider.callback(document, extHostTypeConverter.Range.to(range));
}
registerWorkspaceCommentProvider(
@@ -295,10 +299,28 @@ export class ExtHostCommentThread implements vscode.CommentThread {
return this._resource;
}
set range(range: vscode.Range) {
this._range = range;
this._proxy.$updateCommentThreadRange(this._commentControlHandle, this.handle, extHostTypeConverter.Range.from(this._range));
}
get range(): vscode.Range {
return this._range;
}
private _label: string;
get label(): string {
return this._label;
}
set label(label: string) {
this._label = label;
this._proxy.$updateCommentThreadLabel(this._commentControlHandle, this.handle, this._label);
}
private _comments: vscode.Comment[] = [];
get comments(): vscode.Comment[] {
return this._comments;
}
@@ -308,28 +330,48 @@ export class ExtHostCommentThread implements vscode.CommentThread {
this._comments = newComments;
}
get acceptInputCommands(): vscode.Command[] {
return this._acceptInputCommands;
private _acceptInputCommand: vscode.Command;
get acceptInputCommand(): vscode.Command {
return this._acceptInputCommand;
}
set acceptInputCommands(replyCommands: vscode.Command[]) {
this._acceptInputCommands = replyCommands;
set acceptInputCommand(acceptInputCommand: vscode.Command) {
this._acceptInputCommand = acceptInputCommand;
const internals = replyCommands.map(this._commandsConverter.toInternal.bind(this._commandsConverter));
this._proxy.$updateCommentThreadCommands(this._commentControlHandle, this.handle, internals);
const internal = this._commandsConverter.toInternal(acceptInputCommand);
this._proxy.$updateCommentThreadAcceptInputCommand(this._commentControlHandle, this.handle, internal);
}
private _additionalCommands: vscode.Command[] = [];
get additionalCommands(): vscode.Command[] {
return this._additionalCommands;
}
set additionalCommands(additionalCommands: vscode.Command[]) {
this._additionalCommands = additionalCommands;
const internals = additionalCommands.map(this._commandsConverter.toInternal.bind(this._commandsConverter));
this._proxy.$updateCommentThreadAdditionalCommands(this._commentControlHandle, this.handle, internals);
}
private _collapseState?: vscode.CommentThreadCollapsibleState;
get collapsibleState(): vscode.CommentThreadCollapsibleState {
return this._collapseState;
}
set collapsibleState(newState: vscode.CommentThreadCollapsibleState) {
this._collapseState = newState;
this._proxy.$updateCommentThreadCollapsibleState(this._commentControlHandle, this.handle, convertToCollapsibleState(newState));
}
collapsibleState?: vscode.CommentThreadCollapsibleState;
constructor(
private _proxy: MainThreadCommentsShape,
private readonly _commandsConverter: CommandsConverter,
private _commentControlHandle: number,
private _threadId: string,
private _resource: vscode.Uri,
private _range: vscode.Range,
private _comments: vscode.Comment[],
private _acceptInputCommands: vscode.Command[],
private _collapseState?: vscode.CommentThreadCollapsibleState
private _range: vscode.Range
) {
this._proxy.$createCommentThread(
this._commentControlHandle,
@@ -338,7 +380,8 @@ export class ExtHostCommentThread implements vscode.CommentThread {
this._resource,
extHostTypeConverter.Range.from(this._range),
this._comments.map(comment => { return convertToModeComment(comment, this._commandsConverter); }),
this._acceptInputCommands ? this._acceptInputCommands.map(this._commandsConverter.toInternal.bind(this._commandsConverter)) : [],
this._acceptInputCommand ? this._commandsConverter.toInternal(this._acceptInputCommand) : undefined,
this._additionalCommands ? this._additionalCommands.map(this._commandsConverter.toInternal.bind(this._commandsConverter)) : [],
this._collapseState
);
}
@@ -361,89 +404,48 @@ export class ExtHostCommentThread implements vscode.CommentThread {
}
}
export class ExtHostCommentWidget implements vscode.CommentWidget {
private _onDidChangeInput = new Emitter<string>();
export class ExtHostCommentInputBox implements vscode.CommentInputBox {
private _onDidChangeValue = new Emitter<string>();
get onDidChangeInput(): Event<string> {
return this._onDidChangeInput.event;
get onDidChangeValue(): Event<string> {
return this._onDidChangeValue.event;
}
private _value: string = '';
get value(): string {
return this._value;
}
private _input: string = '';
get input(): string {
return this._input;
}
set input(newInput: string) {
this._input = newInput;
this._onDidChangeInput.fire(this._input);
this._proxy.$setInputValue(this.commentControlHandle, this.commentThread.handle, newInput);
set value(newInput: string) {
this._value = newInput;
this._onDidChangeValue.fire(this._value);
this._proxy.$setInputValue(this.commentControllerHandle, newInput);
}
constructor(
private _proxy: MainThreadCommentsShape,
public commentControlHandle: number,
public commentThread: ExtHostCommentThread,
public comment: vscode.Comment | undefined,
public commentControllerHandle: number,
input: string
) {
this._input = input;
this._value = input;
}
setInput(input: string) {
this._value = input;
}
}
export class ExtHostCommentingRanges implements vscode.CommentingRanges {
private static _handlePool: number = 0;
readonly handle = ExtHostCommentingRanges._handlePool++;
get resource(): vscode.Uri {
return this._resource;
}
get ranges(): vscode.Range[] {
return this._ranges;
}
set ranges(newRanges: vscode.Range[]) {
this._ranges = newRanges;
this._proxy.$updateCommentingRanges(this._commentControlHandle, this.handle, this._ranges.map(extHostTypeConverter.Range.from));
}
get newCommentThreadCommand(): vscode.Command {
return this._command;
}
set newCommentThreadCommand(command: vscode.Command) {
this._command = command;
const internal = this._commandsConverter.toInternal(command);
this._proxy.$updateCommentingRangesCommands(this._commentControlHandle, this.handle, internal);
}
class ExtHostCommentingRangeProvider {
constructor(
private _proxy: MainThreadCommentsShape,
private readonly _commandsConverter: CommandsConverter,
private _commentControlHandle: number,
private _resource: vscode.Uri,
private _ranges: vscode.Range[],
private _command: vscode.Command,
public provider: (document: vscode.TextDocument, token: vscode.CancellationToken) => vscode.ProviderResult<vscode.Range[]>,
public callback: (document: vscode.TextDocument, range: vscode.Range) => void
) {
this._proxy.$createCommentingRanges(
this._commentControlHandle,
this.handle,
this._resource,
this._ranges.map(extHostTypeConverter.Range.from),
this._commandsConverter.toInternal(this._command)
);
}
dispose() {
this._proxy.$deleteCommentingRanges(this._commentControlHandle, this.handle);
}
}
class ExtHostCommentControl implements vscode.CommentControl {
class ExtHostCommentController implements vscode.CommentController {
get id(): string {
return this._id;
}
@@ -452,7 +454,7 @@ class ExtHostCommentControl implements vscode.CommentControl {
return this._label;
}
public widget?: ExtHostCommentWidget;
public inputBox?: ExtHostCommentInputBox;
public activeCommentingRange?: vscode.Range;
public get handle(): number {
@@ -460,7 +462,16 @@ class ExtHostCommentControl implements vscode.CommentControl {
}
private _threads: Map<number, ExtHostCommentThread> = new Map<number, ExtHostCommentThread>();
private _commentingRanges: Map<number, ExtHostCommentingRanges> = new Map<number, ExtHostCommentingRanges>();
private _commentingRangeProvider: ExtHostCommentingRangeProvider | undefined = undefined;
get commentingRangeProvider(): ExtHostCommentingRangeProvider | undefined {
return this._commentingRangeProvider;
}
set commentingRangeProvider(commentingRangeProvider: ExtHostCommentingRangeProvider | undefined) {
this._commentingRangeProvider = commentingRangeProvider;
}
constructor(
_extension: IExtensionDescription,
@@ -470,37 +481,25 @@ class ExtHostCommentControl implements vscode.CommentControl {
private _id: string,
private _label: string
) {
this._proxy.$registerCommentControl(this.handle, _id, _label);
this._proxy.$registerCommentController(this.handle, _id, _label);
}
createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[], acceptInputCommands: vscode.Command[], collapsibleState?: vscode.CommentThreadCollapsibleState): vscode.CommentThread {
const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this.handle, id, resource, range, comments, acceptInputCommands, collapsibleState);
createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range): vscode.CommentThread {
const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this.handle, id, resource, range);
this._threads.set(commentThread.handle, commentThread);
return commentThread;
}
$onActiveCommentWidgetChange(commentThread: modes.CommentThread2, comment: modes.Comment | undefined, input: string) {
const extHostCommentThread = this._threads.get(commentThread.commentThreadHandle);
const extHostCommentWidget = new ExtHostCommentWidget(
this._proxy,
this.handle,
extHostCommentThread,
comment ? extHostCommentThread.getComment(comment.commentId) : undefined,
input || ''
);
this.widget = extHostCommentWidget;
registerCommentingRangeProvider(provider: (document: vscode.TextDocument, token: vscode.CancellationToken) => vscode.ProviderResult<vscode.Range[]>, callback: (document: vscode.TextDocument, range: vscode.Range) => void) {
this._commentingRangeProvider = new ExtHostCommentingRangeProvider(provider, callback);
}
createCommentingRanges(resource: vscode.Uri, ranges: vscode.Range[], newCommentThreadCommand: vscode.Command): vscode.CommentingRanges {
const commentingRange = new ExtHostCommentingRanges(this._proxy, this._commandsConverter, this.handle, resource, ranges, newCommentThreadCommand);
this._commentingRanges.set(commentingRange.handle, commentingRange);
return commentingRange;
}
setActiveCommentingRange(range: vscode.Range) {
this.activeCommentingRange = range;
$onCommentWidgetInputChange(input: string) {
if (!this.inputBox) {
this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, input);
} else {
this.inputBox.setInput(input);
}
}
getCommentThread(handle: number) {
@@ -511,9 +510,6 @@ class ExtHostCommentControl implements vscode.CommentControl {
this._threads.forEach(value => {
value.dispose();
});
this._commentingRanges.forEach(value => {
value.dispose();
});
}
}
@@ -585,7 +581,8 @@ function convertToModeComment(vscodeComment: vscode.Comment, commandsConverter:
userIconPath: iconPath,
isDraft: vscodeComment.isDraft,
editCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.editCommand) : undefined,
deleteCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand) : undefined
deleteCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand) : undefined,
label: vscodeComment.label
};
}
@@ -626,4 +623,16 @@ function convertFromReaction(reaction: modes.CommentReaction): vscode.CommentRea
count: reaction.count,
hasReacted: reaction.hasReacted
};
}
function convertToCollapsibleState(kind: vscode.CommentThreadCollapsibleState | undefined): modes.CommentThreadCollapsibleState {
if (kind !== undefined) {
switch (kind) {
case types.CommentThreadCollapsibleState.Expanded:
return modes.CommentThreadCollapsibleState.Expanded;
case types.CommentThreadCollapsibleState.Collapsed:
return modes.CommentThreadCollapsibleState.Collapsed;
}
}
return modes.CommentThreadCollapsibleState.Collapsed;
}

View File

@@ -7,7 +7,7 @@ import { MainContext, MainThreadOutputServiceShape, IMainContext, ExtHostOutputS
import * as vscode from 'vscode';
import { URI } from 'vs/base/common/uri';
import { join } from 'vs/base/common/path';
import { OutputAppender } from 'vs/workbench/contrib/output/node/outputAppender';
import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender';
import { toLocalISOString } from 'vs/base/common/date';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';

View File

@@ -1,611 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'vs/base/common/path';
import * as arrays from 'vs/base/common/arrays';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
import * as glob from 'vs/base/common/glob';
import * as resources from 'vs/base/common/resources';
import { StopWatch } from 'vs/base/common/stopwatch';
import * as strings from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer';
import { ICachedSearchStats, IFileIndexProviderStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, ISearchCompleteStats } from 'vs/workbench/services/search/common/search';
import { IDirectoryEntry, IDirectoryTree, IInternalFileMatch } from 'vs/workbench/services/search/node/fileSearchManager';
import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search';
import * as vscode from 'vscode';
interface IInternalSearchComplete<T = IFileSearchStats> {
limitHit: boolean;
results: IInternalFileMatch[];
stats: T;
}
export class FileIndexSearchEngine {
private filePattern?: string;
private normalizedFilePatternLowercase: string;
private includePattern?: glob.ParsedExpression;
private maxResults: number | null;
private exists: boolean;
private isLimitHit: boolean;
private resultCount: number;
private isCanceled: boolean;
private filesWalked = 0;
private dirsWalked = 0;
private activeCancellationTokens: Set<CancellationTokenSource>;
private globalExcludePattern?: glob.ParsedExpression;
constructor(private config: IFileQuery, 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): Promise<{ isLimitHit: boolean, stats: IFileIndexProviderStats }> {
// Searches a single folder
const folderQuery = this.config.folderQueries[0];
return new Promise<{ isLimitHit: boolean, stats: IFileIndexProviderStats }>((resolve, reject) => {
const onResult = (match: IInternalFileMatch) => {
this.resultCount++;
_onResult(match);
};
if (this.isCanceled) {
throw canceled();
}
// For each extra file
if (this.config.extraFileResources) {
this.config.extraFileResources
.forEach(extraFile => {
const extraFileStr = extraFile.toString(); // ?
const basename = path.basename(extraFileStr);
if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) {
return; // excluded
}
// File: Check for match on file pattern and include pattern
this.matchFile(onResult, { base: extraFile, basename });
});
}
return Promise.all(this.config.folderQueries.map(fq => this.searchInFolder(folderQuery, onResult))).then(stats => {
resolve({
isLimitHit: this.isLimitHit,
stats: {
directoriesWalked: this.dirsWalked,
filesWalked: this.filesWalked,
fileWalkTime: stats.map(s => s.fileWalkTime).reduce((s, c) => s + c, 0),
providerTime: stats.map(s => s.providerTime).reduce((s, c) => s + c, 0),
providerResultCount: stats.map(s => s.providerResultCount).reduce((s, c) => s + c, 0)
}
});
}, (errs: Error[]) => {
if (!Array.isArray(errs)) {
errs = [errs];
}
errs = arrays.coalesce(errs);
return Promise.reject(errs[0]);
});
});
}
private searchInFolder(fq: IFolderQuery<URI>, onResult: (match: IInternalFileMatch) => void): Promise<IFileIndexProviderStats> {
const cancellation = new CancellationTokenSource();
return new Promise((resolve, reject) => {
const options = this.getSearchOptionsForFolder(fq);
const tree = this.initDirectoryTree();
const queryTester = new QueryGlobTester(this.config, fq);
const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses();
const onProviderResult = (uri: URI) => {
if (this.isCanceled) {
return;
}
// TODO@rob - ???
const relativePath = path.relative(fq.folder.path, uri.path);
if (noSiblingsClauses) {
const basename = path.basename(uri.path);
this.matchFile(onResult, { base: fq.folder, relativePath, basename, original: uri });
return;
}
// TODO: Optimize siblings clauses with ripgrep here.
this.addDirectoryEntries(tree, fq.folder, relativePath, onResult);
};
let providerSW: StopWatch;
let providerTime: number;
let fileWalkTime: number;
new Promise(resolve => process.nextTick(resolve))
.then(() => {
this.activeCancellationTokens.add(cancellation);
providerSW = StopWatch.create();
return this.provider.provideFileIndex(options, cancellation.token);
})
.then(results => {
providerTime = providerSW.elapsed();
const postProcessSW = StopWatch.create();
this.activeCancellationTokens.delete(cancellation);
if (this.isCanceled) {
return null;
}
results!.forEach(onProviderResult);
this.matchDirectoryTree(tree, queryTester, onResult);
fileWalkTime = postProcessSW.elapsed();
return null;
}).then(
() => {
cancellation.dispose();
resolve(<IFileIndexProviderStats>{
providerTime,
fileWalkTime,
directoriesWalked: this.dirsWalked,
filesWalked: this.filesWalked
});
},
err => {
cancellation.dispose();
reject(err);
});
});
}
private getSearchOptionsForFolder(fq: IFolderQuery<URI>): vscode.FileIndexOptions {
const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern);
const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern);
return {
folder: fq.folder,
excludes,
includes,
useIgnoreFiles: !fq.disregardIgnoreFiles,
useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles,
followSymlinks: !fq.ignoreSymlinks
};
}
private initDirectoryTree(): IDirectoryTree {
const tree: IDirectoryTree = {
rootEntries: [],
pathToEntries: Object.create(null)
};
tree.pathToEntries['.'] = tree.rootEntries;
return tree;
}
private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: URI, relativeFile: string, onResult: (result: IInternalFileMatch) => void) {
// Support relative paths to files from a root resource (ignores excludes)
if (relativeFile === this.filePattern) {
const basename = path.basename(this.filePattern);
this.matchFile(onResult, { base: base, relativePath: this.filePattern, basename });
}
function add(relativePath: string) {
const basename = path.basename(relativePath);
const dirname = path.dirname(relativePath);
let entries = pathToEntries[dirname];
if (!entries) {
entries = pathToEntries[dirname] = [];
add(dirname);
}
entries.push({
base,
relativePath,
basename
});
}
add(relativeFile);
}
private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) {
const self = this;
const filePattern = this.filePattern;
function matchDirectory(entries: IDirectoryEntry[]) {
self.dirsWalked++;
for (let i = 0, n = entries.length; i < n; i++) {
const entry = entries[i];
const { relativePath, basename } = entry;
// Check exclude pattern
// If the user searches for the exact file name, we adjust the glob matching
// to ignore filtering by siblings because the user seems to know what she
// is searching for and we want to include the result in that case anyway
const hasSibling = glob.hasSiblingFn(() => entries.map(entry => entry.basename));
if (!queryTester.includedInQuerySync(relativePath, basename, filePattern !== basename ? hasSibling : undefined)) {
continue;
}
const sub = pathToEntries[relativePath];
if (sub) {
matchDirectory(sub);
} else {
self.filesWalked++;
if (relativePath === filePattern) {
continue; // ignore file if its path matches with the file pattern because that is already matched above
}
self.matchFile(onResult, entry);
}
if (self.isLimitHit) {
break;
}
}
}
matchDirectory(rootEntries);
}
private matchFile(onResult: (result: IInternalFileMatch) => void, candidate: IInternalFileMatch): void {
if (this.isFilePatternMatch(candidate.relativePath!) && (!this.includePattern || this.includePattern(candidate.relativePath!, candidate.basename))) {
if (this.exists || (this.maxResults && this.resultCount >= this.maxResults)) {
this.isLimitHit = true;
this.cancel();
}
if (!this.isLimitHit) {
onResult(candidate);
}
}
}
private isFilePatternMatch(path: string): boolean {
// Check for search pattern
if (this.filePattern) {
if (this.filePattern === '*') {
return true; // support the all-matching wildcard
}
return strings.fuzzyContains(path, this.normalizedFilePatternLowercase);
}
// No patterns means we match all
return true;
}
}
export class FileIndexSearchManager {
private static readonly BATCH_SIZE = 512;
private caches: { [cacheKey: string]: Cache; } = Object.create(null);
private readonly folderCacheKeys = new Map<string, Set<string>>();
public fileSearch(config: IFileQuery, provider: vscode.FileIndexProvider, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise<ISearchCompleteStats> {
if (config.sortByScore) {
let sortedSearch = this.trySortedSearchFromCache(config, token);
if (!sortedSearch) {
const engineConfig = config.maxResults ?
{
...config,
...{ maxResults: null }
} :
config;
const engine = new FileIndexSearchEngine(<any>engineConfig, provider);
sortedSearch = this.doSortedSearch(engine, config, token);
}
return sortedSearch.then(complete => {
this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE);
return complete;
});
}
const engine = new FileIndexSearchEngine(config, provider);
return this.doSearch(engine, token)
.then(complete => {
this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE);
return <ISearchCompleteStats>{
limitHit: complete.limitHit,
stats: {
type: 'fileIndexProvider',
detailStats: complete.stats,
fromCache: false,
resultCount: complete.results.length
}
};
});
}
private getFolderCacheKey(config: IFileQuery): 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: IFileQuery, token: CancellationToken): Promise<IInternalSearchComplete> {
let allResultsPromise = createCancelablePromise<IInternalSearchComplete<IFileIndexProviderStats>>(token => {
return this.doSearch(engine, token);
});
const folderCacheKey = this.getFolderCacheKey(config);
let cache: Cache;
if (folderCacheKey) {
cache = this.getOrCreateCache(folderCacheKey);
const cacheRow: ICacheRow = {
promise: allResultsPromise,
resolved: false
};
cache.resultsToSearchCache[config.filePattern!] = cacheRow;
allResultsPromise.then(() => {
cacheRow.resolved = true;
}, err => {
delete cache.resultsToSearchCache[config.filePattern!];
});
allResultsPromise = this.preventCancellation(allResultsPromise);
}
return Promise.resolve<IInternalSearchComplete>(
allResultsPromise.then(complete => {
const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null);
const sortSW = (typeof config.maxResults !== 'number' || config.maxResults > 0) && StopWatch.create();
return this.sortResults(config, complete.results, scorerCache, token)
.then(sortedResults => {
// sortingTime: -1 indicates a "sorted" search that was not sorted, i.e. populating the cache when quickopen is opened.
// Contrasting with findFiles which is not sorted and will have sortingTime: undefined
const sortingTime = sortSW ? sortSW.elapsed() : -1;
return <IInternalSearchComplete>{
limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults, // ??
results: sortedResults,
stats: {
detailStats: complete.stats,
fromCache: false,
resultCount: sortedResults.length,
sortingTime,
type: 'fileIndexProvider'
}
};
});
}));
}
private getOrCreateCache(cacheKey: string): Cache {
const existing = this.caches[cacheKey];
if (existing) {
return existing;
}
return this.caches[cacheKey] = new Cache();
}
private trySortedSearchFromCache(config: IFileQuery, token: CancellationToken): Promise<IInternalSearchComplete> | undefined {
const folderCacheKey = this.getFolderCacheKey(config);
const cache = folderCacheKey && this.caches[folderCacheKey];
if (!cache) {
return undefined;
}
const cached = this.getResultsFromCache(cache, config.filePattern!, token);
if (cached) {
return cached.then(complete => {
const sortSW = StopWatch.create();
return this.sortResults(config, complete.results, cache.scorerCache, token)
.then(sortedResults => {
if (token && token.isCancellationRequested) {
throw canceled();
}
return <IInternalSearchComplete<IFileSearchStats>>{
limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults,
results: sortedResults,
stats: {
fromCache: true,
detailStats: complete.stats,
type: 'fileIndexProvider',
resultCount: sortedResults.length,
sortingTime: sortSW.elapsed()
}
};
});
});
}
return undefined;
}
private sortResults(config: IFileQuery, results: IInternalFileMatch[], scorerCache: ScorerCache, token: CancellationToken): Promise<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, token);
}
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, token: CancellationToken): Promise<IInternalSearchComplete<ICachedSearchStats>> | null {
const cacheLookupSW = StopWatch.create();
if (path.isAbsolute(searchValue)) {
return null; // bypass cache if user looks up an absolute path where matching goes directly on disk
}
// Find cache entries by prefix of search value
const hasPathSep = searchValue.indexOf(path.sep) >= 0;
let cacheRow: ICacheRow | undefined;
for (let previousSearch in cache.resultsToSearchCache) {
// If we narrow down, we might be able to reuse the cached results
if (strings.startsWith(searchValue, previousSearch)) {
if (hasPathSep && previousSearch.indexOf(path.sep) < 0) {
continue; // since a path character widens the search for potential more matches, require it in previous search too
}
const row = cache.resultsToSearchCache[previousSearch];
cacheRow = {
promise: this.preventCancellation(row.promise),
resolved: row.resolved
};
break;
}
}
if (!cacheRow) {
return null;
}
const cacheLookupTime = cacheLookupSW.elapsed();
const cacheFilterSW = StopWatch.create();
return new Promise<IInternalSearchComplete<ICachedSearchStats>>((c, e) => {
token.onCancellationRequested(() => e(canceled()));
cacheRow!.promise.then(complete => {
if (token && token.isCancellationRequested) {
e(canceled());
}
// Pattern match on results
const results: IInternalFileMatch[] = [];
const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase();
for (let i = 0; i < complete.results.length; i++) {
const entry = complete.results[i];
// Check if this entry is a match for the search value
if (!strings.fuzzyContains(entry.relativePath!, normalizedSearchValueLowercase)) {
continue;
}
results.push(entry);
}
c(<IInternalSearchComplete<ICachedSearchStats>>{
limitHit: complete.limitHit,
results,
stats: {
cacheWasResolved: cacheRow!.resolved,
cacheLookupTime,
cacheFilterTime: cacheFilterSW.elapsed(),
cacheEntryCount: complete.results.length
}
});
}, e);
});
}
private doSearch(engine: FileIndexSearchEngine, token: CancellationToken): Promise<IInternalSearchComplete<IFileIndexProviderStats>> {
token.onCancellationRequested(() => engine.cancel());
const results: IInternalFileMatch[] = [];
const onResult = match => results.push(match);
return engine.search(onResult).then(result => {
return <IInternalSearchComplete<IFileIndexProviderStats>>{
limitHit: result.isLimitHit,
results,
stats: result.stats
};
});
}
public clearCache(cacheKey: string): void {
const expandedKeys = this.folderCacheKeys.get(cacheKey);
if (!expandedKeys) {
return undefined;
}
expandedKeys.forEach(key => delete this.caches[key]);
this.folderCacheKeys.delete(cacheKey);
return undefined;
}
private preventCancellation<C>(promise: CancelablePromise<C>): CancelablePromise<C> {
return new class implements CancelablePromise<C> {
cancel() {
// Do nothing
}
then(resolve, reject) {
return promise.then(resolve, reject);
}
catch(reject?) {
return this.then(undefined, reject);
}
finally(onFinally) {
return promise.finally(onFinally);
}
};
}
}
interface ICacheRow {
promise: CancelablePromise<IInternalSearchComplete<IFileIndexProviderStats>>;
resolved: boolean;
}
class Cache {
public resultsToSearchCache: { [searchValue: string]: ICacheRow; } = Object.create(null);
public scorerCache: ScorerCache = Object.create(null);
}
const FileMatchItemAccessor = new class implements IItemAccessor<IInternalFileMatch> {
public getItemLabel(match: IInternalFileMatch): string {
return match.basename; // e.g. myFile.txt
}
public getItemDescription(match: IInternalFileMatch): string {
return match.relativePath!.substr(0, match.relativePath!.length - match.basename.length - 1); // e.g. some/path/to/file
}
public getItemPath(match: IInternalFileMatch): string {
return match.relativePath!; // e.g. some/path/to/file/myFile.txt
}
};

View File

@@ -9,7 +9,6 @@ import { URI, UriComponents } from 'vs/base/common/uri';
import * as extfs from 'vs/base/node/extfs';
import { ILogService } from 'vs/platform/log/common/log';
import { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISearchCompleteStats, ITextQuery } from 'vs/workbench/services/search/common/search';
import { FileIndexSearchManager } from 'vs/workbench/api/node/extHostSearch.fileIndex';
import { FileSearchManager } from 'vs/workbench/services/search/node/fileSearchManager';
import { SearchService } from 'vs/workbench/services/search/node/rawSearchService';
import { RipgrepSearchProvider } from 'vs/workbench/services/search/node/ripgrepSearchProvider';
@@ -30,20 +29,16 @@ export class ExtHostSearch implements ExtHostSearchShape {
private readonly _textSearchUsedSchemes = new Set<string>();
private readonly _fileSearchProvider = new Map<number, vscode.FileSearchProvider>();
private readonly _fileSearchUsedSchemes = new Set<string>();
private readonly _fileIndexProvider = new Map<number, vscode.FileIndexProvider>();
private readonly _fileIndexUsedSchemes = new Set<string>();
private _handlePool: number = 0;
private _internalFileSearchHandle: number;
private _internalFileSearchProvider: SearchService | null;
private _fileSearchManager: FileSearchManager;
private _fileIndexSearchManager: FileIndexSearchManager;
constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer | null, private _logService: ILogService, private _extfs = extfs) {
this._proxy = mainContext.getProxy(MainContext.MainThreadSearch);
this._fileSearchManager = new FileSearchManager();
this._fileIndexSearchManager = new FileIndexSearchManager();
}
private _transformScheme(scheme: string): string {
@@ -96,22 +91,6 @@ export class ExtHostSearch implements ExtHostSearchShape {
});
}
registerFileIndexProvider(scheme: string, provider: vscode.FileIndexProvider): IDisposable {
if (this._fileIndexUsedSchemes.has(scheme)) {
throw new Error(`a provider for the scheme '${scheme}' is already registered`);
}
this._fileIndexUsedSchemes.add(scheme);
const handle = this._handlePool++;
this._fileIndexProvider.set(handle, provider);
this._proxy.$registerFileIndexProvider(handle, this._transformScheme(scheme));
return toDisposable(() => {
this._fileIndexUsedSchemes.delete(scheme);
this._fileSearchProvider.delete(handle);
this._proxy.$unregisterProvider(handle); // TODO@roblou - unregisterFileIndexProvider
});
}
$provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: CancellationToken): Promise<ISearchCompleteStats> {
const query = reviveQuery(rawQuery);
if (handle === this._internalFileSearchHandle) {
@@ -123,14 +102,7 @@ export class ExtHostSearch implements ExtHostSearchShape {
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
}, token);
} else {
const indexProvider = this._fileIndexProvider.get(handle);
if (!indexProvider) {
throw new Error('unknown provider: ' + handle);
}
return this._fileIndexSearchManager.fileSearch(query, indexProvider, batch => {
this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource));
}, token);
throw new Error('unknown provider: ' + handle);
}
}
}
@@ -164,7 +136,6 @@ export class ExtHostSearch implements ExtHostSearchShape {
}
this._fileSearchManager.clearCache(cacheKey);
this._fileIndexSearchManager.clearCache(cacheKey);
return Promise.resolve(undefined);
}

View File

@@ -19,14 +19,19 @@ import * as types from 'vs/workbench/api/node/extHostTypes';
import { ExtHostWorkspace, IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace';
import * as vscode from 'vscode';
import {
TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, ProcessExecutionOptionsDTO, ProcessExecutionDTO,
ShellExecutionOptionsDTO, ShellExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO
TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO,
ProcessExecutionOptionsDTO, ProcessExecutionDTO,
ShellExecutionOptionsDTO, ShellExecutionDTO,
CustomExecutionDTO,
TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO
} from '../shared/tasks';
import { ExtHostVariableResolverService } from 'vs/workbench/api/node/extHostDebugService';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors';
import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration';
import { ExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/node/extHostTerminalService';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { CancellationToken } from 'vs/base/common/cancellation';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
namespace TaskDefinitionDTO {
export function from(value: vscode.TaskDefinition): TaskDefinitionDTO {
@@ -74,7 +79,7 @@ namespace ProcessExecutionOptionsDTO {
}
namespace ProcessExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO): value is ProcessExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is ProcessExecutionDTO {
const candidate = value as ProcessExecutionDTO;
return candidate && !!candidate.process;
}
@@ -115,7 +120,7 @@ namespace ShellExecutionOptionsDTO {
}
namespace ShellExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO): value is ShellExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is ShellExecutionDTO {
const candidate = value as ShellExecutionDTO;
return candidate && (!!candidate.commandLine || !!candidate.command);
}
@@ -148,6 +153,19 @@ namespace ShellExecutionDTO {
}
}
namespace CustomExecutionDTO {
export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is CustomExecutionDTO {
let candidate = value as CustomExecutionDTO;
return candidate && candidate.customExecution === 'customExecution';
}
export function from(value: vscode.CustomExecution): CustomExecutionDTO {
return {
customExecution: 'customExecution'
};
}
}
namespace TaskHandleDTO {
export function from(value: types.Task): TaskHandleDTO {
let folder: UriComponents | undefined;
@@ -181,11 +199,13 @@ namespace TaskDTO {
if (value === undefined || value === null) {
return undefined;
}
let execution: ShellExecutionDTO | ProcessExecutionDTO | undefined;
let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined;
if (value.execution instanceof types.ProcessExecution) {
execution = ProcessExecutionDTO.from(value.execution);
} else if (value.execution instanceof types.ShellExecution) {
execution = ShellExecutionDTO.from(value.execution);
} else if ((<vscode.Task2>value).execution2 && (<vscode.Task2>value).execution2 instanceof types.CustomExecution) {
execution = CustomExecutionDTO.from(<types.CustomExecution>(<vscode.Task2>value).execution2);
}
const definition: TaskDefinitionDTO = TaskDefinitionDTO.from(value.definition);
let scope: number | UriComponents;
@@ -315,15 +335,121 @@ interface HandlerData {
extension: IExtensionDescription;
}
class CustomExecutionData implements IDisposable {
private static waitForDimensionsTimeoutInMs: number = 5000;
private _cancellationSource?: CancellationTokenSource;
private readonly _onTaskExecutionComplete: Emitter<CustomExecutionData> = new Emitter<CustomExecutionData>();
private readonly _disposables: IDisposable[] = [];
private terminal?: vscode.Terminal;
private terminalId?: number;
public result: number | undefined;
constructor(
private readonly customExecution: vscode.CustomExecution,
private readonly terminalService: ExtHostTerminalService) {
}
public dispose(): void {
dispose(this._disposables);
}
public get onTaskExecutionComplete(): Event<CustomExecutionData> {
return this._onTaskExecutionComplete.event;
}
private onDidCloseTerminal(terminal: vscode.Terminal): void {
if (this.terminal === terminal) {
this._cancellationSource.cancel();
}
}
private onDidOpenTerminal(terminal: vscode.Terminal): void {
if (!(terminal instanceof ExtHostTerminal)) {
throw new Error('How could this not be a extension host terminal?');
}
if (this.terminalId && terminal._id === this.terminalId) {
this.startCallback(this.terminalId);
}
}
public async startCallback(terminalId: number): Promise<void> {
this.terminalId = terminalId;
// If we have already started the extension task callback, then
// do not start it again.
// It is completely valid for multiple terminals to be opened
// before the one for our task.
if (this._cancellationSource) {
return undefined;
}
const callbackTerminals: vscode.Terminal[] = this.terminalService.terminals.filter((terminal) => terminal._id === terminalId);
if (!callbackTerminals || callbackTerminals.length === 0) {
this._disposables.push(this.terminalService.onDidOpenTerminal(this.onDidOpenTerminal.bind(this)));
return;
}
if (callbackTerminals.length !== 1) {
throw new Error(`Expected to only have one terminal at this point`);
}
this.terminal = callbackTerminals[0];
const terminalRenderer: vscode.TerminalRenderer = await this.terminalService.resolveTerminalRenderer(terminalId);
// If we don't have the maximum dimensions yet, then we need to wait for them (but not indefinitely).
// Custom executions will expect the dimensions to be set properly before they are launched.
// BUT, due to the API contract VSCode has for terminals and dimensions, they are still responsible for
// handling cases where they are not set.
if (!terminalRenderer.maximumDimensions) {
const dimensionTimeout: Promise<void> = new Promise((resolve) => {
setTimeout(() => {
resolve();
}, CustomExecutionData.waitForDimensionsTimeoutInMs);
});
let dimensionsRegistration: IDisposable | undefined;
const dimensionsPromise: Promise<void> = new Promise((resolve) => {
dimensionsRegistration = terminalRenderer.onDidChangeMaximumDimensions((newDimensions) => {
resolve();
});
});
await Promise.race([dimensionTimeout, dimensionsPromise]);
if (dimensionsRegistration) {
dimensionsRegistration.dispose();
}
}
this._cancellationSource = new CancellationTokenSource();
this._disposables.push(this._cancellationSource);
this._disposables.push(this.terminalService.onDidCloseTerminal(this.onDidCloseTerminal.bind(this)));
// Regardless of how the task completes, we are done with this custom execution task.
this.customExecution.callback(terminalRenderer, this._cancellationSource.token).then(
(success) => {
this.result = success;
this._onTaskExecutionComplete.fire(this);
}, (rejected) => {
this._onTaskExecutionComplete.fire(this);
});
}
}
export class ExtHostTask implements ExtHostTaskShape {
private _proxy: MainThreadTaskShape;
private _workspaceProvider: IExtHostWorkspaceProvider;
private _editorService: ExtHostDocumentsAndEditors;
private _configurationService: ExtHostConfiguration;
private _terminalService: ExtHostTerminalService;
private _handleCounter: number;
private _handlers: Map<number, HandlerData>;
private _taskExecutions: Map<string, TaskExecutionImpl>;
private _providedCustomExecutions: Map<string, CustomExecutionData>;
private _activeCustomExecutions: Map<string, CustomExecutionData>;
private readonly _onDidExecuteTask: Emitter<vscode.TaskStartEvent> = new Emitter<vscode.TaskStartEvent>();
private readonly _onDidTerminateTask: Emitter<vscode.TaskEndEvent> = new Emitter<vscode.TaskEndEvent>();
@@ -331,14 +457,22 @@ export class ExtHostTask implements ExtHostTaskShape {
private readonly _onDidTaskProcessStarted: Emitter<vscode.TaskProcessStartEvent> = new Emitter<vscode.TaskProcessStartEvent>();
private readonly _onDidTaskProcessEnded: Emitter<vscode.TaskProcessEndEvent> = new Emitter<vscode.TaskProcessEndEvent>();
constructor(mainContext: IMainContext, workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfiguration) {
constructor(
mainContext: IMainContext,
workspaceService: ExtHostWorkspace,
editorService: ExtHostDocumentsAndEditors,
configurationService: ExtHostConfiguration,
extHostTerminalService: ExtHostTerminalService) {
this._proxy = mainContext.getProxy(MainContext.MainThreadTask);
this._workspaceProvider = workspaceService;
this._editorService = editorService;
this._configurationService = configurationService;
this._terminalService = extHostTerminalService;
this._handleCounter = 0;
this._handlers = new Map<number, HandlerData>();
this._taskExecutions = new Map<string, TaskExecutionImpl>();
this._providedCustomExecutions = new Map<string, CustomExecutionData>();
this._activeCustomExecutions = new Map<string, CustomExecutionData>();
}
public registerTaskProvider(extension: IExtensionDescription, provider: vscode.TaskProvider): vscode.Disposable {
@@ -402,7 +536,26 @@ export class ExtHostTask implements ExtHostTaskShape {
return this._onDidExecuteTask.event;
}
public async $onDidStartTask(execution: TaskExecutionDTO): Promise<void> {
public async $onDidStartTask(execution: TaskExecutionDTO, terminalId: number): Promise<void> {
// Once a terminal is spun up for the custom execution task this event will be fired.
// At that point, we need to actually start the callback, but
// only if it hasn't already begun.
const extensionCallback: CustomExecutionData | undefined = this._providedCustomExecutions.get(execution.id);
if (extensionCallback) {
if (this._activeCustomExecutions.get(execution.id) !== undefined) {
throw new Error('We should not be trying to start the same custom task executions twice.');
}
this._activeCustomExecutions.set(execution.id, extensionCallback);
const taskExecutionComplete: IDisposable = extensionCallback.onTaskExecutionComplete(() => {
this.customExecutionComplete(execution);
taskExecutionComplete.dispose();
});
extensionCallback.startCallback(terminalId);
}
this._onDidExecuteTask.fire({
execution: await this.getTaskExecution(execution)
});
@@ -415,6 +568,7 @@ export class ExtHostTask implements ExtHostTaskShape {
public async $OnDidEndTask(execution: TaskExecutionDTO): Promise<void> {
const _execution = await this.getTaskExecution(execution);
this._taskExecutions.delete(execution.id);
this.customExecutionComplete(execution);
this._onDidTerminateTask.fire({
execution: _execution
});
@@ -453,21 +607,57 @@ export class ExtHostTask implements ExtHostTaskShape {
if (!handler) {
return Promise.reject(new Error('no handler found'));
}
return asPromise(() => handler.provider.provideTasks(CancellationToken.None)).then(value => {
const sanitized: vscode.Task[] = [];
// For custom execution tasks, we need to store the execution objects locally
// since we obviously cannot send callback functions through the proxy.
// So, clear out any existing ones.
this._providedCustomExecutions.clear();
// Set up a list of task ID promises that we can wait on
// before returning the provided tasks. The ensures that
// our task IDs are calculated for any custom execution tasks.
// Knowing this ID ahead of time is needed because when a task
// start event is fired this is when the custom execution is called.
// The task start event is also the first time we see the ID from the main
// thread, which is too late for us because we need to save an map
// from an ID to the custom execution function. (Kind of a cart before the horse problem).
const taskIdPromises: Promise<void>[] = [];
const fetchPromise = asPromise(() => handler.provider.provideTasks(CancellationToken.None)).then(value => {
const taskDTOs: TaskDTO[] = [];
for (let task of value) {
if (task.definition && validTypes[task.definition.type] === true) {
sanitized.push(task);
} else {
sanitized.push(task);
if (!task.definition || !validTypes[task.definition.type]) {
console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`);
}
const taskDTO: TaskDTO = TaskDTO.from(task, handler.extension);
taskDTOs.push(taskDTO);
if (CustomExecutionDTO.is(taskDTO.execution)) {
taskIdPromises.push(new Promise((resolve) => {
// The ID is calculated on the main thread task side, so, let's call into it here.
// We need the task id's pre-computed for custom task executions because when OnDidStartTask
// is invoked, we have to be able to map it back to our data.
this._proxy.$createTaskId(taskDTO).then((taskId) => {
this._providedCustomExecutions.set(taskId, new CustomExecutionData(<vscode.CustomExecution>(<vscode.Task2>task).execution2, this._terminalService));
resolve();
});
}));
}
}
return {
tasks: TaskDTO.fromMany(sanitized, handler.extension),
tasks: taskDTOs,
extension: handler.extension
};
});
return new Promise((resolve) => {
fetchPromise.then((result) => {
Promise.all(taskIdPromises).then(() => {
resolve(result);
});
});
});
}
public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> {
@@ -525,4 +715,13 @@ export class ExtHostTask implements ExtHostTaskShape {
this._taskExecutions.set(execution.id, result);
return result;
}
private customExecutionComplete(execution: TaskExecutionDTO): void {
const extensionCallback: CustomExecutionData | undefined = this._activeCustomExecutions.get(execution.id);
if (extensionCallback) {
this._activeCustomExecutions.delete(execution.id);
this._proxy.$customExecutionComplete(execution.id, extensionCallback.result);
extensionCallback.dispose();
}
}
}

View File

@@ -233,13 +233,17 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco
constructor(
proxy: MainThreadTerminalServiceShape,
private _name: string,
private _terminal: ExtHostTerminal
private _terminal: ExtHostTerminal,
id?: number
) {
super(proxy);
this._proxy.$createTerminalRenderer(this._name).then(id => {
this._runQueuedRequests(id);
(<any>this._terminal)._runQueuedRequests(id);
});
super(proxy, id);
if (!id) {
this._proxy.$createTerminalRenderer(this._name).then(id => {
this._runQueuedRequests(id);
(<any>this._terminal)._runQueuedRequests(id);
});
}
}
public write(data: string): void {
@@ -313,6 +317,21 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
return renderer;
}
public async resolveTerminalRenderer(id: number): Promise<vscode.TerminalRenderer> {
// Check to see if the extension host already knows about this terminal.
for (const terminalRenderer of this._terminalRenderers) {
if (terminalRenderer._id === id) {
return terminalRenderer;
}
}
const terminal = this._getTerminalById(id);
const renderer = new ExtHostTerminalRenderer(this._proxy, terminal.name, terminal, terminal._id);
this._terminalRenderers.push(renderer);
return renderer;
}
public $acceptActiveTerminalChanged(id: number | null): void {
const original = this._activeTerminal;
if (id === null) {
@@ -372,11 +391,10 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
public $acceptTerminalClosed(id: number): void {
const index = this._getTerminalObjectIndexById(this.terminals, id);
if (index === null) {
return;
if (index !== null) {
const terminal = this._terminals.splice(index, 1)[0];
this._onDidCloseTerminal.fire(terminal);
}
const terminal = this._terminals.splice(index, 1)[0];
this._onDidCloseTerminal.fire(terminal);
}
public $acceptTerminalOpened(id: number, name: string): void {
@@ -386,6 +404,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape {
this._onDidOpenTerminal.fire(this.terminals[index]);
return;
}
const renderer = this._getTerminalRendererById(id);
const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined);
this._terminals.push(terminal);

View File

@@ -75,7 +75,7 @@ export class ExtHostEditors implements ExtHostEditorsShape {
}
return this._proxy.$tryShowTextDocument(document.uri, options).then(id => {
const editor = this._extHostDocumentsAndEditors.getEditor(id);
const editor = id && this._extHostDocumentsAndEditors.getEditor(id);
if (editor) {
return editor;
} else {

View File

@@ -1708,9 +1708,33 @@ export enum TaskScope {
Workspace = 2
}
@es5ClassCompat
export class Task implements vscode.Task {
export class CustomExecution implements vscode.CustomExecution {
private _callback: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable<number>;
constructor(callback: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable<number>) {
this._callback = callback;
}
public computeId(): string {
const hash = crypto.createHash('md5');
hash.update('customExecution');
hash.update(generateUuid());
return hash.digest('hex');
}
public set callback(value: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable<number>) {
this._callback = value;
}
public get callback(): (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable<number> {
return this._callback;
}
}
@es5ClassCompat
export class Task implements vscode.Task2 {
private static ExtensionCallbackType: string = 'customExecution';
private static ProcessType: string = 'process';
private static ShellType: string = 'shell';
private static EmptyType: string = '$empty';
@@ -1720,7 +1744,7 @@ export class Task implements vscode.Task {
private _definition: vscode.TaskDefinition;
private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined;
private _name: string;
private _execution: ProcessExecution | ShellExecution | undefined;
private _execution: ProcessExecution | ShellExecution | CustomExecution | undefined;
private _problemMatchers: string[];
private _hasDefinedMatchers: boolean;
private _isBackground: boolean;
@@ -1729,8 +1753,8 @@ export class Task implements vscode.Task {
private _presentationOptions: vscode.TaskPresentationOptions;
private _runOptions: vscode.RunOptions;
constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]);
constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]);
constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]);
constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]);
constructor(definition: vscode.TaskDefinition, arg2: string | (vscode.TaskScope.Global | vscode.TaskScope.Workspace) | vscode.WorkspaceFolder, arg3: any, arg4?: any, arg5?: any, arg6?: any) {
this.definition = definition;
let problemMatchers: string | string[];
@@ -1795,6 +1819,11 @@ export class Task implements vscode.Task {
type: Task.ShellType,
id: this._execution.computeId()
};
} else if (this._execution instanceof CustomExecution) {
this._definition = {
type: Task.ExtensionCallbackType,
id: this._execution.computeId()
};
} else {
this._definition = {
type: Task.EmptyType,
@@ -1837,17 +1866,25 @@ export class Task implements vscode.Task {
}
get execution(): ProcessExecution | ShellExecution | undefined {
return this._execution;
return (this._execution instanceof CustomExecution) ? undefined : this._execution;
}
set execution(value: ProcessExecution | ShellExecution | undefined) {
this.execution2 = value;
}
get execution2(): ProcessExecution | ShellExecution | CustomExecution | undefined {
return this._execution;
}
set execution2(value: ProcessExecution | ShellExecution | CustomExecution | undefined) {
if (value === null) {
value = undefined;
}
this.clear();
this._execution = value;
const type = this._definition.type;
if (Task.EmptyType === type || Task.ProcessType === type || Task.ShellType === type) {
if (Task.EmptyType === type || Task.ProcessType === type || Task.ShellType === type || Task.ExtensionCallbackType === type) {
this.computeDefinitionBasedOnExecution();
}
}

View File

@@ -321,10 +321,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
return folders[0].uri.fsPath;
}
getRelativePath(pathOrUri: string | vscode.Uri, includeWorkspace?: boolean): string | undefined {
getRelativePath(pathOrUri: string | vscode.Uri, includeWorkspace?: boolean): string {
let resource: URI | undefined;
let path: string | undefined;
let path: string = '';
if (typeof pathOrUri === 'string') {
resource = URI.file(pathOrUri);
path = pathOrUri;
@@ -354,7 +354,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspac
if (includeWorkspace && folder.name) {
result = `${folder.name}/${result}`;
}
return result;
return result!;
}
private trySetWorkspaceFolders(folders: vscode.WorkspaceFolder[]): void {