diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index 7977eca56b4..aabb67dd4aa 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1877,6 +1877,13 @@ export interface CommentThread { isTemplate: boolean; } +/** + * @internal + */ +export interface AddedCommentThread extends CommentThread { + editorId?: string; +} + /** * @internal */ @@ -1971,7 +1978,7 @@ export interface CommentThreadChangedEvent { /** * Added comment threads. */ - readonly added: CommentThread[]; + readonly added: AddedCommentThread[]; /** * Removed comment threads. diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 9d97e583ba1..d5e11a86dc1 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -180,7 +180,8 @@ export class MainThreadCommentThread implements languages.CommentThread { public resource: string, private _range: T | undefined, private _canReply: boolean, - private _isTemplate: boolean + private _isTemplate: boolean, + public editorId?: string ) { this._isDisposed = false; if (_isTemplate) { @@ -291,7 +292,8 @@ export class MainThreadCommentController implements ICommentController { threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, - isTemplate: boolean + isTemplate: boolean, + editorId?: string ): languages.CommentThread { const thread = new MainThreadCommentThread( commentThreadHandle, @@ -301,7 +303,8 @@ export class MainThreadCommentController implements ICommentController { URI.revive(resource).toString(), range, true, - isTemplate + isTemplate, + editorId ); this._threads.set(commentThreadHandle, thread); @@ -479,8 +482,8 @@ export class MainThreadCommentController implements ICommentController { return ret; } - createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): Promise { - return this._proxy.$createCommentThreadTemplate(this.handle, resource, range); + createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined, editorId?: string): Promise { + return this._proxy.$createCommentThreadTemplate(this.handle, resource, range, editorId); } async updateCommentThreadTemplate(threadHandle: number, range: IRange) { @@ -580,7 +583,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments resource: UriComponents, range: IRange | ICellRange | undefined, extensionId: ExtensionIdentifier, - isTemplate: boolean + isTemplate: boolean, + editorId?: string ): languages.CommentThread | undefined { const provider = this._commentControllers.get(handle); @@ -588,7 +592,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments return undefined; } - return provider.createCommentThread(extensionId.value, commentThreadHandle, threadId, resource, range, isTemplate); + return provider.createCommentThread(extensionId.value, commentThreadHandle, threadId, resource, range, isTemplate, editorId); } $updateCommentThread(handle: number, diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9f2dd18fb7f..a2c75722122 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -143,7 +143,7 @@ export interface MainThreadCommentsShape extends IDisposable { $registerCommentController(handle: number, id: string, label: string, extensionId: string): void; $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; - $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, extensionId: ExtensionIdentifier, isTemplate: boolean): languages.CommentThread | undefined; + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange | ICellRange | undefined, extensionId: ExtensionIdentifier, isTemplate: boolean, editorId?: string): languages.CommentThread | undefined; $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, changes: CommentThreadChanges): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $updateCommentingRanges(handle: number, resourceHints?: languages.CommentingRangeResourceHint): void; @@ -2458,7 +2458,7 @@ export interface ExtHostProgressShape { } export interface ExtHostCommentsShape { - $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined): Promise; + $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined, editorId?: string): Promise; $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange): Promise; $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number): void; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined>; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 38678540e4a..b3f54666152 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -160,14 +160,14 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return commentController.value; } - async $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined): Promise { + async $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined, editorId?: string): Promise { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController) { return; } - commentController.$createCommentThreadTemplate(uriComponents, range); + commentController.$createCommentThreadTemplate(uriComponents, range, editorId); } async $setActiveComment(controllerHandle: number, commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number }): Promise { @@ -409,7 +409,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo private _range: vscode.Range | undefined, private _comments: vscode.Comment[], public readonly extensionDescription: IExtensionDescription, - private _isTemplate: boolean + private _isTemplate: boolean, + editorId?: string ) { this._acceptInputDisposables.value = new DisposableStore(); @@ -424,7 +425,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo this._uri, extHostTypeConverter.Range.from(this._range), extensionDescription.identifier, - this._isTemplate + this._isTemplate, + editorId ); this._localDisposables = []; @@ -680,8 +682,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo } } - $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange | undefined): ExtHostCommentThread { - const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension, true); + $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange | undefined, editorId?: string): ExtHostCommentThread { + const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension, true, editorId); commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; this._threads.set(commentThread.handle, commentThread); return commentThread; diff --git a/src/vs/workbench/contrib/comments/browser/commentReply.ts b/src/vs/workbench/contrib/comments/browser/commentReply.ts index 632eeed2e83..209b4a77ba5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentReply.ts +++ b/src/vs/workbench/contrib/comments/browser/commentReply.ts @@ -60,6 +60,7 @@ export class CommentReply extends Disposable { private _commentOptions: languages.CommentOptions | undefined, private _pendingComment: string | undefined, private _parentThread: ICommentThreadWidget, + focus: boolean, private _actionRunDelegate: (() => void) | null, @ICommentService private commentService: ICommentService, @IThemeService private themeService: IThemeService, @@ -75,10 +76,10 @@ export class CommentReply extends Disposable { this.commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService); this.commentEditorIsEmpty.set(!this._pendingComment); - this.initialize(); + this.initialize(focus); } - async initialize() { + async initialize(focus: boolean) { const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0; const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID); const params = JSON.stringify({ @@ -118,7 +119,7 @@ export class CommentReply extends Disposable { // Only add the additional step of clicking a reply button to expand the textarea when there are existing comments if (hasExistingComments) { this.createReplyButton(this.commentEditor, this.form); - } else if ((this._commentThread.comments && this._commentThread.comments.length === 0) || this._pendingComment) { + } else if (focus && ((this._commentThread.comments && this._commentThread.comments.length === 0) || this._pendingComment)) { this.expandReplyArea(); } this._error = dom.append(this.form, dom.$('.validation-error.hidden')); @@ -140,12 +141,13 @@ export class CommentReply extends Disposable { public updateCommentThread(commentThread: languages.CommentThread) { const isReplying = this.commentEditor.hasTextFocus(); + const oldAndNewBothEmpty = !this._commentThread.comments?.length && !commentThread.comments?.length; if (!this._reviewThreadReplyButton) { this.createReplyButton(this.commentEditor, this.form); } - if (this._commentThread.comments && this._commentThread.comments.length === 0) { + if (this._commentThread.comments && this._commentThread.comments.length === 0 && !oldAndNewBothEmpty) { this.expandReplyArea(); } diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 5771a5d8a59..1d20812ebe5 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -63,7 +63,7 @@ export interface ICommentController { options?: CommentOptions; contextValue?: string; owner: string; - createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): Promise; + createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined, editorId?: string): Promise; updateCommentThreadTemplate(threadHandle: number, range: IRange): Promise; deleteCommentThreadMain(commentThreadId: string): void; toggleReaction(uri: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise; @@ -97,7 +97,7 @@ export interface ICommentService { registerCommentController(uniqueOwner: string, commentControl: ICommentController): void; unregisterCommentController(uniqueOwner?: string): void; getCommentController(uniqueOwner: string): ICommentController | undefined; - createCommentThreadTemplate(uniqueOwner: string, resource: URI, range: Range | undefined): Promise; + createCommentThreadTemplate(uniqueOwner: string, resource: URI, range: Range | undefined, editorId?: string): Promise; updateCommentThreadTemplate(uniqueOwner: string, threadHandle: number, range: Range): Promise; getCommentMenus(uniqueOwner: string): CommentMenus; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; @@ -361,14 +361,14 @@ export class CommentService extends Disposable implements ICommentService { return this._commentControls.get(uniqueOwner); } - async createCommentThreadTemplate(uniqueOwner: string, resource: URI, range: Range | undefined): Promise { + async createCommentThreadTemplate(uniqueOwner: string, resource: URI, range: Range | undefined, editorId?: string): Promise { const commentController = this._commentControls.get(uniqueOwner); if (!commentController) { return; } - return commentController.createCommentThreadTemplate(resource, range); + return commentController.createCommentThreadTemplate(resource, range, editorId); } async updateCommentThreadTemplate(uniqueOwner: string, threadHandle: number, range: Range) { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 43b9b5d3ff4..3c61b8bc26f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -230,7 +230,7 @@ export class CommentThreadWidget extends } } - async display(lineHeight: number) { + async display(lineHeight: number, focus: boolean) { const headHeight = Math.max(23, Math.ceil(lineHeight * 1.2)); // 23 is the value of `Math.ceil(lineHeight * 1.2)` with the default editor font size this._header.updateHeight(headHeight); @@ -238,7 +238,7 @@ export class CommentThreadWidget extends // create comment thread only when it supports reply if (this._commentThread.canReply) { - this._createCommentForm(); + this._createCommentForm(focus); } this._createAdditionalActions(); @@ -272,7 +272,7 @@ export class CommentThreadWidget extends this._commentReply.updateCanReply(); } else { if (this._commentThread.canReply) { - this._createCommentForm(); + this._createCommentForm(false); } } })); @@ -286,7 +286,7 @@ export class CommentThreadWidget extends })); } - private _createCommentForm() { + private _createCommentForm(focus: boolean) { this._commentReply = this._scopedInstantiationService.createInstance( CommentReply, this._owner, @@ -299,6 +299,7 @@ export class CommentThreadWidget extends this._commentOptions, this._pendingComment, this, + focus, this._containerDelegate.actionRunner ); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts index 6ce5527186e..7a6edd60c6d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadZoneWidget.ts @@ -356,13 +356,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThreadWidget.layout(widthInPixel); } - async display(range: IRange | undefined) { + async display(range: IRange | undefined, shouldReveal: boolean) { if (range) { this._commentGlyph = new CommentGlyphWidget(this.editor, range?.endLineNumber ?? -1); this._commentGlyph.setThreadState(this._commentThread.state); } - await this._commentThreadWidget.display(this.editor.getOption(EditorOption.lineHeight)); + await this._commentThreadWidget.display(this.editor.getOption(EditorOption.lineHeight), shouldReveal); this._disposables.add(this._commentThreadWidget.onDidResize(dimension => { this._refresh(dimension); })); @@ -371,7 +371,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } // If this is a new comment thread awaiting user input then we need to reveal it. - if (this._commentThread.canReply && this._commentThread.isTemplate && (!this._commentThread.comments || (this._commentThread.comments.length === 0))) { + if (shouldReveal) { this.reveal(); } @@ -463,10 +463,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._viewZone.afterLineNumber = currentPosition.lineNumber; } - if (!this._commentThread.comments || !this._commentThread.comments.length) { - this._commentThreadWidget.focusCommentEditor(); - } - const capture = StableEditorScrollState.capture(this.editor); this._relayout(computedLinesNumber); capture.restore(this.editor); diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index 5a847e234c7..5ba6ca59e04 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -838,6 +838,40 @@ export class CommentController implements IEditorContribution { } } + private async handleCommentAdded(editorId: string | undefined, uniqueOwner: string, thread: languages.AddedCommentThread): Promise { + const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === uniqueOwner && zoneWidget.commentThread.threadId === thread.threadId); + if (matchedZones.length) { + return; + } + + const matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === uniqueOwner && zoneWidget.commentThread.commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); + + if (matchedNewCommentThreadZones.length) { + matchedNewCommentThreadZones[0].update(thread); + return; + } + + const continueOnCommentIndex = this._inProcessContinueOnComments.get(uniqueOwner)?.findIndex(pending => { + if (pending.range === undefined) { + return thread.range === undefined; + } else { + return Range.lift(pending.range).equalsRange(thread.range); + } + }); + let continueOnCommentText: string | undefined; + if ((continueOnCommentIndex !== undefined) && continueOnCommentIndex >= 0) { + continueOnCommentText = this._inProcessContinueOnComments.get(uniqueOwner)?.splice(continueOnCommentIndex, 1)[0].body; + } + + const pendingCommentText = (this._pendingNewCommentCache[uniqueOwner] && this._pendingNewCommentCache[uniqueOwner][thread.threadId]) + ?? continueOnCommentText; + const pendingEdits = this._pendingEditsCache[uniqueOwner] && this._pendingEditsCache[uniqueOwner][thread.threadId]; + const shouldReveal = thread.canReply && thread.isTemplate && (!thread.comments || (thread.comments.length === 0)) && (!thread.editorId || (thread.editorId === editorId)); + await this.displayCommentThread(uniqueOwner, thread, shouldReveal, pendingCommentText, pendingEdits); + this._commentInfos.filter(info => info.uniqueOwner === uniqueOwner)[0].threads.push(thread); + this.tryUpdateReservedSpace(); + } + public onModelChanged(): void { this.localToDispose.clear(); this.tryUpdateReservedSpace(); @@ -903,45 +937,17 @@ export class CommentController implements IEditorContribution { } }); - changed.forEach(thread => { + for (const thread of changed) { const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === e.uniqueOwner && zoneWidget.commentThread.threadId === thread.threadId); if (matchedZones.length) { const matchedZone = matchedZones[0]; matchedZone.update(thread); this.openCommentsView(thread); } - }); + } + const editorId = this.editor?.getId(); for (const thread of added) { - const matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === e.uniqueOwner && zoneWidget.commentThread.threadId === thread.threadId); - if (matchedZones.length) { - return; - } - - const matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.uniqueOwner === e.uniqueOwner && zoneWidget.commentThread.commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); - - if (matchedNewCommentThreadZones.length) { - matchedNewCommentThreadZones[0].update(thread); - return; - } - - const continueOnCommentIndex = this._inProcessContinueOnComments.get(e.uniqueOwner)?.findIndex(pending => { - if (pending.range === undefined) { - return thread.range === undefined; - } else { - return Range.lift(pending.range).equalsRange(thread.range); - } - }); - let continueOnCommentText: string | undefined; - if ((continueOnCommentIndex !== undefined) && continueOnCommentIndex >= 0) { - continueOnCommentText = this._inProcessContinueOnComments.get(e.uniqueOwner)?.splice(continueOnCommentIndex, 1)[0].body; - } - - const pendingCommentText = (this._pendingNewCommentCache[e.uniqueOwner] && this._pendingNewCommentCache[e.uniqueOwner][thread.threadId]) - ?? continueOnCommentText; - const pendingEdits = this._pendingEditsCache[e.uniqueOwner] && this._pendingEditsCache[e.uniqueOwner][thread.threadId]; - await this.displayCommentThread(e.uniqueOwner, thread, pendingCommentText, pendingEdits); - this._commentInfos.filter(info => info.uniqueOwner === e.uniqueOwner)[0].threads.push(thread); - this.tryUpdateReservedSpace(); + await this.handleCommentAdded(editorId, e.uniqueOwner, thread); } for (const thread of pending) { @@ -1020,7 +1026,7 @@ export class CommentController implements IEditorContribution { return undefined; } - private async displayCommentThread(uniqueOwner: string, thread: languages.CommentThread, pendingComment: string | undefined, pendingEdits: { [key: number]: string } | undefined): Promise { + private async displayCommentThread(uniqueOwner: string, thread: languages.CommentThread, shouldReveal: boolean, pendingComment: string | undefined, pendingEdits: { [key: number]: string } | undefined): Promise { const editor = this.editor?.getModel(); if (!editor) { return; @@ -1034,7 +1040,7 @@ export class CommentController implements IEditorContribution { continueOnCommentReply = this.commentService.removeContinueOnComment({ uniqueOwner, uri: editor.uri, range: thread.range, isReply: true }); } const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, uniqueOwner, thread, pendingComment ?? continueOnCommentReply?.body, pendingEdits); - await zoneWidget.display(thread.range); + await zoneWidget.display(thread.range, shouldReveal); this._commentWidgets.push(zoneWidget); this.openCommentsView(thread); } @@ -1202,7 +1208,7 @@ export class CommentController implements IEditorContribution { if (!this.editor) { return; } - this.commentService.createCommentThreadTemplate(ownerId, this.editor.getModel()!.uri, range); + this.commentService.createCommentThreadTemplate(ownerId, this.editor.getModel()!.uri, range, this.editor.getId()); this.processNextThreadToAdd(); return; } @@ -1325,7 +1331,7 @@ export class CommentController implements IEditorContribution { pendingEdits = providerEditsCacheStore[thread.threadId]; } - await this.displayCommentThread(info.uniqueOwner, thread, pendingComment, pendingEdits); + await this.displayCommentThread(info.uniqueOwner, thread, false, pendingComment, pendingEdits); } for (const thread of info.pendingCommentThreads ?? []) { this.resumePendingComment(this.editor!.getModel()!.uri, thread); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts index 86682ca341d..b8ccbf07abf 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellComments.ts @@ -84,7 +84,7 @@ export class CellComments extends CellContentPart { const layoutInfo = this.notebookEditor.getLayoutInfo(); - await this._commentThreadWidget.display(layoutInfo.fontInfo.lineHeight); + await this._commentThreadWidget.display(layoutInfo.fontInfo.lineHeight, true); this._applyTheme(); this.commentTheadDisposables.add(this._commentThreadWidget.onDidResize(() => {