diff --git a/src/vs/editor/common/languages.ts b/src/vs/editor/common/languages.ts index f1560d6f8a7..48b53b97b16 100644 --- a/src/vs/editor/common/languages.ts +++ b/src/vs/editor/common/languages.ts @@ -1621,6 +1621,7 @@ export interface CommentThread { export interface CommentingRanges { readonly resource: URI; ranges: IRange[]; + fileComments: boolean; } /** diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index e754d811964..0ecd0d8a195 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -381,7 +381,8 @@ export class MainThreadCommentController implements ICommentController { threads: [], commentingRanges: { resource: resource, - ranges: [] + ranges: [], + fileComments: false } }; } @@ -402,7 +403,8 @@ export class MainThreadCommentController implements ICommentController { threads: ret, commentingRanges: { resource: resource, - ranges: commentingRanges || [] + ranges: commentingRanges?.ranges || [], + fileComments: commentingRanges?.fileComments } }; } @@ -431,11 +433,6 @@ export class MainThreadCommentController implements ICommentController { }; } - async getCommentingRanges(resource: URI, token: CancellationToken): Promise { - const commentingRanges = await this._proxy.$provideCommentingRanges(this.handle, resource, token); - return commentingRanges || []; - } - async toggleReaction(uri: URI, thread: languages.CommentThread, comment: languages.Comment, reaction: languages.CommentReaction, token: CancellationToken): Promise { return this._proxy.$toggleReaction(this._handle, thread.commentThreadHandle, uri, comment, reaction); } @@ -449,7 +446,7 @@ export class MainThreadCommentController implements ICommentController { return ret; } - createCommentThreadTemplate(resource: UriComponents, range: IRange): void { + createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): void { this._proxy.$createCommentThreadTemplate(this.handle, resource, range); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 4b21c314904..bae88633ffb 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -2071,10 +2071,10 @@ export interface ExtHostProgressShape { } export interface ExtHostCommentsShape { - $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange): void; + $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined): void; $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange): Promise; $deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number): void; - $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; + $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined>; $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: languages.Comment, reaction: languages.CommentReaction): Promise; } diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 495c22963fd..ffa1ff11e42 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -20,6 +20,7 @@ import type * as vscode from 'vscode'; import { ExtHostCommentsShape, IMainContext, MainContext, CommentThreadChanges, CommentChanges } from './extHost.protocol'; import { ExtHostCommands } from './extHostCommands'; import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { withNullAsUndefined } from 'vs/base/common/types'; type ProviderHandle = number; @@ -158,7 +159,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return commentController.value; } - $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange): void { + $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined): void { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController) { @@ -184,7 +185,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo commentController?.$deleteCommentThread(commentThreadHandle); } - $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise { + $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined> { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController || !commentController.commentingRangeProvider) { @@ -192,9 +193,33 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo } const document = documents.getDocument(URI.revive(uriComponents)); - return asPromise(() => { - return commentController.commentingRangeProvider!.provideCommentingRanges(document, token); - }).then(ranges => ranges ? ranges.map(x => extHostTypeConverter.Range.from(x)) : undefined); + return asPromise(async () => { + const rangesResult = await (commentController.commentingRangeProvider as vscode.CommentingRangeProvider2).provideCommentingRanges(document, token); + let ranges: { ranges: vscode.Range[]; fileComments: boolean } | undefined; + if (Array.isArray(rangesResult)) { + ranges = { + ranges: rangesResult, + fileComments: false + }; + } else if (rangesResult) { + ranges = { + ranges: rangesResult.ranges || [], + fileComments: rangesResult.fileComments || false + }; + } else { + ranges = withNullAsUndefined(rangesResult); + } + return ranges; + }).then(ranges => { + let convertedResult: { ranges: IRange[]; fileComments: boolean } | undefined = undefined; + if (ranges) { + convertedResult = { + ranges: ranges.ranges.map(x => extHostTypeConverter.Range.from(x)), + fileComments: ranges.fileComments + }; + } + return convertedResult; + }); } $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: languages.Comment, reaction: languages.CommentReaction): Promise { @@ -606,7 +631,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo return commentThread; } - $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange): ExtHostCommentThread { + $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); commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded; this._threads.set(commentThread.handle, commentThread); diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 36edd189e91..5dc5ddf30e6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -61,13 +61,12 @@ export interface ICommentController { }; options?: CommentOptions; contextValue?: string; - createCommentThreadTemplate(resource: UriComponents, range: IRange): void; + createCommentThreadTemplate(resource: UriComponents, range: IRange | undefined): void; updateCommentThreadTemplate(threadHandle: number, range: IRange): Promise; deleteCommentThreadMain(commentThreadId: string): void; toggleReaction(uri: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise; getDocumentComments(resource: URI, token: CancellationToken): Promise; getNotebookComments(resource: URI, token: CancellationToken): Promise; - getCommentingRanges(resource: URI, token: CancellationToken): Promise; } export interface ICommentService { @@ -90,7 +89,7 @@ export interface ICommentService { registerCommentController(owner: string, commentControl: ICommentController): void; unregisterCommentController(owner: string): void; getCommentController(owner: string): ICommentController | undefined; - createCommentThreadTemplate(owner: string, resource: URI, range: Range): void; + createCommentThreadTemplate(owner: string, resource: URI, range: Range | undefined): void; updateCommentThreadTemplate(owner: string, threadHandle: number, range: Range): Promise; getCommentMenus(owner: string): CommentMenus; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; @@ -99,7 +98,6 @@ export interface ICommentService { getDocumentComments(resource: URI): Promise<(ICommentInfo | null)[]>; getNotebookComments(resource: URI): Promise<(INotebookCommentInfo | null)[]>; updateCommentingRanges(ownerId: string): void; - getCommentingRanges(resource: URI): Promise; hasReactionHandler(owner: string): boolean; toggleReaction(owner: string, resource: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction): Promise; setActiveCommentThread(commentThread: CommentThread | null): void; @@ -250,7 +248,7 @@ export class CommentService extends Disposable implements ICommentService { return this._commentControls.get(owner); } - createCommentThreadTemplate(owner: string, resource: URI, range: Range): void { + createCommentThreadTemplate(owner: string, resource: URI, range: Range | undefined): void { const commentController = this._commentControls.get(owner); if (!commentController) { @@ -345,15 +343,4 @@ export class CommentService extends Disposable implements ICommentService { return Promise.all(commentControlResult); } - - async getCommentingRanges(resource: URI): Promise { - const commentControlResult: Promise[] = []; - - this._commentControls.forEach(control => { - commentControlResult.push(control.getCommentingRanges(resource, CancellationToken.None)); - }); - - const ret = await Promise.all(commentControlResult); - return ret.reduce((prev, curr) => { prev.push(...curr); return prev; }, []); - } } diff --git a/src/vs/workbench/contrib/comments/browser/commentsController.ts b/src/vs/workbench/contrib/comments/browser/commentsController.ts index d7e975b3042..75481165d7f 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsController.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsController.ts @@ -240,7 +240,22 @@ class CommentingRangeDecorator { } } - public getMatchedCommentAction(commentRange: Range): CommentRangeAction[] { + public getMatchedCommentAction(commentRange: Range | undefined): CommentRangeAction[] { + if (commentRange === undefined) { + const foundInfos = this._infos?.filter(info => info.commentingRanges.fileComments); + if (foundInfos) { + return foundInfos.map(foundInfo => { + return { + ownerId: foundInfo.owner, + extensionId: foundInfo.extensionId, + label: foundInfo.label, + commentingRangesInfo: foundInfo.commentingRanges + }; + }); + } + return []; + } + // keys is ownerId const foundHoverActions = new Map(); for (const decoration of this.commentingRangeDecorations) { @@ -293,7 +308,7 @@ export class CommentController implements IEditorContribution { private _commentingRangeSpaceReserved = false; private _computePromise: CancelablePromise> | null; private _addInProgress!: boolean; - private _emptyThreadsToAddQueue: [Range, IEditorMouseEvent | undefined][] = []; + private _emptyThreadsToAddQueue: [Range | undefined, IEditorMouseEvent | undefined][] = []; private _computeCommentingRangePromise!: CancelablePromise | null; private _computeCommentingRangeScheduler!: Delayer> | null; private _pendingNewCommentCache: { [key: string]: { [key: string]: string } }; @@ -764,13 +779,13 @@ export class CommentController implements IEditorContribution { } } - public async addOrToggleCommentAtLine(commentRange: Range, e: IEditorMouseEvent | undefined): Promise { + public async addOrToggleCommentAtLine(commentRange: Range | undefined, e: IEditorMouseEvent | undefined): Promise { // If an add is already in progress, queue the next add and process it after the current one finishes to // prevent empty comment threads from being added to the same line. if (!this._addInProgress) { this._addInProgress = true; // The widget's position is undefined until the widget has been displayed, so rely on the glyph position instead - const existingCommentsAtLine = this._commentWidgets.filter(widget => widget.getGlyphPosition() === commentRange.endLineNumber); + const existingCommentsAtLine = this._commentWidgets.filter(widget => widget.getGlyphPosition() === (commentRange ? commentRange.endLineNumber : 0)); if (existingCommentsAtLine.length) { const allExpanded = existingCommentsAtLine.every(widget => widget.expanded); existingCommentsAtLine.forEach(allExpanded ? widget => widget.collapse() : widget => widget.expand()); @@ -792,14 +807,14 @@ export class CommentController implements IEditorContribution { } } - public addCommentAtLine(range: Range, e: IEditorMouseEvent | undefined): Promise { + public addCommentAtLine(range: Range | undefined, e: IEditorMouseEvent | undefined): Promise { const newCommentInfos = this._commentingRangeDecorator.getMatchedCommentAction(range); if (!newCommentInfos.length || !this.editor?.hasModel()) { return Promise.resolve(); } if (newCommentInfos.length > 1) { - if (e) { + if (e && range) { const anchor = { x: e.event.posx, y: e.event.posy }; this.contextMenuService.showContextMenu({ @@ -868,7 +883,7 @@ export class CommentController implements IEditorContribution { return actions; } - public addCommentAtLine2(range: Range, ownerId: string) { + public addCommentAtLine2(range: Range | undefined, ownerId: string) { if (!this.editor) { return; } diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 5da02b74bb5..6d01db98cb4 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -18,6 +18,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ActiveCursorHasCommentingRange, CommentController, ID } from 'vs/workbench/contrib/comments/browser/commentsController'; +import { IRange, Range } from 'vs/editor/common/core/range'; export class NextCommentThreadAction extends EditorAction { constructor() { @@ -88,7 +89,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { const ADD_COMMENT_COMMAND = 'workbench.action.addComment'; CommandsRegistry.registerCommand({ id: ADD_COMMENT_COMMAND, - handler: (accessor) => { + handler: (accessor, args: { range: IRange; fileComment: boolean }) => { const activeEditor = getActiveEditor(accessor); if (!activeEditor) { return Promise.resolve(); @@ -99,7 +100,8 @@ CommandsRegistry.registerCommand({ return Promise.resolve(); } - const position = activeEditor.getSelection(); + const position = args.range ? new Range(args.range.startLineNumber, args.range.startLineNumber, args.range.endLineNumber, args.range.endColumn) + : (args.fileComment ? undefined : activeEditor.getSelection()); return controller.addOrToggleCommentAtLine(position, undefined); } }); diff --git a/src/vscode-dts/vscode.proposed.fileComments.d.ts b/src/vscode-dts/vscode.proposed.fileComments.d.ts index 7484e8c1a64..09c729145f2 100644 --- a/src/vscode-dts/vscode.proposed.fileComments.d.ts +++ b/src/vscode-dts/vscode.proposed.fileComments.d.ts @@ -75,4 +75,11 @@ declare module 'vscode' { export interface CommentController { createCommentThread(uri: Uri, range: Range | undefined, comments: readonly Comment[]): CommentThread | CommentThread2; } + + export interface CommentingRangeProvider2 { + /** + * Provide a list of ranges which allow new comment threads creation or null for a given document + */ + provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; + } }