API to find the active comment thread (#204486)

* API to find the active comment thread
Fixes #204484

* Add issue ref

* Add activeComment proposal to api tests

* Add settimeout to blur event
This commit is contained in:
Alex Ross 2024-02-07 08:59:24 +01:00 committed by GitHub
parent 9db1b0571e
commit f8546bc73f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 129 additions and 24 deletions

View file

@ -5,6 +5,7 @@
"publisher": "vscode",
"license": "MIT",
"enabledApiProposals": [
"activeComment",
"authSession",
"chatAgents2",
"defaultChatAgent",

View file

@ -242,7 +242,7 @@ export class MainThreadCommentController implements ICommentController {
}
private readonly _threads: Map<number, MainThreadCommentThread<IRange | ICellRange>> = new Map<number, MainThreadCommentThread<IRange | ICellRange>>();
public activeCommentThread?: MainThreadCommentThread<IRange | ICellRange>;
public activeEditingCommentThread?: MainThreadCommentThread<IRange | ICellRange>;
get features(): CommentProviderFeatures {
return this._features;
@ -258,6 +258,10 @@ export class MainThreadCommentController implements ICommentController {
private _features: CommentProviderFeatures
) { }
async setActiveCommentAndThread(commentInfo: { thread: languages.CommentThread; comment?: languages.Comment } | undefined) {
return this._proxy.$setActiveComment(this._handle, commentInfo ? { commentThreadHandle: commentInfo.thread.commentThreadHandle, uniqueIdInThread: commentInfo.comment?.uniqueIdInThread } : undefined);
}
updateFeatures(features: CommentProviderFeatures) {
this._features = features;
}
@ -357,7 +361,7 @@ export class MainThreadCommentController implements ICommentController {
}
updateInput(input: string) {
const thread = this.activeCommentThread;
const thread = this.activeEditingCommentThread;
if (thread && thread.input) {
const commentInput = thread.input;
@ -477,8 +481,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments
private _handlers = new Map<number, string>();
private _commentControllers = new Map<number, MainThreadCommentController>();
private _activeCommentThread?: MainThreadCommentThread<IRange | ICellRange>;
private readonly _activeCommentThreadDisposables = this._register(new DisposableStore());
private _activeEditingCommentThread?: MainThreadCommentThread<IRange | ICellRange>;
private readonly _activeEditingCommentThreadDisposables = this._register(new DisposableStore());
private _openViewListener: IDisposable | null = null;
@ -493,7 +497,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments);
this._commentService.unregisterCommentController();
this._register(this._commentService.onDidChangeActiveCommentThread(async thread => {
this._register(this._commentService.onDidChangeActiveEditingCommentThread(async thread => {
const handle = (thread as MainThreadCommentThread<IRange | ICellRange>).controllerHandle;
const controller = this._commentControllers.get(handle);
@ -501,9 +505,9 @@ export class MainThreadComments extends Disposable implements MainThreadComments
return;
}
this._activeCommentThreadDisposables.clear();
this._activeCommentThread = thread as MainThreadCommentThread<IRange | ICellRange>;
controller.activeCommentThread = this._activeCommentThread;
this._activeEditingCommentThreadDisposables.clear();
this._activeEditingCommentThread = thread as MainThreadCommentThread<IRange | ICellRange>;
controller.activeEditingCommentThread = this._activeEditingCommentThread;
}));
}

View file

@ -2403,6 +2403,7 @@ export interface ExtHostCommentsShape {
$deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number): void;
$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<void>;
$setActiveComment(controllerHandle: number, commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number } | undefined): Promise<void>;
}
export interface INotebookSelectionChangeEvent {

View file

@ -168,6 +168,16 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
commentController.$createCommentThreadTemplate(uriComponents, range);
}
async $setActiveComment(controllerHandle: number, commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number }): Promise<void> {
const commentController = this._commentControllers.get(controllerHandle);
if (!commentController) {
return;
}
commentController.$setActiveComment(commentInfo ?? undefined);
}
async $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange) {
const commentController = this._commentControllers.get(commentControllerHandle);
@ -582,6 +592,19 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
proxy.$updateCommentControllerFeatures(this.handle, { options: this._options });
}
private _activeComment: vscode.Comment | undefined;
get activeComment(): vscode.Comment | undefined {
checkProposedApiEnabled(this._extension, 'activeComment');
return this._activeComment;
}
private _activeThread: vscode.CommentThread2 | undefined;
get activeThread(): vscode.CommentThread2 | undefined {
checkProposedApiEnabled(this._extension, 'activeComment');
return this._activeThread;
}
private _localDisposables: types.Disposable[];
readonly value: vscode.CommentController;
@ -604,6 +627,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
set commentingRangeProvider(commentingRangeProvider: vscode.CommentingRangeProvider | undefined) { that.commentingRangeProvider = commentingRangeProvider; },
get reactionHandler(): ReactionHandler | undefined { return that.reactionHandler; },
set reactionHandler(handler: ReactionHandler | undefined) { that.reactionHandler = handler; },
// get activeComment(): vscode.Comment | undefined { return that.activeComment; },
get activeThread(): vscode.CommentThread2 | undefined { return that.activeThread; },
createCommentThread(uri: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): vscode.CommentThread | vscode.CommentThread2 {
return that.createCommentThread(uri, range, comments).value;
},
@ -627,6 +652,19 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
return commentThread;
}
$setActiveComment(commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number } | undefined) {
if (!commentInfo) {
this._activeComment = undefined;
this._activeThread = undefined;
return;
}
const thread = this._threads.get(commentInfo.commentThreadHandle);
if (thread) {
this._activeComment = commentInfo.uniqueIdInThread ? thread.getCommentByUniqueId(commentInfo.uniqueIdInThread) : undefined;
this._activeThread = thread;
}
}
$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;

