diff --git a/comment-sample/package.json b/comment-sample/package.json
index e96b700b..636a2f5a 100644
--- a/comment-sample/package.json
+++ b/comment-sample/package.json
@@ -19,15 +19,51 @@
"commands": [
{
"command": "mywiki.createNote",
- "title": "Comment API: Create a comment thread"
+ "title": "Create Note"
},
{
- "command": "mywiki.createComment",
- "title": "Comment API: Reply to a comment thread"
+ "command": "mywiki.replyNote",
+ "title": "Reply"
+ },
+ {
+ "command": "mywiki.editNote",
+ "title": "Edit",
+ "icon":{
+ "dark": "resources/edit_inverse.svg",
+ "light": "resources/edit.svg"
+ }
},
{
"command": "mywiki.deleteNote",
- "title": "Comment API: Delete a comment thread"
+ "title": "Delete",
+ "icon":{
+ "dark": "resources/close_inverse.svg",
+ "light": "resources/close.svg"
+ }
+ },
+ {
+ "command": "mywiki.deleteNoteComment",
+ "title": "Delete",
+ "icon":{
+ "dark": "resources/close_inverse.svg",
+ "light": "resources/close.svg"
+ }
+ },
+ {
+ "command": "mywiki.saveNote",
+ "title": "Save"
+ },
+ {
+ "command": "mywiki.cancelsaveNote",
+ "title": "Cancel"
+ },
+ {
+ "command": "mywiki.startDraft",
+ "title": "Start draft"
+ },
+ {
+ "command": "mywiki.finishDraft",
+ "title": "Finish draft"
}
],
"menus": {
@@ -37,12 +73,68 @@
"when": "false"
},
{
- "command": "mywiki.createComment",
+ "command": "mywiki.replyNote",
"when": "false"
},
{
"command": "mywiki.deleteNote",
"when": "false"
+ },
+ {
+ "command": "mywiki.deleteNoteComment",
+ "when": "false"
+ }
+ ],
+ "comments/commentThread/title": [
+ {
+ "command": "mywiki.deleteNote",
+ "group": "navigation",
+ "when": "!commentThreadIsEmpty"
+ }
+ ],
+ "comments/commentThread/context": [
+ {
+ "command": "mywiki.createNote",
+ "group": "inline",
+ "precondition": "!commentIsEmpty",
+ "when": "commentThreadIsEmpty"
+ },
+ {
+ "command": "mywiki.replyNote",
+ "group": "inline",
+ "precondition": "!commentIsEmpty",
+ "when": "!commentThreadIsEmpty"
+ },
+ {
+ "command": "mywiki.startDraft",
+ "group": "inline",
+ "precondition": "!commentIsEmpty",
+ "when": "commentThread != draft"
+ },
+ {
+ "command": "mywiki.finishDraft",
+ "group": "inline",
+ "when": "commentThread == draft"
+ }
+ ],
+ "comments/comment/title": [
+ {
+ "command": "mywiki.editNote",
+ "group": "group@1"
+ },
+ {
+ "command": "mywiki.deleteNoteComment",
+ "group": "group@2"
+ }
+ ],
+ "comments/comment/context": [
+ {
+ "command": "mywiki.cancelsaveNote",
+ "group": "inline@1"
+ },
+ {
+ "command": "mywiki.saveNote",
+ "group": "inline@2"
}
]
}
diff --git a/comment-sample/resources/close.svg b/comment-sample/resources/close.svg
new file mode 100644
index 00000000..fde34404
--- /dev/null
+++ b/comment-sample/resources/close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/comment-sample/resources/close_inverse.svg b/comment-sample/resources/close_inverse.svg
new file mode 100644
index 00000000..d88aa121
--- /dev/null
+++ b/comment-sample/resources/close_inverse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/comment-sample/resources/edit.svg b/comment-sample/resources/edit.svg
new file mode 100755
index 00000000..ecde9240
--- /dev/null
+++ b/comment-sample/resources/edit.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/comment-sample/resources/edit_inverse.svg b/comment-sample/resources/edit_inverse.svg
new file mode 100755
index 00000000..da956cb2
--- /dev/null
+++ b/comment-sample/resources/edit_inverse.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/comment-sample/src/extension.ts b/comment-sample/src/extension.ts
index e57d6c3f..e946c2d3 100644
--- a/comment-sample/src/extension.ts
+++ b/comment-sample/src/extension.ts
@@ -2,13 +2,26 @@
import * as vscode from 'vscode';
-let threadId = 0;
-let commentId = 0;
+let commentId = 1;
+
+class NoteComment implements vscode.Comment {
+ id: number;
+ label: string | undefined;
+ constructor(
+ public body: string | vscode.MarkdownString,
+ public mode: vscode.CommentMode,
+ public author: vscode.CommentAuthorInformation,
+ public parent?: vscode.CommentThread
+ ) {
+ this.id = ++commentId;
+ }
+}
export function activate(context: vscode.ExtensionContext) {
// A `CommentController` is able to provide comments for documents.
- const commentController = vscode.comment.createCommentController('comment-sample', 'Comment API Sample');
+ const commentController = vscode.comments.createCommentController('comment-sample', 'Comment API Sample');
context.subscriptions.push(commentController);
+ vscode.commands.executeCommand('setContext', 'inDraft', false);
// commenting range provider
commentController.commentingRangeProvider = {
@@ -17,93 +30,81 @@ export function activate(context: vscode.ExtensionContext) {
return [new vscode.Range(0, 0, lineCount - 1, 0)];
}
};
-
- commentController.template = {
- label: 'Create New Note:',
- acceptInputCommand: {
- title: 'Create Note',
- command: 'mywiki.createNote',
- // Command is responsible for arguments
- arguments: [
- commentController
- ]
- }
- };
- // register `mywiki.createNote` command
- context.subscriptions.push(vscode.commands.registerCommand('mywiki.createNote', (commentController: vscode.CommentController, thread: vscode.CommentThread | undefined) => {
- if (commentController.inputBox) {
- if (!thread) {
- // the comment thread is created from comment thread template
- let thread = commentController.createCommentThread(`${++threadId}`, commentController.inputBox.resource, commentController.inputBox.range, []);
- // by default, a comment thread is collapsed, for newly created empty comment thread, we want to expand it and users can start commenting immediately
- thread.collapsibleState = vscode.CommentThreadCollapsibleState.Expanded;
-
- // In this example, we call it `Create New Note` while for GH PR case, we can call it `Start Review` or `Start New Converstation`
- let text = commentController.inputBox.value;
- let markedString = new vscode.MarkdownString(text);
- let newComment = new vscode.Comment(`${++commentId}`, markedString, 'vscode');
-
- thread.comments = [newComment];
- thread.label = 'Participants: vscode';
-
- // We will render all `acceptInputCommands` as Actions on the bottom of Comment Widget in the editor.
- thread.acceptInputCommand = {
- title: 'Create Comment',
- command: 'mywiki.createComment',
- arguments: [
- commentController,
- thread
- ]
- };
-
- thread.deleteCommand = {
- title: 'Delete Note',
- command: 'mywiki.deleteNote',
- arguments: [
- thread
- ]
- };
-
- // Lastly, we want to clear the textarea in Comment Widget.
- commentController.inputBox.value = '';
- } else {
- // Read text from the focused textarea in the Comment Widget.
- let text = commentController.inputBox.value;
- let markedString = new vscode.MarkdownString(text);
- let newComment = new vscode.Comment(`${++commentId}`, markedString, 'vscode');
-
- thread.comments = [newComment];
- thread.label = 'Participants: vscode';
-
- // After we create the new comment thread, we may want to update the actions on the Comment Widget.
- thread.acceptInputCommand = {
- title: 'Create Comment',
- command: 'mywiki.createComment',
- arguments: [
- commentController,
- thread
- ]
- };
-
- // Lastly, we want to clear the textarea in Comment Widget.
- commentController.inputBox.value = '';
- }
- }
+ context.subscriptions.push(vscode.commands.registerCommand('mywiki.createNote', (reply: vscode.CommentReply) => {
+ replyNote(reply);
}));
- context.subscriptions.push(vscode.commands.registerCommand('mywiki.createComment', (commentController: vscode.CommentController, thread: vscode.CommentThread) => {
- if (commentController.inputBox) {
- let text = commentController.inputBox.value;
- let markedString = new vscode.MarkdownString(text);
- let newComment = new vscode.Comment(`${++commentId}`, markedString, 'vscode');
+ context.subscriptions.push(vscode.commands.registerCommand('mywiki.replyNote', (reply: vscode.CommentReply) => {
+ replyNote(reply);
+ }));
- thread.comments = [...thread.comments, newComment];
- commentController.inputBox.value = '';
+ context.subscriptions.push(vscode.commands.registerCommand('mywiki.startDraft', (reply: vscode.CommentReply) => {
+ let thread = reply.thread;
+ thread.contextValue = 'draft';
+ let newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread);
+ newComment.label = 'pending';
+ thread.comments = [...thread.comments, newComment];
+ }));
+
+ context.subscriptions.push(vscode.commands.registerCommand('mywiki.finishDraft', (reply: vscode.CommentReply) => {
+ vscode.commands.executeCommand('setContext', 'inDraft', false);
+
+ let thread = reply.thread;
+ thread.collapsibleState = undefined;
+ let newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread);
+ thread.comments = [...thread.comments, newComment].map(comment => {
+ comment.label = undefined;
+ return comment;
+ });
+ }));
+
+ context.subscriptions.push(vscode.commands.registerCommand('mywiki.deleteNoteComment', (comment: NoteComment) => {
+ let thread = comment.parent;
+ thread.comments = thread.comments.filter((cmt: NoteComment) => cmt.id !== comment.id);
+
+ if (thread.comments.length === 0) {
+ thread.dispose();
}
}));
context.subscriptions.push(vscode.commands.registerCommand('mywiki.deleteNote', (thread: vscode.CommentThread) => {
thread.dispose();
}));
+
+ context.subscriptions.push(vscode.commands.registerCommand('mywiki.cancelsaveNote', (comment: NoteComment) => {
+ comment.parent.comments = comment.parent.comments.map((cmt: NoteComment) => {
+ if (cmt.id === comment.id) {
+ cmt.mode = vscode.CommentMode.Preview;
+ }
+
+ return cmt;
+ });
+ }));
+
+ context.subscriptions.push(vscode.commands.registerCommand('mywiki.saveNote', (comment: NoteComment) => {
+ comment.parent.comments = comment.parent.comments.map((cmt: NoteComment) => {
+ if (cmt.id === comment.id) {
+ cmt.mode = vscode.CommentMode.Preview;
+ }
+
+ return cmt;
+ });
+ }));
+
+ context.subscriptions.push(vscode.commands.registerCommand('mywiki.editNote', (comment: NoteComment) => {
+ comment.parent.comments = comment.parent.comments.map((cmt: NoteComment) => {
+ if (cmt.id === comment.id) {
+ cmt.mode = vscode.CommentMode.Editing;
+ }
+
+ return cmt;
+ });
+ }));
+
+ function replyNote(reply: vscode.CommentReply) {
+ let thread = reply.thread;
+ let newComment = new NoteComment(reply.text, vscode.CommentMode.Preview, { name: 'vscode' }, thread);
+ thread.comments = [...thread.comments, newComment];
+ }
}
diff --git a/comment-sample/src/typings/vscode.proposed.d.ts b/comment-sample/src/typings/vscode.proposed.d.ts
index ef1b2c19..39b19ece 100644
--- a/comment-sample/src/typings/vscode.proposed.d.ts
+++ b/comment-sample/src/typings/vscode.proposed.d.ts
@@ -35,15 +35,15 @@ declare module 'vscode' {
Expanded = 1
}
+ export enum CommentMode {
+ Editing = 0,
+ Preview = 1
+ }
+
/**
* A collection of [comments](#Comment) representing a conversation at a particular range in a document.
*/
export interface CommentThread {
- /**
- * A unique identifier of the comment thread.
- */
- readonly id: string;
-
/**
* The uri of the document the thread has been created on.
*/
@@ -58,7 +58,7 @@ declare module 'vscode' {
/**
* The ordered comments of the thread.
*/
- comments: Comment[];
+ comments: ReadonlyArray;
/**
* Whether the thread should be collapsed or expanded when opening the document.
@@ -70,28 +70,26 @@ declare module 'vscode' {
* The optional human-readable label describing the [Comment Thread](#CommentThread)
*/
label?: string;
-
+
/**
- * Optional accept input command
- *
- * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost.
- * This command will be invoked when users the user accepts the value in the comment editor.
- * This command will disabled when the comment editor is empty.
+ * Context value of the comment thread. This can be used to contribute thread specific actions.
+ * For example, a comment thread is given a context value as `editable`. When contributing actions to `comments/commentThread/title`
+ * using `menus` extension point, you can specify context value for key `commentThread` in `when` expression like `commentThread == editable`.
+ * ```
+ * "contributes": {
+ * "menus": {
+ * "comments/commentThread/title": [
+ * {
+ * "command": "extension.deleteCommentThread",
+ * "when": "commentThread == editable"
+ * }
+ * ]
+ * }
+ * }
+ * ```
+ * This will show action `extension.deleteCommentThread` only for comment threads with `contextValue` is `editable`.
*/
- acceptInputCommand?: Command;
-
- /**
- * Optional additonal commands.
- *
- * `additionalCommands` are the secondary actions rendered on Comment Widget.
- */
- additionalCommands?: Command[];
-
- /**
- * The command to be executed when users try to delete the comment thread. Currently, this is only called
- * when the user collapses a comment thread that has no comments in it.
- */
- deleteCommand?: Command;
+ contextValue?: string;
/**
* Dispose this comment thread.
@@ -102,76 +100,67 @@ declare module 'vscode' {
}
/**
- * A comment is displayed within the editor or the Comments Panel, depending on how it is provided.
+ * Author information of a [comment](#Comment)
*/
- export class Comment {
+ export interface CommentAuthorInformation {
/**
- * The id of the comment
+ * The display name of the author of the comment
*/
- readonly id: string;
+ name: string;
/**
- * The human-readable comment body
+ * The optional icon path for the author
*/
- readonly body: MarkdownString;
-
- /**
- * The display name of the user who created the comment
- */
- readonly userName: string;
-
- /**
- * Optional label describing the [Comment](#Comment)
- * Label will be rendered next to userName if exists.
- */
- readonly label?: string;
-
- /**
- * The icon path for the user who created the comment
- */
- readonly userIconPath?: Uri;
-
- /**
- * The command to be executed if the comment is selected in the Comments Panel
- */
- readonly selectCommand?: Command;
-
- /**
- * The command to be executed when users try to save the edits to the comment
- */
- readonly editCommand?: Command;
-
- /**
- * The command to be executed when users try to delete the comment
- */
- readonly deleteCommand?: Command;
-
- /**
- * @param id The id of the comment
- * @param body The human-readable comment body
- * @param userName The display name of the user who created the comment
- */
- constructor(id: string, body: MarkdownString, userName: string);
+ iconPath?: Uri;
}
/**
- * The comment input box in Comment Widget.
+ * A comment is displayed within the editor or the Comments Panel, depending on how it is provided.
*/
- export interface CommentInputBox {
+ export interface Comment {
/**
- * Setter and getter for the contents of the comment input box
+ * The human-readable comment body
*/
- value: string;
+ body: string | MarkdownString;
+
+ mode: CommentMode;
/**
- * The uri of the document comment input box has been created on
+ * The author information of the comment
*/
- resource: Uri;
+ author: CommentAuthorInformation;
/**
- * The range the comment input box is located within the document
+ * Optional label describing the [Comment](#Comment)
+ * Label will be rendered next to authorName if exists.
*/
- range: Range;
+ label?: string;
+
+ /**
+ * Context value of the comment. This can be used to contribute comment specific actions.
+ * For example, a comment is given a context value as `editable`. When contributing actions to `comments/comment/title`
+ * using `menus` extension point, you can specify context value for key `comment` in `when` expression like `comment == editable`.
+ * ```
+ * "contributes": {
+ * "menus": {
+ * "comments/comment/title": [
+ * {
+ * "command": "extension.deleteComment",
+ * "when": "comment == editable"
+ * }
+ * ]
+ * }
+ * }
+ * ```
+ * This will show action `extension.deleteComment` only for comments with `contextValue` is `editable`.
+ */
+ contextValue?: string;
+ }
+
+ export interface CommentReply {
+ thread: CommentThread;
+
+ text: string;
}
/**
@@ -184,38 +173,6 @@ declare module 'vscode' {
provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult;
}
- /**
- * Comment thread template for new comment thread creation.
- */
- export interface CommentThreadTemplate {
- /**
- * The human-readable label describing the [Comment Thread](#CommentThread)
- */
- readonly label: string;
-
- /**
- * Optional accept input command
- *
- * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost.
- * This command will be invoked when users the user accepts the value in the comment editor.
- * This command will disabled when the comment editor is empty.
- */
- readonly acceptInputCommand?: Command;
-
- /**
- * Optional additonal commands.
- *
- * `additionalCommands` are the secondary actions rendered on Comment Widget.
- */
- readonly additionalCommands?: Command[];
-
- /**
- * The command to be executed when users try to delete the comment thread. Currently, this is only called
- * when the user collapses a comment thread that has no comments in it.
- */
- readonly deleteCommand?: Command;
- }
-
/**
* A comment controller is able to provide [comments](#CommentThread) support to the editor and
* provide users various ways to interact with comments.
@@ -231,25 +188,6 @@ declare module 'vscode' {
*/
readonly label: string;
- /**
- * The active [comment input box](#CommentInputBox) or `undefined`. The active `inputBox` is the input box of
- * the comment thread widget that currently has focus. It's `undefined` when the focus is not in any CommentInputBox.
- */
- readonly inputBox: CommentInputBox | undefined;
-
- /**
- * Optional comment thread template information.
- *
- * The comment controller will use this information to create the comment widget when users attempt to create new comment thread
- * from the gutter or command palette.
- *
- * When users run `CommentThreadTemplate.acceptInputCommand` or `CommentThreadTemplate.additionalCommands`, extensions should create
- * the approriate [CommentThread](#CommentThread).
- *
- * If not provided, users won't be able to create new comment threads in the editor.
- */
- template?: CommentThreadTemplate;
-
/**
* Optional commenting range provider. Provide a list [ranges](#Range) which support commenting to any given resource uri.
*
@@ -266,7 +204,7 @@ declare module 'vscode' {
* @param range The range the comment thread is located within the document.
* @param comments The ordered comments of the thread.
*/
- createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[]): CommentThread;
+ createCommentThread(uri: Uri, range: Range, comments: Comment[]): CommentThread;
/**
* Dispose this comment controller.
@@ -277,7 +215,7 @@ declare module 'vscode' {
dispose(): void;
}
- namespace comment {
+ namespace comments {
/**
* Creates a new [comment controller](#CommentController) instance.
*