Skip to content

Commit b978d71

Browse files
authored
Add timestamps to comments proposal (microsoft#139849)
1 parent 1ab07c5 commit b978d71

17 files changed

+276
-21
lines changed

.eslintrc.json

+14-4
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,9 @@
568568
"vscode-oniguruma",
569569
"iconv-lite-umd",
570570
"tas-client-umd",
571-
"jschardet"
571+
"jschardet",
572+
"dayjs",
573+
"dayjs/plugin/*"
572574
]
573575
},
574576
{
@@ -600,6 +602,8 @@
600602
"vscode-oniguruma",
601603
"iconv-lite-umd",
602604
"jschardet",
605+
"dayjs",
606+
"dayjs/plugin/*",
603607
"@vscode/vscode-languagedetection",
604608
"@microsoft/applicationinsights-web"
605609
]
@@ -633,7 +637,9 @@
633637
"vscode-textmate",
634638
"vscode-oniguruma",
635639
"iconv-lite-umd",
636-
"jschardet"
640+
"jschardet",
641+
"dayjs",
642+
"dayjs/plugin/*"
637643
]
638644
},
639645
{
@@ -753,7 +759,9 @@
753759
"vscode-textmate",
754760
"vscode-oniguruma",
755761
"iconv-lite-umd",
756-
"jschardet"
762+
"jschardet",
763+
"dayjs",
764+
"dayjs/plugin/*"
757765
]
758766
},
759767
{
@@ -788,7 +796,9 @@
788796
"vscode-textmate",
789797
"vscode-oniguruma",
790798
"iconv-lite-umd",
791-
"jschardet"
799+
"jschardet",
800+
"dayjs",
801+
"dayjs/plugin/*"
792802
]
793803
},
794804
{

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"@vscode/sudo-prompt": "9.3.1",
6767
"@vscode/vscode-languagedetection": "1.0.21",
6868
"applicationinsights": "1.4.2",
69+
"dayjs": "^1.10.7",
6970
"graceful-fs": "4.2.8",
7071
"http-proxy-agent": "^2.1.0",
7172
"https-proxy-agent": "^2.2.3",

remote/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"@vscode/vscode-languagedetection": "1.0.21",
1010
"applicationinsights": "1.4.2",
1111
"cookie": "^0.4.0",
12+
"dayjs": "^1.10.7",
1213
"graceful-fs": "4.2.8",
1314
"http-proxy-agent": "^2.1.0",
1415
"https-proxy-agent": "^2.2.3",

remote/yarn.lock

+5
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,11 @@ data-uri-to-buffer@3:
204204
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636"
205205
integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==
206206

207+
dayjs@^1.10.7:
208+
version "1.10.7"
209+
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468"
210+
integrity sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==
211+
207212
[email protected], debug@^3.1.0:
208213
version "3.1.0"
209214
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"

src/bootstrap-window.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@
142142
'iconv-lite-umd': `${baseNodeModulesPath}/iconv-lite-umd/lib/iconv-lite-umd.js`,
143143
'jschardet': `${baseNodeModulesPath}/jschardet/dist/jschardet.min.js`,
144144
'@vscode/vscode-languagedetection': `${baseNodeModulesPath}/@vscode/vscode-languagedetection/dist/lib/index.js`,
145-
'tas-client-umd': `${baseNodeModulesPath}/tas-client-umd/lib/tas-client-umd.js`
145+
'tas-client-umd': `${baseNodeModulesPath}/tas-client-umd/lib/tas-client-umd.js`,
146+
'dayjs': `${baseNodeModulesPath}/dayjs/dayjs.min.js`,
147+
'dayjs/plugin/relativeTime': `${baseNodeModulesPath}/dayjs/plugin/relativeTime.js`,
148+
'dayjs/plugin/updateLocale': `${baseNodeModulesPath}/dayjs/plugin/updateLocale.js`,
149+
'dayjs/plugin/localizedFormat': `${baseNodeModulesPath}/dayjs/plugin/localizedFormat.js`
146150
};
147151

148152
// Allow to load built-in and other node.js modules via AMD

src/vs/base/common/marshalling.ts

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export const enum MarshalledId {
3131
TimelineActionContext,
3232
NotebookCellActionContext,
3333
TestItemContext,
34+
Date,
3435
}
3536