View file

@ -166,6 +166,17 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
this._register(this.accessibilityService.onDidChangeScreenReaderOptimized(() => {
this.toggleToolbarHidden(true);
}));
this.activeCommentListeners();
}
private activeCommentListeners() {
this._register(dom.addDisposableListener(this._domNode, dom.EventType.FOCUS_IN, () => {
this.commentService.setActiveCommentAndThread(this.owner, { thread: this.commentThread, comment: this.comment });
}, true));
this._register(dom.addDisposableListener(this._domNode, dom.EventType.FOCUS_OUT, () => {
this.commentService.setActiveCommentAndThread(this.owner, undefined);
}, true));
}
private createScroll(container: HTMLElement, body: HTMLElement) {
@ -502,14 +513,16 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
uri: this._commentEditor.getModel()!.uri,
value: this.commentBodyValue
};
this.commentService.setActiveCommentThread(commentThread);
this.commentService.setActiveEditingCommentThread(commentThread);
this.commentService.setActiveCommentAndThread(this.owner, { thread: commentThread, comment: this.comment });
this._commentEditorDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => {
commentThread.input = {
uri: this._commentEditor!.getModel()!.uri,
value: this.commentBodyValue
};
this.commentService.setActiveCommentThread(commentThread);
this.commentService.setActiveEditingCommentThread(commentThread);
this.commentService.setActiveCommentAndThread(this.owner, { thread: commentThread, comment: this.comment });
}));
this._commentEditorDisposables.push(this._commentEditor.onDidChangeModelContent(e => {
@ -519,7 +532,8 @@ export class CommentNode<T extends IRange | ICellRange> extends Disposable {
const input = commentThread.input;
input.value = newVal;
commentThread.input = input;
this.commentService.setActiveCommentThread(commentThread);
this.commentService.setActiveEditingCommentThread(commentThread);
this.commentService.setActiveCommentAndThread(this.owner, { thread: commentThread, comment: this.comment });
}
}
}));

View file

