diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 338645cd9886a7db8a937250567c15a4304cc86b..082dcf810f657cbd1f54da036f85c3a6b519220d 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1215,6 +1215,7 @@ export interface Command { * @internal */ export interface CommentThreadTemplate { + controllerHandle: number; label: string; acceptInputCommand?: Command; additionalCommands?: Command[]; @@ -1281,6 +1282,7 @@ export interface CommentInput { */ export interface CommentThread2 { commentThreadHandle: number; + controllerHandle: number; extensionId?: string; threadId: string | null; resource: string | null; diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index ca44a26a1217b579c1cd6e5e5861d226c324908d..825cf3d08d3e90abcf42f87fd3cca6dafe10d791 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8940,6 +8940,278 @@ declare module 'vscode' { */ export const onDidChange: Event; } + + //#region Comments + + /** + * Collapsible state of a [comment thread](#CommentThread) + */ + export enum CommentThreadCollapsibleState { + /** + * Determines an item is collapsed + */ + Collapsed = 0, + + /** + * Determines an item is expanded + */ + Expanded = 1 + } + + /** + * A collection of [comments](#Comment) representing a conversation at a particular range in a document. + */ + export interface CommentThread { + /** + * A unique identifier of the comment thread. + */ + readonly id: string; + + /** + * The uri of the document the thread has been created on. + */ + readonly resource: Uri; + + /** + * The range the comment thread is located within the document. The thread icon will be shown + * at the first line of the range. + */ + readonly range: Range; + + /** + * The ordered comments of the thread. + */ + comments: Comment[]; + + /** + * Whether the thread should be collapsed or expanded when opening the document. + * Defaults to Collapsed. + */ + collapsibleState: CommentThreadCollapsibleState; + + /** + * The optional human-readable label describing the [Comment Thread](#CommentThread) + */ + label?: string; + + /** + * Optional accept input command + * + * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. + * This command will be invoked when users the user accepts the value in the comment editor. + * This command will disabled when the comment editor is empty. + */ + acceptInputCommand?: Command; + + /** + * Optional additonal commands. + * + * `additionalCommands` are the secondary actions rendered on Comment Widget. + */ + additionalCommands?: Command[]; + + /** + * The command to be executed when users try to delete the comment thread. Currently, this is only called + * when the user collapses a comment thread that has no comments in it. + */ + deleteCommand?: Command; + + /** + * Dispose this comment thread. + * + * Once disposed, this comment thread will be removed from visible editors and Comment Panel when approriate. + */ + dispose(): void; + } + + /** + * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. + */ + export class Comment { + /** + * The id of the comment + */ + readonly id: string; + + /** + * The human-readable comment body + */ + readonly body: MarkdownString; + + /** + * The display name of the user who created the comment + */ + readonly userName: string; + + /** + * Optional label describing the [Comment](#Comment) + * Label will be rendered next to userName if exists. + */ + readonly label?: string; + + /** + * The icon path for the user who created the comment + */ + readonly userIconPath?: Uri; + + /** + * The command to be executed if the comment is selected in the Comments Panel + */ + readonly selectCommand?: Command; + + /** + * The command to be executed when users try to save the edits to the comment + */ + readonly editCommand?: Command; + + /** + * The command to be executed when users try to delete the comment + */ + readonly deleteCommand?: Command; + + /** + * @param id The id of the comment + * @param body The human-readable comment body + * @param userName The display name of the user who created the comment + */ + constructor(id: string, body: MarkdownString, userName: string); + } + + /** + * The comment input box in Comment Widget. + */ + export interface CommentInputBox { + /** + * Setter and getter for the contents of the comment input box + */ + value: string; + + /** + * The uri of the document comment input box has been created on + */ + resource: Uri; + + /** + * The range the comment input box is located within the document + */ + range: Range; + } + + /** + * Commenting range provider for a [comment controller](#CommentController). + */ + export interface CommentingRangeProvider { + /** + * Provide a list of ranges which allow new comment threads creation or null for a given document + */ + provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; + } + + /** + * Comment thread template for new comment thread creation. + */ + export interface CommentThreadTemplate { + /** + * The human-readable label describing the [Comment Thread](#CommentThread) + */ + readonly label: string; + + /** + * Optional accept input command + * + * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. + * This command will be invoked when users the user accepts the value in the comment editor. + * This command will disabled when the comment editor is empty. + */ + readonly acceptInputCommand?: Command; + + /** + * Optional additonal commands. + * + * `additionalCommands` are the secondary actions rendered on Comment Widget. + */ + readonly additionalCommands?: Command[]; + + /** + * The command to be executed when users try to delete the comment thread. Currently, this is only called + * when the user collapses a comment thread that has no comments in it. + */ + readonly deleteCommand?: Command; + } + + /** + * A comment controller is able to provide [comments](#CommentThread) support to the editor and + * provide users various ways to interact with comments. + */ + export interface CommentController { + /** + * The id of this comment controller. + */ + readonly id: string; + + /** + * The human-readable label of this comment controller. + */ + readonly label: string; + + /** + * The active [comment input box](#CommentInputBox) or `undefined`. The active `inputBox` is the input box of + * the comment thread widget that currently has focus. It's `undefined` when the focus is not in any CommentInputBox. + */ + readonly inputBox: CommentInputBox | undefined; + + /** + * Optional comment thread template information. + * + * The comment controller will use this information to create the comment widget when users attempt to create new comment thread + * from the gutter or command palette. + * + * When users run `CommentThreadTemplate.acceptInputCommand` or `CommentThreadTemplate.additionalCommands`, extensions should create + * the approriate [CommentThread](#CommentThread). + * + * If not provided, users won't be able to create new comment threads in the editor. + */ + template?: CommentThreadTemplate; + + /** + * Optional commenting range provider. Provide a list [ranges](#Range) which support commenting to any given resource uri. + * + * If not provided and `emptyCommentThreadFactory` exits, users can leave comments in any document opened in the editor. + */ + commentingRangeProvider?: CommentingRangeProvider; + + /** + * Create a [comment thread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches) + * and Comments Panel once created. + * + * @param id An `id` for the comment thread. + * @param resource The uri of the document the thread has been created on. + * @param range The range the comment thread is located within the document. + * @param comments The ordered comments of the thread. + */ + createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[]): CommentThread; + + /** + * Dispose this comment controller. + * + * Once disposed, all [comment threads](#CommentThread) created by this comment controller will also be removed from the editor + * and Comments Panel. + */ + dispose(): void; + } + + namespace comment { + /** + * Creates a new [comment controller](#CommentController) instance. + * + * @param id An `id` for the comment controller. + * @param label A human-readable string for the comment controller. + * @return An instance of [comment controller](#CommentController). + */ + export function createCommentController(id: string, label: string): CommentController; + } + + //#endregion } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 33e70afbfe1105c24bd9cdd2469c03b37bfdb47d..b1c6b84bb3b6f3d3a1a0260e528e870337558793 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -735,91 +735,10 @@ declare module 'vscode' { inDraftMode?: boolean; } - export enum CommentThreadCollapsibleState { - /** - * Determines an item is collapsed - */ - Collapsed = 0, - /** - * Determines an item is expanded - */ - Expanded = 1 - } - - /** - * A collection of comments representing a conversation at a particular range in a document. - */ - export interface CommentThread { - /** - * A unique identifier of the comment thread. - */ - threadId: string; - - /** - * The uri of the document the thread has been created on. - */ - resource: Uri; - - /** - * The range the comment thread is located within the document. The thread icon will be shown - * at the first line of the range. - */ - range: Range; - - /** - * The human-readable label describing the [Comment Thread](#CommentThread) - */ - label?: string; - - /** - * The ordered comments of the thread. - */ - comments: Comment[]; - - /** - * Optional accept input command - * - * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. - * This command will be invoked when users the user accepts the value in the comment editor. - * This command will disabled when the comment editor is empty. - */ - acceptInputCommand?: Command; - - /** - * Optional additonal commands. - * - * `additionalCommands` are the secondary actions rendered on Comment Widget. - */ - additionalCommands?: Command[]; - - /** - * Whether the thread should be collapsed or expanded when opening the document. - * Defaults to Collapsed. - */ - collapsibleState?: CommentThreadCollapsibleState; - - /** - * The command to be executed when users try to delete the comment thread. Currently, this is only called - * when the user collapses a comment thread that has no comments in it. - */ - deleteCommand?: Command; - - /** - * Dispose this comment thread. - * Once disposed, the comment thread will be removed from visible text editors and Comments Panel. - */ - dispose?(): void; - } - /** * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. */ - export class Comment { - /** - * The id of the comment - */ - readonly id: string; - + export class CommentLegacy extends Comment { /** * The id of the comment * @@ -827,27 +746,6 @@ declare module 'vscode' { */ readonly commentId: string; - /** - * The human-readable comment body - */ - readonly body: MarkdownString; - - /** - * The display name of the user who created the comment - */ - readonly userName: string; - - /** - * Optional label describing the [Comment](#Comment) - * Label will be rendered next to userName if exists. - */ - readonly label?: string; - - /** - * The icon path for the user who created the comment - */ - readonly userIconPath?: Uri; - /** * @deprecated Use userIconPath instead. The avatar src of the user who created the comment */ @@ -879,21 +777,6 @@ declare module 'vscode' { */ command?: Command; - /** - * The command to be executed if the comment is selected in the Comments Panel - */ - readonly selectCommand?: Command; - - /** - * The command to be executed when users try to save the edits to the comment - */ - readonly editCommand?: Command; - - /** - * The command to be executed when users try to delete the comment - */ - readonly deleteCommand?: Command; - /** * Deprecated */ @@ -903,13 +786,6 @@ declare module 'vscode' { * Proposed Comment Reaction */ commentReactions?: CommentReaction[]; - - /** - * @param id The id of the comment - * @param body The human-readable comment body - * @param userName The display name of the user who created the comment - */ - constructor(id: string, body: MarkdownString, userName: string); } /** @@ -1011,16 +887,6 @@ declare module 'vscode' { onDidChangeCommentThreads: Event; } - /** - * The comment input box in Comment Widget. - */ - export interface CommentInputBox { - /** - * Setter and getter for the contents of the comment input box. - */ - value: string; - } - /** * Stay in proposed */ @@ -1029,118 +895,22 @@ declare module 'vscode' { toggleReaction?(document: TextDocument, comment: Comment, reaction: CommentReaction): Promise; } - export interface CommentingRangeProvider { - /** - * Provide a list of ranges which allow new comment threads creation or null for a given document - */ - provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; - } - - export interface CommentThreadTemplate { - /** - * The human-readable label describing the [Comment Thread](#CommentThread) - */ - label: string; - - /** - * Optional accept input command - * - * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. - * This command will be invoked when users the user accepts the value in the comment editor. - * This command will disabled when the comment editor is empty. - */ - acceptInputCommand?: Command; - - /** - * Optional additonal commands. - * - * `additionalCommands` are the secondary actions rendered on Comment Widget. - */ - additionalCommands?: Command[]; - - /** - * The command to be executed when users try to delete the comment thread. Currently, this is only called - * when the user collapses a comment thread that has no comments in it. - */ - deleteCommand?: Command; - } - - export interface EmptyCommentThreadFactory { - template: CommentThreadTemplate; - /** - * When users attempt to create new comment thread from the gutter or command palette, `template` will be used first to create the Comment Thread Widget in the editor for users to start comment drafting. - * Then `createEmptyCommentThread` is called after that. Extensions should still call [`createCommentThread`](CommentController.createCommentThread) to create a real [`CommentThread`](#CommentThread) - * Extensions still need to call `createCommentThread` inside this call when appropriate. - * - * @param document The document in which users attempt to create a new comment thread - * @param range The range the comment threadill located within the document. - * - * @returns commentThread The [`CommentThread`](#CommentThread) created by extensions - */ - createEmptyCommentThread(document: TextDocument, range: Range): ProviderResult; - } export interface CommentController { - /** - * The id of this comment controller. - */ - readonly id: string; - - /** - * The human-readable label of this comment controller. - */ - readonly label: string; - - /** - * The active [comment input box](#CommentInputBox) or `undefined`. The active `inputBox` is the input box of - * the comment thread widget that currently has focus. It's `undefined` when the focus is not in any CommentInputBox. - */ - readonly inputBox: CommentInputBox | undefined; - - /** - * The active [comment thread](#CommentThread) or `undefined`. The `activeCommentThread` is the comment thread of - * the comment thread widget that currently has focus. It's `undefined` when the focus is not in any comment thread widget. - */ - readonly activeCommentThread: CommentThread | undefined; - - /** - * Create a [CommentThread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches) - * and Comments Panel. - * @param id An `id` for the comment thread. - * @param resource The uri of the document the thread has been created on. - * @param range The range the comment thread is located within the document. - * @param comments The ordered comments of the thread. - */ - createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[]): CommentThread; - - /** - * Optional commenting range provider. Provide a list [ranges](#Range) which support commenting to any given resource uri. - * - * If not provided and `emptyCommentThreadFactory` exits, users can leave comments in any document opened in the editor. - */ - commentingRangeProvider?: CommentingRangeProvider; - - /** - * Optional empty comment thread factory. It's necessary for supporting users to trigger Comment Thread creation from the editor or command palette. - * - * If not provided, users won't be able to trigger new comment thread creation from the editor gutter area or command palette. - */ - emptyCommentThreadFactory?: EmptyCommentThreadFactory; - /** * Optional reaction provider * Stay in proposed. */ reactionProvider?: CommentReactionProvider; + } + export interface CommentController { /** - * Dispose this comment controller. + * The active [comment thread](#CommentThread) or `undefined`. The `activeCommentThread` is the comment thread of + * the comment widget that currently has focus. It's `undefined` when the focus is not in any comment thread widget, or + * the comment widget created from [comment thread template](#CommentThreadTemplate). */ - dispose(): void; - } - - namespace comment { - export function createCommentController(id: string, label: string): CommentController; + readonly activeCommentThread: CommentThread | undefined; } namespace workspace { diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index c70e82a6a57bf22746cf7cbe79a5867582376a70..4b4896e6ece94584fddfd75fce17d30ce2ef461c 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -192,7 +192,7 @@ export class MainThreadCommentThread implements modes.CommentThread2 { constructor( public commentThreadHandle: number, - public controller: MainThreadCommentController, + public controllerHandle: number, public extensionId: string, public threadId: string, public resource: string, @@ -232,7 +232,7 @@ export class MainThreadCommentThread implements modes.CommentThread2 { toJSON(): any { return { $mid: 7, - commentControlHandle: this.controller.handle, + commentControlHandle: this.controllerHandle, commentThreadHandle: this.commentThreadHandle, }; } @@ -290,7 +290,7 @@ export class MainThreadCommentController { ): modes.CommentThread2 { let thread = new MainThreadCommentThread( commentThreadHandle, - this, + this.handle, '', threadId, URI.revive(resource).toString(), @@ -395,7 +395,13 @@ export class MainThreadCommentController { } } : [], draftMode: modes.DraftMode.NotSupported, - template: this._features.commentThreadTemplate + template: this._features.commentThreadTemplate ? { + controllerHandle: this.handle, + label: this._features.commentThreadTemplate.label, + acceptInputCommand: this._features.commentThreadTemplate.acceptInputCommand, + additionalCommands: this._features.commentThreadTemplate.additionalCommands, + deleteCommand: this._features.commentThreadTemplate.deleteCommand + } : undefined }; } @@ -421,6 +427,28 @@ export class MainThreadCommentController { return ret; } + getCommentThreadFromTemplate(resource: UriComponents, range: IRange): MainThreadCommentThread { + let thread = new MainThreadCommentThread( + -1, + this.handle, + '', + '', + URI.revive(resource).toString(), + range + ); + + let template = this._features.commentThreadTemplate; + + if (template) { + thread.acceptInputCommand = template.acceptInputCommand; + thread.additionalCommands = template.additionalCommands; + thread.deleteCommand = template.deleteCommand; + thread.label = template.label; + } + + return thread; + } + toJSON(): any { return { $mid: 6, @@ -456,7 +484,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._activeCommentThreadDisposables = []; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); this._disposables.push(this._commentService.onDidChangeActiveCommentThread(async thread => { - let controller = (thread as MainThreadCommentThread).controller; + let handle = (thread as MainThreadCommentThread).controllerHandle; + let controller = this._commentControllers.get(handle); if (!controller) { return; @@ -468,11 +497,11 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._activeCommentThreadDisposables.push(this._activeCommentThread.onDidChangeInput(input => { // todo, dispose this._input = input; - this._proxy.$onCommentWidgetInputChange(controller.handle, this._input ? this._input.value : undefined); + this._proxy.$onCommentWidgetInputChange(handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread!.range, this._input ? this._input.value : undefined); })); await this._proxy.$onActiveCommentThreadChange(controller.handle, controller.activeCommentThread.commentThreadHandle); - await this._proxy.$onCommentWidgetInputChange(controller.handle, this._input ? this._input.value : undefined); + await this._proxy.$onCommentWidgetInputChange(controller.handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread.range, this._input ? this._input.value : undefined); })); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 21443f8288b003373ed6d612a5788719965d0a26..cff14ddb273be507d006802fda9b0ae5026c4302 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1210,12 +1210,12 @@ export interface ExtHostProgressShape { export interface ExtHostCommentsShape { $provideDocumentComments(handle: number, document: UriComponents): Promise; $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; - $onCommentWidgetInputChange(commentControllerHandle: number, input: string | undefined): Promise; + $onCommentWidgetInputChange(commentControllerHandle: number, document: UriComponents, range: IRange, input: string | undefined): Promise; $onActiveCommentThreadChange(commentControllerHandle: number, threadHandle: number | undefined): Promise; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; $provideReactionGroup(commentControllerHandle: number): Promise; $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; - $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise; + $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise; $replyToCommentThread(handle: number, document: UriComponents, range: IRange, commentThread: modes.CommentThread, text: string): Promise; $editComment(handle: number, document: UriComponents, comment: modes.Comment, text: string): Promise; $deleteComment(handle: number, document: UriComponents, comment: modes.Comment): Promise; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 401f29b200f7a4090d017dcb308ca3c7c3764146..9b4fcc054999ea05b9246b2581f3decbae6596a7 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -88,14 +88,14 @@ export class ExtHostComments implements ExtHostCommentsShape { return commentController; } - $onCommentWidgetInputChange(commentControllerHandle: number, input: string): Promise { + $onCommentWidgetInputChange(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, input: string): Promise { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController) { return Promise.resolve(undefined); } - commentController.$onCommentWidgetInputChange(input); + commentController.$onCommentWidgetInputChange(uriComponents, range, input); return Promise.resolve(commentControllerHandle); } @@ -157,34 +157,27 @@ export class ExtHostComments implements ExtHostCommentsShape { }); } - $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise { + $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController) { - return Promise.resolve(undefined); + return Promise.resolve(); } - if (!(commentController as any).emptyCommentThreadFactory && !(commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread) && !(commentController.emptyCommentThreadFactory && commentController.emptyCommentThreadFactory.createEmptyCommentThread)) { - return Promise.resolve(undefined); + if (!(commentController as any).emptyCommentThreadFactory && !(commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread)) { + return Promise.resolve(); } const document = this._documents.getDocument(URI.revive(uriComponents)); return asPromise(() => { - if (commentController.emptyCommentThreadFactory) { - return commentController.emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); + if ((commentController as any).emptyCommentThreadFactory) { + return (commentController as any).emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); } if (commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread) { return commentController.commentingRangeProvider.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); } - - return; - }).then((commentThread: ExtHostCommentThread | undefined) => { - if (commentThread) { - return Promise.resolve(commentThread.handle); - } - return Promise.resolve(undefined); - }); + }).then(() => Promise.resolve()); } registerWorkspaceCommentProvider( @@ -384,7 +377,11 @@ export class ExtHostCommentThread implements vscode.CommentThread { private static _handlePool: number = 0; readonly handle = ExtHostCommentThread._handlePool++; get threadId(): string { - return this._threadId; + return this._id; + } + + get id(): string { + return this._id; } get resource(): vscode.Uri { @@ -416,11 +413,11 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._onDidUpdateCommentThread.fire(); } - get comments(): vscode.Comment[] { + get comments(): (vscode.Comment & vscode.CommentLegacy)[] { return this._comments; } - set comments(newComments: vscode.Comment[]) { + set comments(newComments: (vscode.Comment & vscode.CommentLegacy)[]) { this._comments = newComments; this._onDidUpdateCommentThread.fire(); } @@ -478,15 +475,15 @@ export class ExtHostCommentThread implements vscode.CommentThread { private _proxy: MainThreadCommentsShape, private readonly _commandsConverter: CommandsConverter, private _commentController: ExtHostCommentController, - private _threadId: string, + private _id: string, private _resource: vscode.Uri, private _range: vscode.Range, - private _comments: vscode.Comment[] + private _comments: (vscode.Comment & vscode.CommentLegacy)[] ) { this._proxy.$createCommentThread( this._commentController.handle, this.handle, - this._threadId, + this._id, this._resource, extHostTypeConverter.Range.from(this._range) ); @@ -515,7 +512,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._proxy.$updateCommentThread( this._commentController.handle, this.handle, - this._threadId, + this._id, this._resource, commentThreadRange, label, @@ -549,12 +546,14 @@ export class ExtHostCommentThread implements vscode.CommentThread { } export class ExtHostCommentInputBox implements vscode.CommentInputBox { - private _onDidChangeValue = new Emitter(); + get resource(): vscode.Uri { + return this._resource; + } - get onDidChangeValue(): Event { - return this._onDidChangeValue.event; + get range(): vscode.Range { + return this._range; } - private _value: string = ''; + get value(): string { return this._value; } @@ -565,16 +564,24 @@ export class ExtHostCommentInputBox implements vscode.CommentInputBox { this._proxy.$setInputValue(this.commentControllerHandle, newInput); } + private _onDidChangeValue = new Emitter(); + + get onDidChangeValue(): Event { + return this._onDidChangeValue.event; + } + constructor( private _proxy: MainThreadCommentsShape, - public commentControllerHandle: number, - input: string + private _resource: vscode.Uri, + private _range: vscode.Range, + private _value: string ) { - this._value = input; } - setInput(input: string) { + setInput(resource: vscode.Uri, range: vscode.Range, input: string) { + this._resource = resource; + this._range = range; this._value = input; } } @@ -607,22 +614,22 @@ class ExtHostCommentController implements vscode.CommentController { private _threads: Map = new Map(); commentingRangeProvider?: vscode.CommentingRangeProvider & { createEmptyCommentThread: (document: vscode.TextDocument, range: types.Range) => Promise; }; - private _emptyCommentThreadFactory: vscode.EmptyCommentThreadFactory | undefined; - get emptyCommentThreadFactory(): vscode.EmptyCommentThreadFactory | undefined { - return this._emptyCommentThreadFactory; + private _template: vscode.CommentThreadTemplate | undefined; + + get template(): vscode.CommentThreadTemplate | undefined { + return this._template; } - set emptyCommentThreadFactory(newEmptyCommentThreadFactory: vscode.EmptyCommentThreadFactory | undefined) { - this._emptyCommentThreadFactory = newEmptyCommentThreadFactory; + set template(newTemplate: vscode.CommentThreadTemplate | undefined) { + this._template = newTemplate; - if (this._emptyCommentThreadFactory && this._emptyCommentThreadFactory.template) { - let template = this._emptyCommentThreadFactory.template; - const acceptInputCommand = template.acceptInputCommand ? this._commandsConverter.toInternal(template.acceptInputCommand) : undefined; - const additionalCommands = template.additionalCommands ? template.additionalCommands.map(x => this._commandsConverter.toInternal(x)) : []; - const deleteCommand = template.deleteCommand ? this._commandsConverter.toInternal(template.deleteCommand) : undefined; + if (newTemplate) { + const acceptInputCommand = newTemplate.acceptInputCommand ? this._commandsConverter.toInternal(newTemplate.acceptInputCommand) : undefined; + const additionalCommands = newTemplate.additionalCommands ? newTemplate.additionalCommands.map(x => this._commandsConverter.toInternal(x)) : []; + const deleteCommand = newTemplate.deleteCommand ? this._commandsConverter.toInternal(newTemplate.deleteCommand) : undefined; this._proxy.$updateCommentControllerFeatures(this.handle, { commentThreadTemplate: { - label: template.label, + label: newTemplate.label, acceptInputCommand, additionalCommands, deleteCommand @@ -655,17 +662,17 @@ class ExtHostCommentController implements vscode.CommentController { this._proxy.$registerCommentController(this.handle, _id, _label); } - createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread { + createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: (vscode.Comment & vscode.CommentLegacy)[]): vscode.CommentThread { const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, id, resource, range, comments); this._threads.set(commentThread.handle, commentThread); return commentThread; } - $onCommentWidgetInputChange(input: string) { + $onCommentWidgetInputChange(uriComponents: UriComponents, range: IRange, input: string) { if (!this.inputBox) { - this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, input); + this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), input); } else { - this.inputBox.setInput(input); + this.inputBox.setInput(URI.revive(uriComponents), extHostTypeConverter.Range.to(range), input); } } @@ -698,25 +705,27 @@ function convertCommentInfo(owner: number, extensionId: ExtensionIdentifier, pro function convertToCommentThread(extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, vscodeCommentThread: vscode.CommentThread, commandsConverter: CommandsConverter): modes.CommentThread { return { extensionId: extensionId.value, - threadId: vscodeCommentThread.threadId, + threadId: vscodeCommentThread.id, resource: vscodeCommentThread.resource.toString(), range: extHostTypeConverter.Range.from(vscodeCommentThread.range), - comments: vscodeCommentThread.comments.map(comment => convertToComment(provider, comment, commandsConverter)), + comments: vscodeCommentThread.comments.map(comment => convertToComment(provider, comment as vscode.Comment & vscode.CommentLegacy, commandsConverter)), collapsibleState: vscodeCommentThread.collapsibleState }; } function convertFromCommentThread(commentThread: modes.CommentThread): vscode.CommentThread { return { + id: commentThread.threadId!, threadId: commentThread.threadId!, resource: URI.parse(commentThread.resource!), range: extHostTypeConverter.Range.to(commentThread.range), comments: commentThread.comments ? commentThread.comments.map(convertFromComment) : [], - collapsibleState: commentThread.collapsibleState - }; + collapsibleState: commentThread.collapsibleState, + dispose: () => { } + } as vscode.CommentThread; } -function convertFromComment(comment: modes.Comment): vscode.Comment { +function convertFromComment(comment: modes.Comment): vscode.Comment & vscode.CommentLegacy { let userIconPath: URI | undefined; if (comment.userIconPath) { try { @@ -745,7 +754,7 @@ function convertFromComment(comment: modes.Comment): vscode.Comment { }; } -function convertToModeComment(commentController: ExtHostCommentController, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter): modes.Comment { +function convertToModeComment(commentController: ExtHostCommentController, vscodeComment: vscode.Comment & vscode.CommentLegacy, commandsConverter: CommandsConverter): modes.Comment { const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar; return { @@ -762,7 +771,7 @@ function convertToModeComment(commentController: ExtHostCommentController, vscod }; } -function convertToComment(provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter): modes.Comment { +function convertToComment(provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, vscodeComment: vscode.Comment & vscode.CommentLegacy, commandsConverter: CommandsConverter): modes.Comment { const canEdit = !!(provider as vscode.DocumentCommentProvider).editComment && vscodeComment.canEdit; const canDelete = !!(provider as vscode.DocumentCommentProvider).deleteComment && vscodeComment.canDelete; const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 7ab3045022ea7e782aa0fe846b0cc4ce2948d041..ffe929454e31bcd155b4661f9feafab90aff8140 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -771,6 +771,7 @@ export function createApiFactory( ColorInformation: extHostTypes.ColorInformation, ColorPresentation: extHostTypes.ColorPresentation, Comment: extHostTypes.Comment, + CommentLegacy: extHostTypes.Comment, CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState, CompletionItem: extHostTypes.CompletionItem, CompletionItemKind: extHostTypes.CompletionItemKind, diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index f4bb3e28d9e527e643332481403a6eddabf2b1a8..296267ceb605b70ec697a5001711b5ba88e87d83 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -66,6 +66,7 @@ export interface ICommentService { deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; getReactionGroup(owner: string): CommentReaction[] | undefined; toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; + getCommentThreadFromTemplate(owner: string, resource: URI, range: IRange, ): CommentThread2 | undefined; setActiveCommentThread(commentThread: CommentThread | null): void; setInput(input: string): void; } @@ -255,6 +256,16 @@ export class CommentService extends Disposable implements ICommentService { } } + getCommentThreadFromTemplate(owner: string, resource: URI, range: IRange, ): CommentThread2 | undefined { + const commentController = this._commentControls.get(owner); + + if (commentController) { + return commentController.getCommentThreadFromTemplate(resource, range); + } + + return undefined; + } + getReactionGroup(owner: string): CommentReaction[] | undefined { const commentProvider = this._commentControls.get(owner); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index c8102c7e739a9ff9b01be5acd1fb8c8815874d7e..db53215e8e62d0d40b169f0e57d508b9eba9b396 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -65,6 +65,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _commentGlyph?: CommentGlyphWidget; private _submitActionsDisposables: IDisposable[]; private _globalToDispose: IDisposable[]; + private _commentThreadDisposables: IDisposable[] = []; private _markdownRenderer: MarkdownRenderer; private _styleElement: HTMLStyleElement; private _formActions: HTMLElement | null; @@ -103,6 +104,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._resizeObserver = null; this._isExpanded = _commentThread.collapsibleState ? _commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded : undefined; this._globalToDispose = []; + this._commentThreadDisposables = []; this._submitActionsDisposables = []; this._formActions = null; this.create(); @@ -214,6 +216,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget if (deleteCommand) { this.commentService.setActiveCommentThread(this._commentThread); return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); + } else if (this._commentEditor.getValue() === '') { + this.dispose(); + return Promise.resolve(); } } } @@ -291,6 +296,12 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentElements = newCommentNodeList; this.createThreadLabel(replaceTemplate); + if (replaceTemplate) { + // since we are replacing the old comment thread, we need to rebind the listeners. + this._commentThreadDisposables.forEach(global => global.dispose()); + this._commentThreadDisposables = []; + } + if (replaceTemplate) { this.createTextModelListener(); } @@ -395,7 +406,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentEditor.setModel(model); this._disposables.push(this._commentEditor); this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => this.setCommentEditorDecorations())); - if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined && !fromTemplate) { + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { this.createTextModelListener(); } @@ -445,7 +456,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } private createTextModelListener() { - this._disposables.push(this._commentEditor.onDidFocusEditorWidget(() => { + this._commentThreadDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { let commentThread = this._commentThread as modes.CommentThread2; commentThread.input = { uri: this._commentEditor.getModel()!.uri, @@ -454,7 +465,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.commentService.setActiveCommentThread(this._commentThread); })); - this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { + this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { let modelContent = this._commentEditor.getValue(); let thread = (this._commentThread as modes.CommentThread2); if (thread.input && thread.input.uri === this._commentEditor.getModel()!.uri && thread.input.value !== modelContent) { @@ -464,7 +475,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } })); - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => { + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => { let thread = (this._commentThread as modes.CommentThread2); if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) { @@ -489,31 +500,31 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } })); - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => { + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => { await this.update(this._commentThread); })); - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => { + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => { this.createThreadLabel(); })); } private createCommentWidgetActionsListener(container: HTMLElement, model: ITextModel) { - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => { + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => { if (container) { dom.clearNode(container); this.createCommentWidgetActions2(container, model); } })); - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => { + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => { if (container) { dom.clearNode(container); this.createCommentWidgetActions2(container, model); } })); - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => { + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => { // Move comment glyph widget and show position if the line has changed. const lineNumber = this._commentThread.range.startLineNumber; let shouldMoveWidget = false; @@ -529,7 +540,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } })); - this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => { + this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => { if (state === modes.CommentThreadCollapsibleState.Expanded && !this._isExpanded) { const lineNumber = this._commentThread.range.startLineNumber; @@ -1062,6 +1073,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } this._globalToDispose.forEach(global => global.dispose()); + this._commentThreadDisposables.forEach(global => global.dispose()); this._submitActionsDisposables.forEach(local => local.dispose()); this._onDidClose.fire(undefined); } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index f3e51559ddbc415067a7035d23582cd71311e34c..d91b88891df4934f6d22137cb6d00100f867204a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -446,6 +446,13 @@ export class ReviewController implements IEditorContribution { return; } + let matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && (zoneWidget.commentThread as any).commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); + + if (matchedNewCommentThreadZones.length) { + matchedNewCommentThreadZones[0].update(thread, true); + return; + } + const pendingCommentText = this._pendingCommentCache[e.owner] && this._pendingCommentCache[e.owner][thread.threadId]; this.displayCommentThread(e.owner, thread, pendingCommentText, draftMode); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); @@ -462,26 +469,18 @@ export class ReviewController implements IEditorContribution { this._commentWidgets.push(zoneWidget); } - private addCommentThreadFromTemplate(lineNumber: number, ownerId: string, extensionId: string | undefined, template: modes.CommentThreadTemplate): ReviewZoneWidget { - let templateReviewZoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, ownerId, { - commentThreadHandle: -1, - label: template!.label, - acceptInputCommand: template.acceptInputCommand, - additionalCommands: template.additionalCommands, - deleteCommand: template.deleteCommand, - extensionId: extensionId, - threadId: null, - resource: null, - comments: [], - range: { - startLineNumber: lineNumber, - startColumn: 0, - endLineNumber: lineNumber, - endColumn: 0 - }, - collapsibleState: modes.CommentThreadCollapsibleState.Expanded, - }, - '', modes.DraftMode.NotSupported); + private addCommentThreadFromTemplate(lineNumber: number, ownerId: string): ReviewZoneWidget { + let templateCommentThread = this.commentService.getCommentThreadFromTemplate(ownerId, this.editor.getModel()!.uri, { + startLineNumber: lineNumber, + startColumn: 1, + endLineNumber: lineNumber, + endColumn: 1 + })!; + + templateCommentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; + templateCommentThread.comments = []; + + let templateReviewZoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, ownerId, templateCommentThread, '', modes.DraftMode.NotSupported); return templateReviewZoneWidget; } @@ -692,22 +691,19 @@ export class ReviewController implements IEditorContribution { public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined) { if (commentingRangesInfo) { let range = new Range(lineNumber, 1, lineNumber, 1); - if (commentingRangesInfo.newCommentThreadCallback && template) { + if (template) { // create comment widget through template - let commentThreadWidget = this.addCommentThreadFromTemplate(lineNumber, ownerId, extensionId, template); + let commentThreadWidget = this.addCommentThreadFromTemplate(lineNumber, ownerId); commentThreadWidget.display(lineNumber, true); - - return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel()!.uri, range) - .then(commentThread => { - commentThreadWidget.update(commentThread!, true); - this._commentWidgets.push(commentThreadWidget); - this.processNextThreadToAdd(); - }) - .catch(e => { - this.notificationService.error(nls.localize('commentThreadAddFailure', "Adding a new comment thread failed: {0}.", e.message)); - commentThreadWidget.dispose(); - this.processNextThreadToAdd(); - }); + this._commentWidgets.push(commentThreadWidget); + commentThreadWidget.onDidClose(() => { + this._commentWidgets = this._commentWidgets.filter(zoneWidget => !( + zoneWidget.owner === commentThreadWidget.owner && + (zoneWidget.commentThread as any).commentThreadHandle === -1 && + Range.equalsRange(zoneWidget.commentThread.range, commentThreadWidget.commentThread.range) + )); + }); + this.processNextThreadToAdd(); } else if (commentingRangesInfo.newCommentThreadCallback) { return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel()!.uri, range) .then(_ => {