diff --git a/src/vs/workbench/api/browser/mainThreadComments.ts b/src/vs/workbench/api/browser/mainThreadComments.ts index b7af867d10cd0..bbd29b585a213 100644 --- a/src/vs/workbench/api/browser/mainThreadComments.ts +++ b/src/vs/workbench/api/browser/mainThreadComments.ts @@ -16,9 +16,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { Extensions as PanelExtensions, PanelDescriptor, PanelRegistry } from 'vs/workbench/browser/panel'; import { ICommentInfo, ICommentService } from 'vs/workbench/contrib/comments/browser/commentService'; -import { CommentsPanel, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsPanel'; +import { CommentsPanel } from 'vs/workbench/contrib/comments/browser/commentsPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { CommentProviderFeatures, ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape } from '../common/extHost.protocol'; +import { COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; export class MainThreadCommentThread implements modes.CommentThread { diff --git a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts index 27440e3e996b6..5f0e33758a410 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsPanel.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsPanel.ts @@ -4,34 +4,31 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/panel'; +import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { IAction } from 'vs/base/common/actions'; -import { Event } from 'vs/base/common/event'; -import { CollapseAllAction, DefaultAccessibilityProvider, DefaultController, DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; +import { IAction, Action } from 'vs/base/common/actions'; +import { CollapseAllAction } from 'vs/base/browser/ui/tree/treeDefaults'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Panel } from 'vs/workbench/browser/panel'; import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; import { ReviewController } from 'vs/workbench/contrib/comments/browser/commentsEditorContribution'; -import { CommentsDataFilter, CommentsDataSource, CommentsModelRenderer } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; import { ICommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/browser/commentService'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ResourceLabels } from 'vs/workbench/browser/labels'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { CommentsList, COMMENTS_PANEL_ID, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/browser/commentsTreeViewer'; -export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; -export const COMMENTS_PANEL_TITLE = 'Comments'; export class CommentsPanel extends Panel { private treeLabels: ResourceLabels; - private tree: WorkbenchTree; + private tree: CommentsList; private treeContainer: HTMLElement; private messageBoxContainer: HTMLElement; private messageBox: HTMLElement; @@ -42,7 +39,6 @@ export class CommentsPanel extends Panel { @IInstantiationService private readonly instantiationService: IInstantiationService, @ICommentService private readonly commentService: ICommentService, @IEditorService private readonly editorService: IEditorService, - @IOpenerService private readonly openerService: IOpenerService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService @@ -113,7 +109,7 @@ export class CommentsPanel extends Panel { public getActions(): IAction[] { if (!this.collapseAllAction) { - this.collapseAllAction = this.instantiationService.createInstance(CollapseAllAction, this.tree, this.commentsModel.hasCommentThreads()); + this.collapseAllAction = new Action('vs.tree.collapse', nls.localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); this._register(this.collapseAllAction); } @@ -141,22 +137,11 @@ export class CommentsPanel extends Panel { private createTree(): void { this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); + this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer)); - this.tree = this._register(this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { - dataSource: new CommentsDataSource(), - renderer: new CommentsModelRenderer(this.treeLabels, this.openerService), - accessibilityProvider: new DefaultAccessibilityProvider, - controller: new DefaultController(), - dnd: new DefaultDragAndDrop(), - filter: new CommentsDataFilter() - }, { - twistiePixels: 20, - ariaLabel: COMMENTS_PANEL_TITLE - })); - - const commentsNavigator = this._register(new TreeResourceNavigator(this.tree, { openOnFocus: true })); - this._register(Event.debounce(commentsNavigator.openResource, (last, event) => event, 100, true)(options => { - this.openFile(options.element, options.editorOptions.pinned, options.editorOptions.preserveFocus, options.sideBySide); + const commentsNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: true })); + this._register(commentsNavigator.onDidOpenResource(e => { + this.openFile(e.element, e.editorOptions.pinned, e.editorOptions.preserveFocus, e.sideBySide); })); } @@ -213,7 +198,7 @@ export class CommentsPanel extends Panel { this.collapseAllAction.enabled = this.commentsModel.hasCommentThreads(); dom.toggleClass(this.treeContainer, 'hidden', !this.commentsModel.hasCommentThreads()); - this.tree.refresh().then(() => { + this.tree.updateChildren().then(() => { this.renderMessage(); }, (e) => { console.log(e); @@ -243,4 +228,4 @@ CommandsRegistry.registerCommand({ panelService.openPanel(COMMENTS_PANEL_ID, true); } } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts index 090ffa5465099..7ab435e96ca24 100644 --- a/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/browser/commentsTreeViewer.ts @@ -9,30 +9,28 @@ import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IDataSource, IFilter, IRenderer as ITreeRenderer, ITree } from 'vs/base/parts/tree/browser/tree'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel'; - -export class CommentsDataSource implements IDataSource { - public getId(tree: ITree, element: any): string { - if (element instanceof CommentsModel) { - return 'root'; - } - if (element instanceof ResourceWithCommentThreads) { - return `${element.owner}-${element.id}`; - } - if (element instanceof CommentNode) { - return `${element.owner}-${element.resource.toString()}-${element.threadId}-${element.comment.uniqueIdInThread}` + (element.isRoot ? '-root' : ''); - } - return ''; - } - - public hasChildren(tree: ITree, element: any): boolean { +import { IAsyncDataSource, ITreeNode } from 'vs/base/browser/ui/tree/tree'; +import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; + +export const COMMENTS_PANEL_ID = 'workbench.panel.comments'; +export const COMMENTS_PANEL_TITLE = 'Comments'; + +export class CommentsAsyncDataSource implements IAsyncDataSource { + hasChildren(element: any): boolean { return element instanceof CommentsModel || element instanceof ResourceWithCommentThreads || (element instanceof CommentNode && !!element.replies.length); } - public getChildren(tree: ITree, element: any): Promise { + getChildren(element: any): any[] | Promise { if (element instanceof CommentsModel) { return Promise.resolve(element.resourceCommentThreads); } @@ -44,14 +42,6 @@ export class CommentsDataSource implements IDataSource { } return Promise.resolve([]); } - - public getParent(tree: ITree, element: any): Promise { - return Promise.resolve(undefined); - } - - public shouldAutoexpand(tree: ITree, element: any): boolean { - return true; - } } interface IResourceTemplateData { @@ -65,61 +55,36 @@ interface ICommentThreadTemplateData { disposables: IDisposable[]; } -export class CommentsModelRenderer implements ITreeRenderer { +export class CommentsModelVirualDelegate implements IListVirtualDelegate { private static RESOURCE_ID = 'resource-with-comments'; private static COMMENT_ID = 'comment-node'; - constructor( - private labels: ResourceLabels, - @IOpenerService private readonly openerService: IOpenerService - ) { - } - public getHeight(tree: ITree, element: any): number { + getHeight(element: any): number { return 22; } - public getTemplateId(tree: ITree, element: any): string { + public getTemplateId(element: any): string { if (element instanceof ResourceWithCommentThreads) { - return CommentsModelRenderer.RESOURCE_ID; + return CommentsModelVirualDelegate.RESOURCE_ID; } if (element instanceof CommentNode) { - return CommentsModelRenderer.COMMENT_ID; + return CommentsModelVirualDelegate.COMMENT_ID; } return ''; } +} - public renderTemplate(ITree: ITree, templateId: string, container: HTMLElement): any { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - return this.renderResourceTemplate(container); - case CommentsModelRenderer.COMMENT_ID: - return this.renderCommentTemplate(container); - } - } - - public disposeTemplate(tree: ITree, templateId: string, templateData: any): void { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - (templateData).resourceLabel.dispose(); - break; - case CommentsModelRenderer.COMMENT_ID: - (templateData).disposables.forEach(disposeable => disposeable.dispose()); - break; - } - } +export class ResourceWithCommentsRenderer implements IListRenderer, IResourceTemplateData> { + templateId: string = 'resource-with-comments'; - public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { - switch (templateId) { - case CommentsModelRenderer.RESOURCE_ID: - return this.renderResourceElement(tree, element, templateData); - case CommentsModelRenderer.COMMENT_ID: - return this.renderCommentElement(tree, element, templateData); - } + constructor( + private labels: ResourceLabels + ) { } - private renderResourceTemplate(container: HTMLElement): IResourceTemplateData { + renderTemplate(container: HTMLElement) { const data = Object.create(null); const labelContainer = dom.append(container, dom.$('.resource-container')); data.resourceLabel = this.labels.create(labelContainer); @@ -127,7 +92,23 @@ export class CommentsModelRenderer implements ITreeRenderer { return data; } - private renderCommentTemplate(container: HTMLElement): ICommentThreadTemplateData { + renderElement(node: ITreeNode, index: number, templateData: IResourceTemplateData, height: number | undefined): void { + templateData.resourceLabel.setFile(node.element.resource); + } + + disposeTemplate(templateData: IResourceTemplateData): void { + templateData.resourceLabel.dispose(); + } +} + +export class CommentNodeRenderer implements IListRenderer, ICommentThreadTemplateData> { + templateId: string = 'comment-node'; + + constructor( + @IOpenerService private readonly openerService: IOpenerService + ) { } + + renderTemplate(container: HTMLElement) { const data = Object.create(null); const labelContainer = dom.append(container, dom.$('.comment-container')); data.userName = dom.append(labelContainer, dom.$('.user')); @@ -137,16 +118,12 @@ export class CommentsModelRenderer implements ITreeRenderer { return data; } - private renderResourceElement(tree: ITree, element: ResourceWithCommentThreads, templateData: IResourceTemplateData) { - templateData.resourceLabel.setFile(element.resource); - } - - private renderCommentElement(tree: ITree, element: CommentNode, templateData: ICommentThreadTemplateData) { - templateData.userName.textContent = element.comment.userName; + renderElement(node: ITreeNode, index: number, templateData: ICommentThreadTemplateData, height: number | undefined): void { + templateData.userName.textContent = node.element.comment.userName; templateData.commentText.innerHTML = ''; const disposables = new DisposableStore(); templateData.disposables.push(disposables); - const renderedComment = renderMarkdown(element.comment.body, { + const renderedComment = renderMarkdown(node.element.comment.body, { inline: true, actionHandler: { callback: (content) => { @@ -171,16 +148,71 @@ export class CommentsModelRenderer implements ITreeRenderer { templateData.commentText.appendChild(renderedComment); } + + disposeTemplate(templateData: ICommentThreadTemplateData): void { + templateData.disposables.forEach(disposeable => disposeable.dispose()); + } } -export class CommentsDataFilter implements IFilter { - public isVisible(tree: ITree, element: any): boolean { - if (element instanceof CommentsModel) { - return element.resourceCommentThreads.length > 0; - } - if (element instanceof ResourceWithCommentThreads) { - return element.commentThreads.length > 0; - } - return true; +export class CommentsList extends WorkbenchAsyncDataTree { + constructor( + labels: ResourceLabels, + container: HTMLElement, + @IContextKeyService contextKeyService: IContextKeyService, + @IListService listService: IListService, + @IThemeService themeService: IThemeService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService keybindingService: IKeybindingService, + @IAccessibilityService accessibilityService: IAccessibilityService + ) { + const delegate = new CommentsModelVirualDelegate(); + const dataSource = new CommentsAsyncDataSource(); + + const renderers = [ + instantiationService.createInstance(ResourceWithCommentsRenderer, labels), + instantiationService.createInstance(CommentNodeRenderer) + ]; + + super( + container, + delegate, + renderers, + dataSource, + { + ariaLabel: COMMENTS_PANEL_TITLE, + keyboardSupport: true, + identityProvider: { + getId: (element: any) => { + if (element instanceof CommentsModel) { + return 'root'; + } + if (element instanceof ResourceWithCommentThreads) { + return `${element.owner}-${element.id}`; + } + if (element instanceof CommentNode) { + return `${element.owner}-${element.resource.toString()}-${element.threadId}-${element.comment.uniqueIdInThread}` + (element.isRoot ? '-root' : ''); + } + return ''; + } + }, + expandOnlyOnTwistieClick: (element: any) => { + if (element instanceof CommentsModel || element instanceof ResourceWithCommentThreads) { + return false; + } + + return true; + }, + collapseByDefault: () => { + return false; + } + }, + contextKeyService, + listService, + themeService, + configurationService, + keybindingService, + accessibilityService + ); } }