@ -233,11 +233,20 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
private createTextModelListener(commentEditor: ICodeEditor, commentForm: HTMLElement) {
this._commentThreadDisposables.push(commentEditor.onDidFocusEditorWidget(() => {
this._commentThread.input = {
uri: commentEditor.getModel()!.uri,
value: commentEditor.getValue()
};
this.commentService.setActiveCommentThread(this._commentThread);
// Add a setTimeout so that the blur event doesn't fire before the focus event
// https://github.com/microsoft/vscode/blob/f6d945edbdc1b2e8a176624fdf612bb61468944f/src/vs/base/browser/dom.ts#L1322-L1328
setTimeout(() => {
this._commentThread.input = {
uri: commentEditor.getModel()!.uri,
value: commentEditor.getValue()
};
this.commentService.setActiveEditingCommentThread(this._commentThread);
this.commentService.setActiveCommentAndThread(this.owner, { thread: this._commentThread });
}, 0);
}));
this._commentThreadDisposables.push(commentEditor.onDidBlurEditorWidget(() => {
this.commentService.setActiveCommentAndThread(this.owner, undefined);
}));
this._commentThreadDisposables.push(commentEditor.getModel()!.onDidChangeContent(() => {
@ -247,7 +256,7 @@ export class CommentReply<T extends IRange | ICellRange> extends Disposable {
newInput.value = modelContent;
this._commentThread.input = newInput;
}
this.commentService.setActiveCommentThread(this._commentThread);
this.commentService.setActiveEditingCommentThread(this._commentThread);
}));
this._commentThreadDisposables.push(this._commentThread.onDidChangeInput(input => {

View file

@ -67,6 +67,7 @@ export interface ICommentController {
toggleReaction(uri: URI, thread: CommentThread, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise<void>;
getDocumentComments(resource: URI, token: CancellationToken): Promise<ICommentInfo>;
getNotebookComments(resource: URI, token: CancellationToken): Promise<INotebookCommentInfo>;
setActiveCommentAndThread(commentInfo: { thread: CommentThread; comment?: Comment } | undefined): Promise<void>;
}
export interface IContinueOnCommentProvider {
@ -79,7 +80,7 @@ export interface ICommentService {
readonly onDidSetAllCommentThreads: Event<IWorkspaceCommentThreadsEvent>;
readonly onDidUpdateCommentThreads: Event<ICommentThreadChangedEvent>;
readonly onDidUpdateNotebookCommentThreads: Event<INotebookCommentThreadChangedEvent>;
readonly onDidChangeActiveCommentThread: Event<CommentThread | null>;
readonly onDidChangeActiveEditingCommentThread: Event<CommentThread | null>;
readonly onDidChangeCurrentCommentThread: Event<CommentThread | undefined>;
readonly onDidUpdateCommentingRanges: Event<{ owner: string }>;
readonly onDidChangeActiveCommentingRange: Event<{ range: Range; commentingRangesInfo: CommentingRanges }>;
@ -105,8 +106,9 @@ export interface ICommentService {
updateCommentingRanges(ownerId: string): void;
hasReactionHandler(owner: string): boolean;
toggleReaction(owner: string, resource: URI, thread: CommentThread<IRange | ICellRange>, comment: Comment, reaction: CommentReaction): Promise<void>;
setActiveCommentThread(commentThread: CommentThread<IRange | ICellRange> | null): void;
setActiveEditingCommentThread(commentThread: CommentThread<IRange | ICellRange> | null): void;
setCurrentCommentThread(commentThread: CommentThread<IRange | ICellRange> | undefined): void;
setActiveCommentAndThread(owner: string, commentInfo: { thread: CommentThread<IRange | ICellRange>; comment?: Comment } | undefined): Promise<void>;
enableCommenting(enable: boolean): void;
registerContinueOnCommentProvider(provider: IContinueOnCommentProvider): IDisposable;
removeContinueOnComment(pendingComment: { range: IRange | undefined; uri: URI; owner: string; isReply?: boolean }): PendingCommentThread | undefined;
@ -138,8 +140,8 @@ export class CommentService extends Disposable implements ICommentService {
private readonly _onDidUpdateCommentingRanges: Emitter<{ owner: string }> = this._register(new Emitter<{ owner: string }>());
readonly onDidUpdateCommentingRanges: Event<{ owner: string }> = this._onDidUpdateCommentingRanges.event;
private readonly _onDidChangeActiveCommentThread = this._register(new Emitter<CommentThread | null>());
readonly onDidChangeActiveCommentThread = this._onDidChangeActiveCommentThread.event;
private readonly _onDidChangeActiveEditingCommentThread = this._register(new Emitter<CommentThread | null>());
readonly onDidChangeActiveEditingCommentThread = this._onDidChangeActiveEditingCommentThread.event;
private readonly _onDidChangeCurrentCommentThread = this._register(new Emitter<CommentThread | undefined>());
readonly onDidChangeCurrentCommentThread = this._onDidChangeCurrentCommentThread.event;
@ -266,8 +268,18 @@ export class CommentService extends Disposable implements ICommentService {
* The active comment thread is the the thread that is currently being edited.
* @param commentThread
*/
setActiveCommentThread(commentThread: CommentThread | null) {
this._onDidChangeActiveCommentThread.fire(commentThread);
setActiveEditingCommentThread(commentThread: CommentThread | null) {
this._onDidChangeActiveEditingCommentThread.fire(commentThread);
}
async setActiveCommentAndThread(owner: string, commentInfo: { thread: CommentThread<IRange>; comment?: Comment } | undefined) {
const commentController = this._commentControls.get(owner);
if (!commentController) {
return;
}
return commentController.setActiveCommentAndThread(commentInfo);
}
setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void {

View file

@ -60,7 +60,7 @@ export class CommentThreadBody<T extends IRange | ICellRange = IRange> extends D
this._register(dom.addDisposableListener(container, dom.EventType.FOCUS_IN, e => {
// TODO @rebornix, limit T to IRange | ICellRange
this.commentService.setActiveCommentThread(this._commentThread);
this.commentService.setActiveEditingCommentThread(this._commentThread);
}));
this._markdownRenderer = this._register(new MarkdownRenderer(this._options, this.languageService, this.openerService));

View file

@ -68,6 +68,9 @@ class TestCommentController implements ICommentController {
getNotebookComments(resource: URI, token: CancellationToken): Promise<INotebookCommentInfo> {
throw new Error('Method not implemented.');
}
setActiveCommentAndThread(commentInfo: { thread: CommentThread; comment: Comment } | undefined): Promise<void> {
throw new Error('Method not implemented.');
}
}

View file

@ -6,6 +6,7 @@
// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.
export const allApiProposals = Object.freeze({
activeComment: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.activeComment.d.ts',
aiRelatedInformation: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.aiRelatedInformation.d.ts',
authGetSessions: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authGetSessions.d.ts',
authSession: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.authSession.d.ts',

View file

@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'vscode' {
// @alexr00 https://github.com/microsoft/vscode/issues/204484
export interface CommentController {
/**
* The currently active comment or `undefined`. The active comment is the one
* that currently has focus or, when none has focus, undefined.
*/
// readonly activeComment: Comment | undefined;
/**
* The currently active comment thread or `undefined`. The active comment thread is the one
* that currently has focus or, when none has focus, undefined.
*/
readonly activeThread: CommentThread | undefined;
}
}