Path: blob/main/src/vs/workbench/contrib/comments/browser/comments.contribution.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as nls from '../../../../nls.js';6import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';7import { Registry } from '../../../../platform/registry/common/platform.js';8import './commentsEditorContribution.js';9import { ICommentService, CommentService, IWorkspaceCommentThreadsEvent } from './commentService.js';10import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from '../../../../platform/configuration/common/configurationRegistry.js';11import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';12import { Disposable, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';13import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';14import { Extensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from '../../../common/contributions.js';15import { IActivityService, NumberBadge } from '../../../services/activity/common/activity.js';16import { COMMENTS_VIEW_ID } from './commentsTreeViewer.js';17import { CommentThreadState } from '../../../../editor/common/languages.js';18import { LifecyclePhase } from '../../../services/lifecycle/common/lifecycle.js';19import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';20import { CONTEXT_KEY_HAS_COMMENTS, CONTEXT_KEY_SOME_COMMENTS_EXPANDED, CommentsPanel } from './commentsView.js';21import { ViewAction } from '../../../browser/parts/views/viewPane.js';22import { Codicon } from '../../../../base/common/codicons.js';23import { IEditorService } from '../../../services/editor/common/editorService.js';24import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js';25import { revealCommentThread } from './commentsController.js';26import { MarshalledCommentThreadInternal } from '../../../common/comments.js';27import { accessibleViewCurrentProviderId, accessibleViewIsShown } from '../../accessibility/browser/accessibilityConfiguration.js';28import { AccessibleViewProviderId } from '../../../../platform/accessibility/browser/accessibleView.js';29import { AccessibleViewRegistry } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';30import { CommentsAccessibleView, CommentThreadAccessibleView } from './commentsAccessibleView.js';31import { CommentsAccessibilityHelp } from './commentsAccessibility.js';3233registerAction2(class Collapse extends ViewAction<CommentsPanel> {34constructor() {35super({36viewId: COMMENTS_VIEW_ID,37id: 'comments.collapse',38title: nls.localize('collapseAll', "Collapse All"),39f1: false,40icon: Codicon.collapseAll,41menu: {42id: MenuId.ViewTitle,43group: 'navigation',44when: ContextKeyExpr.and(ContextKeyExpr.and(ContextKeyExpr.equals('view', COMMENTS_VIEW_ID), CONTEXT_KEY_HAS_COMMENTS), CONTEXT_KEY_SOME_COMMENTS_EXPANDED),45order: 10046}47});48}49runInView(_accessor: ServicesAccessor, view: CommentsPanel) {50view.collapseAll();51}52});5354registerAction2(class Expand extends ViewAction<CommentsPanel> {55constructor() {56super({57viewId: COMMENTS_VIEW_ID,58id: 'comments.expand',59title: nls.localize('expandAll', "Expand All"),60f1: false,61icon: Codicon.expandAll,62menu: {63id: MenuId.ViewTitle,64group: 'navigation',65when: ContextKeyExpr.and(ContextKeyExpr.and(ContextKeyExpr.equals('view', COMMENTS_VIEW_ID), CONTEXT_KEY_HAS_COMMENTS), ContextKeyExpr.not(CONTEXT_KEY_SOME_COMMENTS_EXPANDED.key)),66order: 10067}68});69}70runInView(_accessor: ServicesAccessor, view: CommentsPanel) {71view.expandAll();72}73});7475registerAction2(class Reply extends Action2 {76constructor() {77super({78id: 'comments.reply',79title: nls.localize('reply', "Reply"),80icon: Codicon.reply,81precondition: ContextKeyExpr.equals('canReply', true),82menu: [{83id: MenuId.CommentsViewThreadActions,84order: 10085},86{87id: MenuId.AccessibleView,88when: ContextKeyExpr.and(accessibleViewIsShown, ContextKeyExpr.equals(accessibleViewCurrentProviderId.key, AccessibleViewProviderId.Comments)),89}]90});91}9293override run(accessor: ServicesAccessor, marshalledCommentThread: MarshalledCommentThreadInternal): void {94const commentService = accessor.get(ICommentService);95const editorService = accessor.get(IEditorService);96const uriIdentityService = accessor.get(IUriIdentityService);97revealCommentThread(commentService, editorService, uriIdentityService, marshalledCommentThread.thread, marshalledCommentThread.thread.comments![marshalledCommentThread.thread.comments!.length - 1], true);98}99});100101Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({102id: 'comments',103order: 20,104title: nls.localize('commentsConfigurationTitle', "Comments"),105type: 'object',106properties: {107'comments.openPanel': {108enum: ['neverOpen', 'openOnSessionStart', 'openOnSessionStartWithComments'],109default: 'openOnSessionStartWithComments',110description: nls.localize('openComments', "Controls when the comments panel should open."),111restricted: false,112markdownDeprecationMessage: nls.localize('comments.openPanel.deprecated', "This setting is deprecated in favor of `comments.openView`.")113},114'comments.openView': {115enum: ['never', 'file', 'firstFile', 'firstFileUnresolved'],116enumDescriptions: [nls.localize('comments.openView.never', "The comments view will never be opened."), nls.localize('comments.openView.file', "The comments view will open when a file with comments is active."), nls.localize('comments.openView.firstFile', "If the comments view has not been opened yet during this session it will open the first time during a session that a file with comments is active."), nls.localize('comments.openView.firstFileUnresolved', "If the comments view has not been opened yet during this session and the comment is not resolved, it will open the first time during a session that a file with comments is active.")],117default: 'firstFile',118description: nls.localize('comments.openView', "Controls when the comments view should open."),119restricted: false120},121'comments.useRelativeTime': {122type: 'boolean',123default: true,124description: nls.localize('useRelativeTime', "Determines if relative time will be used in comment timestamps (ex. '1 day ago').")125},126'comments.visible': {127type: 'boolean',128default: true,129description: nls.localize('comments.visible', "Controls the visibility of the comments bar and comment threads in editors that have commenting ranges and comments. Comments are still accessible via the Comments view and will cause commenting to be toggled on in the same way running the command \"Comments: Toggle Editor Commenting\" toggles comments.")130},131'comments.maxHeight': {132type: 'boolean',133default: true,134description: nls.localize('comments.maxHeight', "Controls whether the comments widget scrolls or expands.")135},136'comments.collapseOnResolve': {137type: 'boolean',138default: true,139description: nls.localize('collapseOnResolve', "Controls whether the comment thread should collapse when the thread is resolved.")140},141'comments.thread.confirmOnCollapse': {142type: 'string',143enum: ['whenHasUnsubmittedComments', 'never'],144enumDescriptions: [nls.localize('confirmOnCollapse.whenHasUnsubmittedComments', "Show a confirmation dialog when collapsing a comment thread with unsubmitted comments."), nls.localize('confirmOnCollapse.never', "Never show a confirmation dialog when collapsing a comment thread.")],145default: 'whenHasUnsubmittedComments',146description: nls.localize('confirmOnCollapse', "Controls whether a confirmation dialog is shown when collapsing a comment thread.")147}148}149});150151registerSingleton(ICommentService, CommentService, InstantiationType.Delayed);152153export class UnresolvedCommentsBadge extends Disposable implements IWorkbenchContribution {154private readonly activity = this._register(new MutableDisposable<IDisposable>());155private totalUnresolved = 0;156157constructor(158@ICommentService private readonly _commentService: ICommentService,159@IActivityService private readonly activityService: IActivityService) {160super();161this._register(this._commentService.onDidSetAllCommentThreads(this.onAllCommentsChanged, this));162this._register(this._commentService.onDidUpdateCommentThreads(this.onCommentsUpdated, this));163this._register(this._commentService.onDidDeleteDataProvider(this.onCommentsUpdated, this));164}165166private onAllCommentsChanged(e: IWorkspaceCommentThreadsEvent): void {167let unresolved = 0;168for (const thread of e.commentThreads) {169if (thread.state === CommentThreadState.Unresolved) {170unresolved++;171}172}173this.updateBadge(unresolved);174}175176private onCommentsUpdated(): void {177let unresolved = 0;178for (const resource of this._commentService.commentsModel.resourceCommentThreads) {179for (const thread of resource.commentThreads) {180if (thread.threadState === CommentThreadState.Unresolved) {181unresolved++;182}183}184}185this.updateBadge(unresolved);186}187188private updateBadge(unresolved: number) {189if (unresolved === this.totalUnresolved) {190return;191}192193this.totalUnresolved = unresolved;194const message = nls.localize('totalUnresolvedComments', '{0} Unresolved Comments', this.totalUnresolved);195this.activity.value = this.activityService.showViewActivity(COMMENTS_VIEW_ID, { badge: new NumberBadge(this.totalUnresolved, () => message) });196}197}198199Registry.as<IWorkbenchContributionsRegistry>(Extensions.Workbench).registerWorkbenchContribution(UnresolvedCommentsBadge, LifecyclePhase.Eventually);200201AccessibleViewRegistry.register(new CommentsAccessibleView());202AccessibleViewRegistry.register(new CommentThreadAccessibleView());203AccessibleViewRegistry.register(new CommentsAccessibilityHelp());204205206