Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Edit/delete comments within the editor #600

Merged
merged 2 commits into from
Oct 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/common/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,8 @@ export interface Comment {
created_at: string;
updated_at: string;
html_url: string;

absolutePosition?: number;
canEdit: boolean;
canDelete: boolean;
}
8 changes: 7 additions & 1 deletion src/github/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class CredentialStore {
if (octokit) {
this._octokits.set(host, octokit);
}
this.updateAuthenticationStatusBar(remote);
await this.updateAuthenticationStatusBar(remote);
return this._octokits.has(host);
}

Expand Down Expand Up @@ -145,6 +145,11 @@ export class CredentialStore {
return octokit;
}

public isCurrentUser(username: string, remote: Remote): boolean {
const octokit = this.getOctokit(remote);
return octokit && (octokit as any).currentUser && (octokit as any).currentUser.login === username;
}

private createOctokit(type: string, creds: IHostConfiguration): Octokit {
const octokit = new Octokit({
baseUrl: `${HostHelper.getApiHost(creds).toString().slice(0, -1)}${HostHelper.getApiPath(creds, '')}`,
Expand Down Expand Up @@ -176,6 +181,7 @@ export class CredentialStore {
if (octokit) {
try {
const user = await octokit.users.get({});
(octokit as any).currentUser = user.data;
text = `$(mark-github) ${user.data.login}`;
} catch (e) {
text = '$(mark-github) Signed in';
Expand Down
2 changes: 2 additions & 0 deletions src/github/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ export interface IPullRequestManager {
createCommentReply(pullRequest: IPullRequestModel, body: string, reply_to: string): Promise<Comment>;
createComment(pullRequest: IPullRequestModel, body: string, path: string, position: number): Promise<Comment>;
mergePullRequest(pullRequest: IPullRequestModel): Promise<any>;
editComment(pullRequest: IPullRequestModel, commentId: string, text: string): Promise<Comment>;
deleteComment(pullRequest: IPullRequestModel, commentId: string): Promise<void>;
closePullRequest(pullRequest: IPullRequestModel): Promise<any>;
approvePullRequest(pullRequest: IPullRequestModel, message?: string): Promise<any>;
requestChanges(pullRequest: IPullRequestModel, message?: string): Promise<any>;
Expand Down
50 changes: 45 additions & 5 deletions src/github/pullRequestManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export class PullRequestManager implements IPullRequestManager {
number: pullRequest.prNumber,
per_page: 100
});
const rawComments = reviewData.data;
const rawComments = reviewData.data.map(comment => this.addCommentPermissions(comment, remote));
return parserCommentDiffHunk(rawComments);
}

Expand Down Expand Up @@ -316,7 +316,7 @@ export class PullRequestManager implements IPullRequestManager {
review_id: reviewId
});

const rawComments = reviewData.data;
const rawComments = reviewData.data.map(comment => this.addCommentPermissions(comment, remote));
return parserCommentDiffHunk(rawComments);
}

Expand Down Expand Up @@ -356,7 +356,7 @@ export class PullRequestManager implements IPullRequestManager {
repo: remote.repositoryName
});

return promise.data;
return this.addCommentPermissions(promise.data, remote);
}

async createCommentReply(pullRequest: IPullRequestModel, body: string, reply_to: string): Promise<Comment> {
Expand All @@ -371,7 +371,7 @@ export class PullRequestManager implements IPullRequestManager {
in_reply_to: Number(reply_to)
});

return ret.data;
return this.addCommentPermissions(ret.data, remote);
} catch (e) {
this.handleError(e);
}
Expand All @@ -391,12 +391,52 @@ export class PullRequestManager implements IPullRequestManager {
position: position
});

return ret.data;
return this.addCommentPermissions(ret.data, remote);
} catch (e) {
this.handleError(e);
}
}

async editComment(pullRequest: IPullRequestModel, commentId: string, text: string): Promise<Comment> {
try {
const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure();

const ret = await octokit.pullRequests.editComment({
owner: remote.owner,
repo: remote.repositoryName,
body: text,
comment_id: commentId
});

return this.addCommentPermissions(ret.data, remote);
} catch (e) {
throw new Error(formatError(e));
}
}

async deleteComment(pullRequest: IPullRequestModel, commentId: string): Promise<void> {
try {
const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure();

await octokit.pullRequests.deleteComment({
owner: remote.owner,
repo: remote.repositoryName,
comment_id: commentId
});
} catch (e) {
throw new Error(formatError(e));
}
}

private addCommentPermissions(rawComment: Comment, remote: Remote): Comment {
const isCurrentUser = this._credentialStore.isCurrentUser(rawComment.user.login, remote);
const notOutdated = rawComment.position !== null;
rawComment.canEdit = isCurrentUser && notOutdated;
rawComment.canDelete = isCurrentUser && notOutdated;

return rawComment;
}

