diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index d84cc6f377b..8d4cb25444e 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -1233,6 +1233,26 @@ export interface CommentWidget { commentThread: CommentThread; comment?: Comment; input: string; + onDidChangeInput: Event; +} + +/** + * @internal + */ + +export interface CommentThread2 { + commentThreadHandle: number; // use optional type for now to avoid breaking existing api + extensionId: string; + threadId: string; + resource: string; + range: IRange; + comments: Comment[]; + onDidChangeComments: Event; + collapsibleState?: CommentThreadCollapsibleState; + input: string; + onDidChangeInput: Event; + acceptInputCommands: Command[]; + onDidChangeAcceptInputCommands: Event; } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index ecbd03224c1..ba50fdd0169 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -787,6 +787,7 @@ declare module 'vscode' { * The ordered comments of the thread. */ comments: Comment[]; + acceptInputCommands?: Command[]; /** * Whether the thread should be collapsed or expanded when opening the document. Defaults to Collapsed. @@ -941,12 +942,6 @@ declare module 'vscode' { */ commentThread: CommentThread; - /* - * Focused Comment - * This comment must be part of CommentWidget.commentThread - */ - comment?: Comment; - /* * Textarea content in the comment widget. * There is only one active input box in a comment widget. @@ -961,7 +956,7 @@ declare module 'vscode' { * The active (focused) comment widget. */ readonly widget?: CommentWidget; - createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[], collapsibleState?: CommentThreadCollapsibleState): CommentThread; + createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[], acceptInputCommands: Command[], collapsibleState?: CommentThreadCollapsibleState): CommentThread; dispose(): void; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadComments.ts b/src/vs/workbench/api/electron-browser/mainThreadComments.ts index 2eabd7a6ee0..574d274f40e 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadComments.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadComments.ts @@ -11,7 +11,7 @@ import { keys } from 'vs/base/common/map'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentProviderFeatures } from '../node/extHost.protocol'; -import { ICommentService } from 'vs/workbench/contrib/comments/electron-browser/commentService'; +import { ICommentService, ICommentInfo } from 'vs/workbench/contrib/comments/electron-browser/commentService'; import { COMMENTS_PANEL_ID, CommentsPanel, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/electron-browser/commentsPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -80,7 +80,7 @@ export class MainThreadDocumentCommentProvider implements modes.DocumentCommentP onDidChangeCommentThreads = null; } -export class MainThreadCommentThread { +export class MainThreadCommentThread implements modes.CommentThread2 { private _input: string = ''; get input(): string { return this._input; @@ -109,6 +109,30 @@ export class MainThreadCommentThread { get onDidChangeActiveComment(): Event { return this._onDidChangeActiveComment.event; } + public get comments(): modes.Comment[] { + return this._comments; + } + + public set comments(newComments: modes.Comment[]) { + this._comments = newComments; + this._onDidChangeComments.fire(this._comments); + } + + private _onDidChangeComments = new Emitter(); + get onDidChangeComments(): Event { return this._onDidChangeComments.event; } + + set acceptInputCommands(newCommands: modes.Command[]) { + this._acceptInputCommands = newCommands; + this._onDidChangeAcceptInputCommands.fire(this._acceptInputCommands); + } + + get acceptInputCommands(): modes.Command[] { + return this._acceptInputCommands; + } + + private _onDidChangeAcceptInputCommands = new Emitter(); + get onDidChangeAcceptInputCommands(): Event { return this._onDidChangeAcceptInputCommands.event; } + constructor( public commentThreadHandle: number, public control: MainThreadCommentControl, @@ -116,13 +140,26 @@ export class MainThreadCommentThread { public threadId: string, public resource: string, public range: IRange, - public comments: modes.Comment[], + private _comments: modes.Comment[], + private _acceptInputCommands: modes.Command[], public collapsibleState?: modes.CommentThreadCollapsibleState, - public reply?: modes.Command ) { } + + dispose() { + + } + + toJSON(): any { + return { + $mid: 7, + commentControlHandle: this.control.handle, + commentThreadHandle: this.commentThreadHandle, + }; + } } + export class MainThreadCommentControl { get handle(): number { return this._handle; @@ -136,23 +173,67 @@ export class MainThreadCommentControl { private _label: string ) { } - createCommentThread(commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread { + createCommentThread(commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], commands: modes.Command[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread2 { let thread = new MainThreadCommentThread( commentThreadHandle, this, '', threadId, - resource.toString(), + URI.revive(resource).toString(), range, comments, - collapseState, - undefined + commands, + collapseState ); this._threads.set(commentThreadHandle, thread); return thread; } + + deleteCommentThread(commentThreadHandle: number) { + let thread = this._threads.get(commentThreadHandle); + + thread.dispose(); + } + + updateComments(commentThreadHandle: number, comments: modes.Comment[]) { + let thread = this._threads.get(commentThreadHandle); + thread.comments = comments; + } + + updateAcceptInputCommands(commentThreadHandle: number, acceptInputCommands: modes.Command[]) { + let thread = this._threads.get(commentThreadHandle); + thread.acceptInputCommands = acceptInputCommands; + } + + updateInput(commentThreadHandle: number, input: string) { + let thread = this._threads.get(commentThreadHandle); + thread.input = input; + } + + getDocumentComments(resource: URI) { + let ret = []; + for (let thread of keys(this._threads)) { + if (this._threads.get(thread).resource === resource.toString()) { + ret.push(this._threads.get(thread)); + } +} + + return { + owner: String(this.handle), + threads: ret, + commentingRanges: [], + draftMode: modes.DraftMode.NotSupported + }; + } + + toJSON(): any { + return { + $mid: 6, + handle: this.handle + }; + } } @extHostNamedCustomer(MainContext.MainThreadComments) @@ -180,9 +261,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments super(); this._disposables = []; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); - this._disposables.push(this._commentService.onDidChangeActiveCommentThread(thread => { - console.log(thread.threadId); - + this._disposables.push(this._commentService.onDidChangeActiveCommentThread(async thread => { let control = (thread as MainThreadCommentThread).control; if (!control) { @@ -201,36 +280,65 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._proxy.$onActiveCommentWidgetChange(control.handle, this._activeCommentThread, this._activeComment, this._input); }); - this._proxy.$onActiveCommentWidgetChange(control.handle, this._activeCommentThread, this._activeComment, this._input); - })); - - this._disposables.push(this._commentService.onDidChangeInput(input => { - console.log(input); - let control = this._activeCommentThread ? this._activeCommentThread.control : undefined; - - if (!control) { - return; - } - - this._input = input; - - this._proxy.$onActiveCommentWidgetChange(control.handle, this._activeCommentThread, this._activeComment, this._input); + await this._proxy.$onActiveCommentWidgetChange(control.handle, this._activeCommentThread, this._activeComment, this._input); })); } $registerCommentControl(handle: number, id: string, label: string): void { const provider = new MainThreadCommentControl(this._proxy, handle, id, label); + this._commentService.registerCommentControl(String(handle), provider); this._commentControls.set(handle, provider); } - $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread | undefined { + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], commands: modes.Command[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread2 | undefined { let provider = this._commentControls.get(handle); if (!provider) { return; } - return provider.createCommentThread(commentThreadHandle, threadId, resource, range, comments, collapseState); + return provider.createCommentThread(commentThreadHandle, threadId, resource, range, comments, commands, collapseState); + } + + $deleteCommentThread(handle: number, commentThreadHandle: number) { + let provider = this._commentControls.get(handle); + + if (!provider) { + return; + } + + return provider.deleteCommentThread(commentThreadHandle); + } + + $updateComments(handle: number, commentThreadHandle: number, comments: modes.Comment[]) { + let provider = this._commentControls.get(handle); + + if (!provider) { + return; + } + + provider.updateComments(commentThreadHandle, comments); + } + + $setInputValue(handle: number, commentThreadHandle: number, input: string) { + let provider = this._commentControls.get(handle); + + if (!provider) { + return; + } + + provider.updateInput(commentThreadHandle, input); + + } + + $updateCommentThreadCommands(handle: number, commentThreadHandle: number, acceptInputCommands: modes.Command[]) { + let provider = this._commentControls.get(handle); + + if (!provider) { + return; + } + + provider.updateAcceptInputCommands(commentThreadHandle, acceptInputCommands); } $registerDocumentCommentProvider(handle: number, features: CommentProviderFeatures): void { diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 678233b1ee7..63a23b7eb03 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -115,7 +115,7 @@ export function createApiFactory( const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostLogService, extHostCommands)); const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); - const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands.converter, extHostDocuments)); + const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, schemeTransformer, extHostLogService)); const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index d75e96cbc3b..acee0716e91 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -120,7 +120,11 @@ export interface CommentProviderFeatures { export interface MainThreadCommentsShape extends IDisposable { $registerCommentControl(handle: number, id: string, label: string): void; - $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread | undefined; + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], commands: modes.Command[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread2 | undefined; + $deleteCommentThread(handle: number, commentThreadHandle: number): void; + $updateComments(handle: number, commentThreadHandle: number, comments: modes.Comment[]): void; + $setInputValue(handle: number, commentThreadHandle: number, input: string): void; + $updateCommentThreadCommands(handle: number, commentThreadHandle: number, acceptInputCommands: modes.Command[]): void; $registerDocumentCommentProvider(handle: number, features: CommentProviderFeatures): void; $unregisterDocumentCommentProvider(handle: number): void; $registerWorkspaceCommentProvider(handle: number, extensionId: ExtensionIdentifier): void; diff --git a/src/vs/workbench/api/node/extHostComments.ts b/src/vs/workbench/api/node/extHostComments.ts index f3f73b471ce..104ed3a0ec0 100644 --- a/src/vs/workbench/api/node/extHostComments.ts +++ b/src/vs/workbench/api/node/extHostComments.ts @@ -10,7 +10,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters'; import * as vscode from 'vscode'; import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from './extHost.protocol'; -import { CommandsConverter } from './extHostCommands'; +import { CommandsConverter, ExtHostCommands } from './extHostCommands'; import { IRange } from 'vs/editor/common/core/range'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -39,17 +39,45 @@ export class ExtHostComments implements ExtHostCommentsShape { constructor( mainContext: IMainContext, - private readonly _commandsConverter: CommandsConverter, + private _commands: ExtHostCommands, private readonly _documents: ExtHostDocuments, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadComments); + + _commands.registerArgumentProcessor({ + processArgument: arg => { + if (arg && arg.$mid === 6) { + const commentControl = this._commentControls.get(arg.handle); + + if (!commentControl) { + return arg; + } + + return commentControl; + } else if (arg && arg.$mid === 7) { + const commentControl = this._commentControls.get(arg.commentControlHandle); + + if (!commentControl) { + return arg; + } + + const commentThread = commentControl.getCommentThread(arg.commentThreadHandle); + + if (!commentThread) { + return arg; + } + + return commentThread; + } + + return arg; + } + }); } createCommentControl(extension: IExtensionDescription, id: string, label: string): vscode.CommentControl { - const handle = ExtHostComments.handlePool++; - - const commentControl = new ExtHostCommentControl(extension, this._proxy, id, label); - this._commentControls.set(handle, commentControl); + const commentControl = new ExtHostCommentControl(extension, this._commands.converter, this._proxy, id, label); + this._commentControls.set(commentControl.handle, commentControl); const commentControls = this._commentControlsByExtension.get(ExtensionIdentifier.toKey(extension.identifier)) || []; commentControls.push(commentControl); @@ -58,7 +86,7 @@ export class ExtHostComments implements ExtHostCommentsShape { return commentControl; } - $onActiveCommentWidgetChange(commentControlhandle: number, commentThread: modes.CommentThread, comment: modes.Comment | undefined, input: string): Promise { + $onActiveCommentWidgetChange(commentControlhandle: number, commentThread: modes.CommentThread2, comment: modes.Comment | undefined, input: string): Promise { const commentControl = this._commentControls.get(commentControlhandle); if (!commentControl) { @@ -66,7 +94,7 @@ export class ExtHostComments implements ExtHostCommentsShape { } commentControl.$onActiveCommentWidgetChange(commentThread, comment, input); - return Promise.resolve(undefined); + return Promise.resolve(commentControlhandle); } $onCommentWidgetInputChange(commentControlhandle: number, value: string): Promise { @@ -76,7 +104,7 @@ export class ExtHostComments implements ExtHostCommentsShape { return Promise.resolve(undefined); } - commentControl.widget.$onCommentWidgetInputChange(value); + commentControl.widget.input = value; return Promise.resolve(undefined); } @@ -136,7 +164,7 @@ export class ExtHostComments implements ExtHostCommentsShape { const handlerData = this._documentProviders.get(handle); return asPromise(() => { return handlerData.provider.createNewCommentThread(data.document, ran, text, CancellationToken.None); - }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commandsConverter) : null); + }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commands.converter) : null); } $replyToCommentThread(handle: number, uri: UriComponents, range: IRange, thread: modes.CommentThread, text: string): Promise { @@ -150,7 +178,7 @@ export class ExtHostComments implements ExtHostCommentsShape { const handlerData = this._documentProviders.get(handle); return asPromise(() => { return handlerData.provider.replyToCommentThread(data.document, ran, convertFromCommentThread(thread), text, CancellationToken.None); - }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commandsConverter) : null); + }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commands.converter) : null); } $editComment(handle: number, uri: UriComponents, comment: modes.Comment, text: string): Promise { @@ -216,7 +244,7 @@ export class ExtHostComments implements ExtHostCommentsShape { const handlerData = this._documentProviders.get(handle); return asPromise(() => { return handlerData.provider.provideDocumentComments(document, CancellationToken.None); - }).then(commentInfo => commentInfo ? convertCommentInfo(handle, handlerData.extensionId, handlerData.provider, commentInfo, this._commandsConverter) : null); + }).then(commentInfo => commentInfo ? convertCommentInfo(handle, handlerData.extensionId, handlerData.provider, commentInfo, this._commands.converter) : null); } $provideWorkspaceComments(handle: number): Promise { @@ -228,7 +256,7 @@ export class ExtHostComments implements ExtHostCommentsShape { return asPromise(() => { return handlerData.provider.provideWorkspaceComments(CancellationToken.None); }).then(comments => - comments.map(comment => convertToCommentThread(handlerData.extensionId, handlerData.provider, comment, this._commandsConverter) + comments.map(comment => convertToCommentThread(handlerData.extensionId, handlerData.provider, comment, this._commands.converter) )); } @@ -236,9 +264,9 @@ export class ExtHostComments implements ExtHostCommentsShape { provider.onDidChangeCommentThreads(event => { this._proxy.$onDidCommentThreadsChange(handle, { - changed: event.changed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commandsConverter)), - added: event.added.map(thread => convertToCommentThread(extensionId, provider, thread, this._commandsConverter)), - removed: event.removed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commandsConverter)), + changed: event.changed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)), + added: event.added.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)), + removed: event.removed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)), draftMode: !!(provider as vscode.DocumentCommentProvider).startDraft && !!(provider as vscode.DocumentCommentProvider).finishDraft ? (event.inDraftMode ? modes.DraftMode.InDraft : modes.DraftMode.NotInDraft) : modes.DraftMode.NotSupported }); }); @@ -255,21 +283,41 @@ export class ExtHostCommentThread implements vscode.CommentThread { get resource(): vscode.Uri { return this._resource; } + get range(): vscode.Range { return this._range; } + get comments(): vscode.Comment[] { return this._comments; } + set comments(newComments: vscode.Comment[]) { + this._proxy.$updateComments(this._commentControlHandle, this.handle, newComments.map(cmt => { return convertToModeComment(cmt, this._commandsConverter); })); + this._comments = newComments; + } + + get acceptInputCommands(): vscode.Command[] { + return this._acceptInputCommands; + } + + set acceptInputCommands(replyCommands: vscode.Command[]) { + this._acceptInputCommands = replyCommands; + + const internals = replyCommands.map(this._commandsConverter.toInternal.bind(this._commandsConverter)); + this._proxy.$updateCommentThreadCommands(this._commentControlHandle, this.handle, internals); + } + collapsibleState?: vscode.CommentThreadCollapsibleState; constructor( private _proxy: MainThreadCommentsShape, + private readonly _commandsConverter: CommandsConverter, private _commentControlHandle: number, private _threadId: string, private _resource: vscode.Uri, private _range: vscode.Range, private _comments: vscode.Comment[], + private _acceptInputCommands: vscode.Command[], private _collapseState?: vscode.CommentThreadCollapsibleState ) { this._proxy.$createCommentThread( @@ -278,7 +326,8 @@ export class ExtHostCommentThread implements vscode.CommentThread { this._threadId, this._resource, extHostTypeConverter.Range.from(this._range), - this._comments.map(convertToModeComment), + this._comments.map(comment => { return convertToModeComment(comment, this._commandsConverter); }), + this._acceptInputCommands ? this._acceptInputCommands.map(this._commandsConverter.toInternal.bind(this._commandsConverter)) : [], this._collapseState ); } @@ -290,8 +339,16 @@ export class ExtHostCommentThread implements vscode.CommentThread { return comments[0]; } - return; + return undefined; } + + dispose() { + this._proxy.$deleteCommentThread( + this._commentControlHandle, + this.handle + ); +} + } export class ExtHostCommentWidget implements vscode.CommentWidget { @@ -306,22 +363,23 @@ export class ExtHostCommentWidget implements vscode.CommentWidget { return this._input; } + set input(newInput: string) { + this._input = newInput; + this._onDidChangeInput.fire(this._input); + this._proxy.$setInputValue(this.commentControlHandle, this.commentThread.handle, newInput); + } + constructor( - public commentThread: vscode.CommentThread, + private _proxy: MainThreadCommentsShape, + + public commentControlHandle: number, + + public commentThread: ExtHostCommentThread, public comment: vscode.Comment | undefined, input: string ) { this._input = input; } - - $onCommentWidgetInputChange(value: string) { - this.updateValue(value); - } - - private updateValue(value: string): void { - this._input = value; - this._onDidChangeInput.fire(value); - } } class ExtHostCommentControl implements vscode.CommentControl { @@ -337,11 +395,12 @@ class ExtHostCommentControl implements vscode.CommentControl { public widget?: ExtHostCommentWidget; - private handle: number = ExtHostCommentControl._handlePool++; + public handle: number = ExtHostCommentControl._handlePool++; private _threads: Map = new Map(); constructor( _extension: IExtensionDescription, + private readonly _commandsConverter: CommandsConverter, private _proxy: MainThreadCommentsShape, private _id: string, private _label: string @@ -349,25 +408,33 @@ class ExtHostCommentControl implements vscode.CommentControl { this._proxy.$registerCommentControl(this.handle, _id, _label); } - createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[], collapsibleState?: vscode.CommentThreadCollapsibleState): vscode.CommentThread { - const commentThread = new ExtHostCommentThread(this._proxy, this.handle, id, resource, range, comments, collapsibleState); + createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[], acceptInputCommands: vscode.Command[], collapsibleState?: vscode.CommentThreadCollapsibleState): vscode.CommentThread { + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this.handle, id, resource, range, comments, acceptInputCommands, collapsibleState); this._threads.set(commentThread.handle, commentThread); + // onDidDispose + return commentThread; } - $onActiveCommentWidgetChange(commentThread: modes.CommentThread, comment: modes.Comment | undefined, input: string) { + $onActiveCommentWidgetChange(commentThread: modes.CommentThread2, comment: modes.Comment | undefined, input: string) { let extHostCommentThread = this._threads.get(commentThread.commentThreadHandle); const extHostCommentWidget = new ExtHostCommentWidget( + this._proxy, + this.handle, extHostCommentThread, comment ? extHostCommentThread.getComment(comment.commentId) : undefined, - input + input || '' ); this.widget = extHostCommentWidget; } + getCommentThread(handle: number) { + return this._threads.get(handle); + } + dispose(): void { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/contrib/comments/electron-browser/commentService.ts b/src/vs/workbench/contrib/comments/electron-browser/commentService.ts index d2bc0d9464d..3d8d5b48a0e 100644 --- a/src/vs/workbench/contrib/comments/electron-browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentService.ts @@ -13,6 +13,7 @@ import { keys } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; import { assign } from 'vs/base/common/objects'; import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; +import { MainThreadCommentControl } from 'vs/workbench/api/electron-browser/mainThreadComments'; export const ICommentService = createDecorator('commentService'); @@ -42,6 +43,7 @@ export interface ICommentService { setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void; setWorkspaceComments(owner: string, commentsByResource: CommentThread[]): void; removeWorkspaceComments(owner: string): void; + registerCommentControl(owner: string, commentControl: MainThreadCommentControl): void; registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void; unregisterDataProvider(owner: string): void; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; @@ -89,6 +91,8 @@ export class CommentService extends Disposable implements ICommentService { private _commentProviders = new Map(); + private _commentControls = new Map(); + constructor() { super(); } @@ -113,6 +117,11 @@ export class CommentService extends Disposable implements ICommentService { this._onDidSetAllCommentThreads.fire({ ownerId: owner, commentThreads: [] }); } + registerCommentControl(owner: string, commentControl: MainThreadCommentControl): void { + this._commentControls.set(owner, commentControl); + this._onDidSetDataProvider.fire(); + } + registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void { this._commentProviders.set(owner, commentProvider); this._onDidSetDataProvider.fire(); @@ -258,7 +267,7 @@ export class CommentService extends Disposable implements ICommentService { return undefined; } - getComments(resource: URI): Promise<(ICommentInfo | null)[]> { + async getComments(resource: URI): Promise<(ICommentInfo | null)[]> { const result: Promise[] = []; for (const owner of keys(this._commentProviders)) { const provider = this._commentProviders.get(owner); @@ -279,6 +288,13 @@ export class CommentService extends Disposable implements ICommentService { } } - return Promise.all(result); + let ret = await Promise.all(result); + for (const owner of keys(this._commentControls)) { + const control = this._commentControls.get(owner); + + ret.push(control.getDocumentComments(resource)); + } + + return ret; } } diff --git a/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts index 94c6ee1e9f8..338a66384b3 100644 --- a/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts @@ -40,6 +40,7 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITextModel } from 'vs/editor/common/model'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ICommandService } from 'vs/platform/commands/common/commands'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-x'; @@ -62,7 +63,7 @@ export class ReviewZoneWidget extends ZoneWidget { private _onDidCreateThread = new Emitter(); private _isCollapsed; private _collapseAction: Action; - private _commentThread: modes.CommentThread; + private _commentThread: modes.CommentThread | modes.CommentThread2; private _commentGlyph: CommentGlyphWidget; private _owner: string; private _pendingComment: string; @@ -92,6 +93,7 @@ export class ReviewZoneWidget extends ZoneWidget { constructor( private instantiationService: IInstantiationService, private modeService: IModeService, + private commandService: ICommandService, private modelService: IModelService, private themeService: IThemeService, private commentService: ICommentService, @@ -101,7 +103,7 @@ export class ReviewZoneWidget extends ZoneWidget { private contextMenuService: IContextMenuService, editor: ICodeEditor, owner: string, - commentThread: modes.CommentThread, + commentThread: modes.CommentThread | modes.CommentThread2, pendingComment: string, draftMode: modes.DraftMode, options: IOptions = { keepEditorSelection: true } @@ -226,7 +228,7 @@ export class ReviewZoneWidget extends ZoneWidget { this._collapseAction.run(); } - update(commentThread: modes.CommentThread) { + update(commentThread: modes.CommentThread | modes.CommentThread2) { const oldCommentsLen = this._commentElements.length; const newCommentsLen = commentThread.comments.length; @@ -339,9 +341,35 @@ export class ReviewZoneWidget extends ZoneWidget { this._commentEditor.setModel(model); this._localToDispose.push(this._commentEditor); this._localToDispose.push(this._commentEditor.getModel().onDidChangeContent(() => this.setCommentEditorDecorations())); + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { this._localToDispose.push(this._commentEditor.getModel().onDidChangeContent(() => { - this.commentService.setInput(this._commentEditor.getModel().getValue()); + let modelContent = this._commentEditor.getValue(); + if ((this._commentThread as modes.CommentThread2).input !== modelContent) { + (this._commentThread as modes.CommentThread2).input = modelContent; + } })); + + this._localToDispose.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => { + if (this._commentEditor.getValue() !== input) { + this._commentEditor.setValue(input); + + if (input === '') { + 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'); + } + } + })); + + this._localToDispose.push((this._commentThread as modes.CommentThread2).onDidChangeComments(_ => { + this.update(this._commentThread); + })); + } + this.setCommentEditorDecorations(); // Only add the additional step of clicking a reply button to expand the textarea when there are existing comments @@ -378,7 +406,16 @@ export class ReviewZoneWidget extends ZoneWidget { this._error = dom.append(this._commentForm, dom.$('.validation-error.hidden')); this._formActions = dom.append(this._commentForm, dom.$('.form-actions')); + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { + this.createCommentWidgetActions2(this._formActions, model); + + this._localToDispose.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommands(_ => { + dom.clearNode(this._formActions); + this.createCommentWidgetActions2(this._formActions, model); + })); + } else { this.createCommentWidgetActions(this._formActions, model); + } this._resizeObserver = new MutationObserver(this._refresh.bind(this)); @@ -503,6 +540,28 @@ export class ReviewZoneWidget extends ZoneWidget { } } + /** + * Command based actions. + */ + private createCommentWidgetActions2(container: HTMLElement, model: ITextModel) { + let commentThread = this._commentThread as modes.CommentThread2; + + commentThread.acceptInputCommands.reverse().forEach(command => { + const button = new Button(container); + this._localToDispose.push(attachButtonStyler(button, this.themeService)); + + button.label = command.title; + let commandId = command.id; + let args = command.arguments || []; + this._localToDispose.push(button.onDidClick(async () => { + commentThread.input = this._commentEditor.getValue(); + this.commentService.setActiveCommentThread(this._commentThread); + + await this.commandService.executeCommand(commandId, ...args); + })); + }); + } + private createNewCommentNode(comment: modes.Comment): CommentNode { let newCommentNode = new CommentNode( comment, @@ -614,7 +673,11 @@ export class ReviewZoneWidget extends ZoneWidget { private createReplyButton() { this._reviewThreadReplyButton = dom.append(this._commentForm, dom.$('button.review-thread-reply-button')); + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { + // this._reviewThreadReplyButton.title = (this._commentThread as modes.CommentThread2).acceptInputCommands.title; + } else { this._reviewThreadReplyButton.title = nls.localize('reply', "Reply..."); + } this._reviewThreadReplyButton.textContent = nls.localize('reply', "Reply..."); // bind click/escape actions for reviewThreadReplyButton and textArea this._localToDispose.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea())); diff --git a/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts index e53dd62b1e7..6db738d0638 100644 --- a/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts @@ -37,6 +37,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async import { overviewRulerCommentingRangeForeground } from 'vs/workbench/contrib/comments/electron-browser/commentGlyphWidget'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; +import { ICommandService } from 'vs/platform/commands/common/commands'; export const ctxReviewPanelVisible = new RawContextKey('reviewPanelVisible', false); @@ -171,6 +172,7 @@ export class ReviewController implements IEditorContribution { @IContextKeyService contextKeyService: IContextKeyService, @IThemeService private readonly themeService: IThemeService, @ICommentService private readonly commentService: ICommentService, + @ICommandService private readonly _commandService: ICommandService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModeService private readonly modeService: IModeService, @@ -396,7 +398,7 @@ export class ReviewController implements IEditorContribution { } }); added.forEach(thread => { - let zoneWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, e.owner, thread, null, draftMode); + let zoneWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this._commandService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, e.owner, thread, null, draftMode); zoneWidget.display(thread.range.startLineNumber); this._commentWidgets.push(zoneWidget); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); @@ -415,7 +417,7 @@ export class ReviewController implements IEditorContribution { // add new comment this._reviewPanelVisible.set(true); - this._newCommentWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, ownerId, { + this._newCommentWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this._commandService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, ownerId, { extensionId: extensionId, threadId: null, resource: null, @@ -573,7 +575,7 @@ export class ReviewController implements IEditorContribution { thread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; } - let zoneWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, info.owner, thread, pendingComment, info.draftMode); + let zoneWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this._commandService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, info.owner, thread, pendingComment, info.draftMode); zoneWidget.display(thread.range.startLineNumber); this._commentWidgets.push(zoneWidget); });