From 4186bc0a44a1e6216dfe7e6ff2c07b74d9507fd5 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 17 May 2019 11:49:00 -0700 Subject: [PATCH 01/22] menu items --- src/vs/editor/common/modes.ts | 9 + src/vs/platform/actions/common/actions.ts | 4 + src/vs/vscode.d.ts | 170 ++++++++++++++++ src/vs/vscode.proposed.d.ts | 185 ++---------------- .../api/browser/mainThreadComments.ts | 73 ++++--- .../workbench/api/common/extHost.protocol.ts | 2 + .../workbench/api/common/extHostComments.ts | 76 ++++--- src/vs/workbench/api/common/extHostTypes.ts | 6 + .../api/common/menusExtensionPoint.ts | 26 ++- src/vs/workbench/api/node/extHost.api.impl.ts | 1 + .../contrib/comments/browser/commentMenus.ts | 70 +++++++ .../comments/browser/commentService.ts | 49 +++-- .../comments/browser/commentThreadWidget.ts | 44 ++--- .../browser/commentsEditorContribution.ts | 65 ++---- .../comments/common/commentContextKeys.ts | 17 ++ 15 files changed, 471 insertions(+), 326 deletions(-) create mode 100644 src/vs/workbench/contrib/comments/browser/commentMenus.ts create mode 100644 src/vs/workbench/contrib/comments/common/commentContextKeys.ts diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 082dcf810f6..a71c1680e74 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1347,6 +1347,14 @@ export interface CommentReaction { readonly canEdit?: boolean; } +/** + * @internal + */ +export enum CommentMode { + Editing = 0, + Preview = 1 +} + /** * @internal */ @@ -1363,6 +1371,7 @@ export interface Comment { readonly isDraft?: boolean; readonly commentReactions?: CommentReaction[]; readonly label?: string; + readonly mode?: CommentMode; } /** diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 8aacb21dfb2..d0290c179fd 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -95,6 +95,10 @@ export const enum MenuId { TouchBarContext, ViewItemContext, ViewTitle, + CommentThreadTitle, + CommentThreadActions, + CommentTitle, + CommentActions } export interface IMenuActionOptions { diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index a4bf1796b7d..6081b3f6ce6 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8940,6 +8940,176 @@ declare module 'vscode' { */ export const onDidChange: Event; } + + //#region Comments + + /** + * Collapsible state of a [comment thread](#CommentThread) + */ + export enum CommentThreadCollapsibleState { + /** + * Determines an item is collapsed + */ + Collapsed = 0, + + /** + * Determines an item is expanded + */ + 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 { + /** + * The uri of the document the thread has been created on. + */ + readonly 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. + */ + readonly range: Range; + + /** + * The ordered comments of the thread. + */ + comments: ReadonlyArray; + + /** + * Whether the thread should be collapsed or expanded when opening the document. + * Defaults to Collapsed. + */ + collapsibleState: CommentThreadCollapsibleState; + + /** + * The optional human-readable label describing the [Comment Thread](#CommentThread) + */ + label?: string; + + /** + * Dispose this comment thread. + * + * Once disposed, this comment thread will be removed from visible editors and Comment Panel when approriate. + */ + dispose(): void; + } + + /** + * Author information of a [comment](#Comment) + */ + export interface CommentAuthorInformation { + /** + * The display name of the author of the comment + */ + name: string; + + /** + * The optional icon path for the author + */ + iconPath?: Uri; + } + + /** + * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. + */ + export interface Comment { + /** + * The human-readable comment body + */ + body: string | MarkdownString; + + mode: CommentMode; + + /** + * The author information of the comment + */ + author: CommentAuthorInformation; + + /** + * Optional label describing the [Comment](#Comment) + * Label will be rendered next to authorName if exists. + */ + label?: string; + } + + export interface CommentReply { + thread: CommentThread; + + text: string; + } + + /** + * Commenting range provider for a [comment controller](#CommentController). + */ + export interface CommentingRangeProvider { + /** + * Provide a list of ranges which allow new comment threads creation or null for a given document + */ + provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; + } + + /** + * A comment controller is able to provide [comments](#CommentThread) support to the editor and + * provide users various ways to interact with comments. + */ + export interface CommentController { + /** + * The id of this comment controller. + */ + readonly id: string; + + /** + * The human-readable label of this comment controller. + */ + readonly label: string; + + /** + * Optional commenting range provider. Provide a list [ranges](#Range) which support commenting to any given resource uri. + * + * If not provided and `emptyCommentThreadFactory` exits, users can leave comments in any document opened in the editor. + */ + commentingRangeProvider?: CommentingRangeProvider; + + /** + * Create a [comment thread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches) + * and Comments Panel once created. + * + * @param id An `id` for the comment thread. + * @param resource The uri of the document the thread has been created on. + * @param range The range the comment thread is located within the document. + * @param comments The ordered comments of the thread. + */ + createCommentThread(uri: Uri, range: Range, comments: Comment[]): CommentThread; + + /** + * Dispose this comment controller. + * + * Once disposed, all [comment threads](#CommentThread) created by this comment controller will also be removed from the editor + * and Comments Panel. + */ + dispose(): void; + } + + namespace comment { + /** + * Creates a new [comment controller](#CommentController) instance. + * + * @param id An `id` for the comment controller. + * @param label A human-readable string for the comment controller. + * @return An instance of [comment controller](#CommentController). + */ + export function createCommentController(id: string, label: string): CommentController; + } + + //#endregion } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 51846936ee6..b7cf411783c 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -939,15 +939,6 @@ declare module 'vscode' { reactionProvider?: CommentReactionProvider; } - export interface CommentController { - /** - * The active [comment thread](#CommentThread) or `undefined`. The `activeCommentThread` is the comment thread of - * the comment widget that currently has focus. It's `undefined` when the focus is not in any comment thread widget, or - * the comment widget created from [comment thread template](#CommentThreadTemplate). - */ - readonly activeCommentThread: CommentThread | undefined; - } - namespace workspace { /** * DEPRECATED @@ -961,21 +952,6 @@ declare module 'vscode' { export function registerWorkspaceCommentProvider(provider: WorkspaceCommentProvider): Disposable; } - /** - * Collapsible state of a [comment thread](#CommentThread) - */ - export enum CommentThreadCollapsibleState { - /** - * Determines an item is collapsed - */ - Collapsed = 0, - - /** - * Determines an item is expanded - */ - Expanded = 1 - } - /** * A collection of [comments](#Comment) representing a conversation at a particular range in a document. */ @@ -990,28 +966,6 @@ declare module 'vscode' { */ readonly uri: Uri; - /** - * The range the comment thread is located within the document. The thread icon will be shown - * at the first line of the range. - */ - readonly 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; - - /** - * The optional human-readable label describing the [Comment Thread](#CommentThread) - */ - label?: string; - /** * Optional accept input command * @@ -1020,46 +974,6 @@ declare module 'vscode' { * This command will disabled when the comment editor is empty. */ acceptInputCommand?: Command; - - - /** - * Dispose this comment thread. - * - * Once disposed, this comment thread will be removed from visible editors and Comment Panel when approriate. - */ - dispose(): void; - } - - /** - * Author information of a [comment](#Comment) - */ - - export interface CommentAuthorInformation { - /** - * The display name of the author of the comment - */ - name: string; - - /** - * The optional icon path for the author - */ - iconPath?: Uri; - } - - /** - * Author information of a [comment](#Comment) - */ - - export interface CommentAuthorInformation { - /** - * The display name of the author of the comment - */ - name: string; - - /** - * The optional icon path for the author - */ - iconPath?: Uri; } /** @@ -1071,22 +985,6 @@ declare module 'vscode' { */ id: string; - /** - * The human-readable comment body - */ - body: MarkdownString; - - /** - * The author information of the comment - */ - author: CommentAuthorInformation; - - /** - * Optional label describing the [Comment](#Comment) - * Label will be rendered next to authorName if exists. - */ - label?: string; - /** * The command to be executed if the comment is selected in the Comments Panel */ @@ -1106,16 +1004,6 @@ declare module 'vscode' { * Setter and getter for the contents of the comment input box */ value: string; - - /** - * The uri of the document comment input box has been created on - */ - resource: Uri; - - /** - * The range the comment input box is located within the document - */ - range: Range; } /** @@ -1128,36 +1016,12 @@ declare module 'vscode' { provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; } - /** - * Comment thread template for new comment thread creation. - */ - export interface CommentThreadTemplate { + export interface EmptyCommentThreadFactory { /** - * The human-readable label describing the [Comment Thread](#CommentThread) + * The method `createEmptyCommentThread` is called when users attempt to create new comment thread from the gutter or command palette. + * Extensions still need to call `createCommentThread` inside this call when appropriate. */ - 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; + createEmptyCommentThread(document: TextDocument, range: Range): ProviderResult; } /** @@ -1165,41 +1029,12 @@ declare module 'vscode' { * provide users various ways to interact with comments. */ export interface CommentController { - /** - * The id of this comment controller. - */ - readonly id: string; - - /** - * The human-readable label of this comment controller. - */ - 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. - * - * If not provided and `emptyCommentThreadFactory` exits, users can leave comments in any document opened in the editor. - */ - commentingRangeProvider?: CommentingRangeProvider; + readonly inputBox?: CommentInputBox; /** * Create a [comment thread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches) @@ -1212,6 +1047,16 @@ declare module 'vscode' { */ createCommentThread(id: string, uri: Uri, range: Range, comments: Comment[]): CommentThread; + /** + * Optional new comment thread factory. + */ + emptyCommentThreadFactory?: EmptyCommentThreadFactory; + + /** + * Optional reaction provider + */ + reactionProvider?: CommentReactionProvider; + /** * Dispose this comment controller. * diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 4b4896e6ece..df7790d8e3a 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -25,6 +25,8 @@ import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'v import { IRange, Range } from 'vs/editor/common/core/range'; import { Emitter, Event } from 'vs/base/common/event'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export class MainThreadDocumentCommentProvider implements modes.DocumentCommentProvider { private readonly _proxy: ExtHostCommentsShape; @@ -247,6 +249,10 @@ export class MainThreadCommentController { return this._id; } + get contextValue(): string { + return this._id; + } + get proxy(): ExtHostCommentsShape { return this._proxy; } @@ -377,31 +383,26 @@ export class MainThreadCommentController { } let commentingRanges = await this._proxy.$provideCommentingRanges(this.handle, resource, token); + let staticContribution = await this._proxy.$checkStaticContribution(this.handle); return { owner: this._uniqueId, label: this.label, threads: ret, - commentingRanges: commentingRanges ? - { - resource: resource, ranges: commentingRanges, newCommentThreadCallback: async (uri: UriComponents, range: IRange) => { - let threadHandle = await this._proxy.$createNewCommentWidgetCallback(this.handle, uri, range, token); + commentingRanges: commentingRanges ? { + resource: resource, + ranges: commentingRanges, + newCommentThreadCallback: staticContribution ? undefined : async (uri: UriComponents, range: IRange) => { + let threadHandle = await this._proxy.$createNewCommentWidgetCallback(this.handle, uri, range, token); - if (threadHandle !== undefined) { - return this.getKnownThread(threadHandle); - } - - return; + if (threadHandle !== undefined) { + return this.getKnownThread(threadHandle); } - } : [], - draftMode: modes.DraftMode.NotSupported, - template: this._features.commentThreadTemplate ? { - controllerHandle: this.handle, - label: this._features.commentThreadTemplate.label, - acceptInputCommand: this._features.commentThreadTemplate.acceptInputCommand, - additionalCommands: this._features.commentThreadTemplate.additionalCommands, - deleteCommand: this._features.commentThreadTemplate.deleteCommand - } : undefined + + return; + } + } : [], + draftMode: modes.DraftMode.NotSupported }; } @@ -427,26 +428,8 @@ export class MainThreadCommentController { return ret; } - getCommentThreadFromTemplate(resource: UriComponents, range: IRange): MainThreadCommentThread { - let thread = new MainThreadCommentThread( - -1, - this.handle, - '', - '', - URI.revive(resource).toString(), - range - ); - - let template = this._features.commentThreadTemplate; - - if (template) { - thread.acceptInputCommand = template.acceptInputCommand; - thread.additionalCommands = template.additionalCommands; - thread.deleteCommand = template.deleteCommand; - thread.label = template.label; - } - - return thread; + createCommentThreadTemplate(resource: UriComponents, range: IRange): void { + this._proxy.$createCommentThreadTemplate(this.handle, resource, range); } toJSON(): any { @@ -471,18 +454,27 @@ export class MainThreadComments extends Disposable implements MainThreadComments private _input?: modes.CommentInput; private _openPanelListener: IDisposable | null; + private _commentThreadEmpty: IContextKey; + private _commentEmpty: IContextKey; + constructor( extHostContext: IExtHostContext, @IEditorService private readonly _editorService: IEditorService, @ICommentService private readonly _commentService: ICommentService, @IPanelService private readonly _panelService: IPanelService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IConfigurationService private readonly _configurationService: IConfigurationService, ) { super(); this._disposables = []; this._activeCommentThreadDisposables = []; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); + + this._commentThreadEmpty = CommentContextKeys.commentThreadIsEmpty.bindTo(_commentService.contextKeyService); + this._commentEmpty = CommentContextKeys.commentIsEmpty.bindTo(_commentService.contextKeyService); + this._commentThreadEmpty.set(true); + this._commentEmpty.set(true); + this._disposables.push(this._commentService.onDidChangeActiveCommentThread(async thread => { let handle = (thread as MainThreadCommentThread).controllerHandle; let controller = this._commentControllers.get(handle); @@ -495,9 +487,12 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._activeCommentThread = thread as MainThreadCommentThread; controller.activeCommentThread = this._activeCommentThread; + this._commentThreadEmpty.set(controller.activeCommentThread.comments!.length === 0); + this._activeCommentThreadDisposables.push(this._activeCommentThread.onDidChangeInput(input => { // todo, dispose this._input = input; this._proxy.$onCommentWidgetInputChange(handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread!.range, this._input ? this._input.value : undefined); + this._commentEmpty.set(this._input ? true : this._input!.value.length === 0); })); await this._proxy.$onActiveCommentThreadChange(controller.handle, controller.activeCommentThread.commentThreadHandle); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index cff14ddb273..609c00305cf 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1210,9 +1210,11 @@ export interface ExtHostProgressShape { export interface ExtHostCommentsShape { $provideDocumentComments(handle: number, document: UriComponents): Promise; $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; + $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange): void; $onCommentWidgetInputChange(commentControllerHandle: number, document: UriComponents, range: IRange, input: string | undefined): Promise; $onActiveCommentThreadChange(commentControllerHandle: number, threadHandle: number | undefined): Promise; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; + $checkStaticContribution(commentControllerHandle: number): Promise; $provideReactionGroup(commentControllerHandle: number): Promise; $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): Promise; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index a36f7409934..6b07a0eab22 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -88,6 +88,16 @@ export class ExtHostComments implements ExtHostCommentsShape { return commentController; } + $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange): void { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController) { + return; + } + + commentController.$createCommentThreadTemplate(uriComponents, range); + } + $onCommentWidgetInputChange(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, input: string): Promise { const commentController = this._commentControllers.get(commentControllerHandle); @@ -164,7 +174,7 @@ export class ExtHostComments implements ExtHostCommentsShape { return Promise.resolve(); } - if (!(commentController as any).emptyCommentThreadFactory && !(commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread)) { + if (!(commentController as any).emptyCommentThreadFactory) { return Promise.resolve(); } @@ -173,13 +183,23 @@ export class ExtHostComments implements ExtHostCommentsShape { if ((commentController as any).emptyCommentThreadFactory) { return (commentController as any).emptyCommentThreadFactory!.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); } - - if (commentController.commentingRangeProvider && commentController.commentingRangeProvider.createEmptyCommentThread) { - return commentController.commentingRangeProvider.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); - } }).then(() => Promise.resolve()); } + $checkStaticContribution(commentControllerHandle: number): Promise { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController) { + return Promise.resolve(false); + } + + if (!(commentController as any).emptyCommentThreadFactory) { + return Promise.resolve(true); + } + + return Promise.resolve(false); + } + registerWorkspaceCommentProvider( extensionId: ExtensionIdentifier, provider: vscode.WorkspaceCommentProvider @@ -618,30 +638,6 @@ class ExtHostCommentController implements vscode.CommentController { private _threads: Map = new Map(); commentingRangeProvider?: vscode.CommentingRangeProvider & { createEmptyCommentThread: (document: vscode.TextDocument, range: types.Range) => Promise; }; - private _template: vscode.CommentThreadTemplate | undefined; - - get template(): vscode.CommentThreadTemplate | undefined { - return this._template; - } - - set template(newTemplate: vscode.CommentThreadTemplate | undefined) { - this._template = newTemplate; - - if (newTemplate) { - const acceptInputCommand = newTemplate.acceptInputCommand ? this._commandsConverter.toInternal(newTemplate.acceptInputCommand) : undefined; - const additionalCommands = newTemplate.additionalCommands ? newTemplate.additionalCommands.map(x => this._commandsConverter.toInternal(x)) : []; - const deleteCommand = newTemplate.deleteCommand ? this._commandsConverter.toInternal(newTemplate.deleteCommand) : undefined; - this._proxy.$updateCommentControllerFeatures(this.handle, { - commentThreadTemplate: { - label: newTemplate.label, - acceptInputCommand, - additionalCommands, - deleteCommand - } - }); - } - } - private _commentReactionProvider?: vscode.CommentReactionProvider; get reactionProvider(): vscode.CommentReactionProvider | undefined { @@ -666,8 +662,23 @@ class ExtHostCommentController implements vscode.CommentController { this._proxy.$registerCommentController(this.handle, _id, _label); } - createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, id, resource, range, comments); + createCommentThread(resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread; + createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread; + createCommentThread(arg0: vscode.Uri | string, arg1: vscode.Uri | vscode.Range, arg2: vscode.Range | vscode.Comment[], arg3?: vscode.Comment[]): vscode.CommentThread { + if (typeof arg0 === 'string') { + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, arg0, arg1 as vscode.Uri, arg2 as vscode.Range, arg3 as vscode.Comment[]); + this._threads.set(commentThread.handle, commentThread); + return commentThread; + } else { + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, '', arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[]); + this._threads.set(commentThread.handle, commentThread); + return commentThread; + } + } + + $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange) { + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, '', URI.revive(uriComponents), extHostTypeConverter.Range.to(range), []); + commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; this._threads.set(commentThread.handle, commentThread); return commentThread; } @@ -759,7 +770,8 @@ function convertFromComment(comment: modes.Comment): vscode.Comment { count: reaction.count, hasReacted: reaction.hasReacted }; - }) : undefined + }) : undefined, + mode: comment.mode ? comment.mode : modes.CommentMode.Preview }; } diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index d105723a885..cf0d658c20c 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -2297,6 +2297,12 @@ export enum CommentThreadCollapsibleState { */ Expanded = 1 } + +export enum CommentMode { + Editing = 0, + Preview = 1 +} + //#endregion @es5ClassCompat diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index bf05772230d..646324ba825 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -45,6 +45,10 @@ namespace schema { case 'statusBar/windowIndicator': return MenuId.StatusBarWindowIndicatorMenu; case 'view/title': return MenuId.ViewTitle; case 'view/item/context': return MenuId.ViewItemContext; + case 'commentThread/title': return MenuId.CommentThreadTitle; + case 'commentThread/actions': return MenuId.CommentThreadActions; + case 'comment/title': return MenuId.CommentTitle; + case 'comment/actions': return MenuId.CommentActions; } return undefined; @@ -182,7 +186,27 @@ namespace schema { description: localize('view.itemContext', "The contributed view item context menu"), type: 'array', items: menuItem - } + }, + 'commentThread/title': { + description: localize('commentThread.title', "The contributed comment thread title menu"), + type: 'array', + items: menuItem + }, + 'commentThread/actions': { + description: localize('commentThread.actions', "The contributed comment thread actions"), + type: 'array', + items: menuItem + }, + 'comment/title': { + description: localize('comment.title', "The contributed comment title menu"), + type: 'array', + items: menuItem + }, + 'comment/actions': { + description: localize('comment.actions', "The contributed comment actions"), + type: 'array', + items: menuItem + }, } }; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 2ad4fa84ca9..e30688fc1cd 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -771,6 +771,7 @@ export function createApiFactory( ColorInformation: extHostTypes.ColorInformation, ColorPresentation: extHostTypes.ColorPresentation, CommentThreadCollapsibleState: extHostTypes.CommentThreadCollapsibleState, + CommentMode: extHostTypes.CommentMode, CompletionItem: extHostTypes.CompletionItem, CompletionItemKind: extHostTypes.CompletionItemKind, CompletionList: extHostTypes.CompletionList, diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts new file mode 100644 index 00000000000..4e57d92e625 --- /dev/null +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable } from 'vs/base/common/lifecycle'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IAction } from 'vs/base/common/actions'; +import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { Comment, CommentThread2 } from 'vs/editor/common/modes'; +import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; + +export class CommentMenus implements IDisposable { + private titleMenu: IMenu; + private titleActions: IAction[] = []; + + private readonly disposables: IDisposable[] = []; + + constructor( + controller: MainThreadCommentController, + private contextKeyService: IContextKeyService, + @IMenuService private readonly menuService: IMenuService, + @IContextMenuService private readonly contextMenuService: IContextMenuService + ) { + const commentControllerKey = this.contextKeyService.createKey('commentController', undefined); + + commentControllerKey.set(controller.contextValue); + } + + getCommentThreadTitleActions(commentThread: CommentThread2): IAction[] { + return this.getActions(MenuId.CommentThreadTitle, commentThread).primary; + } + + getCommentThreadActions(commentThread: CommentThread2): IAction[] { + return []; + } + + getCommentTitleActions(comment: Comment): IAction[] { + return []; + } + + getCommentActions(comment: Comment): IAction[] { + return []; + } + + + private getActions(menuId: MenuId, thread: CommentThread2) { + const contextKeyService = this.contextKeyService.createScoped(); + // contextKeyService.createKey('commentThread', thread.threadId); + + const menu = this.menuService.createMenu(menuId, contextKeyService); + const primary: IAction[] = []; + const secondary: IAction[] = []; + const result = { primary, secondary }; + + fillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => true); + + menu.dispose(); + contextKeyService.dispose(); + + return result; + + } + + dispose(): void { + + } +} diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 296267ceb60..bcfe26b9316 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CommentThread, DocumentCommentProvider, CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread2 } from 'vs/editor/common/modes'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -14,6 +14,9 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { assign } from 'vs/base/common/objects'; import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments'; +import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContext } from 'vs/base/parts/quickopen/browser/quickOpenModel'; export const ICommentService = createDecorator('commentService'); @@ -42,11 +45,14 @@ export interface ICommentService { readonly onDidChangeInput: Event; readonly onDidSetDataProvider: Event; readonly onDidDeleteDataProvider: Event; + readonly contextKeyService: IContextKeyService; setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void; setWorkspaceComments(owner: string, commentsByResource: CommentThread[] | CommentThread2[]): void; removeWorkspaceComments(owner: string): void; registerCommentController(owner: string, commentControl: MainThreadCommentController): void; unregisterCommentController(owner: string): void; + createCommentThreadTemplate(owner: string, resource: URI, range: Range): void; + getCommentMenus(owner: string): CommentMenus; registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void; unregisterDataProvider(owner: string): void; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; @@ -66,7 +72,6 @@ export interface ICommentService { deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; getReactionGroup(owner: string): CommentReaction[] | undefined; toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; - getCommentThreadFromTemplate(owner: string, resource: URI, range: IRange, ): CommentThread2 | undefined; setActiveCommentThread(commentThread: CommentThread | null): void; setInput(input: string): void; } @@ -106,9 +111,15 @@ export class CommentService extends Disposable implements ICommentService { private _commentProviders = new Map(); private _commentControls = new Map(); + private _commentMenus = new Map(); + contextKeyService: IContextKeyService; - constructor() { + constructor( + @IInstantiationService protected instantiationService: IInstantiationService, + @IContextKeyService contextKeyService: IContextKeyService + ) { super(); + this.contextKeyService = contextKeyService.createScoped(); } setActiveCommentThread(commentThread: CommentThread | null) { @@ -141,6 +152,28 @@ export class CommentService extends Disposable implements ICommentService { this._onDidDeleteDataProvider.fire(owner); } + createCommentThreadTemplate(owner: string, resource: URI, range: Range): void { + const commentController = this._commentControls.get(owner); + + if (!commentController) { + return; + } + + commentController.createCommentThreadTemplate(resource, range); + } + + getCommentMenus(owner: string): CommentMenus { + if (this._commentMenus.get(owner)) { + return this._commentMenus.get(owner)!; + } + + let controller = this._commentControls.get(owner); + + let menu = this.instantiationService.createInstance(CommentMenus, controller!, this.contextKeyService); + this._commentMenus.set(owner, menu); + return menu; + } + registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void { this._commentProviders.set(owner, commentProvider); this._onDidSetDataProvider.fire(); @@ -256,16 +289,6 @@ export class CommentService extends Disposable implements ICommentService { } } - getCommentThreadFromTemplate(owner: string, resource: URI, range: IRange, ): CommentThread2 | undefined { - const commentController = this._commentControls.get(owner); - - if (commentController) { - return commentController.getCommentThreadFromTemplate(resource, range); - } - - return undefined; - } - getReactionGroup(owner: string): CommentReaction[] | undefined { const commentProvider = this._commentControls.get(owner); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index db53215e8e6..595b9c1a4b6 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; -import { Action } from 'vs/base/common/actions'; +import { Action, IAction } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; @@ -38,6 +38,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { generateUuid } from 'vs/base/common/uuid'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { withNullAsUndefined } from 'vs/base/common/types'; +import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-x'; @@ -86,6 +87,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget return this._draftMode; } + private _commentMenus: CommentMenus; + constructor( editor: ICodeEditor, private _owner: string, @@ -107,6 +110,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThreadDisposables = []; this._submitActionsDisposables = []; this._formActions = null; + this._commentMenus = this.commentService.getCommentMenus(this._owner); this.create(); this._styleElement = dom.createStyleSheet(this.domNode); @@ -203,7 +207,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => this.collapse()); - this._actionbarWidget.push(this._collapseAction, { label: false, icon: true }); + let secondaryActions: IAction[] = []; + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { + secondaryActions = this._commentMenus.getCommentThreadTitleActions(this._commentThread as modes.CommentThread2); + } + + this._actionbarWidget.push([...secondaryActions, this._collapseAction], { label: false, icon: true }); + this._actionbarWidget.context = this._commentThread; } public collapse(): Promise { @@ -245,7 +255,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - async update(commentThread: modes.CommentThread | modes.CommentThread2, replaceTemplate: boolean = false) { + async update(commentThread: modes.CommentThread | modes.CommentThread2) { const oldCommentsLen = this._commentElements.length; const newCommentsLen = commentThread.comments ? commentThread.comments.length : 0; @@ -294,26 +304,12 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentThread = commentThread; this._commentElements = newCommentNodeList; - this.createThreadLabel(replaceTemplate); - - if (replaceTemplate) { - // since we are replacing the old comment thread, we need to rebind the listeners. - this._commentThreadDisposables.forEach(global => global.dispose()); - this._commentThreadDisposables = []; - } - - if (replaceTemplate) { - this.createTextModelListener(); - } + this.createThreadLabel(); if (this._formActions && this._commentEditor.hasModel()) { dom.clearNode(this._formActions); const model = this._commentEditor.getModel(); this.createCommentWidgetActions2(this._formActions, model); - - if (replaceTemplate) { - this.createCommentWidgetActionsListener(this._formActions, model); - } } // Move comment glyph widget and show position if the line has changed. @@ -368,7 +364,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } - display(lineNumber: number, fromTemplate: boolean = false) { + display(lineNumber: number) { this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber); this._disposables.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); @@ -426,9 +422,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._formActions = dom.append(this._commentForm, dom.$('.form-actions')); if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { this.createCommentWidgetActions2(this._formActions, model); - if (!fromTemplate) { - this.createCommentWidgetActionsListener(this._formActions, model); - } + this.createCommentWidgetActionsListener(this._formActions, model); } else { this.createCommentWidgetActions(this._formActions, model); } @@ -827,14 +821,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } - private createThreadLabel(replaceTemplate: boolean = false) { + private createThreadLabel() { let label: string | undefined; if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { label = (this._commentThread as modes.CommentThread2).label; } - if (label === undefined && !replaceTemplate) { - // if it's for replacing the comment thread template, the comment thread widget title can be undefined as extensions may set it later + if (label === undefined) { if (this._commentThread.comments && this._commentThread.comments.length) { const participantsList = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', '); label = nls.localize('commentThreadParticipants', "Participants: {0}", participantsList); @@ -847,7 +840,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._headingLabel.innerHTML = strings.escape(label); this._headingLabel.setAttribute('aria-label', label); } - } private expandReplyArea() { diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 866bd19ca5a..1cd7b487f01 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -64,7 +64,7 @@ class CommentingRangeDecoration { return this._decorationId; } - constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private _template: modes.CommentThreadTemplate | undefined, private commentingRangesInfo?: modes.CommentingRanges) { + constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _label: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private commentingRangesInfo?: modes.CommentingRanges) { const startLineNumber = _range.startLineNumber; const endLineNumber = _range.endLineNumber; let commentingRangeDecorations = [{ @@ -81,14 +81,13 @@ class CommentingRangeDecoration { } } - public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined } { + public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined } { return { extensionId: this._extensionId, label: this._label, replyCommand: this._reply, ownerId: this._ownerId, - commentingRangesInfo: this.commentingRangesInfo, - template: this._template + commentingRangesInfo: this.commentingRangesInfo }; } @@ -125,11 +124,11 @@ class CommentingRangeDecorator { for (const info of commentInfos) { if (Array.isArray(info.commentingRanges)) { info.commentingRanges.forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, info.reply, this.decorationOptions, info.template)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, info.reply, this.decorationOptions)); }); } else { (info.commentingRanges ? info.commentingRanges.ranges : []).forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, undefined, this.decorationOptions, info.template, info.commentingRanges as modes.CommentingRanges)); + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, info.label, range, undefined, this.decorationOptions, info.commentingRanges as modes.CommentingRanges)); }); } } @@ -449,7 +448,7 @@ export class ReviewController implements IEditorContribution { let matchedNewCommentThreadZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && (zoneWidget.commentThread as any).commentThreadHandle === -1 && Range.equalsRange(zoneWidget.commentThread.range, thread.range)); if (matchedNewCommentThreadZones.length) { - matchedNewCommentThreadZones[0].update(thread, true); + matchedNewCommentThreadZones[0].update(thread); return; } @@ -469,22 +468,6 @@ export class ReviewController implements IEditorContribution { this._commentWidgets.push(zoneWidget); } - private addCommentThreadFromTemplate(lineNumber: number, ownerId: string): ReviewZoneWidget { - let templateCommentThread = this.commentService.getCommentThreadFromTemplate(ownerId, this.editor.getModel()!.uri, { - startLineNumber: lineNumber, - startColumn: 1, - endLineNumber: lineNumber, - endColumn: 1 - })!; - - templateCommentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; - templateCommentThread.comments = []; - - let templateReviewZoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, ownerId, templateCommentThread, '', modes.DraftMode.NotSupported); - - return templateReviewZoneWidget; - } - private addComment(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, draftMode: modes.DraftMode | undefined, pendingComment: string | null) { if (this._newCommentWidget) { this.notificationService.warn(`Please submit the comment at line ${this._newCommentWidget.position ? this._newCommentWidget.position.lineNumber : -1} before creating a new one.`); @@ -640,16 +623,16 @@ export class ReviewController implements IEditorContribution { const commentInfos = newCommentInfos.filter(info => info.ownerId === pick.id); if (commentInfos.length) { - const { replyCommand, ownerId, extensionId, commentingRangesInfo, template } = commentInfos[0]; - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template); + const { replyCommand, ownerId, extensionId, commentingRangesInfo } = commentInfos[0]; + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); } }).then(() => { this._addInProgress = false; }); } } else { - const { replyCommand, ownerId, extensionId, commentingRangesInfo, template } = newCommentInfos[0]!; - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template); + const { replyCommand, ownerId, extensionId, commentingRangesInfo } = newCommentInfos[0]!; + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); } return Promise.resolve(); @@ -668,11 +651,11 @@ export class ReviewController implements IEditorContribution { return picks; } - private getContextMenuActions(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined }[], lineNumber: number): (IAction | ContextSubMenu)[] { + private getContextMenuActions(commentInfos: { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, label: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined }[], lineNumber: number): (IAction | ContextSubMenu)[] { const actions: (IAction | ContextSubMenu)[] = []; commentInfos.forEach(commentInfo => { - const { replyCommand, ownerId, extensionId, label, commentingRangesInfo, template } = commentInfo; + const { replyCommand, ownerId, extensionId, label, commentingRangesInfo } = commentInfo; actions.push(new Action( 'addCommentThread', @@ -680,7 +663,7 @@ export class ReviewController implements IEditorContribution { undefined, true, () => { - this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo, template); + this.addCommentAtLine2(lineNumber, replyCommand, ownerId, extensionId, commentingRangesInfo); return Promise.resolve(); } )); @@ -688,23 +671,10 @@ export class ReviewController implements IEditorContribution { return actions; } - public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined, template: modes.CommentThreadTemplate | undefined) { + public addCommentAtLine2(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined) { if (commentingRangesInfo) { let range = new Range(lineNumber, 1, lineNumber, 1); - if (template) { - // create comment widget through template - let commentThreadWidget = this.addCommentThreadFromTemplate(lineNumber, ownerId); - commentThreadWidget.display(lineNumber, true); - this._commentWidgets.push(commentThreadWidget); - commentThreadWidget.onDidClose(() => { - this._commentWidgets = this._commentWidgets.filter(zoneWidget => !( - zoneWidget.owner === commentThreadWidget.owner && - (zoneWidget.commentThread as any).commentThreadHandle === -1 && - Range.equalsRange(zoneWidget.commentThread.range, commentThreadWidget.commentThread.range) - )); - }); - this.processNextThreadToAdd(); - } else if (commentingRangesInfo.newCommentThreadCallback) { + if (commentingRangesInfo.newCommentThreadCallback) { return commentingRangesInfo.newCommentThreadCallback(this.editor.getModel()!.uri, range) .then(_ => { this.processNextThreadToAdd(); @@ -713,6 +683,11 @@ export class ReviewController implements IEditorContribution { this.notificationService.error(nls.localize('commentThreadAddFailure', "Adding a new comment thread failed: {0}.", e.message)); this.processNextThreadToAdd(); }); + } else { + // latest api, no comments creation callback + this.commentService.createCommentThreadTemplate(ownerId, this.editor.getModel()!.uri, range); + this.processNextThreadToAdd(); + return; } } else { const commentInfo = this._commentInfos.filter(info => info.owner === ownerId); diff --git a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts new file mode 100644 index 00000000000..b58660a19cc --- /dev/null +++ b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export namespace CommentContextKeys { + /** + * A context key that is set when the editor's text has focus (cursor is blinking). + */ + export const commentThreadIsEmpty = new RawContextKey('commentThreadIsEmpty', false); + /** + * A context key that is set when the editor's text or an editor's widget has focus. + */ + export const commentIsEmpty = new RawContextKey('commentIsEmpty', false); +} \ No newline at end of file From e6152cc29f6bc2bc82fe53bf2893fe058b5eb38e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 17 May 2019 16:34:47 -0700 Subject: [PATCH 02/22] icons for menu bar --- src/vs/editor/common/modes.ts | 1 + .../workbench/api/common/extHostComments.ts | 53 +++++++++--- .../contrib/comments/browser/commentMenus.ts | 12 ++- .../contrib/comments/browser/commentNode.ts | 82 +++++++++++-------- .../comments/browser/commentThreadWidget.ts | 57 ++++++++++++- .../browser/commentsEditorContribution.ts | 2 +- .../contrib/comments/browser/media/review.css | 10 +++ 7 files changed, 161 insertions(+), 56 deletions(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index a71c1680e74..d6ec3a832a8 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1360,6 +1360,7 @@ export enum CommentMode { */ export interface Comment { readonly commentId: string; + readonly uniqueIdInThread?: number; readonly body: IMarkdownString; readonly userName: string; readonly userIconPath?: string; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 6b07a0eab22..3182fb118d0 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -69,6 +69,23 @@ export class ExtHostComments implements ExtHostCommentsShape { } return commentThread; + } else if (arg && arg.$mid === 8) { + const commentController = this._commentControllers.get(arg.thread.commentControlHandle); + + if (!commentController) { + return arg; + } + + const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle); + + if (!commentThread) { + return arg; + } + + return { + thread: commentThread, + text: arg.text + }; } return arg; @@ -190,7 +207,7 @@ export class ExtHostComments implements ExtHostCommentsShape { const commentController = this._commentControllers.get(commentControllerHandle); if (!commentController) { - return Promise.resolve(false); + return Promise.resolve(false); } if (!(commentController as any).emptyCommentThreadFactory) { @@ -396,12 +413,14 @@ export class ExtHostComments implements ExtHostCommentsShape { export class ExtHostCommentThread implements vscode.CommentThread { private static _handlePool: number = 0; readonly handle = ExtHostCommentThread._handlePool++; + public commentHandle: number = 0; + get threadId(): string { - return this._id; + return this._id!; } get id(): string { - return this._id; + return this._id!; } get resource(): vscode.Uri { @@ -495,15 +514,21 @@ export class ExtHostCommentThread implements vscode.CommentThread { return this._isDiposed; } + private _commentsMap: Map = new Map(); + constructor( private _proxy: MainThreadCommentsShape, private readonly _commandsConverter: CommandsConverter, private _commentController: ExtHostCommentController, - private _id: string, + private _id: string | undefined, private _uri: vscode.Uri, private _range: vscode.Range, private _comments: vscode.Comment[] ) { + if (this._id === undefined) { + this._id = `${_commentController.id}.${this.handle}`; + } + this._proxy.$createCommentThread( this._commentController.handle, this.handle, @@ -527,7 +552,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { eventuallyUpdateCommentThread(): void { const commentThreadRange = extHostTypeConverter.Range.from(this._range); const label = this.label; - const comments = this._comments.map(cmt => { return convertToModeComment(this._commentController, cmt, this._commandsConverter); }); + const comments = this._comments.map(cmt => { return convertToModeComment2(this, this._commentController, cmt, this._commandsConverter, this._commentsMap); }); const acceptInputCommand = this._acceptInputCommand ? this._commandsConverter.toInternal(this._acceptInputCommand) : undefined; const additionalCommands = this._additionalCommands ? this._additionalCommands.map(x => this._commandsConverter.toInternal(x)) : []; const deleteCommand = this._deleteCommand ? this._commandsConverter.toInternal(this._deleteCommand) : undefined; @@ -536,7 +561,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._proxy.$updateCommentThread( this._commentController.handle, this.handle, - this._id, + this._id!, this._uri, commentThreadRange, label, @@ -670,14 +695,14 @@ class ExtHostCommentController implements vscode.CommentController { this._threads.set(commentThread.handle, commentThread); return commentThread; } else { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, '', arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[]); + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, undefined, arg0 as vscode.Uri, arg1 as vscode.Range, arg2 as vscode.Comment[]); this._threads.set(commentThread.handle, commentThread); return commentThread; } } $createCommentThreadTemplate(uriComponents: UriComponents, range: IRange) { - const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, '', URI.revive(uriComponents), extHostTypeConverter.Range.to(range), []); + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, undefined, URI.revive(uriComponents), extHostTypeConverter.Range.to(range), []); commentThread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; this._threads.set(commentThread.handle, commentThread); return commentThread; @@ -775,12 +800,18 @@ function convertFromComment(comment: modes.Comment): vscode.Comment { }; } -function convertToModeComment(commentController: ExtHostCommentController, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter): modes.Comment { - const iconPath = vscodeComment.author && vscodeComment.author.iconPath ? vscodeComment.author.iconPath.toString() : - (vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar); +function convertToModeComment2(thread: ExtHostCommentThread, commentController: ExtHostCommentController, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter, commentsMap: Map): modes.Comment { + let commentUniqueId = commentsMap.get(vscodeComment)!; + if (!commentUniqueId) { + commentUniqueId = ++thread.commentHandle; + commentsMap.set(vscodeComment, commentUniqueId); + } + + const iconPath = vscodeComment.author && vscodeComment.author.iconPath ? vscodeComment.author.iconPath.toString() : undefined; return { commentId: vscodeComment.id || vscodeComment.commentId, + uniqueIdInThread: commentUniqueId, body: extHostTypeConverter.MarkdownString.from(vscodeComment.body), userName: vscodeComment.author ? vscodeComment.author.name : vscodeComment.userName, userIconPath: iconPath, diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index 4e57d92e625..bec3348343d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -30,25 +30,23 @@ export class CommentMenus implements IDisposable { } getCommentThreadTitleActions(commentThread: CommentThread2): IAction[] { - return this.getActions(MenuId.CommentThreadTitle, commentThread).primary; + return this.getActions(MenuId.CommentThreadTitle).primary; } getCommentThreadActions(commentThread: CommentThread2): IAction[] { - return []; + return this.getActions(MenuId.CommentThreadActions).primary; } getCommentTitleActions(comment: Comment): IAction[] { - return []; + return this.getActions(MenuId.CommentTitle).primary; } getCommentActions(comment: Comment): IAction[] { - return []; + return this.getActions(MenuId.CommentActions).primary; } - - private getActions(menuId: MenuId, thread: CommentThread2) { + private getActions(menuId: MenuId) { const contextKeyService = this.contextKeyService.createScoped(); - // contextKeyService.createKey('commentThread', thread.threadId); const menu = this.menuService.createMenu(menuId, contextKeyService); const primary: IAction[] = []; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 8b4b7bbfe0a..1e5b13762f7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import * as modes from 'vs/editor/common/modes'; import { ActionsOrientation, ActionViewItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; -import { Action, IActionRunner } from 'vs/base/common/actions'; +import { Action, IActionRunner, IAction } from 'vs/base/common/actions'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { ITextModel } from 'vs/editor/common/model'; @@ -35,6 +35,9 @@ import { ToggleReactionsAction, ReactionAction, ReactionActionViewItem } from '. import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; +import { MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); const UPDATE_IN_PROGRESS_LABEL = nls.localize('label.updatingComment', "Updating comment..."); @@ -85,6 +88,7 @@ export class CommentNode extends Disposable { @IModelService private modelService: IModelService, @IModeService private modeService: IModeService, @IDialogService private dialogService: IDialogService, + @IKeybindingService private keybindingService: IKeybindingService, @INotificationService private notificationService: INotificationService, @IContextMenuService private contextMenuService: IContextMenuService ) { @@ -139,7 +143,7 @@ export class CommentNode extends Disposable { } private createActionsToolbar() { - const actions: Action[] = []; + const actions: IAction[] = []; let reactionGroup = this.commentService.getReactionGroup(this.owner); if (reactionGroup && reactionGroup.length) { @@ -163,6 +167,10 @@ export class CommentNode extends Disposable { actions.push(this._deleteAction); } + let commentMenus = this.commentService.getCommentMenus(this.owner); + let titleActions = commentMenus.getCommentTitleActions(this.comment); + actions.push(...titleActions); + if (actions.length) { this.toolbar = new ToolBar(this._actionsToolbarContainer, this.contextMenuService, { actionViewItemProvider: action => { @@ -187,6 +195,7 @@ export class CommentNode extends Disposable { this.registerActionBarListeners(this._actionsToolbarContainer); this.toolbar.setActions(actions, [])(); + this.toolbar.context = this.comment; this._toDispose.push(this.toolbar); } } @@ -196,12 +205,15 @@ export class CommentNode extends Disposable { if (action.id === 'comment.delete' || action.id === 'comment.edit' || action.id === ToggleReactionsAction.ID) { options = { label: false, icon: true }; } else { - options = { label: true, icon: true }; + options = { label: false, icon: true }; } if (action.id === ReactionAction.ID) { let item = new ReactionActionViewItem(action); return item; + } else if (action instanceof MenuItemAction) { + let item = new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return item; } else { let item = new ActionViewItem({}, action, options); return item; @@ -514,39 +526,43 @@ export class CommentNode extends Disposable { private createEditAction(commentDetailsContainer: HTMLElement): Action { return new Action('comment.edit', nls.localize('label.edit', "Edit"), 'octicon octicon-pencil', true, () => { - this.isEditing = true; - this._body.classList.add('hidden'); - this._commentEditContainer = dom.append(commentDetailsContainer, dom.$('.edit-container')); - this.createCommentEditor(); - - this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); - const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); - - const cancelEditButton = new Button(formActions); - cancelEditButton.label = nls.localize('label.cancel', "Cancel"); - this._toDispose.push(attachButtonStyler(cancelEditButton, this.themeService)); - - this._toDispose.push(cancelEditButton.onDidClick(_ => { - this.removeCommentEditor(); - })); - - this._updateCommentButton = new Button(formActions); - this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - this._toDispose.push(attachButtonStyler(this._updateCommentButton, this.themeService)); - - this._toDispose.push(this._updateCommentButton.onDidClick(_ => { - this.editComment(); - })); - - this._commentEditorDisposables.push(this._commentEditor!.onDidChangeModelContent(_ => { - this._updateCommentButton.enabled = !!this._commentEditor!.getValue(); - })); - - this._editAction.enabled = false; - return Promise.resolve(); + return this.editCommentAction(commentDetailsContainer); }); } + private editCommentAction(commentDetailsContainer: HTMLElement) { + this.isEditing = true; + this._body.classList.add('hidden'); + this._commentEditContainer = dom.append(commentDetailsContainer, dom.$('.edit-container')); + this.createCommentEditor(); + + this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); + const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); + + const cancelEditButton = new Button(formActions); + cancelEditButton.label = nls.localize('label.cancel', "Cancel"); + this._toDispose.push(attachButtonStyler(cancelEditButton, this.themeService)); + + this._toDispose.push(cancelEditButton.onDidClick(_ => { + this.removeCommentEditor(); + })); + + this._updateCommentButton = new Button(formActions); + this._updateCommentButton.label = UPDATE_COMMENT_LABEL; + this._toDispose.push(attachButtonStyler(this._updateCommentButton, this.themeService)); + + this._toDispose.push(this._updateCommentButton.onDidClick(_ => { + this.editComment(); + })); + + this._commentEditorDisposables.push(this._commentEditor!.onDidChangeModelContent(_ => { + this._updateCommentButton.enabled = !!this._commentEditor!.getValue(); + })); + + this._editAction.enabled = false; + return Promise.resolve(); + } + private registerActionBarListeners(actionsContainer: HTMLElement): void { this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseenter', () => { actionsContainer.classList.remove('hidden'); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 595b9c1a4b6..06fce1d7ac8 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, ActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; import { Action, IAction } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; @@ -39,9 +39,14 @@ import { generateUuid } from 'vs/base/common/uuid'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { withNullAsUndefined } from 'vs/base/common/types'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; +import { MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; -const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-x'; +const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-chevron-up'; const COMMENT_SCHEME = 'comment'; @@ -101,7 +106,10 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget @IModelService private modelService: IModelService, @IThemeService private themeService: IThemeService, @ICommentService private commentService: ICommentService, - @IOpenerService private openerService: IOpenerService + @IOpenerService private openerService: IOpenerService, + @IKeybindingService private keybindingService: IKeybindingService, + @INotificationService private notificationService: INotificationService, + @IContextMenuService private contextMenuService: IContextMenuService ) { super(editor, { keepEditorSelection: true }); this._resizeObserver = null; @@ -202,7 +210,18 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.createThreadLabel(); const actionsContainer = dom.append(this._headElement, dom.$('.review-actions')); - this._actionbarWidget = new ActionBar(actionsContainer, {}); + this._actionbarWidget = new ActionBar(actionsContainer, { + actionViewItemProvider: (action: IAction) => { + if (action instanceof MenuItemAction) { + let item = new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + return item; + } else { + let item = new ActionViewItem({}, action, { label: false, icon: true }); + return item; + } + } + }); + this._disposables.push(this._actionbarWidget); this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => this.collapse()); @@ -698,6 +717,25 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget })); }); } + + let actions = this._commentMenus.getCommentThreadActions(commentThread); + + actions.forEach(action => { + const button = new Button(container); + this._disposables.push(attachButtonStyler(button, this.themeService)); + + button.label = action.label; + + this._disposables.push(button.onDidClick(async () => { + action.run({ + thread: this._commentThread, + text: this._commentEditor.getValue(), + $mid: 8 + }); + + this.hideReplyArea(); + })); + }); } private createNewCommentNode(comment: modes.Comment): CommentNode { @@ -849,6 +887,17 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } } + private hideReplyArea() { + this._commentEditor.setValue(''); + this._pendingComment = ''; + if (dom.hasClass(this._commentForm, 'expand')) { + dom.removeClass(this._commentForm, 'expand'); + } + this._commentEditor.getDomNode()!.style.outline = ''; + this._error.textContent = ''; + dom.addClass(this._error, 'hidden'); + } + private createReplyButton() { this._reviewThreadReplyButton = dom.append(this._commentForm, dom.$('button.review-thread-reply-button')); if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { diff --git a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts index 1cd7b487f01..e1888faedda 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsEditorContribution.ts @@ -423,7 +423,7 @@ export class ReviewController implements IEditorContribution { } removed.forEach(thread => { - let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId); + let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId && zoneWidget.commentThread.threadId !== ''); if (matchedZones.length) { let matchedZone = matchedZones[0]; let index = this._commentWidgets.indexOf(matchedZone); diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index aa195a0cd7a..4ed2c4be005 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -198,6 +198,16 @@ background-image: url(./reaction-hc.svg); } +.monaco-editor .review-widget .body .review-comment .comment-title .action-label { + display: block; + height: 18px; + line-height: 18px; + min-width: 28px; + background-size: 16px; + background-position: center center; + background-repeat: no-repeat; +} + .monaco-editor .review-widget .body .review-comment .comment-title .action-label.toolbar-toggle-pickReactions { background-image: url(./reaction.svg); width: 18px; From 537d88f7b103ac108cf7eb1e4ed690f97423b0d6 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 17 May 2019 16:35:14 -0700 Subject: [PATCH 03/22] remove duplicate imports --- .../workbench/contrib/comments/browser/commentThreadWidget.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 06fce1d7ac8..f337ccca2d1 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { ActionBar, ActionViewItem, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; import { Action, IAction } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; From 925426e8b04ec57a7b18e76876cee8aecb191325 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Fri, 17 May 2019 16:52:27 -0700 Subject: [PATCH 04/22] Remove activeCommentThread from extHost comments, set context keys on simple comment editor --- .../api/browser/mainThreadComments.ts | 36 ------------------- .../workbench/api/common/extHost.protocol.ts | 1 - .../workbench/api/common/extHostComments.ts | 24 ------------- .../contrib/comments/browser/commentNode.ts | 7 ++-- .../comments/browser/commentService.ts | 26 +++++--------- .../comments/browser/commentThreadWidget.ts | 12 +++---- .../comments/browser/simpleCommentEditor.ts | 10 +++++- .../comments/common/commentContextKeys.ts | 2 +- 8 files changed, 28 insertions(+), 90 deletions(-) diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index df7790d8e3a..2f9c24b6da1 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -25,8 +25,6 @@ import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'v import { IRange, Range } from 'vs/editor/common/core/range'; import { Emitter, Event } from 'vs/base/common/event'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export class MainThreadDocumentCommentProvider implements modes.DocumentCommentProvider { private readonly _proxy: ExtHostCommentsShape; @@ -450,13 +448,8 @@ export class MainThreadComments extends Disposable implements MainThreadComments private _handlers = new Map(); private _commentControllers = new Map(); - private _activeCommentThread?: MainThreadCommentThread; - private _input?: modes.CommentInput; private _openPanelListener: IDisposable | null; - private _commentThreadEmpty: IContextKey; - private _commentEmpty: IContextKey; - constructor( extHostContext: IExtHostContext, @IEditorService private readonly _editorService: IEditorService, @@ -469,35 +462,6 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._disposables = []; this._activeCommentThreadDisposables = []; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); - - this._commentThreadEmpty = CommentContextKeys.commentThreadIsEmpty.bindTo(_commentService.contextKeyService); - this._commentEmpty = CommentContextKeys.commentIsEmpty.bindTo(_commentService.contextKeyService); - this._commentThreadEmpty.set(true); - this._commentEmpty.set(true); - - this._disposables.push(this._commentService.onDidChangeActiveCommentThread(async thread => { - let handle = (thread as MainThreadCommentThread).controllerHandle; - let controller = this._commentControllers.get(handle); - - if (!controller) { - return; - } - - this._activeCommentThreadDisposables = dispose(this._activeCommentThreadDisposables); - this._activeCommentThread = thread as MainThreadCommentThread; - controller.activeCommentThread = this._activeCommentThread; - - this._commentThreadEmpty.set(controller.activeCommentThread.comments!.length === 0); - - this._activeCommentThreadDisposables.push(this._activeCommentThread.onDidChangeInput(input => { // todo, dispose - this._input = input; - this._proxy.$onCommentWidgetInputChange(handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread!.range, this._input ? this._input.value : undefined); - this._commentEmpty.set(this._input ? true : this._input!.value.length === 0); - })); - - await this._proxy.$onActiveCommentThreadChange(controller.handle, controller.activeCommentThread.commentThreadHandle); - await this._proxy.$onCommentWidgetInputChange(controller.handle, URI.parse(this._activeCommentThread!.resource), this._activeCommentThread.range, this._input ? this._input.value : undefined); - })); } $registerCommentController(handle: number, id: string, label: string): void { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 609c00305cf..b32a1fd51ee 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -1212,7 +1212,6 @@ export interface ExtHostCommentsShape { $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; $createCommentThreadTemplate(commentControllerHandle: number, uriComponents: UriComponents, range: IRange): void; $onCommentWidgetInputChange(commentControllerHandle: number, document: UriComponents, range: IRange, input: string | undefined): Promise; - $onActiveCommentThreadChange(commentControllerHandle: number, threadHandle: number | undefined): Promise; $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; $checkStaticContribution(commentControllerHandle: number): Promise; $provideReactionGroup(commentControllerHandle: number): Promise; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 3182fb118d0..225cd2b2753 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -126,17 +126,6 @@ export class ExtHostComments implements ExtHostCommentsShape { return Promise.resolve(commentControllerHandle); } - $onActiveCommentThreadChange(commentControllerHandle: number, threadHandle: number): Promise { - const commentController = this._commentControllers.get(commentControllerHandle); - - if (!commentController) { - return Promise.resolve(undefined); - } - - commentController.$onActiveCommentThreadChange(threadHandle); - return Promise.resolve(threadHandle); - } - $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise { const commentController = this._commentControllers.get(commentControllerHandle); @@ -644,15 +633,6 @@ class ExtHostCommentController implements vscode.CommentController { } public inputBox: ExtHostCommentInputBox | undefined; - private _activeCommentThread: ExtHostCommentThread | undefined; - - public get activeCommentThread(): ExtHostCommentThread | undefined { - if (this._activeCommentThread && this._activeCommentThread.isDisposed) { - this._activeCommentThread = undefined; - } - - return this._activeCommentThread; - } public activeCommentingRange?: vscode.Range; @@ -716,10 +696,6 @@ class ExtHostCommentController implements vscode.CommentController { } } - $onActiveCommentThreadChange(threadHandle: number) { - this._activeCommentThread = this.getCommentThread(threadHandle); - } - getCommentThread(handle: number) { return this._threads.get(handle); } diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 1e5b13762f7..2bc22702044 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -403,14 +403,14 @@ export class CommentNode extends Disposable { uri: this._commentEditor.getModel()!.uri, value: this.comment.body.value }; - this.commentService.setActiveCommentThread(commentThread); + this.commentService.onDidChangeActiveCommentThread(commentThread); this._commentEditorDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { commentThread.input = { uri: this._commentEditor!.getModel()!.uri, value: this.comment.body.value }; - this.commentService.setActiveCommentThread(commentThread); + this.commentService.onDidChangeActiveCommentThread(commentThread); })); this._commentEditorDisposables.push(this._commentEditor.onDidChangeModelContent(e => { @@ -462,7 +462,7 @@ export class CommentNode extends Disposable { uri: this._commentEditor.getModel()!.uri, value: newBody }; - this.commentService.setActiveCommentThread(commentThread); + this.commentService.onDidChangeActiveCommentThread(commentThread); let commandId = this.comment.editCommand.id; let args = this.comment.editCommand.arguments || []; @@ -500,7 +500,6 @@ export class CommentNode extends Disposable { if (result.confirmed) { try { if (this.comment.deleteCommand) { - this.commentService.setActiveCommentThread(this.commentThread as modes.CommentThread2); let commandId = this.comment.deleteCommand.id; let args = this.comment.deleteCommand.arguments || []; diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index bcfe26b9316..6a25b32ae2c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -15,8 +15,8 @@ import { assign } from 'vs/base/common/objects'; import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IContext } from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export const ICommentService = createDecorator('commentService'); @@ -40,9 +40,7 @@ export interface ICommentService { readonly onDidSetResourceCommentInfos: Event; readonly onDidSetAllCommentThreads: Event; readonly onDidUpdateCommentThreads: Event; - readonly onDidChangeActiveCommentThread: Event; readonly onDidChangeActiveCommentingRange: Event<{ range: Range, commentingRangesInfo: CommentingRanges }>; - readonly onDidChangeInput: Event; readonly onDidSetDataProvider: Event; readonly onDidDeleteDataProvider: Event; readonly contextKeyService: IContextKeyService; @@ -72,8 +70,7 @@ export interface ICommentService { deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; getReactionGroup(owner: string): CommentReaction[] | undefined; toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; - setActiveCommentThread(commentThread: CommentThread | null): void; - setInput(input: string): void; + onDidChangeActiveCommentThread(commentThread: CommentThread | null): void; } export class CommentService extends Disposable implements ICommentService { @@ -94,11 +91,6 @@ export class CommentService extends Disposable implements ICommentService { private readonly _onDidUpdateCommentThreads: Emitter = this._register(new Emitter()); readonly onDidUpdateCommentThreads: Event = this._onDidUpdateCommentThreads.event; - private readonly _onDidChangeActiveCommentThread = this._register(new Emitter()); - readonly onDidChangeActiveCommentThread: Event = this._onDidChangeActiveCommentThread.event; - - private readonly _onDidChangeInput: Emitter = this._register(new Emitter()); - readonly onDidChangeInput: Event = this._onDidChangeInput.event; private readonly _onDidChangeActiveCommentingRange: Emitter<{ range: Range, commentingRangesInfo: CommentingRanges @@ -112,6 +104,9 @@ export class CommentService extends Disposable implements ICommentService { private _commentControls = new Map(); private _commentMenus = new Map(); + + private _activeThreadIsEmpty: IContextKey; + contextKeyService: IContextKeyService; constructor( @@ -120,14 +115,11 @@ export class CommentService extends Disposable implements ICommentService { ) { super(); this.contextKeyService = contextKeyService.createScoped(); + this._activeThreadIsEmpty = CommentContextKeys.commentThreadIsEmpty.bindTo(this.contextKeyService); } - setActiveCommentThread(commentThread: CommentThread | null) { - this._onDidChangeActiveCommentThread.fire(commentThread); - } - - setInput(input: string) { - this._onDidChangeInput.fire(input); + onDidChangeActiveCommentThread(commentThread: CommentThread | null) { + this._activeThreadIsEmpty.set(!!commentThread && !!commentThread.comments && !!commentThread.comments.length); } setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index f337ccca2d1..79640fe0665 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -199,7 +199,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget container.appendChild(this._bodyElement); dom.addDisposableListener(this._bodyElement, dom.EventType.FOCUS_IN, e => { - this.commentService.setActiveCommentThread(this._commentThread); + this.commentService.onDidChangeActiveCommentThread(this._commentThread); }); } @@ -243,7 +243,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } else { const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand; if (deleteCommand) { - this.commentService.setActiveCommentThread(this._commentThread); + this.commentService.onDidChangeActiveCommentThread(this._commentThread); return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); } else if (this._commentEditor.getValue() === '') { this.dispose(); @@ -475,7 +475,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; - this.commentService.setActiveCommentThread(this._commentThread); + this.commentService.onDidChangeActiveCommentThread(this._commentThread); })); this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { @@ -687,7 +687,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; - this.commentService.setActiveCommentThread(this._commentThread); + this.commentService.onDidChangeActiveCommentThread(this._commentThread); await this.commandService.executeCommand(acceptInputCommand.id, ...(acceptInputCommand.arguments || [])); })); @@ -712,7 +712,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; - this.commentService.setActiveCommentThread(this._commentThread); + this.commentService.onDidChangeActiveCommentThread(this._commentThread); await this.commandService.executeCommand(command.id, ...(command.arguments || [])); })); }); @@ -783,7 +783,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; - this.commentService.setActiveCommentThread(this._commentThread); + this.commentService.onDidChangeActiveCommentThread(this._commentThread); let commandId = commentThread.acceptInputCommand.id; let args = commentThread.acceptInputCommand.arguments || []; diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index f158e3f3294..f30b5f16a29 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -22,6 +22,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export const ctxCommentEditorFocused = new RawContextKey('commentEditorFocused', false); @@ -30,6 +31,7 @@ export class SimpleCommentEditor extends CodeEditorWidget { private _parentEditor: ICodeEditor; private _parentThread: ICommentThreadWidget; private _commentEditorFocused: IContextKey; + private _commentEditorEmpty: IContextKey; constructor( domElement: HTMLElement, @@ -57,10 +59,16 @@ export class SimpleCommentEditor extends CodeEditorWidget { super(domElement, options, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); this._commentEditorFocused = ctxCommentEditorFocused.bindTo(this._contextKeyService); + this._commentEditorEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService); this._parentEditor = parentEditor; this._parentThread = parentThread; - this._register(this.onDidFocusEditorWidget(_ => this._commentEditorFocused.set(true))); + this._register(this.onDidFocusEditorWidget(_ => { + this._commentEditorFocused.set(true); + this._commentEditorEmpty.set(!this.getValue()); + })); + + this._register(this.onDidChangeModelContent(e => this._commentEditorEmpty.set(!this.getValue()))); this._register(this.onDidBlurEditorWidget(_ => this._commentEditorFocused.reset())); } diff --git a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts index b58660a19cc..c4af337d789 100644 --- a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts +++ b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export namespace CommentContextKeys { /** From 7554ac4387d69f0d4e3a6e7d34cf6822bc097678 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 17 May 2019 18:16:14 -0700 Subject: [PATCH 05/22] Edit comment --- .../workbench/api/common/extHostComments.ts | 60 ++++++++++++++++++ .../contrib/comments/browser/commentNode.ts | 61 +++++++++++++++++-- 2 files changed, 117 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 225cd2b2753..5411bbc9f77 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -86,6 +86,53 @@ export class ExtHostComments implements ExtHostCommentsShape { thread: commentThread, text: arg.text }; + } else if (arg && arg.$mid === 9) { + const commentController = this._commentControllers.get(arg.thread.commentControlHandle); + + if (!commentController) { + return arg; + } + + const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle); + + if (!commentThread) { + return arg; + } + + let commentUniqueId = arg.commentUniqueId; + + let comment = commentThread.getCommentByUniqueId(commentUniqueId); + + if (!comment) { + return arg; + } + + return comment; + + } else if (arg && arg.$mid === 10) { + const commentController = this._commentControllers.get(arg.thread.commentControlHandle); + + if (!commentController) { + return arg; + } + + const commentThread = commentController.getCommentThread(arg.thread.commentThreadHandle); + + if (!commentThread) { + return arg; + } + + let body = arg.text; + let commentUniqueId = arg.commentUniqueId; + + let comment = commentThread.getCommentByUniqueId(commentUniqueId); + + if (!comment) { + return arg; + } + + comment.body = body; + return comment; } return arg; @@ -572,6 +619,18 @@ export class ExtHostCommentThread implements vscode.CommentThread { return undefined; } + getCommentByUniqueId(uniqueId: number): vscode.Comment | undefined { + for (let key of this._commentsMap) { + let comment = key[0]; + let id = key[1]; + if (uniqueId === id) { + return comment; + } + } + + return; + } + dispose() { this._localDisposables.forEach(disposable => disposable.dispose()); this._proxy.$deleteCommentThread( @@ -787,6 +846,7 @@ function convertToModeComment2(thread: ExtHostCommentThread, commentController: return { commentId: vscodeComment.id || vscodeComment.commentId, + mode: vscodeComment.mode, uniqueIdInThread: commentUniqueId, body: extHostTypeConverter.MarkdownString.from(vscodeComment.body), userName: vscodeComment.author ? vscodeComment.author.name : vscodeComment.userName, diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 2bc22702044..ef0f9951425 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -36,7 +36,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); @@ -193,9 +193,14 @@ export class CommentNode extends Disposable { orientation: ActionsOrientation.HORIZONTAL }); + this.toolbar.context = { + thread: this.commentThread, + commentUniqueId: this.comment.uniqueIdInThread, + $mid: 9 + }; + this.registerActionBarListeners(this._actionsToolbarContainer); this.toolbar.setActions(actions, [])(); - this.toolbar.context = this.comment; this._toDispose.push(this.toolbar); } } @@ -212,7 +217,7 @@ export class CommentNode extends Disposable { let item = new ReactionActionViewItem(action); return item; } else if (action instanceof MenuItemAction) { - let item = new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + let item = new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); return item; } else { let item = new ActionViewItem({}, action, options); @@ -431,7 +436,9 @@ export class CommentNode extends Disposable { private removeCommentEditor() { this.isEditing = false; - this._editAction.enabled = true; + if (this._editAction) { + this._editAction.enabled = true; + } this._body.classList.remove('hidden'); this._commentEditorModel.dispose(); @@ -523,6 +530,44 @@ export class CommentNode extends Disposable { }); } + public switchToEditMode() { + if (this.isEditing) { + return; + } + + this.isEditing = true; + this._body.classList.add('hidden'); + this._commentEditContainer = dom.append(this._commentDetailsContainer, dom.$('.edit-container')); + this.createCommentEditor(); + + this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); + const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); + + let menus = this.commentService.getCommentMenus(this.owner); + let actions = menus.getCommentActions(this.comment); + + actions.forEach(action => { + let button = new Button(formActions); + this._toDispose.push(attachButtonStyler(button, this.themeService)); + + button.label = action.label; + + this._toDispose.push(button.onDidClick(async () => { + let text = this._commentEditor!.getValue(); + + action.run({ + thread: this.commentThread, + commentUniqueId: this.comment.uniqueIdInThread, + text: text, + $mid: 10 + }); + + // this.hideReplyArea(); + this.removeCommentEditor(); + })); + }); + } + private createEditAction(commentDetailsContainer: HTMLElement): Action { return new Action('comment.edit', nls.localize('label.edit', "Edit"), 'octicon octicon-pencil', true, () => { return this.editCommentAction(commentDetailsContainer); @@ -625,6 +670,14 @@ export class CommentNode extends Disposable { if (this.comment.commentReactions && this.comment.commentReactions.length) { this.createReactionsContainer(this._commentDetailsContainer); } + + if (this.comment.mode !== undefined) { + if (this.comment.mode === modes.CommentMode.Editing) { + this.switchToEditMode(); + } else { + this.removeCommentEditor(); + } + } } focus() { From a3e7e68c2a42775f18896000b5e180450d68dd3c Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 17 May 2019 18:31:10 -0700 Subject: [PATCH 06/22] Save comment and cancel --- .../contrib/comments/browser/commentNode.ts | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index ef0f9951425..7ee2022e399 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -441,7 +441,10 @@ export class CommentNode extends Disposable { } this._body.classList.remove('hidden'); - this._commentEditorModel.dispose(); + if (this._commentEditorModel) { + this._commentEditorModel.dispose(); + } + this._commentEditorDisposables.forEach(dispose => dispose.dispose()); this._commentEditorDisposables = []; if (this._commentEditor) { @@ -539,10 +542,17 @@ export class CommentNode extends Disposable { this._body.classList.add('hidden'); this._commentEditContainer = dom.append(this._commentDetailsContainer, dom.$('.edit-container')); this.createCommentEditor(); - this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); + // const cancelEditButton = new Button(formActions); + // cancelEditButton.label = nls.localize('label.cancel', "Cancel"); + // this._toDispose.push(attachButtonStyler(cancelEditButton, this.themeService)); + + // this._toDispose.push(cancelEditButton.onDidClick(_ => { + // this.removeCommentEditor(); + // })); + let menus = this.commentService.getCommentMenus(this.owner); let actions = menus.getCommentActions(this.comment); @@ -641,6 +651,14 @@ export class CommentNode extends Disposable { this._body.appendChild(this._md); } + if (newComment.mode !== undefined && newComment.mode !== this.comment.mode) { + if (newComment.mode === modes.CommentMode.Editing) { + this.switchToEditMode(); + } else { + this.removeCommentEditor(); + } + } + const shouldUpdateActions = newComment.editCommand !== this.comment.editCommand || newComment.deleteCommand !== this.comment.deleteCommand; this.comment = newComment; @@ -670,14 +688,6 @@ export class CommentNode extends Disposable { if (this.comment.commentReactions && this.comment.commentReactions.length) { this.createReactionsContainer(this._commentDetailsContainer); } - - if (this.comment.mode !== undefined) { - if (this.comment.mode === modes.CommentMode.Editing) { - this.switchToEditMode(); - } else { - this.removeCommentEditor(); - } - } } focus() { From 7285e7659bb86ed0ab6c90d63795b1bef74d52b3 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Fri, 17 May 2019 18:40:49 -0700 Subject: [PATCH 07/22] Contextaware menu for comment thread --- .../workbench/contrib/comments/browser/commentThreadWidget.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 79640fe0665..97edd7fa777 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -40,10 +40,10 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { withNullAsUndefined } from 'vs/base/common/types'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; import { MenuItemAction } from 'vs/platform/actions/common/actions'; -import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-chevron-up'; @@ -213,7 +213,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._actionbarWidget = new ActionBar(actionsContainer, { actionViewItemProvider: (action: IAction) => { if (action instanceof MenuItemAction) { - let item = new MenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + let item = new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService); return item; } else { let item = new ActionViewItem({}, action, { label: false, icon: true }); From 82127d5c22754f06f87249fd62bc32e4a074c539 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 20 May 2019 11:42:34 -0700 Subject: [PATCH 08/22] Scope context key service by thread and comment --- .../contrib/comments/browser/commentMenus.ts | 32 +++++++------------ .../contrib/comments/browser/commentNode.ts | 13 ++++---- .../comments/browser/commentService.ts | 19 ++--------- .../comments/browser/commentThreadWidget.ts | 25 ++++++++------- 4 files changed, 33 insertions(+), 56 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index bec3348343d..929ca896a4b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -5,7 +5,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -13,14 +13,9 @@ import { Comment, CommentThread2 } from 'vs/editor/common/modes'; import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export class CommentMenus implements IDisposable { - private titleMenu: IMenu; - private titleActions: IAction[] = []; - - private readonly disposables: IDisposable[] = []; - constructor( controller: MainThreadCommentController, - private contextKeyService: IContextKeyService, + @IContextKeyService private contextKeyService: IContextKeyService, @IMenuService private readonly menuService: IMenuService, @IContextMenuService private readonly contextMenuService: IContextMenuService ) { @@ -29,25 +24,23 @@ export class CommentMenus implements IDisposable { commentControllerKey.set(controller.contextValue); } - getCommentThreadTitleActions(commentThread: CommentThread2): IAction[] { - return this.getActions(MenuId.CommentThreadTitle).primary; + getCommentThreadTitleActions(commentThread: CommentThread2, contextKeyService: IContextKeyService): IAction[] { + return this.getActions(MenuId.CommentThreadTitle, contextKeyService).primary; } - getCommentThreadActions(commentThread: CommentThread2): IAction[] { - return this.getActions(MenuId.CommentThreadActions).primary; + getCommentThreadActions(commentThread: CommentThread2, contextKeyService: IContextKeyService): IAction[] { + return this.getActions(MenuId.CommentThreadActions, contextKeyService).primary; } - getCommentTitleActions(comment: Comment): IAction[] { - return this.getActions(MenuId.CommentTitle).primary; + getCommentTitleActions(comment: Comment, contextKeyService: IContextKeyService): IAction[] { + return this.getActions(MenuId.CommentTitle, contextKeyService).primary; } - getCommentActions(comment: Comment): IAction[] { - return this.getActions(MenuId.CommentActions).primary; + getCommentActions(comment: Comment, contextKeyService: IContextKeyService): IAction[] { + return this.getActions(MenuId.CommentActions, contextKeyService).primary; } - private getActions(menuId: MenuId) { - const contextKeyService = this.contextKeyService.createScoped(); - + private getActions(menuId: MenuId, contextKeyService: IContextKeyService) { const menu = this.menuService.createMenu(menuId, contextKeyService); const primary: IAction[] = []; const secondary: IAction[] = []; @@ -56,10 +49,7 @@ export class CommentMenus implements IDisposable { fillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => true); menu.dispose(); - contextKeyService.dispose(); - return result; - } dispose(): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 7ee2022e399..55d518ad67b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -38,6 +38,7 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); const UPDATE_IN_PROGRESS_LABEL = nls.localize('label.updatingComment', "Updating comment..."); @@ -60,6 +61,7 @@ export class CommentNode extends Disposable { private _updateCommentButton: Button; private _errorEditingContainer: HTMLElement; private _isPendingLabel: HTMLElement; + private _contextKeyService: IContextKeyService; private _deleteAction: Action; protected actionRunner?: IActionRunner; @@ -90,11 +92,13 @@ export class CommentNode extends Disposable { @IDialogService private dialogService: IDialogService, @IKeybindingService private keybindingService: IKeybindingService, @INotificationService private notificationService: INotificationService, - @IContextMenuService private contextMenuService: IContextMenuService + @IContextMenuService private contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(); this._domNode = dom.$('div.review-comment'); + this._contextKeyService = contextKeyService.createScoped(this._domNode); this._domNode.tabIndex = 0; const avatar = dom.append(this._domNode, dom.$('div.avatar-container')); if (comment.userIconPath) { @@ -168,7 +172,7 @@ export class CommentNode extends Disposable { } let commentMenus = this.commentService.getCommentMenus(this.owner); - let titleActions = commentMenus.getCommentTitleActions(this.comment); + let titleActions = commentMenus.getCommentTitleActions(this.comment, this._contextKeyService); actions.push(...titleActions); if (actions.length) { @@ -408,14 +412,12 @@ export class CommentNode extends Disposable { uri: this._commentEditor.getModel()!.uri, value: this.comment.body.value }; - this.commentService.onDidChangeActiveCommentThread(commentThread); this._commentEditorDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { commentThread.input = { uri: this._commentEditor!.getModel()!.uri, value: this.comment.body.value }; - this.commentService.onDidChangeActiveCommentThread(commentThread); })); this._commentEditorDisposables.push(this._commentEditor.onDidChangeModelContent(e => { @@ -472,7 +474,6 @@ export class CommentNode extends Disposable { uri: this._commentEditor.getModel()!.uri, value: newBody }; - this.commentService.onDidChangeActiveCommentThread(commentThread); let commandId = this.comment.editCommand.id; let args = this.comment.editCommand.arguments || []; @@ -554,7 +555,7 @@ export class CommentNode extends Disposable { // })); let menus = this.commentService.getCommentMenus(this.owner); - let actions = menus.getCommentActions(this.comment); + let actions = menus.getCommentActions(this.comment, this._contextKeyService); actions.forEach(action => { let button = new Button(formActions); diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index 6a25b32ae2c..e3a3a82c32d 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -15,8 +15,6 @@ import { assign } from 'vs/base/common/objects'; import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export const ICommentService = createDecorator('commentService'); @@ -43,7 +41,6 @@ export interface ICommentService { readonly onDidChangeActiveCommentingRange: Event<{ range: Range, commentingRangesInfo: CommentingRanges }>; readonly onDidSetDataProvider: Event; readonly onDidDeleteDataProvider: Event; - readonly contextKeyService: IContextKeyService; setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void; setWorkspaceComments(owner: string, commentsByResource: CommentThread[] | CommentThread2[]): void; removeWorkspaceComments(owner: string): void; @@ -70,7 +67,6 @@ export interface ICommentService { deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; getReactionGroup(owner: string): CommentReaction[] | undefined; toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; - onDidChangeActiveCommentThread(commentThread: CommentThread | null): void; } export class CommentService extends Disposable implements ICommentService { @@ -105,21 +101,10 @@ export class CommentService extends Disposable implements ICommentService { private _commentControls = new Map(); private _commentMenus = new Map(); - private _activeThreadIsEmpty: IContextKey; - - contextKeyService: IContextKeyService; - constructor( - @IInstantiationService protected instantiationService: IInstantiationService, - @IContextKeyService contextKeyService: IContextKeyService + @IInstantiationService protected instantiationService: IInstantiationService ) { super(); - this.contextKeyService = contextKeyService.createScoped(); - this._activeThreadIsEmpty = CommentContextKeys.commentThreadIsEmpty.bindTo(this.contextKeyService); - } - - onDidChangeActiveCommentThread(commentThread: CommentThread | null) { - this._activeThreadIsEmpty.set(!!commentThread && !!commentThread.comments && !!commentThread.comments.length); } setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void { @@ -161,7 +146,7 @@ export class CommentService extends Disposable implements ICommentService { let controller = this._commentControls.get(owner); - let menu = this.instantiationService.createInstance(CommentMenus, controller!, this.contextKeyService); + let menu = this.instantiationService.createInstance(CommentMenus, controller!); this._commentMenus.set(owner, menu); return menu; } diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 97edd7fa777..594d8f47a85 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -44,6 +44,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-chevron-up'; @@ -76,6 +78,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _styleElement: HTMLStyleElement; private _formActions: HTMLElement | null; private _error: HTMLElement; + private _contextKeyService: IContextKeyService; + private _threadIsEmpty: IContextKey; public get owner(): string { return this._owner; @@ -109,9 +113,14 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget @IOpenerService private openerService: IOpenerService, @IKeybindingService private keybindingService: IKeybindingService, @INotificationService private notificationService: INotificationService, - @IContextMenuService private contextMenuService: IContextMenuService + @IContextMenuService private contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(editor, { keepEditorSelection: true }); + this._contextKeyService = contextKeyService.createScoped(this.domNode); + this._threadIsEmpty = CommentContextKeys.commentThreadIsEmpty.bindTo(this._contextKeyService); + this._threadIsEmpty.set(!_commentThread.comments || !_commentThread.comments.length); + this._resizeObserver = null; this._isExpanded = _commentThread.collapsibleState ? _commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded : undefined; this._globalToDispose = []; @@ -197,10 +206,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._bodyElement = dom.$('.body'); container.appendChild(this._bodyElement); - - dom.addDisposableListener(this._bodyElement, dom.EventType.FOCUS_IN, e => { - this.commentService.onDidChangeActiveCommentThread(this._commentThread); - }); } protected _fillHead(container: HTMLElement): void { @@ -228,7 +233,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget let secondaryActions: IAction[] = []; if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - secondaryActions = this._commentMenus.getCommentThreadTitleActions(this._commentThread as modes.CommentThread2); + secondaryActions = this._commentMenus.getCommentThreadTitleActions(this._commentThread as modes.CommentThread2, this._contextKeyService); } this._actionbarWidget.push([...secondaryActions, this._collapseAction], { label: false, icon: true }); @@ -243,7 +248,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget } else { const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand; if (deleteCommand) { - this.commentService.onDidChangeActiveCommentThread(this._commentThread); return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || [])); } else if (this._commentEditor.getValue() === '') { this.dispose(); @@ -277,6 +281,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget async update(commentThread: modes.CommentThread | modes.CommentThread2) { const oldCommentsLen = this._commentElements.length; const newCommentsLen = commentThread.comments ? commentThread.comments.length : 0; + this._threadIsEmpty.set(!newCommentsLen); let commentElementsToDel: CommentNode[] = []; let commentElementsToDelIndex: number[] = []; @@ -475,7 +480,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; - this.commentService.onDidChangeActiveCommentThread(this._commentThread); })); this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { @@ -687,7 +691,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; - this.commentService.onDidChangeActiveCommentThread(this._commentThread); await this.commandService.executeCommand(acceptInputCommand.id, ...(acceptInputCommand.arguments || [])); })); @@ -712,13 +715,12 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; - this.commentService.onDidChangeActiveCommentThread(this._commentThread); await this.commandService.executeCommand(command.id, ...(command.arguments || [])); })); }); } - let actions = this._commentMenus.getCommentThreadActions(commentThread); + let actions = this._commentMenus.getCommentThreadActions(commentThread, this._contextKeyService); actions.forEach(action => { const button = new Button(container); @@ -783,7 +785,6 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget uri: this._commentEditor.getModel()!.uri, value: this._commentEditor.getValue() }; - this.commentService.onDidChangeActiveCommentThread(this._commentThread); let commandId = commentThread.acceptInputCommand.id; let args = commentThread.acceptInputCommand.arguments || []; From e0666b43238d9a4b43e710ce42b3a32f0c916215 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 20 May 2019 11:57:04 -0700 Subject: [PATCH 09/22] precondition for commands --- .../workbench/api/common/menusExtensionPoint.ts | 17 +++++++++++++++++ .../comments/browser/commentThreadWidget.ts | 1 + 2 files changed, 18 insertions(+) diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 646324ba825..cc52a02590d 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -21,6 +21,7 @@ namespace schema { export interface IUserFriendlyMenuItem { command: string; alt?: string; + precondition?: string; when?: string; group?: string; } @@ -78,6 +79,10 @@ namespace schema { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'alt')); return false; } + if (item.precondition && typeof item.precondition !== 'string') { + collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'precondition')); + return false; + } if (item.when && typeof item.when !== 'string') { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'when')); return false; @@ -102,6 +107,10 @@ namespace schema { description: localize('vscode.extension.contributes.menuItem.alt', 'Identifier of an alternative command to execute. The command must be declared in the \'commands\'-section'), type: 'string' }, + precondition: { + description: localize('vscode.extension.contributes.menuItem.precondition', 'Condition which must be true to enable this item'), + type: 'string' + }, when: { description: localize('vscode.extension.contributes.menuItem.when', 'Condition which must be true to show this item'), type: 'string' @@ -425,6 +434,14 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM } } + if (item.precondition) { + command.precondition = ContextKeyExpr.deserialize(item.precondition); + } + + if (item.alt && alt && item.precondition) { + alt.precondition = command.precondition; + } + const registration = MenuRegistry.appendMenuItem(menu, { command, alt, diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 594d8f47a85..3859811155a 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -724,6 +724,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget actions.forEach(action => { const button = new Button(container); + button.enabled = action.enabled; this._disposables.push(attachButtonStyler(button, this.themeService)); button.label = action.label; From 569b36c307b451082dd81bda737f7252b24705d5 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 20 May 2019 12:00:00 -0700 Subject: [PATCH 10/22] no need to check item.alt --- src/vs/workbench/api/common/menusExtensionPoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index cc52a02590d..c12f1b41fe8 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -438,7 +438,7 @@ ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyM command.precondition = ContextKeyExpr.deserialize(item.precondition); } - if (item.alt && alt && item.precondition) { + if (alt && item.precondition) { alt.precondition = command.precondition; } From 13d1dcaa2be9c62c43ae62eecc0ca69a38fb1623 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 20 May 2019 14:17:37 -0700 Subject: [PATCH 11/22] Fix initial value for commentIsEmpty context key --- .../contrib/comments/browser/simpleCommentEditor.ts | 10 ++++------ .../contrib/comments/common/commentContextKeys.ts | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts index f30b5f16a29..12a7897fec6 100644 --- a/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/browser/simpleCommentEditor.ts @@ -58,15 +58,13 @@ export class SimpleCommentEditor extends CodeEditorWidget { super(domElement, options, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); - this._commentEditorFocused = ctxCommentEditorFocused.bindTo(this._contextKeyService); - this._commentEditorEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService); + this._commentEditorFocused = ctxCommentEditorFocused.bindTo(contextKeyService); + this._commentEditorEmpty = CommentContextKeys.commentIsEmpty.bindTo(contextKeyService); + this._commentEditorEmpty.set(!this.getValue()); this._parentEditor = parentEditor; this._parentThread = parentThread; - this._register(this.onDidFocusEditorWidget(_ => { - this._commentEditorFocused.set(true); - this._commentEditorEmpty.set(!this.getValue()); - })); + this._register(this.onDidFocusEditorWidget(_ => this._commentEditorFocused.set(true))); this._register(this.onDidChangeModelContent(e => this._commentEditorEmpty.set(!this.getValue()))); this._register(this.onDidBlurEditorWidget(_ => this._commentEditorFocused.reset())); diff --git a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts index c4af337d789..3593481d5ca 100644 --- a/src/vs/workbench/contrib/comments/common/commentContextKeys.ts +++ b/src/vs/workbench/contrib/comments/common/commentContextKeys.ts @@ -7,11 +7,11 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export namespace CommentContextKeys { /** - * A context key that is set when the editor's text has focus (cursor is blinking). + * A context key that is set when the comment thread has no comments. */ export const commentThreadIsEmpty = new RawContextKey('commentThreadIsEmpty', false); /** - * A context key that is set when the editor's text or an editor's widget has focus. + * A context key that is set when the comment has no input. */ export const commentIsEmpty = new RawContextKey('commentIsEmpty', false); } \ No newline at end of file From 4d68f1560ba7255e811bf1e76a5ce2c1c6ef4072 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 20 May 2019 14:51:45 -0700 Subject: [PATCH 12/22] comment editor authority --- .../workbench/contrib/comments/browser/commentService.ts | 5 +++++ .../contrib/comments/browser/commentThreadWidget.ts | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentService.ts b/src/vs/workbench/contrib/comments/browser/commentService.ts index e3a3a82c32d..86ee098d6b2 100644 --- a/src/vs/workbench/contrib/comments/browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/browser/commentService.ts @@ -46,6 +46,7 @@ export interface ICommentService { removeWorkspaceComments(owner: string): void; registerCommentController(owner: string, commentControl: MainThreadCommentController): void; unregisterCommentController(owner: string): void; + getCommentController(owner: string): MainThreadCommentController | undefined; createCommentThreadTemplate(owner: string, resource: URI, range: Range): void; getCommentMenus(owner: string): CommentMenus; registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void; @@ -129,6 +130,10 @@ export class CommentService extends Disposable implements ICommentService { this._onDidDeleteDataProvider.fire(owner); } + getCommentController(owner: string): MainThreadCommentController | undefined { + return this._commentControls.get(owner); + } + createCommentThreadTemplate(owner: string, resource: URI, range: Range): void { const commentController = this._commentControls.get(owner); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 3859811155a..3e4c72e6087 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -420,7 +420,13 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget extensionId: this.extensionId, commentThreadId: this.commentThread.threadId }); - const resource = URI.parse(`${COMMENT_SCHEME}:commentinput-${modeId}.md?${params}`); + + let resource = URI.parse(`${COMMENT_SCHEME}://${this.extensionId}/commentinput-${modeId}.md?${params}`); // TODO. Remove params once extensions adopt authority. + let commentController = this.commentService.getCommentController(this.owner); + if (commentController) { + resource = resource.with({ authority: commentController.id }); + } + const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource.path), resource, false); this._disposables.push(model); this._commentEditor.setModel(model); From 96654db6e527c09a04b9f6a87094fd001126fbc1 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Mon, 20 May 2019 15:05:33 -0700 Subject: [PATCH 13/22] Range can be modified. --- src/vs/vscode.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 6081b3f6ce6..2ae29a02f28 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8976,7 +8976,7 @@ declare module 'vscode' { * The range the comment thread is located within the document. The thread icon will be shown * at the first line of the range. */ - readonly range: Range; + range: Range; /** * The ordered comments of the thread. From aaa55e107000ce41f1421e773b2ef31ad7d0c95d Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Mon, 20 May 2019 17:00:19 -0700 Subject: [PATCH 14/22] Menus update on context key change --- .../contrib/comments/browser/commentMenus.ts | 23 +++--- .../contrib/comments/browser/commentNode.ts | 64 ++++++++++----- .../comments/browser/commentThreadWidget.ts | 79 ++++++++++++++----- 3 files changed, 114 insertions(+), 52 deletions(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index 929ca896a4b..d6b7767814b 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -5,7 +5,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { MainThreadCommentController } from 'vs/workbench/api/browser/mainThreadComments'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -24,23 +24,23 @@ export class CommentMenus implements IDisposable { commentControllerKey.set(controller.contextValue); } - getCommentThreadTitleActions(commentThread: CommentThread2, contextKeyService: IContextKeyService): IAction[] { - return this.getActions(MenuId.CommentThreadTitle, contextKeyService).primary; + getCommentThreadTitleActions(commentThread: CommentThread2, contextKeyService: IContextKeyService): IMenu { + return this.getMenu(MenuId.CommentThreadTitle, contextKeyService); } - getCommentThreadActions(commentThread: CommentThread2, contextKeyService: IContextKeyService): IAction[] { - return this.getActions(MenuId.CommentThreadActions, contextKeyService).primary; + getCommentThreadActions(commentThread: CommentThread2, contextKeyService: IContextKeyService): IMenu { + return this.getMenu(MenuId.CommentThreadActions, contextKeyService); } - getCommentTitleActions(comment: Comment, contextKeyService: IContextKeyService): IAction[] { - return this.getActions(MenuId.CommentTitle, contextKeyService).primary; + getCommentTitleActions(comment: Comment, contextKeyService: IContextKeyService): IMenu { + return this.getMenu(MenuId.CommentTitle, contextKeyService); } - getCommentActions(comment: Comment, contextKeyService: IContextKeyService): IAction[] { - return this.getActions(MenuId.CommentActions, contextKeyService).primary; + getCommentActions(comment: Comment, contextKeyService: IContextKeyService): IMenu { + return this.getMenu(MenuId.CommentActions, contextKeyService); } - private getActions(menuId: MenuId, contextKeyService: IContextKeyService) { + private getMenu(menuId: MenuId, contextKeyService: IContextKeyService): IMenu { const menu = this.menuService.createMenu(menuId, contextKeyService); const primary: IAction[] = []; const secondary: IAction[] = []; @@ -48,8 +48,7 @@ export class CommentMenus implements IDisposable { fillInContextMenuActions(menu, { shouldForwardArgs: true }, result, this.contextMenuService, g => true); - menu.dispose(); - return result; + return menu; } dispose(): void { diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 55d518ad67b..e48a3019a58 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -35,7 +35,7 @@ import { ToggleReactionsAction, ReactionAction, ReactionActionViewItem } from '. import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; -import { MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuItemAction, IMenu } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -172,8 +172,16 @@ export class CommentNode extends Disposable { } let commentMenus = this.commentService.getCommentMenus(this.owner); - let titleActions = commentMenus.getCommentTitleActions(this.comment, this._contextKeyService); - actions.push(...titleActions); + const menu = commentMenus.getCommentTitleActions(this.comment, this._contextKeyService); + this._toDispose.push(menu); + this._toDispose.push(menu.onDidChange(e => { + const contributedActions = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); + // TODO ensure original actions are properly disposed + this.toolbar.setActions(contributedActions); + })); + + const contributedActions = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); + actions.push(...contributedActions); if (actions.length) { this.toolbar = new ToolBar(this._actionsToolbarContainer, this.contextMenuService, { @@ -555,28 +563,44 @@ export class CommentNode extends Disposable { // })); let menus = this.commentService.getCommentMenus(this.owner); - let actions = menus.getCommentActions(this.comment, this._contextKeyService); + const menu = menus.getCommentActions(this.comment, this._contextKeyService); + this._toDispose.push(menu); + this._toDispose.push(menu.onDidChange(() => { + this.createCommentActions(formActions, menu); + })); - actions.forEach(action => { - let button = new Button(formActions); - this._toDispose.push(attachButtonStyler(button, this.themeService)); + // TODO fix disposal + // TODO is backcompat needed here + this.createCommentActions(formActions, menu); - button.label = action.label; + } - this._toDispose.push(button.onDidClick(async () => { - let text = this._commentEditor!.getValue(); + private createCommentActions(container: HTMLElement, menu: IMenu): void { + const groups = menu ? menu.getActions({ shouldForwardArgs: true }) : []; + for (const group of groups) { + const [, actions] = group; - action.run({ - thread: this.commentThread, - commentUniqueId: this.comment.uniqueIdInThread, - text: text, - $mid: 10 - }); + actions.forEach(action => { + let button = new Button(container); + this._toDispose.push(attachButtonStyler(button, this.themeService)); - // this.hideReplyArea(); - this.removeCommentEditor(); - })); - }); + button.label = action.label; + + this._toDispose.push(button.onDidClick(async () => { + let text = this._commentEditor!.getValue(); + + action.run({ + thread: this.commentThread, + commentUniqueId: this.comment.uniqueIdInThread, + text: text, + $mid: 10 + }); + + // this.hideReplyArea(); + this.removeCommentEditor(); + })); + }); + } } private createEditAction(commentDetailsContainer: HTMLElement): Action { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 3e4c72e6087..ec26771d539 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -39,7 +39,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; import { withNullAsUndefined } from 'vs/base/common/types'; import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus'; -import { MenuItemAction } from 'vs/platform/actions/common/actions'; +import { MenuItemAction, IMenu } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -80,6 +80,9 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _error: HTMLElement; private _contextKeyService: IContextKeyService; private _threadIsEmpty: IContextKey; + private _commentEditorIsEmpty: IContextKey; + private _commentThreadActionDisposables: IDisposable[] = []; + private _commentThreadActionButtons: HTMLElement[] = []; public get owner(): string { return this._owner; @@ -231,15 +234,26 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => this.collapse()); - let secondaryActions: IAction[] = []; if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { - secondaryActions = this._commentMenus.getCommentThreadTitleActions(this._commentThread as modes.CommentThread2, this._contextKeyService); + const menu = this._commentMenus.getCommentThreadTitleActions(this._commentThread as modes.CommentThread2, this._contextKeyService); + this.setActionBarActions(menu); + + this._disposables.push(menu); + this._disposables.push(menu.onDidChange(e => { + this.setActionBarActions(menu); + })); + } else { + this._actionbarWidget.push([this._collapseAction], { label: false, icon: true }); } - this._actionbarWidget.push([...secondaryActions, this._collapseAction], { label: false, icon: true }); this._actionbarWidget.context = this._commentThread; } + private setActionBarActions(menu: IMenu): void { + const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); + this._actionbarWidget.push([...groups, this._collapseAction], { label: false, icon: true }); + } + public collapse(): Promise { if (this._commentThread.comments && this._commentThread.comments.length === 0) { if ((this._commentThread as modes.CommentThread2).commentThreadHandle === undefined) { @@ -414,6 +428,8 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0; this._commentForm = dom.append(this._bodyElement, dom.$('.comment-form')); this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this); + this._commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService); + this._commentEditorIsEmpty.set(!this._pendingComment); const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID); const params = JSON.stringify({ @@ -431,7 +447,11 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._disposables.push(model); this._commentEditor.setModel(model); this._disposables.push(this._commentEditor); - this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => this.setCommentEditorDecorations())); + this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { + this.setCommentEditorDecorations(); + this._commentEditorIsEmpty.set(!this._commentEditor.getValue()); + })); + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { this.createTextModelListener(); } @@ -726,25 +746,44 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget }); } - let actions = this._commentMenus.getCommentThreadActions(commentThread, this._contextKeyService); + const menu = this._commentMenus.getCommentThreadActions(commentThread, this._contextKeyService); - actions.forEach(action => { - const button = new Button(container); - button.enabled = action.enabled; - this._disposables.push(attachButtonStyler(button, this.themeService)); + this._disposables.push(menu); + this._disposables.push(menu.onDidChange(() => { + this.createCommentThreadActions(container, menu); + })); - button.label = action.label; + this.createCommentThreadActions(container, menu); + } - this._disposables.push(button.onDidClick(async () => { - action.run({ - thread: this._commentThread, - text: this._commentEditor.getValue(), - $mid: 8 - }); + private createCommentThreadActions(container: HTMLElement, menu: IMenu | undefined): void { + this._commentThreadActionDisposables.forEach(d => d.dispose()); + this._commentThreadActionButtons.forEach(b => dom.removeNode(b)); + this._commentThreadActionDisposables = []; + const groups = menu ? menu.getActions({ shouldForwardArgs: true }) : []; + for (const group of groups) { + const [, actions] = group; - this.hideReplyArea(); - })); - }); + actions.forEach(action => { + const button = new Button(container); + this._commentThreadActionButtons.push(button.element); + this._commentThreadActionDisposables.push(button); + button.enabled = action.enabled; + this._commentThreadActionDisposables.push(attachButtonStyler(button, this.themeService)); + + button.label = action.label; + + this._commentThreadActionDisposables.push(button.onDidClick(async () => { + action.run({ + thread: this._commentThread, + text: this._commentEditor.getValue(), + $mid: 8 + }); + + this.hideReplyArea(); + })); + }); + } } private createNewCommentNode(comment: modes.Comment): CommentNode { From 95c4bd3317f97e1c33cd507fb30ccccbc7cd0ffb Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Tue, 21 May 2019 11:33:59 -0700 Subject: [PATCH 15/22] Clear comment thread action bar when updating --- src/vs/workbench/contrib/comments/browser/commentNode.ts | 1 - src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index e48a3019a58..782823bbcb8 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -176,7 +176,6 @@ export class CommentNode extends Disposable { this._toDispose.push(menu); this._toDispose.push(menu.onDidChange(e => { const contributedActions = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); - // TODO ensure original actions are properly disposed this.toolbar.setActions(contributedActions); })); diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index ec26771d539..48b4d6647c9 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -251,6 +251,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private setActionBarActions(menu: IMenu): void { const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], []); + this._actionbarWidget.clear(); this._actionbarWidget.push([...groups, this._collapseAction], { label: false, icon: true }); } From c930494b2985892299ef02b7ae46a340569a46c4 Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Tue, 21 May 2019 15:04:32 -0700 Subject: [PATCH 16/22] Extract comment form actions to own class --- .../comments/browser/commentFormActions.ts | 46 +++++++++++++++ .../contrib/comments/browser/commentNode.ts | 59 ++++++------------- .../comments/browser/commentThreadWidget.ts | 46 ++++----------- 3 files changed, 77 insertions(+), 74 deletions(-) create mode 100644 src/vs/workbench/contrib/comments/browser/commentFormActions.ts diff --git a/src/vs/workbench/contrib/comments/browser/commentFormActions.ts b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts new file mode 100644 index 00000000000..72f669eda8a --- /dev/null +++ b/src/vs/workbench/contrib/comments/browser/commentFormActions.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { IAction } from 'vs/base/common/actions'; +import { Disposable, dispose } from 'vs/base/common/lifecycle'; +import { IMenu } from 'vs/platform/actions/common/actions'; +import { attachButtonStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; + +export class CommentFormActions extends Disposable { + private _buttonElements: HTMLElement[] = []; + + constructor( + private container: HTMLElement, + private actionHandler: (action: IAction) => void, + private themeService: IThemeService + ) { + super(); + } + + setActions(menu: IMenu) { + dispose(this._toDispose); + this._buttonElements.forEach(b => DOM.removeNode(b)); + + const groups = menu.getActions({ shouldForwardArgs: true }); + for (const group of groups) { + const [, actions] = group; + + actions.forEach(action => { + const button = new Button(this.container); + this._buttonElements.push(button.element); + + this._toDispose.push(button); + this._toDispose.push(attachButtonStyler(button, this.themeService)); + this._toDispose.push(button.onDidClick(() => this.actionHandler(action))); + + button.enabled = action.enabled; + button.label = action.label; + }); + } + } +} \ No newline at end of file diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 782823bbcb8..139b9b867ea 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -35,10 +35,11 @@ import { ToggleReactionsAction, ReactionAction, ReactionActionViewItem } from '. import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; -import { MenuItemAction, IMenu } from 'vs/platform/actions/common/actions'; +import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); const UPDATE_IN_PROGRESS_LABEL = nls.localize('label.updatingComment', "Updating comment..."); @@ -66,6 +67,7 @@ export class CommentNode extends Disposable { private _deleteAction: Action; protected actionRunner?: IActionRunner; protected toolbar: ToolBar; + private _commentFormActions: CommentFormActions; private _onDidDelete = new Emitter(); @@ -553,53 +555,28 @@ export class CommentNode extends Disposable { this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); - // const cancelEditButton = new Button(formActions); - // cancelEditButton.label = nls.localize('label.cancel', "Cancel"); - // this._toDispose.push(attachButtonStyler(cancelEditButton, this.themeService)); - - // this._toDispose.push(cancelEditButton.onDidClick(_ => { - // this.removeCommentEditor(); - // })); - - let menus = this.commentService.getCommentMenus(this.owner); + const menus = this.commentService.getCommentMenus(this.owner); const menu = menus.getCommentActions(this.comment, this._contextKeyService); + this._toDispose.push(menu); this._toDispose.push(menu.onDidChange(() => { - this.createCommentActions(formActions, menu); + this._commentFormActions.setActions(menu); })); - // TODO fix disposal - // TODO is backcompat needed here - this.createCommentActions(formActions, menu); + this._commentFormActions = new CommentFormActions(formActions, (action: IAction): void => { + let text = this._commentEditor!.getValue(); - } - - private createCommentActions(container: HTMLElement, menu: IMenu): void { - const groups = menu ? menu.getActions({ shouldForwardArgs: true }) : []; - for (const group of groups) { - const [, actions] = group; - - actions.forEach(action => { - let button = new Button(container); - this._toDispose.push(attachButtonStyler(button, this.themeService)); - - button.label = action.label; - - this._toDispose.push(button.onDidClick(async () => { - let text = this._commentEditor!.getValue(); - - action.run({ - thread: this.commentThread, - commentUniqueId: this.comment.uniqueIdInThread, - text: text, - $mid: 10 - }); - - // this.hideReplyArea(); - this.removeCommentEditor(); - })); + action.run({ + thread: this.commentThread, + commentUniqueId: this.comment.uniqueIdInThread, + text: text, + $mid: 10 }); - } + + this.removeCommentEditor(); + }, this.themeService); + + this._commentFormActions.setActions(menu); } private createEditAction(commentDetailsContainer: HTMLElement): Action { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 48b4d6647c9..40153cc1c80 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -46,6 +46,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys'; +import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-chevron-up'; @@ -81,8 +82,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _contextKeyService: IContextKeyService; private _threadIsEmpty: IContextKey; private _commentEditorIsEmpty: IContextKey; - private _commentThreadActionDisposables: IDisposable[] = []; - private _commentThreadActionButtons: HTMLElement[] = []; + private _commentFormActions: CommentFormActions; public get owner(): string { return this._owner; @@ -751,40 +751,20 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._disposables.push(menu); this._disposables.push(menu.onDidChange(() => { - this.createCommentThreadActions(container, menu); + this._commentFormActions.setActions(menu); })); - this.createCommentThreadActions(container, menu); - } - - private createCommentThreadActions(container: HTMLElement, menu: IMenu | undefined): void { - this._commentThreadActionDisposables.forEach(d => d.dispose()); - this._commentThreadActionButtons.forEach(b => dom.removeNode(b)); - this._commentThreadActionDisposables = []; - const groups = menu ? menu.getActions({ shouldForwardArgs: true }) : []; - for (const group of groups) { - const [, actions] = group; - - actions.forEach(action => { - const button = new Button(container); - this._commentThreadActionButtons.push(button.element); - this._commentThreadActionDisposables.push(button); - button.enabled = action.enabled; - this._commentThreadActionDisposables.push(attachButtonStyler(button, this.themeService)); - - button.label = action.label; - - this._commentThreadActionDisposables.push(button.onDidClick(async () => { - action.run({ - thread: this._commentThread, - text: this._commentEditor.getValue(), - $mid: 8 - }); - - this.hideReplyArea(); - })); + this._commentFormActions = new CommentFormActions(container, (action: IAction) => { + action.run({ + thread: this._commentThread, + text: this._commentEditor.getValue(), + $mid: 8 }); - } + + this.hideReplyArea(); + }, this.themeService); + + this._commentFormActions.setActions(menu); } private createNewCommentNode(comment: modes.Comment): CommentNode { From fd9b74985f4804d92081274050de94e7b0049688 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 May 2019 12:02:53 -0700 Subject: [PATCH 17/22] rename namespace for comments --- src/vs/vscode.d.ts | 2 +- .../workbench/api/common/menusExtensionPoint.ts | 16 ++++++++-------- src/vs/workbench/api/node/extHost.api.impl.ts | 3 +++ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 2ae29a02f28..51ac6fe4732 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -9098,7 +9098,7 @@ declare module 'vscode' { dispose(): void; } - namespace comment { + namespace comments { /** * Creates a new [comment controller](#CommentController) instance. * diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index c12f1b41fe8..63045208ddc 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -46,10 +46,10 @@ namespace schema { case 'statusBar/windowIndicator': return MenuId.StatusBarWindowIndicatorMenu; case 'view/title': return MenuId.ViewTitle; case 'view/item/context': return MenuId.ViewItemContext; - case 'commentThread/title': return MenuId.CommentThreadTitle; - case 'commentThread/actions': return MenuId.CommentThreadActions; - case 'comment/title': return MenuId.CommentTitle; - case 'comment/actions': return MenuId.CommentActions; + case 'comments/commentThread/title': return MenuId.CommentThreadTitle; + case 'comments/commentThread/actions': return MenuId.CommentThreadActions; + case 'comments/comment/title': return MenuId.CommentTitle; + case 'comments/comment/actions': return MenuId.CommentActions; } return undefined; @@ -196,22 +196,22 @@ namespace schema { type: 'array', items: menuItem }, - 'commentThread/title': { + 'comments/commentThread/title': { description: localize('commentThread.title', "The contributed comment thread title menu"), type: 'array', items: menuItem }, - 'commentThread/actions': { + 'comments/commentThread/actions': { description: localize('commentThread.actions', "The contributed comment thread actions"), type: 'array', items: menuItem }, - 'comment/title': { + 'comments/comment/title': { description: localize('comment.title', "The contributed comment title menu"), type: 'array', items: menuItem }, - 'comment/actions': { + 'comments/comment/actions': { description: localize('comment.actions', "The contributed comment actions"), type: 'array', items: menuItem diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index e30688fc1cd..273399d925d 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -673,6 +673,8 @@ export function createApiFactory( } }; + const comments = comment; + // namespace: debug const debug: typeof vscode.debug = { get activeDebugSession() { @@ -756,6 +758,7 @@ export function createApiFactory( languages, scm, comment, + comments, tasks, window, workspace, From b8c1a34885abda380f56fe564dc5ea797dad48cb Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 May 2019 14:56:43 -0700 Subject: [PATCH 18/22] thread id is still updatable --- src/vs/workbench/api/common/extHostComments.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index 5411bbc9f77..de6aa82e298 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -451,6 +451,10 @@ export class ExtHostCommentThread implements vscode.CommentThread { readonly handle = ExtHostCommentThread._handlePool++; public commentHandle: number = 0; + set threadId(id: string) { + this._id = id; + } + get threadId(): string { return this._id!; } From 7efeab3289f5ae1ef7bb3d8cd03a1be02b5601fa Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 May 2019 15:11:59 -0700 Subject: [PATCH 19/22] contextValue --- src/vs/vscode.d.ts | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 51ac6fe4732..ac4ff09b2f5 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8989,6 +8989,26 @@ declare module 'vscode' { */ collapsibleState: CommentThreadCollapsibleState; + /** + * 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`. + */ + contextValue?: string; + /** * The optional human-readable label describing the [Comment Thread](#CommentThread) */ @@ -9033,6 +9053,26 @@ declare module 'vscode' { */ author: CommentAuthorInformation; + /** + * 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; + /** * Optional label describing the [Comment](#Comment) * Label will be rendered next to authorName if exists. From 538605bd9b43d897c00cafc5d3c13984f696232e Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 May 2019 15:20:00 -0700 Subject: [PATCH 20/22] update comments --- src/vs/vscode.d.ts | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index ac4ff09b2f5..dafc98abc04 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -8958,8 +8958,18 @@ declare module 'vscode' { Expanded = 1 } + /** + * Comment mode of a [comment](#Comment) + */ export enum CommentMode { + /** + * Displays the comment editor + */ Editing = 0, + + /** + * Displays the preview of the comment + */ Preview = 1 } @@ -9046,10 +9056,13 @@ declare module 'vscode' { */ body: string | MarkdownString; + /** + * [Comment mode](#CommentMode) of the comment + */ mode: CommentMode; /** - * The author information of the comment + * The [author information](#CommentAuthorInformation) of the comment */ author: CommentAuthorInformation; @@ -9080,9 +9093,18 @@ declare module 'vscode' { label?: string; } + /** + * Command argument for actions registered in `comments/commentThread/actions`. + */ export interface CommentReply { + /** + * The active [comment thread](#CommentThread) + */ thread: CommentThread; + /** + * The value in the comment editor + */ text: string; } @@ -9114,7 +9136,7 @@ declare module 'vscode' { /** * Optional commenting range provider. Provide a list [ranges](#Range) which support commenting to any given resource uri. * - * If not provided and `emptyCommentThreadFactory` exits, users can leave comments in any document opened in the editor. + * If not provided, users can leave comments in any document opened in the editor. */ commentingRangeProvider?: CommentingRangeProvider; @@ -9122,7 +9144,6 @@ declare module 'vscode' { * Create a [comment thread](#CommentThread). The comment thread will be displayed in visible text editors (if the resource matches) * and Comments Panel once created. * - * @param id An `id` for the comment thread. * @param resource The uri of the document the thread has been created on. * @param range The range the comment thread is located within the document. * @param comments The ordered comments of the thread. From e26568b586b9fd68f12b9c8a4df84d7339baedb0 Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Tue, 21 May 2019 16:59:34 -0700 Subject: [PATCH 21/22] Add contextValue support --- src/vs/editor/common/modes.ts | 3 +++ .../api/browser/mainThreadComments.ts | 18 ++++++++++++++++-- .../workbench/api/common/extHost.protocol.ts | 2 +- src/vs/workbench/api/common/extHostComments.ts | 14 ++++++++++++++ .../contrib/comments/browser/commentMenus.ts | 1 + .../contrib/comments/browser/commentNode.ts | 11 ++++++++++- .../comments/browser/commentThreadWidget.ts | 8 ++++++++ 7 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index d6ec3a832a8..90833069146 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1288,6 +1288,7 @@ export interface CommentThread2 { resource: string | null; range: IRange; label: string; + contextValue: string | undefined; comments: Comment[] | undefined; onDidChangeComments: Event; collapsibleState?: CommentThreadCollapsibleState; @@ -1326,6 +1327,7 @@ export interface CommentThread { collapsibleState?: CommentThreadCollapsibleState; reply?: Command; isDisposed?: boolean; + contextValue?: string; } /** @@ -1364,6 +1366,7 @@ export interface Comment { readonly body: IMarkdownString; readonly userName: string; readonly userIconPath?: string; + readonly contextValue?: string; readonly canEdit?: boolean; readonly canDelete?: boolean; readonly selectCommand?: Command; diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index 2f9c24b6da1..3371106e1a5 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -106,6 +106,16 @@ export class MainThreadCommentThread implements modes.CommentThread2 { this._onDidChangeLabel.fire(this._label); } + private _contextValue: string | undefined; + + get contextValue(): string | undefined { + return this._contextValue; + } + + set contextValue(context: string | undefined) { + this._contextValue = context; + } + private _onDidChangeLabel = new Emitter(); get onDidChangeLabel(): Event { return this._onDidChangeLabel.event; } @@ -204,6 +214,7 @@ export class MainThreadCommentThread implements modes.CommentThread2 { batchUpdate( range: IRange, label: string, + contextValue: string | undefined, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], @@ -211,6 +222,7 @@ export class MainThreadCommentThread implements modes.CommentThread2 { collapsibleState: modes.CommentThreadCollapsibleState) { this._range = range; this._label = label; + this._contextValue = contextValue; this._comments = comments; this._acceptInputCommand = acceptInputCommand; this._additionalCommands = additionalCommands; @@ -323,13 +335,14 @@ export class MainThreadCommentController { resource: UriComponents, range: IRange, label: string, + contextValue: string | undefined, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], deleteCommand: modes.Command | undefined, collapsibleState: modes.CommentThreadCollapsibleState): void { let thread = this.getKnownThread(commentThreadHandle); - thread.batchUpdate(range, label, comments, acceptInputCommand, additionalCommands, deleteCommand, collapsibleState); + thread.batchUpdate(range, label, contextValue, comments, acceptInputCommand, additionalCommands, deleteCommand, collapsibleState); this._commentService.updateComments(this._uniqueId, { added: [], @@ -521,6 +534,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments resource: UriComponents, range: IRange, label: string, + contextValue: string | undefined, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], @@ -532,7 +546,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments return undefined; } - return provider.updateCommentThread(commentThreadHandle, threadId, resource, range, label, comments, acceptInputCommand, additionalCommands, deleteCommand, collapsibleState); + return provider.updateCommentThread(commentThreadHandle, threadId, resource, range, label, contextValue, comments, acceptInputCommand, additionalCommands, deleteCommand, collapsibleState); } $deleteCommentThread(handle: number, commentThreadHandle: number) { diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index b32a1fd51ee..afad2ca86e6 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -138,7 +138,7 @@ export interface MainThreadCommentsShape extends IDisposable { $unregisterCommentController(handle: number): void; $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange): modes.CommentThread2 | undefined; - $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], deleteCommand: modes.Command | undefined, collapseState: modes.CommentThreadCollapsibleState): void; + $updateCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, label: string, contextValue: string | undefined, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], deleteCommand: modes.Command | undefined, collapseState: modes.CommentThreadCollapsibleState): void; $deleteCommentThread(handle: number, commentThreadHandle: number): void; $setInputValue(handle: number, input: string): void; $registerDocumentCommentProvider(handle: number, features: CommentProviderFeatures): void; diff --git a/src/vs/workbench/api/common/extHostComments.ts b/src/vs/workbench/api/common/extHostComments.ts index de6aa82e298..fb783e83775 100644 --- a/src/vs/workbench/api/common/extHostComments.ts +++ b/src/vs/workbench/api/common/extHostComments.ts @@ -496,6 +496,17 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._onDidUpdateCommentThread.fire(); } + private _contextValue: string | undefined; + + get contextValue(): string | undefined { + return this._contextValue; + } + + set contextValue(context: string | undefined) { + this._contextValue = context; + this._onDidUpdateCommentThread.fire(); + } + get comments(): vscode.Comment[] { return this._comments; } @@ -592,6 +603,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { eventuallyUpdateCommentThread(): void { const commentThreadRange = extHostTypeConverter.Range.from(this._range); const label = this.label; + const contextValue = this.contextValue; const comments = this._comments.map(cmt => { return convertToModeComment2(this, this._commentController, cmt, this._commandsConverter, this._commentsMap); }); const acceptInputCommand = this._acceptInputCommand ? this._commandsConverter.toInternal(this._acceptInputCommand) : undefined; const additionalCommands = this._additionalCommands ? this._additionalCommands.map(x => this._commandsConverter.toInternal(x)) : []; @@ -605,6 +617,7 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._uri, commentThreadRange, label, + contextValue, comments, acceptInputCommand, additionalCommands, @@ -851,6 +864,7 @@ function convertToModeComment2(thread: ExtHostCommentThread, commentController: return { commentId: vscodeComment.id || vscodeComment.commentId, mode: vscodeComment.mode, + contextValue: vscodeComment.contextValue, uniqueIdInThread: commentUniqueId, body: extHostTypeConverter.MarkdownString.from(vscodeComment.body), userName: vscodeComment.author ? vscodeComment.author.name : vscodeComment.userName, diff --git a/src/vs/workbench/contrib/comments/browser/commentMenus.ts b/src/vs/workbench/contrib/comments/browser/commentMenus.ts index d6b7767814b..3d83ab86f0c 100644 --- a/src/vs/workbench/contrib/comments/browser/commentMenus.ts +++ b/src/vs/workbench/contrib/comments/browser/commentMenus.ts @@ -42,6 +42,7 @@ export class CommentMenus implements IDisposable { private getMenu(menuId: MenuId, contextKeyService: IContextKeyService): IMenu { const menu = this.menuService.createMenu(menuId, contextKeyService); + const primary: IAction[] = []; const secondary: IAction[] = []; const result = { primary, secondary }; diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 139b9b867ea..8d9cfdc2585 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -38,7 +38,7 @@ import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/comme import { MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions'; const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); @@ -63,6 +63,7 @@ export class CommentNode extends Disposable { private _errorEditingContainer: HTMLElement; private _isPendingLabel: HTMLElement; private _contextKeyService: IContextKeyService; + private _commentContextValue: IContextKey; private _deleteAction: Action; protected actionRunner?: IActionRunner; @@ -101,6 +102,8 @@ export class CommentNode extends Disposable { this._domNode = dom.$('div.review-comment'); this._contextKeyService = contextKeyService.createScoped(this._domNode); + this._commentContextValue = this._contextKeyService.createKey('comment', comment.contextValue); + this._domNode.tabIndex = 0; const avatar = dom.append(this._domNode, dom.$('div.avatar-container')); if (comment.userIconPath) { @@ -689,6 +692,12 @@ export class CommentNode extends Disposable { if (this.comment.commentReactions && this.comment.commentReactions.length) { this.createReactionsContainer(this._commentDetailsContainer); } + + if (this.comment.contextValue) { + this._commentContextValue.set(this.comment.contextValue); + } else { + this._commentContextValue.reset(); + } } focus() { diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts index 40153cc1c80..d9a4fb60eac 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadWidget.ts @@ -81,6 +81,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget private _error: HTMLElement; private _contextKeyService: IContextKeyService; private _threadIsEmpty: IContextKey; + private _commentThreadContextValue: IContextKey; private _commentEditorIsEmpty: IContextKey; private _commentFormActions: CommentFormActions; @@ -123,6 +124,7 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this._contextKeyService = contextKeyService.createScoped(this.domNode); this._threadIsEmpty = CommentContextKeys.commentThreadIsEmpty.bindTo(this._contextKeyService); this._threadIsEmpty.set(!_commentThread.comments || !_commentThread.comments.length); + this._commentThreadContextValue = contextKeyService.createKey('commentThread', _commentThread.contextValue); this._resizeObserver = null; this._isExpanded = _commentThread.collapsibleState ? _commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded : undefined; @@ -381,6 +383,12 @@ export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget this.hide(); } } + + if (this._commentThread.contextValue) { + this._commentThreadContextValue.set(this._commentThread.contextValue); + } else { + this._commentThreadContextValue.reset(); + } } updateDraftMode(draftMode: modes.DraftMode | undefined) { From 48f6cce7e8ec3c29f5081ad31838dffeedabf8ac Mon Sep 17 00:00:00 2001 From: Peng Lyu Date: Thu, 23 May 2019 09:28:05 -0700 Subject: [PATCH 22/22] change comment/actions to comment/context to keep align --- src/vs/workbench/api/common/menusExtensionPoint.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 63045208ddc..3c7148d14fe 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -47,9 +47,9 @@ namespace schema { case 'view/title': return MenuId.ViewTitle; case 'view/item/context': return MenuId.ViewItemContext; case 'comments/commentThread/title': return MenuId.CommentThreadTitle; - case 'comments/commentThread/actions': return MenuId.CommentThreadActions; + case 'comments/commentThread/context': return MenuId.CommentThreadActions; case 'comments/comment/title': return MenuId.CommentTitle; - case 'comments/comment/actions': return MenuId.CommentActions; + case 'comments/comment/context': return MenuId.CommentActions; } return undefined;