private async changePullRequestState(state: 'open' | 'closed', pullRequest: IPullRequestModel): Promise<any> {
const { octokit, remote } = await (pullRequest as PullRequestModel).githubRepository.ensure();

Expand Down
117 changes: 110 additions & 7 deletions src/typings/vscode.proposed.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,14 @@ declare module 'vscode' {
*/

interface CommentInfo {
/**
* All of the comment threads associated with the document.
*/
threads: CommentThread[];

/**
* The ranges of the document which support commenting.
*/
commentingRanges?: Range[];
}

Expand All @@ -533,19 +540,85 @@ declare module 'vscode' {
Expanded = 1
}

/**
* A collection of comments representing a conversation at a particular range in a document.
*/
interface CommentThread {
/**
* A unique identifier of the comment thread.
*/
threadId: string;

/**
* The uri of the document the thread has been created on.
*/
resource: Uri;

/**
* The range the comment thread is located within the document. The thread icon will be shown
* at the first line of the range.
*/
range: Range;

/**
* The ordered comments of the thread.
*/
comments: Comment[];

/**
* Whether the thread should be collapsed or expanded when opening the document. Defaults to Collapsed.
*/
collapsibleState?: CommentThreadCollapsibleState;
}

/**
* A comment is displayed within the editor or the Comments Panel, depending on how it is provided.
*/
interface Comment {
/**
* The id of the comment
*/
commentId: string;

/**
* The text of the comment
*/
body: MarkdownString;

/**
* The display name of the user who created the comment
*/
userName: string;
gravatar: string;

/**
* The icon path for the user who created the comment
*/
userIconPath?: Uri;

/**
* @deprecated Use userIconPath instead. The avatar src of the user who created the comment
*/
gravatar?: string;

/**
* Whether the current user has permission to edit the comment.
*
* This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or
* if it is provided by a `DocumentCommentProvider` and no `editComment` method is given.
*/
canEdit?: boolean;

/**
* Whether the current user has permission to delete the comment.
*
* This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or
* if it is provided by a `DocumentCommentProvider` and no `deleteComment` method is given.
*/
canDelete?: boolean;

/**
* The command to be executed if the comment is selected in the Comments Panel
*/
command?: Command;
}

Expand All @@ -567,18 +640,48 @@ declare module 'vscode' {
}

interface DocumentCommentProvider {
/**
* Provide the commenting ranges and comment threads for the given document. The comments are displayed within the editor.
*/
provideDocumentComments(document: TextDocument, token: CancellationToken): Promise<CommentInfo>;
createNewCommentThread?(document: TextDocument, range: Range, text: string, token: CancellationToken): Promise<CommentThread>;
replyToCommentThread?(document: TextDocument, range: Range, commentThread: CommentThread, text: string, token: CancellationToken): Promise<CommentThread>;
onDidChangeCommentThreads?: Event<CommentThreadChangedEvent>;

/**
* Called when a user adds a new comment thread in the document at the specified range, with body text.
*/
createNewCommentThread(document: TextDocument, range: Range, text: string, token: CancellationToken): Promise<CommentThread>;

/**
* Called when a user replies to a new comment thread in the document at the specified range, with body text.
*/
replyToCommentThread(document: TextDocument, range: Range, commentThread: CommentThread, text: string, token: CancellationToken): Promise<CommentThread>;

/**
* Called when a user edits the comment body to the be new text text.
*/
editComment?(document: TextDocument, comment: Comment, text: string, token: CancellationToken): Promise<void>;

/**
* Called when a user deletes the comment.
*/
deleteComment?(document: TextDocument, comment: Comment, token: CancellationToken): Promise<void>;

/**
* Notify of updates to comment threads.
*/
onDidChangeCommentThreads: Event<CommentThreadChangedEvent>;
}

interface WorkspaceCommentProvider {
/**
* Provide all comments for the workspace. Comments are shown within the comments panel. Selecting a comment
* from the panel runs the comment's command.
*/
provideWorkspaceComments(token: CancellationToken): Promise<CommentThread[]>;
createNewCommentThread?(document: TextDocument, range: Range, text: string, token: CancellationToken): Promise<CommentThread>;
replyToCommentThread?(document: TextDocument, range: Range, commentThread: CommentThread, text: string, token: CancellationToken): Promise<CommentThread>;

onDidChangeCommentThreads?: Event<CommentThreadChangedEvent>;
/**
* Notify of updates to comment threads.
*/
onDidChangeCommentThreads: Event<CommentThreadChangedEvent>;
}

namespace workspace {
Expand Down
24 changes: 23 additions & 1 deletion src/view/prDocumentCommentProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { fromPRUri } from '../common/uri';

export class PRDocumentCommentProvider implements vscode.DocumentCommentProvider {
private _onDidChangeCommentThreads: vscode.EventEmitter<vscode.CommentThreadChangedEvent> = new vscode.EventEmitter<vscode.CommentThreadChangedEvent>();
public onDidChangeCommentThreads?: vscode.Event<vscode.CommentThreadChangedEvent> = this._onDidChangeCommentThreads.event;
public onDidChangeCommentThreads: vscode.Event<vscode.CommentThreadChangedEvent> = this._onDidChangeCommentThreads.event;

private _prDocumentCommentProviders: {[key: number]: vscode.DocumentCommentProvider} = {};

Expand Down Expand Up @@ -64,6 +64,28 @@ export class PRDocumentCommentProvider implements vscode.DocumentCommentProvider

return await this._prDocumentCommentProviders[params.prNumber].replyToCommentThread(document, range, commentThread, text, token);
}

async editComment(document: vscode.TextDocument, comment: vscode.Comment, text: string, token: vscode.CancellationToken): Promise<void> {
const params = fromPRUri(document.uri);
const commentProvider = this._prDocumentCommentProviders[params.prNumber];

if (!commentProvider) {
throw new Error(`Couldn't find document provider`);
}

return await commentProvider.editComment(document, comment, text, token);
}

async deleteComment(document: vscode.TextDocument, comment: vscode.Comment, token: vscode.CancellationToken): Promise<void> {
const params = fromPRUri(document.uri);
const commentProvider = this._prDocumentCommentProviders[params.prNumber];

if (!commentProvider) {
throw new Error(`Couldn't find document provider`);
}

return await commentProvider.deleteComment(document, comment, token);
}
}

const prDocumentCommentProvider = new PRDocumentCommentProvider();
Expand Down
Loading