Conversation
622cd57 to
a311283
Compare
Co-authored-by: RMacfarlane <ramacfar@microsoft.com>
Co-authored-by: RMacfarlane <ramacfar@microsoft.com>
Co-authored-by: RMacfarlane <ramacfar@microsoft.com>
Co-authored-by: RMacfarlane <ramacfar@microsoft.com>
smashwilson
left a comment
There was a problem hiding this comment.
I've looked over the code and didn't find anything showstopping - a few places that could be tidied up but nothing urgent. I'll take it for a spin locally tomorrow and leave a 👍 if it works properly 😄
| "tslint-webpack-plugin": "^1.2.2", | ||
| "tty": "1.0.1", | ||
| "typescript": "^3", | ||
| "typescript": "^3.4.5", |
There was a problem hiding this comment.
👍 Probably a good idea.
| handler.startReview(thread); | ||
| let handler = resolveCommentHandler(reply.thread); | ||
|
|
||
| if (handler) { |
There was a problem hiding this comment.
Should we fail more noisily when the comment handler isn't found; emitting a logger method with the thread info, say, rather than silently doing nothing?
I get the sense that this is one of those "this shouldn't happen" guards. I always prefer to know when those assumptions fail or change, though - it would give us a little more to go on than a user reporting "this command does nothing".
There was a problem hiding this comment.
Yeah, adding a log is a good idea. Added it to the resolveCommentHandler method itself
src/commands.ts
Outdated
|
|
||
| return cmt; | ||
| }); | ||
| } |
There was a problem hiding this comment.
instanceof checks are a design smell to me. Usually they hint to me that what you really want to do is add common behavior to the types you're checking against. As a first pass I'd do something like this:
class GHPRComment {
editGHPRComment(id: string) {
if (this.commentId === id) {
this.mode = vscode.CommentMode.Editing;
}
}
editTemporaryComment() {}
setParentEditMode() {
for (const parentComment of this.parent.comments) {
parentComment.editGHPRComment(this.commentId);
}
}
}
class TemporaryComment {
editGHPRComment() {}
editTemporaryComment(id: string) {
if (this.id === id) {
this.mode = vscode.CommentMode.Editing;
}
}
setParentEditMode() {
for (const parentComment of this.parent.comments) {
parentComment.editTemporaryComment(this.id);
}
}
}Then these if statements can be replaced with:
context.subscriptions.push(vscode.commands.registerCommand('pr.editComment', async (comment: GHPRComment | TemporaryComment) => {
comment.setParentEditMode();
}There was a problem hiding this comment.
IMO having an editTemporaryComment method on a different class that does nothing is a bit harder to glance at and understand than the instanceof check, so I didn't adopt this whole proposal. But I did add startEdit and cancelEdit methods to both comment types in the spirit of having common behavior in class methods
src/commands.ts
Outdated
| } | ||
|
|
||
| if (comment instanceof TemporaryComment) { | ||
| comment.parent.comments = comment.parent.comments.map(cmt => { |
There was a problem hiding this comment.
Is there a reason this reassignment back to comment.parent.comments is done? It looks like the map function body is doing all of the actual work.
There was a problem hiding this comment.
yeah, mutating the comments property on the thread causes the UI to update, only updated the property on the comment does not. Should have called that out in the description, we didn't want to create VS Code listeners for every single property change
src/commands.ts
Outdated
| context.subscriptions.push(vscode.commands.registerCommand('pr.cancelEditComment', async (comment: GHPRComment | TemporaryComment) => { | ||
| telemetry.on('pr.cancelEditComment'); | ||
|
|
||
| if (comment instanceof GHPRComment) { |
There was a problem hiding this comment.
A similar pattern could be used here.
| public author: vscode.CommentAuthorInformation; | ||
|
|
||
| /** | ||
| * The label to display on the comment, 'Pending' or nothing |
There was a problem hiding this comment.
This will also be useful for marking comments as "first-time contributor", "owner", and so on (author associations).
src/github/prComment.ts
Outdated
| constructor(comment: IComment, parent: GHPRCommentThread) { | ||
| this._rawComment = comment; | ||
| this.commentId = comment.id.toString(); | ||
| this.body = new vscode.MarkdownString(`hello [create pull request](command:pr.create)`); // comment.body); |
There was a problem hiding this comment.
Is this hardcoded body value meant to be here?
There was a problem hiding this comment.
good catch! nope :)
| if (patchedComment._rawComment.canEdit) { | ||
| patchedComment.editCommand = getEditCommand(thread, vscodeComment, node); | ||
| if (comment instanceof GHPRComment) { | ||
| comment._rawComment.isDraft = false; |
There was a problem hiding this comment.
This could be another method defined on GHPRComment and TemporaryComment ☝️
src/view/reviewCommentController.ts
Outdated
| this.commentController!.inputBox!.value = ''; | ||
| public async finishReview(thread: GHPRCommentThread, input: string): Promise<void> { | ||
| try { | ||
| this.createOrReplyComment(thread, input); |
There was a problem hiding this comment.
Should createOrReplyComment() be awaited here?
There was a problem hiding this comment.
yeah, you're right
| // _workspaceFileChangeCommentThreads | ||
| for (let fileName in this._workspaceFileChangeCommentThreads) { | ||
| this.updateFileChangeCommentThreads(localFileChanges, fileName, inDraftMode); | ||
| this.updateFileChangeCommentThreads(localFileChanges, fileName); |
There was a problem hiding this comment.
This method is async - should this be awaited too? It might not matter in this case I suppose.
|
A few glitches that I've noticed taking this for a spin this morning:
The duplicated comment persisted until I checked out another PR and came back.
(I couldn't find a stack trace.)
|
|
@smashwilson Thanks for testing! I'm having trouble reproducing these. Do you remember if you saw these when the PR was checked out? And what type of file was open? (i.e. was the file opened from the "GitHub Pull Requests" tree, the "Changes in Pull Request" tree, or from the file explorer/file search) |
|
Hmmm... I wonder if these are race conditions, then. It's possible that they were pre-existing problems as well - I've definitely seen the "duplicated comment" case crop up elsewhere 🤔
Yes, the PR was checked out.
It was opened from the "Changes in Pull Request" tree. |


History
A longer explanation is here: microsoft/vscode#68020 (comment)
The first iteration of the comments API was provider-based, which is a common pattern used by many VS Code APIs. In a provider based model, the extension is responsible for providing the data, and the rendering of the data is controlled by VS Code. The comments provider was essentially a class the extension implemented that had methods for returning comments for a specific document, adding, editing, and deleting comments, an event to fire for updates to comments, and methods for handling reviews.
The pros of this API were that it created consistent commenting experiences across extensions since all of the rendering was done by VS Code, and it was very explicit about how it should be used. The drawback to this API was that it was difficult to extend in a generic way. When we added the ability to create an entire review instead of leaving individual comments, we had to add explicit
startDraft,deleteDraft, andfinishDraftmethods to the comment provider. The major driver for changing the comment API was to allow extensions more control over what actions a comment provider could take.The new API
In full, the new API looks like:
Now, instead of a provider, the extension creates an instance of a comment controller that can be used to display comments on a document. The comment controller does not have any other methods, instead, all other comment reactions are now specified through contribution points in the package.json. (https://code.visualstudio.com/api/references/contribution-points#contributes.menus) For a particular place in the UI, the extension registers a command it would like to be displayed with some icon or title.

For comments, commands can be added to the comment thread titlebar (top circle), comment titlebar (middle circle), the comment thread context (bottom circle), or comment context (buttons for a particular comment's text area, for example the command for finishing an edit). Since these are all commands, the extension can control their visibility with "when" clauses and their enablement with "enablement" properties. When a command is triggered by the user, it gets called with an argument containing the current thread and any text in the comment editor.
What's going on in this PR
The bulk of the changes are in the
pullRequestNode.tsandreviewDocumentCommentProvider.ts, nowreviewCommentController.tsfiles. We have previously already moved away from the provider to the controller, since then the major change was to have commands contributed as part of the package.json instead of on the controller.