Path: blob/main/src/vs/workbench/api/common/extHostComments.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { asPromise } from '../../../base/common/async.js';6import { CancellationToken } from '../../../base/common/cancellation.js';7import { debounce } from '../../../base/common/decorators.js';8import { Emitter } from '../../../base/common/event.js';9import { DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js';10import { MarshalledId } from '../../../base/common/marshallingIds.js';11import { URI, UriComponents } from '../../../base/common/uri.js';12import { IRange } from '../../../editor/common/core/range.js';13import * as languages from '../../../editor/common/languages.js';14import { ExtensionIdentifierMap, IExtensionDescription } from '../../../platform/extensions/common/extensions.js';15import { ExtHostDocuments } from './extHostDocuments.js';16import * as extHostTypeConverter from './extHostTypeConverters.js';17import * as types from './extHostTypes.js';18import type * as vscode from 'vscode';19import { ExtHostCommentsShape, IMainContext, MainContext, CommentThreadChanges, CommentChanges } from './extHost.protocol.js';20import { ExtHostCommands } from './extHostCommands.js';21import { checkProposedApiEnabled } from '../../services/extensions/common/extensions.js';22import { MarshalledCommentThread } from '../../common/comments.js';2324type ProviderHandle = number;2526interface ExtHostComments {27createCommentController(extension: IExtensionDescription, id: string, label: string): vscode.CommentController;28}2930export function createExtHostComments(mainContext: IMainContext, commands: ExtHostCommands, documents: ExtHostDocuments): ExtHostCommentsShape & ExtHostComments {31const proxy = mainContext.getProxy(MainContext.MainThreadComments);3233class ExtHostCommentsImpl implements ExtHostCommentsShape, ExtHostComments {3435private static handlePool = 0;363738private _commentControllers: Map<ProviderHandle, ExtHostCommentController> = new Map<ProviderHandle, ExtHostCommentController>();3940private _commentControllersByExtension: ExtensionIdentifierMap<ExtHostCommentController[]> = new ExtensionIdentifierMap<ExtHostCommentController[]>();414243constructor(44) {45commands.registerArgumentProcessor({46processArgument: arg => {47if (arg && arg.$mid === MarshalledId.CommentController) {48const commentController = this._commentControllers.get(arg.handle);4950if (!commentController) {51return arg;52}5354return commentController.value;55} else if (arg && arg.$mid === MarshalledId.CommentThread) {56const marshalledCommentThread: MarshalledCommentThread = arg;57const commentController = this._commentControllers.get(marshalledCommentThread.commentControlHandle);5859if (!commentController) {60return marshalledCommentThread;61}6263const commentThread = commentController.getCommentThread(marshalledCommentThread.commentThreadHandle);6465if (!commentThread) {66return marshalledCommentThread;67}6869return commentThread.value;70} else if (arg && (arg.$mid === MarshalledId.CommentThreadReply || arg.$mid === MarshalledId.CommentThreadInstance)) {71const commentController = this._commentControllers.get(arg.thread.commentControlHandle);7273if (!commentController) {74return arg;75}7677const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle);7879if (!commentThread) {80return arg;81}8283if (arg.$mid === MarshalledId.CommentThreadInstance) {84return commentThread.value;85}8687return {88thread: commentThread.value,89text: arg.text90};91} else if (arg && arg.$mid === MarshalledId.CommentNode) {92const commentController = this._commentControllers.get(arg.thread.commentControlHandle);9394if (!commentController) {95return arg;96}9798const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle);99100if (!commentThread) {101return arg;102}103104const commentUniqueId = arg.commentUniqueId;105106const comment = commentThread.getCommentByUniqueId(commentUniqueId);107108if (!comment) {109return arg;110}111112return comment;113114} else if (arg && arg.$mid === MarshalledId.CommentThreadNode) {115const commentController = this._commentControllers.get(arg.thread.commentControlHandle);116117if (!commentController) {118return arg;119}120121const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle);122123if (!commentThread) {124return arg;125}126127const body: string = arg.text;128const commentUniqueId = arg.commentUniqueId;129130const comment = commentThread.getCommentByUniqueId(commentUniqueId);131132if (!comment) {133return arg;134}135136// If the old comment body was a markdown string, use a markdown string here too.137if (typeof comment.body === 'string') {138comment.body = body;139} else {140comment.body = new types.MarkdownString(body);141}142return comment;143}144145return arg;146}147});148}149150createCommentController(extension: IExtensionDescription, id: string, label: string): vscode.CommentController {151const handle = ExtHostCommentsImpl.handlePool++;152const commentController = new ExtHostCommentController(extension, handle, id, label);153this._commentControllers.set(commentController.handle, commentController);154155const commentControllers = this._commentControllersByExtension.get(extension.identifier) || [];156commentControllers.push(commentController);157this._commentControllersByExtension.set(extension.identifier, commentControllers);158159return commentController.value;160}161162async $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange | undefined, editorId?: string): Promise<void> {163const commentController = this._commentControllers.get(commentControllerHandle);164165if (!commentController) {166return;167}168169commentController.$createCommentThreadTemplate(uriComponents, range, editorId);170}171172async $setActiveComment(controllerHandle: number, commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number }): Promise<void> {173const commentController = this._commentControllers.get(controllerHandle);174175if (!commentController) {176return;177}178179commentController.$setActiveComment(commentInfo ?? undefined);180}181182async $updateCommentThreadTemplate(commentControllerHandle: number, threadHandle: number, range: IRange) {183const commentController = this._commentControllers.get(commentControllerHandle);184185if (!commentController) {186return;187}188189commentController.$updateCommentThreadTemplate(threadHandle, range);190}191192$deleteCommentThread(commentControllerHandle: number, commentThreadHandle: number) {193const commentController = this._commentControllers.get(commentControllerHandle);194195commentController?.$deleteCommentThread(commentThreadHandle);196}197198async $updateCommentThread(commentControllerHandle: number, commentThreadHandle: number, changes: CommentThreadChanges) {199const commentController = this._commentControllers.get(commentControllerHandle);200201commentController?.$updateCommentThread(commentThreadHandle, changes);202}203204async $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise<{ ranges: IRange[]; fileComments: boolean } | undefined> {205const commentController = this._commentControllers.get(commentControllerHandle);206207if (!commentController || !commentController.commentingRangeProvider) {208return Promise.resolve(undefined);209}210211const document = await documents.ensureDocumentData(URI.revive(uriComponents));212return asPromise(async () => {213const rangesResult = await commentController.commentingRangeProvider?.provideCommentingRanges(document.document, token);214let ranges: { ranges: vscode.Range[]; fileComments: boolean } | undefined;215if (Array.isArray(rangesResult)) {216ranges = {217ranges: rangesResult,218fileComments: false219};220} else if (rangesResult) {221ranges = {222ranges: rangesResult.ranges || [],223fileComments: rangesResult.enableFileComments || false224};225} else {226ranges = rangesResult ?? undefined;227}228return ranges;229}).then(ranges => {230let convertedResult: { ranges: IRange[]; fileComments: boolean } | undefined = undefined;231if (ranges) {232convertedResult = {233ranges: ranges.ranges.map(x => extHostTypeConverter.Range.from(x)),234fileComments: ranges.fileComments235};236}237return convertedResult;238});239}240241$toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: languages.Comment, reaction: languages.CommentReaction): Promise<void> {242const commentController = this._commentControllers.get(commentControllerHandle);243244if (!commentController || !commentController.reactionHandler) {245return Promise.resolve(undefined);246}247248return asPromise(() => {249const commentThread = commentController.getCommentThread(threadHandle);250if (commentThread) {251const vscodeComment = commentThread.getCommentByUniqueId(comment.uniqueIdInThread);252253if (commentController !== undefined && vscodeComment) {254if (commentController.reactionHandler) {255return commentController.reactionHandler(vscodeComment, convertFromReaction(reaction));256}257}258}259260return Promise.resolve(undefined);261});262}263}264type CommentThreadModification = Partial<{265range: vscode.Range;266label: string | undefined;267contextValue: string | undefined;268comments: vscode.Comment[];269collapsibleState: vscode.CommentThreadCollapsibleState;270canReply: boolean | vscode.CommentAuthorInformation;271state: vscode.CommentThreadState;272isTemplate: boolean;273applicability: vscode.CommentThreadApplicability;274}>;275276class ExtHostCommentThread implements vscode.CommentThread2 {277private static _handlePool: number = 0;278readonly handle = ExtHostCommentThread._handlePool++;279public commentHandle: number = 0;280281private modifications: CommentThreadModification = Object.create(null);282283set threadId(id: string) {284this._id = id;285}286287get threadId(): string {288return this._id!;289}290291get id(): string {292return this._id!;293}294295get resource(): vscode.Uri {296return this._uri;297}298299get uri(): vscode.Uri {300return this._uri;301}302303private readonly _onDidUpdateCommentThread = new Emitter<void>();304readonly onDidUpdateCommentThread = this._onDidUpdateCommentThread.event;305306set range(range: vscode.Range | undefined) {307if (((range === undefined) !== (this._range === undefined)) || (!range || !this._range || !range.isEqual(this._range))) {308this._range = range;309this.modifications.range = range;310this._onDidUpdateCommentThread.fire();311}312}313314get range(): vscode.Range | undefined {315return this._range;316}317318private _canReply: boolean | vscode.CommentAuthorInformation = true;319320set canReply(state: boolean | vscode.CommentAuthorInformation) {321if (this._canReply !== state) {322this._canReply = state;323this.modifications.canReply = state;324this._onDidUpdateCommentThread.fire();325}326}327get canReply() {328return this._canReply;329}330331private _label: string | undefined;332333get label(): string | undefined {334return this._label;335}336337set label(label: string | undefined) {338this._label = label;339this.modifications.label = label;340this._onDidUpdateCommentThread.fire();341}342343private _contextValue: string | undefined;344345get contextValue(): string | undefined {346return this._contextValue;347}348349set contextValue(context: string | undefined) {350this._contextValue = context;351this.modifications.contextValue = context;352this._onDidUpdateCommentThread.fire();353}354355get comments(): vscode.Comment[] {356return this._comments;357}358359set comments(newComments: vscode.Comment[]) {360this._comments = newComments;361this.modifications.comments = newComments;362this._onDidUpdateCommentThread.fire();363}364365private _collapseState?: vscode.CommentThreadCollapsibleState;366367get collapsibleState(): vscode.CommentThreadCollapsibleState {368return this._collapseState!;369}370371set collapsibleState(newState: vscode.CommentThreadCollapsibleState) {372if (this._collapseState === newState) {373return;374}375this._collapseState = newState;376this.modifications.collapsibleState = newState;377this._onDidUpdateCommentThread.fire();378}379380private _state?: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability };381382get state(): vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined {383return this._state!;384}385386set state(newState: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability }) {387this._state = newState;388if (typeof newState === 'object') {389checkProposedApiEnabled(this.extensionDescription, 'commentThreadApplicability');390this.modifications.state = newState.resolved;391this.modifications.applicability = newState.applicability;392} else {393this.modifications.state = newState;394}395this._onDidUpdateCommentThread.fire();396}397398private _localDisposables: types.Disposable[];399400private _isDiposed: boolean;401402public get isDisposed(): boolean {403return this._isDiposed;404}405406private _commentsMap: Map<vscode.Comment, number> = new Map<vscode.Comment, number>();407408private readonly _acceptInputDisposables = new MutableDisposable<DisposableStore>();409410readonly value: vscode.CommentThread2;411412constructor(413commentControllerId: string,414private _commentControllerHandle: number,415private _id: string | undefined,416private _uri: vscode.Uri,417private _range: vscode.Range | undefined,418private _comments: vscode.Comment[],419public readonly extensionDescription: IExtensionDescription,420private _isTemplate: boolean,421editorId?: string422) {423this._acceptInputDisposables.value = new DisposableStore();424425if (this._id === undefined) {426this._id = `${commentControllerId}.${this.handle}`;427}428429proxy.$createCommentThread(430_commentControllerHandle,431this.handle,432this._id,433this._uri,434extHostTypeConverter.Range.from(this._range),435this._comments.map(cmt => convertToDTOComment(this, cmt, this._commentsMap, this.extensionDescription)),436extensionDescription.identifier,437this._isTemplate,438editorId439);440441this._localDisposables = [];442this._isDiposed = false;443444this._localDisposables.push(this.onDidUpdateCommentThread(() => {445this.eventuallyUpdateCommentThread();446}));447448this._localDisposables.push({449dispose: () => {450proxy.$deleteCommentThread(451_commentControllerHandle,452this.handle453);454}455});456457const that = this;458this.value = {459get uri() { return that.uri; },460get range() { return that.range; },461set range(value: vscode.Range | undefined) { that.range = value; },462get comments() { return that.comments; },463set comments(value: vscode.Comment[]) { that.comments = value; },464get collapsibleState() { return that.collapsibleState; },465set collapsibleState(value: vscode.CommentThreadCollapsibleState) { that.collapsibleState = value; },466get canReply() { return that.canReply; },467set canReply(state: boolean | vscode.CommentAuthorInformation) { that.canReply = state; },468get contextValue() { return that.contextValue; },469set contextValue(value: string | undefined) { that.contextValue = value; },470get label() { return that.label; },471set label(value: string | undefined) { that.label = value; },472get state(): vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined { return that.state; },473set state(value: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability }) { that.state = value; },474reveal: (comment?: vscode.Comment | vscode.CommentThreadRevealOptions, options?: vscode.CommentThreadRevealOptions) => that.reveal(comment, options),475hide: () => that.hide(),476dispose: () => {477that.dispose();478}479};480}481482private updateIsTemplate() {483if (this._isTemplate) {484this._isTemplate = false;485this.modifications.isTemplate = false;486}487}488489@debounce(100)490eventuallyUpdateCommentThread(): void {491if (this._isDiposed) {492return;493}494this.updateIsTemplate();495496if (!this._acceptInputDisposables.value) {497this._acceptInputDisposables.value = new DisposableStore();498}499500const modified = (value: keyof CommentThreadModification): boolean =>501Object.prototype.hasOwnProperty.call(this.modifications, value);502503const formattedModifications: CommentThreadChanges = {};504if (modified('range')) {505formattedModifications.range = extHostTypeConverter.Range.from(this._range);506}507if (modified('label')) {508formattedModifications.label = this.label;509}510if (modified('contextValue')) {511/*512* null -> cleared contextValue513* undefined -> no change514*/515formattedModifications.contextValue = this.contextValue ?? null;516}517if (modified('comments')) {518formattedModifications.comments =519this._comments.map(cmt => convertToDTOComment(this, cmt, this._commentsMap, this.extensionDescription));520}521if (modified('collapsibleState')) {522formattedModifications.collapseState = convertToCollapsibleState(this._collapseState);523}524if (modified('canReply')) {525formattedModifications.canReply = this.canReply;526}527if (modified('state')) {528formattedModifications.state = convertToState(this._state);529}530if (modified('applicability')) {531formattedModifications.applicability = convertToRelevance(this._state);532}533if (modified('isTemplate')) {534formattedModifications.isTemplate = this._isTemplate;535}536this.modifications = {};537538proxy.$updateCommentThread(539this._commentControllerHandle,540this.handle,541this._id!,542this._uri,543formattedModifications544);545}546547getCommentByUniqueId(uniqueId: number): vscode.Comment | undefined {548for (const key of this._commentsMap) {549const comment = key[0];550const id = key[1];551if (uniqueId === id) {552return comment;553}554}555556return;557}558559async reveal(commentOrOptions?: vscode.Comment | vscode.CommentThreadRevealOptions, options?: vscode.CommentThreadRevealOptions): Promise<void> {560checkProposedApiEnabled(this.extensionDescription, 'commentReveal');561let comment: vscode.Comment | undefined;562if (commentOrOptions && (commentOrOptions as vscode.Comment).body !== undefined) {563comment = commentOrOptions as vscode.Comment;564} else {565options = options ?? commentOrOptions as vscode.CommentThreadRevealOptions;566}567let commentToReveal = comment ? this._commentsMap.get(comment) : undefined;568commentToReveal ??= this._commentsMap.get(this._comments[0])!;569let preserveFocus: boolean = true;570let focusReply: boolean = false;571if (options?.focus === types.CommentThreadFocus.Reply) {572focusReply = true;573preserveFocus = false;574} else if (options?.focus === types.CommentThreadFocus.Comment) {575preserveFocus = false;576}577return proxy.$revealCommentThread(this._commentControllerHandle, this.handle, commentToReveal, { preserveFocus, focusReply });578}579580async hide(): Promise<void> {581return proxy.$hideCommentThread(this._commentControllerHandle, this.handle);582}583584dispose() {585this._isDiposed = true;586this._acceptInputDisposables.dispose();587this._localDisposables.forEach(disposable => disposable.dispose());588}589}590591type ReactionHandler = (comment: vscode.Comment, reaction: vscode.CommentReaction) => Promise<void>;592593class ExtHostCommentController {594get id(): string {595return this._id;596}597598get label(): string {599return this._label;600}601602public get handle(): number {603return this._handle;604}605606private _threads: Map<number, ExtHostCommentThread> = new Map<number, ExtHostCommentThread>();607608private _commentingRangeProvider?: vscode.CommentingRangeProvider;609get commentingRangeProvider(): vscode.CommentingRangeProvider | undefined {610return this._commentingRangeProvider;611}612613set commentingRangeProvider(provider: vscode.CommentingRangeProvider | undefined) {614this._commentingRangeProvider = provider;615if (provider?.resourceHints) {616checkProposedApiEnabled(this._extension, 'commentingRangeHint');617}618proxy.$updateCommentingRanges(this.handle, provider?.resourceHints);619}620621private _reactionHandler?: ReactionHandler;622623get reactionHandler(): ReactionHandler | undefined {624return this._reactionHandler;625}626627set reactionHandler(handler: ReactionHandler | undefined) {628this._reactionHandler = handler;629630proxy.$updateCommentControllerFeatures(this.handle, { reactionHandler: !!handler });631}632633private _options: languages.CommentOptions | undefined;634635get options() {636return this._options;637}638639set options(options: languages.CommentOptions | undefined) {640this._options = options;641642proxy.$updateCommentControllerFeatures(this.handle, { options: this._options });643}644645private _activeComment: vscode.Comment | undefined;646647get activeComment(): vscode.Comment | undefined {648checkProposedApiEnabled(this._extension, 'activeComment');649return this._activeComment;650}651652private _activeThread: ExtHostCommentThread | undefined;653654get activeCommentThread(): vscode.CommentThread2 | undefined {655checkProposedApiEnabled(this._extension, 'activeComment');656return this._activeThread?.value;657}658659private _localDisposables: types.Disposable[];660readonly value: vscode.CommentController;661662constructor(663private _extension: IExtensionDescription,664private _handle: number,665private _id: string,666private _label: string667) {668proxy.$registerCommentController(this.handle, _id, _label, this._extension.identifier.value);669670const that = this;671this.value = Object.freeze({672id: that.id,673label: that.label,674get options() { return that.options; },675set options(options: vscode.CommentOptions | undefined) { that.options = options; },676get commentingRangeProvider(): vscode.CommentingRangeProvider | undefined { return that.commentingRangeProvider; },677set commentingRangeProvider(commentingRangeProvider: vscode.CommentingRangeProvider | undefined) { that.commentingRangeProvider = commentingRangeProvider; },678get reactionHandler(): ReactionHandler | undefined { return that.reactionHandler; },679set reactionHandler(handler: ReactionHandler | undefined) { that.reactionHandler = handler; },680// get activeComment(): vscode.Comment | undefined { return that.activeComment; },681get activeCommentThread(): vscode.CommentThread2 | undefined { return that.activeCommentThread; },682createCommentThread(uri: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): vscode.CommentThread | vscode.CommentThread2 {683return that.createCommentThread(uri, range, comments).value;684},685dispose: () => { that.dispose(); },686}) as any; // TODO @alexr00 remove this cast when the proposed API is stable687688this._localDisposables = [];689this._localDisposables.push({690dispose: () => {691proxy.$unregisterCommentController(this.handle);692}693});694}695696createCommentThread(resource: vscode.Uri, range: vscode.Range | undefined, comments: vscode.Comment[]): ExtHostCommentThread {697const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, resource, range, comments, this._extension, false);698this._threads.set(commentThread.handle, commentThread);699return commentThread;700}701702$setActiveComment(commentInfo: { commentThreadHandle: number; uniqueIdInThread?: number } | undefined) {703if (!commentInfo) {704this._activeComment = undefined;705this._activeThread = undefined;706return;707}708const thread = this._threads.get(commentInfo.commentThreadHandle);709if (thread) {710this._activeComment = commentInfo.uniqueIdInThread ? thread.getCommentByUniqueId(commentInfo.uniqueIdInThread) : undefined;711this._activeThread = thread;712}713}714715$createCommentThreadTemplate(uriComponents: UriComponents, range: IRange | undefined, editorId?: string): ExtHostCommentThread {716const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension, true, editorId);717commentThread.collapsibleState = languages.CommentThreadCollapsibleState.Expanded;718this._threads.set(commentThread.handle, commentThread);719return commentThread;720}721722$updateCommentThreadTemplate(threadHandle: number, range: IRange): void {723const thread = this._threads.get(threadHandle);724if (thread) {725thread.range = extHostTypeConverter.Range.to(range);726}727}728729$updateCommentThread(threadHandle: number, changes: CommentThreadChanges): void {730const thread = this._threads.get(threadHandle);731if (!thread) {732return;733}734735const modified = (value: keyof CommentThreadChanges): boolean =>736Object.prototype.hasOwnProperty.call(changes, value);737738if (modified('collapseState')) {739thread.collapsibleState = convertToCollapsibleState(changes.collapseState);740}741}742743$deleteCommentThread(threadHandle: number): void {744const thread = this._threads.get(threadHandle);745746thread?.dispose();747748this._threads.delete(threadHandle);749}750751getCommentThread(handle: number): ExtHostCommentThread | undefined {752return this._threads.get(handle);753}754755dispose(): void {756this._threads.forEach(value => {757value.dispose();758});759760this._localDisposables.forEach(disposable => disposable.dispose());761}762}763764function convertToDTOComment(thread: ExtHostCommentThread, vscodeComment: vscode.Comment, commentsMap: Map<vscode.Comment, number>, extension: IExtensionDescription): CommentChanges {765let commentUniqueId = commentsMap.get(vscodeComment)!;766if (!commentUniqueId) {767commentUniqueId = ++thread.commentHandle;768commentsMap.set(vscodeComment, commentUniqueId);769}770771if (vscodeComment.state !== undefined) {772checkProposedApiEnabled(extension, 'commentsDraftState');773}774775if (vscodeComment.reactions?.some(reaction => reaction.reactors !== undefined)) {776checkProposedApiEnabled(extension, 'commentReactor');777}778779return {780mode: vscodeComment.mode,781contextValue: vscodeComment.contextValue,782uniqueIdInThread: commentUniqueId,783body: (typeof vscodeComment.body === 'string') ? vscodeComment.body : extHostTypeConverter.MarkdownString.from(vscodeComment.body),784userName: vscodeComment.author.name,785userIconPath: vscodeComment.author.iconPath,786label: vscodeComment.label,787commentReactions: vscodeComment.reactions ? vscodeComment.reactions.map(reaction => convertToReaction(reaction)) : undefined,788state: vscodeComment.state,789timestamp: vscodeComment.timestamp?.toJSON()790};791}792793function convertToReaction(reaction: vscode.CommentReaction): languages.CommentReaction {794return {795label: reaction.label,796iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined,797count: reaction.count,798hasReacted: reaction.authorHasReacted,799reactors: ((reaction.reactors && (reaction.reactors.length > 0) && (typeof reaction.reactors[0] !== 'string')) ? (reaction.reactors as languages.CommentAuthorInformation[]).map(reactor => reactor.name) : reaction.reactors) as string[]800};801}802803function convertFromReaction(reaction: languages.CommentReaction): vscode.CommentReaction {804return {805label: reaction.label || '',806count: reaction.count || 0,807iconPath: reaction.iconPath ? URI.revive(reaction.iconPath) : '',808authorHasReacted: reaction.hasReacted || false,809reactors: reaction.reactors?.map(reactor => ({ name: reactor }))810};811}812813function convertToCollapsibleState(kind: vscode.CommentThreadCollapsibleState | undefined): languages.CommentThreadCollapsibleState {814if (kind !== undefined) {815switch (kind) {816case types.CommentThreadCollapsibleState.Expanded:817return languages.CommentThreadCollapsibleState.Expanded;818case types.CommentThreadCollapsibleState.Collapsed:819return languages.CommentThreadCollapsibleState.Collapsed;820}821}822return languages.CommentThreadCollapsibleState.Collapsed;823}824825function convertToState(kind: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined): languages.CommentThreadState {826let resolvedKind: vscode.CommentThreadState | undefined;827if (typeof kind === 'object') {828resolvedKind = kind.resolved;829} else {830resolvedKind = kind;831}832833if (resolvedKind !== undefined) {834switch (resolvedKind) {835case types.CommentThreadState.Unresolved:836return languages.CommentThreadState.Unresolved;837case types.CommentThreadState.Resolved:838return languages.CommentThreadState.Resolved;839}840}841return languages.CommentThreadState.Unresolved;842}843844function convertToRelevance(kind: vscode.CommentThreadState | { resolved?: vscode.CommentThreadState; applicability?: vscode.CommentThreadApplicability } | undefined): languages.CommentThreadApplicability {845let applicabilityKind: vscode.CommentThreadApplicability | undefined = undefined;846if (typeof kind === 'object') {847applicabilityKind = kind.applicability;848}849850if (applicabilityKind !== undefined) {851switch (applicabilityKind) {852case types.CommentThreadApplicability.Current:853return languages.CommentThreadApplicability.Current;854case types.CommentThreadApplicability.Outdated:855return languages.CommentThreadApplicability.Outdated;856}857}858return languages.CommentThreadApplicability.Current;859}860861return new ExtHostCommentsImpl();862}863864865