diff --git a/src/vs/workbench/api/electron-browser/mainThreadComments.ts b/src/vs/workbench/api/electron-browser/mainThreadComments.ts index 5b84627c86e..a7fd5ec48e2 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadComments.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadComments.ts @@ -7,7 +7,6 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ITextModel } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; import { ReviewController } from 'vs/editor/contrib/review/review'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @@ -19,6 +18,7 @@ import { ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, Mai import { ICommentService } from 'vs/workbench/services/comments/electron-browser/commentService'; import { COMMENTS_PANEL_ID } from 'vs/workbench/parts/comments/electron-browser/commentsPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import URI from 'vs/base/common/uri'; @extHostNamedCustomer(MainContext.MainThreadComments) export class MainThreadComments extends Disposable implements MainThreadCommentsShape { @@ -47,9 +47,10 @@ export class MainThreadComments extends Disposable implements MainThreadComments return; } - this.provideComments(outerEditor.getModel()).then(commentThreads => { + const outerEditorURI = outerEditor.getModel().uri; + this.provideComments(outerEditorURI).then(commentThreads => { controller.setComments(commentThreads); - this._commentService.updateComments(commentThreads); + this._commentService.setCommentsForResource(outerEditorURI, commentThreads); }); }); } @@ -76,10 +77,10 @@ export class MainThreadComments extends Disposable implements MainThreadComments return editor; } - async provideComments(model: ITextModel): Promise { + async provideComments(resource: URI): Promise { const result: modes.CommentThread[] = []; for (const handle of keys(this._providers)) { - result.push(...await this._proxy.$providerComments(handle, model.uri)); + result.push(...await this._proxy.$providerComments(handle, resource)); } return result; } diff --git a/src/vs/workbench/parts/comments/electron-browser/comments.contribution.ts b/src/vs/workbench/parts/comments/electron-browser/comments.contribution.ts index 3b45b0748fe..311c0b3be09 100644 --- a/src/vs/workbench/parts/comments/electron-browser/comments.contribution.ts +++ b/src/vs/workbench/parts/comments/electron-browser/comments.contribution.ts @@ -3,12 +3,12 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { CommentsPanel, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from './commentsPanel'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; +import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE, CommentsPanel } from './commentsPanel'; export class CommentPanelVisibilityUpdater implements IWorkbenchContribution { diff --git a/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts b/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts index 91334eb5dbb..16bfe6bee0d 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts @@ -3,17 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { Panel } from 'vs/workbench/browser/panel'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; import * as dom from 'vs/base/browser/dom'; -import { WorkbenchTree } from '../../../../platform/list/browser/listService'; -import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation'; -import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree'; -import { TPromise, Promise } from 'vs/base/common/winjs.base'; -import { ICommentService, CommentsModel, ResourceCommentThread, CommentNode } from 'vs/workbench/services/comments/electron-browser/commentService'; +import { debounceEvent } from 'vs/base/common/event'; +import { Promise, TPromise } from 'vs/base/common/winjs.base'; +import { IDataSource, IFilter, IRenderer, ITree } from 'vs/base/parts/tree/browser/tree'; +import { DefaultAccessibilityProvider, DefaultController, DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; +import { IEditorService } from 'vs/platform/editor/common/editor'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ResourceLabel } from 'vs/workbench/browser/labels'; -import { DefaultAccessibilityProvider, DefaultController, DefaultDragAndDrop, DefaultFilter } from '../../../../base/parts/tree/browser/treeDefaults'; +import { Panel } from 'vs/workbench/browser/panel'; +import { CommentNode, CommentsModel, ICommentService, ResourceCommentThreads } from 'vs/workbench/services/comments/electron-browser/commentService'; export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; export const COMMENTS_PANEL_TITLE = 'Comments'; @@ -23,7 +25,7 @@ export class CommentsDataSource implements IDataSource { if (element instanceof CommentsModel) { return 'root'; } - if (element instanceof ResourceCommentThread) { + if (element instanceof ResourceCommentThreads) { return element.id; } if (element instanceof CommentNode) { @@ -33,14 +35,14 @@ export class CommentsDataSource implements IDataSource { } public hasChildren(tree: ITree, element: any): boolean { - return element instanceof CommentsModel || element instanceof ResourceCommentThread || (element instanceof CommentNode && element.hasReply()); + return element instanceof CommentsModel || element instanceof ResourceCommentThreads || (element instanceof CommentNode && element.hasReply()); } public getChildren(tree: ITree, element: any): Promise { if (element instanceof CommentsModel) { return Promise.as(element.commentThreads); } - if (element instanceof ResourceCommentThread) { + if (element instanceof ResourceCommentThreads) { return Promise.as([element.comments[0]]); } if (element instanceof CommentNode && element.hasReply()) { @@ -73,7 +75,7 @@ export class CommentsModelRenderer implements IRenderer { } public getTemplateId(tree: ITree, element: any): string { - if (element instanceof ResourceCommentThread) { + if (element instanceof ResourceCommentThreads) { return CommentsModelRenderer.COMMENTS_THREAD_ID; } if (element instanceof CommentNode) { @@ -121,8 +123,8 @@ export class CommentsModelRenderer implements IRenderer { return data; } - private renderCommentsThreadElement(tree: ITree, element: ResourceCommentThread, templateData: IResourceMarkersTemplateData) { - templateData.resourceLabel.setLabel({ name: element.id }); + private renderCommentsThreadElement(tree: ITree, element: ResourceCommentThreads, templateData: IResourceMarkersTemplateData) { + templateData.resourceLabel.setLabel({ name: element.resource.toString() }); } private renderCommentElement(tree: ITree, element: CommentNode, templateData: IResourceMarkersTemplateData) { @@ -130,6 +132,18 @@ export class CommentsModelRenderer implements IRenderer { } } +export class DataFilter implements IFilter { + public isVisible(tree: ITree, element: any): boolean { + if (element instanceof CommentsModel) { + return element.commentThreads.length > 0; + } + if (element instanceof ResourceCommentThreads) { + return element.comments.length > 0; + } + return true; + } +} + interface IResourceMarkersTemplateData { resourceLabel: ResourceLabel; } @@ -140,6 +154,7 @@ export class CommentsPanel extends Panel { constructor( @IInstantiationService private instantiationService: IInstantiationService, @ICommentService private commentService: ICommentService, + @IEditorService private editorService: IEditorService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, ) { @@ -171,7 +186,6 @@ export class CommentsPanel extends Panel { private render(): TPromise { dom.toggleClass(this.treeContainer, 'hidden', false); - console.log(this.commentService.commentsModel); return this.tree.setInput(this.commentService.commentsModel); } @@ -190,10 +204,27 @@ export class CommentsPanel extends Panel { accessibilityProvider: new DefaultAccessibilityProvider, controller: new DefaultController(), dnd: new DefaultDragAndDrop(), - filter: new DefaultFilter() + filter: new DataFilter() }, { twistiePixels: 20, ariaLabel: COMMENTS_PANEL_TITLE }); + + const commentsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); + this._register(debounceEvent(commentsNavigator.openResource, (last, event) => event, 75, true)(options => { + if (options.element instanceof ResourceCommentThreads) { + const resource = options.element.resource; + this.editorService.openEditor({ + resource, + options: { + pinned: options.editorOptions.pinned + } + }, + options.sideBySide); + return true; + } else { + return false; + } + })); } } diff --git a/src/vs/workbench/services/comments/electron-browser/commentService.ts b/src/vs/workbench/services/comments/electron-browser/commentService.ts index 8c90291162a..749510f1ff5 100644 --- a/src/vs/workbench/services/comments/electron-browser/commentService.ts +++ b/src/vs/workbench/services/comments/electron-browser/commentService.ts @@ -11,6 +11,7 @@ import { CommentThread, Comment } from 'vs/editor/common/modes'; import { createDecorator } 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'; export const ICommentService = createDecorator('commentService'); @@ -27,16 +28,18 @@ export class CommentNode { } } -export class ResourceCommentThread { +export class ResourceCommentThreads { id: string; - comments: CommentNode[]; + comments: CommentNode[]; // The top level comments on the file. Replys are nested under each node. + resource: URI; - constructor(commentThread: CommentThread) { - this.id = commentThread.threadId; - this.comments = this.createCommentNodes(commentThread.comments); + constructor(resource: URI, commentThreads: CommentThread[]) { + this.id = Math.random().toString(); + this.resource = resource; + this.comments = commentThreads.map(thread => this.createCommentNode(thread.comments)); } - private createCommentNodes(comments: Comment[]): CommentNode[] { + private createCommentNode(comments: Comment[]): CommentNode { const commentNodes: CommentNode[] = comments.map(comment => new CommentNode(comment)); for (var i = 0; i < commentNodes.length - 1; i++) { const commentNode = commentNodes[i]; @@ -45,20 +48,26 @@ export class ResourceCommentThread { commentNodes.push(new CommentNode(comments[comments.length - 1])); - return commentNodes; + return commentNodes[0]; } } export class CommentsModel { - commentThreads: ResourceCommentThread[]; + commentThreads: ResourceCommentThreads[]; + commentThreadsByResource: Map; constructor() { this.commentThreads = []; + this.commentThreadsByResource = new Map(); } // TODO: Should have an additional level of nesting, mapping file to comment threads - public setCommentThreads(commentThreads: CommentThread[]) { - this.commentThreads = commentThreads.map(commentThread => new ResourceCommentThread(commentThread)); + public setCommentThreadsForResource(resource: URI, commentThreads: CommentThread[]) { + this.commentThreadsByResource.set(resource.toString(), new ResourceCommentThreads(resource, commentThreads)); + this.commentThreads = []; + this.commentThreadsByResource.forEach((v, i, m) => { + this.commentThreads.push(v); + }); } } @@ -66,7 +75,7 @@ export interface ICommentService { _serviceBrand: any; readonly commentsModel; readonly onDidChangeCommentThreads: Event; - updateComments(commentThreads: CommentThread[]); + setCommentsForResource(resource: URI, commentThreads: CommentThread[]); } export class CommentService extends Disposable implements ICommentService { @@ -82,8 +91,8 @@ export class CommentService extends Disposable implements ICommentService { this.commentsModel = new CommentsModel(); } - updateComments(commentThreads: CommentThread[]): TPromise { - this.commentsModel.setCommentThreads(commentThreads); + setCommentsForResource(resource: URI, commentThreads: CommentThread[]): TPromise { + this.commentsModel.setCommentThreadsForResource(resource, commentThreads); this._onDidChangeCommentThreads.fire(); return TPromise.as(null); }