3637
export interface MarshalledObject {
@@ -68,6 +69,7 @@ export function revive<T = any>(obj: any, depth = 0): Revived<T> {
6869
switch ((<MarshalledObject>obj).$mid) {
6970
case MarshalledId.Uri: return <any>URI.revive(obj);
7071
case MarshalledId.Regexp: return <any>new RegExp(obj.source, obj.flags);
72+
case MarshalledId.Date: return <any>new Date(obj.source);
7173
}
7274

7375
if (

src/vs/editor/common/languages.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1716,6 +1716,7 @@ export interface Comment {
17161716
readonly commentReactions?: CommentReaction[];
17171717
readonly label?: string;
17181718
readonly mode?: CommentMode;
1719+
readonly detail?: Date | string;
17191720
}
17201721

17211722
/**

src/vs/workbench/api/browser/mainThreadComments.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ import { Registry } from 'vs/platform/registry/common/platform';
1515
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
1616
import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
1717
import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsView';
18-
import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentThreadChanges } from '../common/extHost.protocol';
18+
import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentThreadChanges, CommentChanges } from '../common/extHost.protocol';
1919
import { COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer';
2020
import { ViewContainer, IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewsService, IViewDescriptorService } from 'vs/workbench/common/views';
2121
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
2222
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
2323
import { Codicon } from 'vs/base/common/codicons';
2424
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
2525
import { localize } from 'vs/nls';
26-
import { MarshalledId } from 'vs/base/common/marshalling';
26+
import { MarshalledId, revive } from 'vs/base/common/marshalling';
2727

2828

2929
export class MainThreadCommentThread implements modes.CommentThread {
@@ -139,11 +139,27 @@ export class MainThreadCommentThread implements modes.CommentThread {
139139
if (modified('range')) { this._range = changes.range!; }
140140
if (modified('label')) { this._label = changes.label; }
141141
if (modified('contextValue')) { this._contextValue = changes.contextValue === null ? undefined : changes.contextValue; }
142-
if (modified('comments')) { this._comments = changes.comments; }
142+
if (modified('comments')) { this._comments = this.commentsFromCommentChanges(changes.comments); }
143143
if (modified('collapseState')) { this._collapsibleState = changes.collapseState; }
144144
if (modified('canReply')) { this.canReply = changes.canReply!; }
145145
}
146146

147+
private commentsFromCommentChanges(comments?: CommentChanges[]): modes.Comment[] | undefined {
148+
return comments?.map(comment => {
149+
return {
150+
body: comment.body,
151+
uniqueIdInThread: comment.uniqueIdInThread,
152+
userName: comment.userName,
153+
commentReactions: comment.commentReactions,
154+
contextValue: comment.contextValue,
155+
detail: comment.detail ? <Date | string>revive<Date | string>(comment.detail) : undefined,
156+
label: comment.label,
157+
mode: comment.mode,
158+
userIconPath: comment.userIconPath
159+
};
160+
});
161+
}
162+
147163
dispose() {
148164
this._isDisposed = true;
149165
this._onDidChangeCollasibleState.dispose();

src/vs/workbench/api/common/extHost.protocol.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { SerializedError } from 'vs/base/common/errors';
1010
import { IRelativePattern } from 'vs/base/common/glob';
1111
import { IMarkdownString } from 'vs/base/common/htmlContent';
1212
import { IDisposable } from 'vs/base/common/lifecycle';
13-
import { revive } from 'vs/base/common/marshalling';
13+
import { MarshalledId, revive } from 'vs/base/common/marshalling';
1414
import * as performance from 'vs/base/common/performance';
1515
import Severity from 'vs/base/common/severity';
1616
import { Dto } from 'vs/base/common/types';
@@ -160,11 +160,25 @@ export interface CommentProviderFeatures {
160160
options?: modes.CommentOptions;
161161
}
162162

163+
export interface CommentChanges {
164+
readonly uniqueIdInThread: number;
165+
readonly body: IMarkdownString;
166+
readonly userName: string;
167+
readonly userIconPath?: string;
168+
readonly contextValue?: string;
169+
readonly commentReactions?: modes.CommentReaction[];
170+
readonly label?: string;
171+
readonly mode?: modes.CommentMode;
172+
readonly detail?: {
173+
$mid: MarshalledId.Date
174+
} | string;
175+
}
176+
163177
export type CommentThreadChanges = Partial<{
164178
range: IRange,
165179
label: string,
166180
contextValue: string | null,
167-
comments: modes.Comment[],
181+
comments: CommentChanges[],
168182
collapseState: modes.CommentThreadCollapsibleState;
169183
canReply: boolean;
170184
}>;

src/vs/workbench/api/common/extHostComments.ts

+26-9
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
1616
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
1717
import * as extHostTypeConverter from 'vs/workbench/api/common/extHostTypeConverters';
1818
import * as types from 'vs/workbench/api/common/extHostTypes';
19+
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
1920
import type * as vscode from 'vscode';
20-
import { ExtHostCommentsShape, IMainContext, MainContext, CommentThreadChanges } from './extHost.protocol';
21+
import { ExtHostCommentsShape, IMainContext, MainContext, CommentThreadChanges, CommentChanges } from './extHost.protocol';
2122
import { ExtHostCommands } from './extHostCommands';
2223

2324
type ProviderHandle = number;
@@ -346,7 +347,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
346347
private _uri: vscode.Uri,
347348
private _range: vscode.Range,
348349
private _comments: vscode.Comment[],
349-
extensionId: ExtensionIdentifier
350+
public readonly extensionDescription: IExtensionDescription
350351
) {
351352
this._acceptInputDisposables.value = new DisposableStore();
352353

@@ -360,7 +361,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
360361
this._id,
361362
this._uri,
362363
extHostTypeConverter.Range.from(this._range),
363-
extensionId
364+
extensionDescription.identifier
364365
);
365366

366367
this._localDisposables = [];
@@ -433,7 +434,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
433434
}
434435
if (modified('comments')) {
435436
formattedModifications.comments =
436-
this._comments.map(cmt => convertToModeComment(this, cmt, this._commentsMap));
437+
this._comments.map(cmt => convertToDTOComment(this, cmt, this._commentsMap));
437438
}
438439
if (modified('collapsibleState')) {
439440
formattedModifications.collapseState = convertToCollapsibleState(this._collapseState);
@@ -561,18 +562,18 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
561562
createCommentThread(resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): ExtHostCommentThread;
562563
createCommentThread(arg0: vscode.Uri | string, arg1: vscode.Uri | vscode.Range, arg2: vscode.Range | vscode.Comment[], arg3?: vscode.Comment[]): vscode.CommentThread {
563564
if (typeof arg0 === 'string') {
564-
const commentThread = new ExtHostCommentThread(this.id, this.handle, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[], this._extension.identifier);
565+
const commentThread = new ExtHostCommentThread(this.id, this.handle, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[], this._extension);
565566
this._threads.set(commentThread.handle, commentThread);
566567
return commentThread;
567568
} else {
568-
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[], this._extension.identifier);
569+
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[], this._extension);
569570
this._threads.set(commentThread.handle, commentThread);
570571
return commentThread;
571572
}
572573
}
573574

574575
$createCommentThreadTemplate(uriComponents: UriComponents, range: IRange): ExtHostCommentThread {
575-
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension.identifier);
576+
const commentThread = new ExtHostCommentThread(this.id, this.handle, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), [], this._extension);
576577
commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded;
577578
this._threads.set(commentThread.handle, commentThread);
578579
return commentThread;
@@ -608,7 +609,7 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
608609
}
609610
}
610611

611-
function convertToModeComment(thread: ExtHostCommentThread, vscodeComment: vscode.Comment, commentsMap: Map<vscode.Comment, number>): modes.Comment {
612+
function convertToDTOComment(thread: ExtHostCommentThread, vscodeComment: vscode.Comment, commentsMap: Map<vscode.Comment, number>): CommentChanges {
612613
let commentUniqueId = commentsMap.get(vscodeComment)!;
613614
if (!commentUniqueId) {
614615
commentUniqueId = ++thread.commentHandle;
@@ -617,6 +618,20 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
617618

618619
const iconPath = vscodeComment.author && vscodeComment.author.iconPath ? vscodeComment.author.iconPath.toString() : undefined;
619620

621+
if (vscodeComment.detail) {
622+
checkProposedApiEnabled(thread.extensionDescription, 'commentTimestamp');
623+
}
624+
625+
let detail: { $mid: MarshalledId.Date, source: any } | string | undefined;
626+
if (vscodeComment.detail && (typeof vscodeComment.detail !== 'string')) {
627+
detail = {
628+
source: vscodeComment.detail,
629+
$mid: MarshalledId.Date
630+
};
631+
} else {
632+
detail = vscodeComment.detail;
633+
}
634+
620635
return {
621636
mode: vscodeComment.mode,
622637
contextValue: vscodeComment.contextValue,
@@ -625,7 +640,8 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
625640
userName: vscodeComment.author.name,
626641
userIconPath: iconPath,
627642
label: vscodeComment.label,
628-
commentReactions: vscodeComment.reactions ? vscodeComment.reactions.map(reaction => convertToReaction(reaction)) : undefined
643+
commentReactions: vscodeComment.reactions ? vscodeComment.reactions.map(reaction => convertToReaction(reaction)) : undefined,
644+
detail: detail
629645
};
630646
}
631647

@@ -661,3 +677,4 @@ export function createExtHostComments(mainContext: IMainContext, commands: ExtHo
661677

662678
return new ExtHostCommentsImpl();
663679
}
680+

src/vs/workbench/contrib/comments/browser/commentNode.ts

+38-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
3636
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
3737
import { Codicon } from 'vs/base/common/codicons';
3838
import { MarshalledId } from 'vs/base/common/marshalling';
39+
import { TimestampWidget } from 'vs/workbench/contrib/comments/browser/timestamp';
40+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
3941

4042
export class CommentNode extends Disposable {
4143
private _domNode: HTMLElement;
@@ -53,6 +55,8 @@ export class CommentNode extends Disposable {
5355
private _commentEditorDisposables: IDisposable[] = [];
5456
private _commentEditorModel: ITextModel | null = null;
5557
private _isPendingLabel!: HTMLElement;
58+
private _detail: HTMLElement | undefined;
59+
private _timestamp: TimestampWidget | undefined;
5660
private _contextKeyService: IContextKeyService;
5761
private _commentContextValue: IContextKey<string>;
5862

@@ -83,7 +87,8 @@ export class CommentNode extends Disposable {
8387
@ILanguageService private languageService: ILanguageService,
8488
@INotificationService private notificationService: INotificationService,
8589
@IContextMenuService private contextMenuService: IContextMenuService,
86-
@IContextKeyService contextKeyService: IContextKeyService
90+
@IContextKeyService contextKeyService: IContextKeyService,
91+
@IConfigurationService private configurationService: IConfigurationService
8792
) {
8893
super();
8994

@@ -121,11 +126,38 @@ export class CommentNode extends Disposable {
121126
return this._onDidClick.event;
122127
}
123128

129+
private createDetail(container: HTMLElement) {
130+
this._detail = dom.append(container, dom.$('span.detail'));
131+
this.updateDetail(this.comment.detail);
132+
}
133+
134+
private updateDetail(detail?: Date | string) {
135+
if (!this._detail) {
136+
return;
137+
}
138+
139+
if (!detail) {
140+
this._timestamp?.dispose();
141+
this._detail.innerText = '';
142+
} else if (typeof detail === 'string') {
143+
this._timestamp?.dispose();
144+
this._detail.innerText = detail;
145+
} else {
146+
this._detail.innerText = '';
147+
if (!this._timestamp) {
148+
this._timestamp = new TimestampWidget(this.configurationService, this._detail, detail);
149+
this._register(this._timestamp);
150+
} else {
151+
this._timestamp.setTimestamp(detail);
152+
}
153+
}
154+
}
155+
124156
private createHeader(commentDetailsContainer: HTMLElement): void {
125157
const header = dom.append(commentDetailsContainer, dom.$(`div.comment-title.${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME}`));
126158
const author = dom.append(header, dom.$('strong.author'));
127159
author.innerText = this.comment.userName;
128-
160+
this.createDetail(header);
129161
this._isPendingLabel = dom.append(header, dom.$('span.isPending'));
130162

131163
if (this.comment.label) {
@@ -516,6 +548,10 @@ export class CommentNode extends Disposable {
516548
} else {
517549
this._commentContextValue.reset();
518550
}
551+
552+
if (this.comment.detail) {
553+
this.updateDetail(this.comment.detail);
554+
}
519555
}
520556

521557
focus() {

src/vs/workbench/contrib/comments/browser/comments.contribution.ts

+6
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).regis
2525
default: 'openOnSessionStartWithComments',
2626
description: nls.localize('openComments', "Controls when the comments panel should open."),
2727
restricted: false
28+
},
29+
'comments.useRelativeTime': {
30+
type: 'boolean',
31+
default: true,
32+
description: nls.localize('useRelativeTime', "Determines if relative time will be used in comment timestamps (ex. '1 day ago').")
33+
2834
}
2935
}
3036
});

0 commit comments

Comments
 (0)