mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-09 01:32:34 -05:00
1159 lines
42 KiB
TypeScript
1159 lines
42 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import * as nls from 'vs/nls';
|
|
import * as dom from 'vs/base/browser/dom';
|
|
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';
|
|
import { Color } from 'vs/base/common/color';
|
|
import { Emitter, Event } from 'vs/base/common/event';
|
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
|
import * as strings from 'vs/base/common/strings';
|
|
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
|
import * as modes from 'vs/editor/common/modes';
|
|
import { peekViewBorder } from 'vs/editor/contrib/referenceSearch/referencesWidget';
|
|
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
|
|
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
|
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
|
|
import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget';
|
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
import { IModelService } from 'vs/editor/common/services/modelService';
|
|
import { SimpleCommentEditor } from './simpleCommentEditor';
|
|
import { URI } from 'vs/base/common/uri';
|
|
import { transparent, editorForeground, textLinkActiveForeground, textLinkForeground, focusBorder, textBlockQuoteBackground, textBlockQuoteBorder, contrastBorder, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry';
|
|
import { IModeService } from 'vs/editor/common/services/modeService';
|
|
import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
|
|
import { Range, IRange } from 'vs/editor/common/core/range';
|
|
import { IPosition } from 'vs/editor/common/core/position';
|
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
|
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
|
|
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
|
|
import { CommentNode } from 'vs/workbench/contrib/comments/browser/commentNode';
|
|
import { ITextModel } from 'vs/editor/common/model';
|
|
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';
|
|
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';
|
|
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';
|
|
const COMMENT_SCHEME = 'comment';
|
|
|
|
|
|
let INMEM_MODEL_ID = 0;
|
|
|
|
export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget {
|
|
private _headElement: HTMLElement;
|
|
protected _headingLabel: HTMLElement;
|
|
protected _actionbarWidget: ActionBar;
|
|
private _bodyElement: HTMLElement;
|
|
private _parentEditor: ICodeEditor;
|
|
private _commentEditor: ICodeEditor;
|
|
private _commentsElement: HTMLElement;
|
|
private _commentElements: CommentNode[];
|
|
private _commentForm: HTMLElement;
|
|
private _reviewThreadReplyButton: HTMLElement;
|
|
private _resizeObserver: any;
|
|
private _onDidClose = new Emitter<ReviewZoneWidget | undefined>();
|
|
private _onDidCreateThread = new Emitter<ReviewZoneWidget>();
|
|
private _isExpanded?: boolean;
|
|
private _collapseAction: Action;
|
|
private _commentGlyph?: CommentGlyphWidget;
|
|
private _submitActionsDisposables: IDisposable[];
|
|
private _globalToDispose: IDisposable[];
|
|
private _commentThreadDisposables: IDisposable[] = [];
|
|
private _markdownRenderer: MarkdownRenderer;
|
|
private _styleElement: HTMLStyleElement;
|
|
private _formActions: HTMLElement | null;
|
|
private _error: HTMLElement;
|
|
private _contextKeyService: IContextKeyService;
|
|
private _threadIsEmpty: IContextKey<boolean>;
|
|
private _commentThreadContextValue: IContextKey<string>;
|
|
private _commentEditorIsEmpty: IContextKey<boolean>;
|
|
private _commentFormActions: CommentFormActions;
|
|
|
|
public get owner(): string {
|
|
return this._owner;
|
|
}
|
|
public get commentThread(): modes.CommentThread {
|
|
return this._commentThread as modes.CommentThread;
|
|
}
|
|
|
|
public get extensionId(): string | undefined {
|
|
return this._commentThread.extensionId;
|
|
}
|
|
|
|
public get draftMode(): modes.DraftMode | undefined {
|
|
return this._draftMode;
|
|
}
|
|
|
|
private _commentMenus: CommentMenus;
|
|
|
|
constructor(
|
|
editor: ICodeEditor,
|
|
private _owner: string,
|
|
private _commentThread: modes.CommentThread | modes.CommentThread2,
|
|
private _pendingComment: string,
|
|
private _draftMode: modes.DraftMode | undefined,
|
|
@IInstantiationService private instantiationService: IInstantiationService,
|
|
@IModeService private modeService: IModeService,
|
|
@ICommandService private commandService: ICommandService,
|
|
@IModelService private modelService: IModelService,
|
|
@IThemeService private themeService: IThemeService,
|
|
@ICommentService private commentService: ICommentService,
|
|
@IOpenerService private openerService: IOpenerService,
|
|
@IKeybindingService private keybindingService: IKeybindingService,
|
|
@INotificationService private notificationService: INotificationService,
|
|
@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._commentThreadContextValue = contextKeyService.createKey('commentThread', _commentThread.contextValue);
|
|
|
|
this._resizeObserver = null;
|
|
this._isExpanded = _commentThread.collapsibleState ? _commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded : undefined;
|
|
this._globalToDispose = [];
|
|
this._commentThreadDisposables = [];
|
|
this._submitActionsDisposables = [];
|
|
this._formActions = null;
|
|
this._commentMenus = this.commentService.getCommentMenus(this._owner);
|
|
this.create();
|
|
|
|
this._styleElement = dom.createStyleSheet(this.domNode);
|
|
this._globalToDispose.push(this.themeService.onThemeChange(this._applyTheme, this));
|
|
this._globalToDispose.push(this.editor.onDidChangeConfiguration(e => {
|
|
if (e.fontInfo) {
|
|
this._applyTheme(this.themeService.getTheme());
|
|
}
|
|
}));
|
|
this._applyTheme(this.themeService.getTheme());
|
|
|
|
this._markdownRenderer = new MarkdownRenderer(editor, this.modeService, this.openerService);
|
|
this._parentEditor = editor;
|
|
}
|
|
|
|
public get onDidClose(): Event<ReviewZoneWidget | undefined> {
|
|
return this._onDidClose.event;
|
|
}
|
|
|
|
public get onDidCreateThread(): Event<ReviewZoneWidget> {
|
|
return this._onDidCreateThread.event;
|
|
}
|
|
|
|
public getPosition(): IPosition | undefined {
|
|
if (this.position) {
|
|
return this.position;
|
|
}
|
|
|
|
if (this._commentGlyph) {
|
|
return withNullAsUndefined(this._commentGlyph.getPosition().position);
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
protected revealLine(lineNumber: number) {
|
|
// we don't do anything here as we always do the reveal ourselves.
|
|
}
|
|
|
|
public reveal(commentId?: string) {
|
|
if (!this._isExpanded) {
|
|
this.show({ lineNumber: this._commentThread.range.startLineNumber, column: 1 }, 2);
|
|
}
|
|
|
|
if (commentId) {
|
|
let height = this.editor.getLayoutInfo().height;
|
|
let matchedNode = this._commentElements.filter(commentNode => commentNode.comment.commentId === commentId);
|
|
if (matchedNode && matchedNode.length) {
|
|
const commentThreadCoords = dom.getDomNodePagePosition(this._commentElements[0].domNode);
|
|
const commentCoords = dom.getDomNodePagePosition(matchedNode[0].domNode);
|
|
|
|
this.editor.setScrollTop(this.editor.getTopForLineNumber(this._commentThread.range.startLineNumber) - height / 2 + commentCoords.top - commentThreadCoords.top);
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.editor.revealRangeInCenter(this._commentThread.range);
|
|
}
|
|
|
|
public getPendingComment(): string | null {
|
|
if (this._commentEditor) {
|
|
let model = this._commentEditor.getModel();
|
|
|
|
if (model && model.getValueLength() > 0) { // checking length is cheap
|
|
return model.getValue();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
protected _fillContainer(container: HTMLElement): void {
|
|
this.setCssClass('review-widget');
|
|
this._headElement = <HTMLDivElement>dom.$('.head');
|
|
container.appendChild(this._headElement);
|
|
this._fillHead(this._headElement);
|
|
|
|
this._bodyElement = <HTMLDivElement>dom.$('.body');
|
|
container.appendChild(this._bodyElement);
|
|
}
|
|
|
|
protected _fillHead(container: HTMLElement): void {
|
|
let titleElement = dom.append(this._headElement, dom.$('.review-title'));
|
|
|
|
this._headingLabel = dom.append(titleElement, dom.$('span.filename'));
|
|
this.createThreadLabel();
|
|
|
|
const actionsContainer = dom.append(this._headElement, dom.$('.review-actions'));
|
|
this._actionbarWidget = new ActionBar(actionsContainer, {
|
|
actionViewItemProvider: (action: IAction) => {
|
|
if (action instanceof MenuItemAction) {
|
|
let item = new ContextAwareMenuEntryActionViewItem(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());
|
|
|
|
if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
|
|
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.context = this._commentThread;
|
|
}
|
|
|
|
private setActionBarActions(menu: IMenu): void {
|
|
const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], <MenuItemAction[]>[]);
|
|
this._actionbarWidget.clear();
|
|
this._actionbarWidget.push([...groups, this._collapseAction], { label: false, icon: true });
|
|
}
|
|
|
|
public collapse(): Promise<void> {
|
|
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
|
|
if ((this._commentThread as modes.CommentThread2).commentThreadHandle === undefined) {
|
|
this.dispose();
|
|
return Promise.resolve();
|
|
} else {
|
|
const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand;
|
|
if (deleteCommand) {
|
|
return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || []));
|
|
} else if (this._commentEditor.getValue() === '') {
|
|
this.commentService.disposeCommentThread(this._owner, this._commentThread.threadId!);
|
|
this.dispose();
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.hide();
|
|
return Promise.resolve();
|
|
}
|
|
|
|
public getGlyphPosition(): number {
|
|
if (this._commentGlyph) {
|
|
return this._commentGlyph.getPosition().position!.lineNumber;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
toggleExpand(lineNumber: number) {
|
|
if (this._isExpanded) {
|
|
this.hide();
|
|
if (this._commentThread === null || this._commentThread.threadId === null) {
|
|
this.dispose();
|
|
}
|
|
} else {
|
|
this.show({ lineNumber: lineNumber, column: 1 }, 2);
|
|
}
|
|
}
|
|
|
|
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[] = [];
|
|
for (let i = 0; i < oldCommentsLen; i++) {
|
|
let comment = this._commentElements[i].comment;
|
|
let newComment = commentThread.comments ? commentThread.comments.filter(c => c.commentId === comment.commentId) : [];
|
|
|
|
if (newComment.length) {
|
|
this._commentElements[i].update(newComment[0]);
|
|
} else {
|
|
commentElementsToDelIndex.push(i);
|
|
commentElementsToDel.push(this._commentElements[i]);
|
|
}
|
|
}
|
|
|
|
// del removed elements
|
|
for (let i = commentElementsToDel.length - 1; i >= 0; i--) {
|
|
this._commentElements.splice(commentElementsToDelIndex[i], 1);
|
|
this._commentsElement.removeChild(commentElementsToDel[i].domNode);
|
|
}
|
|
|
|
let lastCommentElement: HTMLElement | null = null;
|
|
let newCommentNodeList: CommentNode[] = [];
|
|
for (let i = newCommentsLen - 1; i >= 0; i--) {
|
|
let currentComment = commentThread.comments![i];
|
|
let oldCommentNode = this._commentElements.filter(commentNode => commentNode.comment.commentId === currentComment.commentId);
|
|
if (oldCommentNode.length) {
|
|
oldCommentNode[0].update(currentComment);
|
|
lastCommentElement = oldCommentNode[0].domNode;
|
|
newCommentNodeList.unshift(oldCommentNode[0]);
|
|
} else {
|
|
const newElement = this.createNewCommentNode(currentComment);
|
|
|
|
newCommentNodeList.unshift(newElement);
|
|
if (lastCommentElement) {
|
|
this._commentsElement.insertBefore(newElement.domNode, lastCommentElement);
|
|
lastCommentElement = newElement.domNode;
|
|
} else {
|
|
this._commentsElement.appendChild(newElement.domNode);
|
|
lastCommentElement = newElement.domNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
this._commentThread = commentThread;
|
|
this._commentElements = newCommentNodeList;
|
|
this.createThreadLabel();
|
|
|
|
if (this._formActions && this._commentEditor.hasModel()) {
|
|
dom.clearNode(this._formActions);
|
|
const model = this._commentEditor.getModel();
|
|
this.createCommentWidgetActions2(this._formActions, model);
|
|
}
|
|
|
|
// Move comment glyph widget and show position if the line has changed.
|
|
const lineNumber = this._commentThread.range.startLineNumber;
|
|
let shouldMoveWidget = false;
|
|
if (this._commentGlyph) {
|
|
if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
|
|
shouldMoveWidget = true;
|
|
this._commentGlyph.setLineNumber(lineNumber);
|
|
}
|
|
}
|
|
|
|
if (!this._reviewThreadReplyButton) {
|
|
this.createReplyButton();
|
|
}
|
|
|
|
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
|
|
this.expandReplyArea();
|
|
}
|
|
|
|
if (shouldMoveWidget && this._isExpanded) {
|
|
this.show({ lineNumber, column: 1 }, 2);
|
|
}
|
|
|
|
// The collapsible state is not initialized yet.
|
|
if (this._isExpanded === undefined) {
|
|
if (this._commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded) {
|
|
this.show({ lineNumber, column: 1 }, 2);
|
|
} else {
|
|
this.hide();
|
|
}
|
|
}
|
|
|
|
if (this._commentThread.contextValue) {
|
|
this._commentThreadContextValue.set(this._commentThread.contextValue);
|
|
} else {
|
|
this._commentThreadContextValue.reset();
|
|
}
|
|
}
|
|
|
|
updateDraftMode(draftMode: modes.DraftMode | undefined) {
|
|
if (this._draftMode !== draftMode) {
|
|
this._draftMode = draftMode;
|
|
|
|
if (this._formActions && this._commentEditor.hasModel()) {
|
|
const model = this._commentEditor.getModel();
|
|
dom.clearNode(this._formActions);
|
|
this.createCommentWidgetActions(this._formActions, model);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected _onWidth(widthInPixel: number): void {
|
|
this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
|
|
}
|
|
|
|
protected _doLayout(heightInPixel: number, widthInPixel: number): void {
|
|
this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
|
|
}
|
|
|
|
display(lineNumber: number) {
|
|
this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber);
|
|
|
|
this._disposables.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
|
|
this._disposables.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
|
|
|
|
let headHeight = Math.ceil(this.editor.getConfiguration().lineHeight * 1.2);
|
|
this._headElement.style.height = `${headHeight}px`;
|
|
this._headElement.style.lineHeight = this._headElement.style.height;
|
|
|
|
this._commentsElement = dom.append(this._bodyElement, dom.$('div.comments-container'));
|
|
this._commentsElement.setAttribute('role', 'presentation');
|
|
|
|
this._commentElements = [];
|
|
if (this._commentThread.comments) {
|
|
for (const comment of this._commentThread.comments) {
|
|
const newCommentNode = this.createNewCommentNode(comment);
|
|
|
|
this._commentElements.push(newCommentNode);
|
|
this._commentsElement.appendChild(newCommentNode.domNode);
|
|
}
|
|
}
|
|
|
|
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({
|
|
extensionId: this.extensionId,
|
|
commentThreadId: this.commentThread.threadId
|
|
});
|
|
|
|
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);
|
|
this._disposables.push(this._commentEditor);
|
|
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();
|
|
}
|
|
|
|
this.setCommentEditorDecorations();
|
|
|
|
// Only add the additional step of clicking a reply button to expand the textarea when there are existing comments
|
|
if (hasExistingComments) {
|
|
this.createReplyButton();
|
|
} else {
|
|
if (this._commentThread.comments && this._commentThread.comments.length === 0) {
|
|
this.expandReplyArea();
|
|
}
|
|
}
|
|
|
|
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.createCommentWidgetActionsListener(this._formActions, model);
|
|
} else {
|
|
this.createCommentWidgetActions(this._formActions, model);
|
|
}
|
|
|
|
this._resizeObserver = new MutationObserver(this._refresh.bind(this));
|
|
|
|
this._resizeObserver.observe(this._bodyElement, {
|
|
attributes: true,
|
|
childList: true,
|
|
characterData: true,
|
|
subtree: true
|
|
});
|
|
|
|
if (this._commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded) {
|
|
this.show({ lineNumber: lineNumber, column: 1 }, 2);
|
|
}
|
|
|
|
// If there are no existing comments, place focus on the text area. This must be done after show, which also moves focus.
|
|
// if this._commentThread.comments is undefined, it doesn't finish initialization yet, so we don't focus the editor immediately.
|
|
if ((this._commentThread as modes.CommentThread).reply && this._commentThread.comments && !this._commentThread.comments.length) {
|
|
this._commentEditor.focus();
|
|
} else if (this._commentEditor.getModel()!.getValueLength() > 0) {
|
|
this.expandReplyArea();
|
|
}
|
|
}
|
|
|
|
private createTextModelListener() {
|
|
this._commentThreadDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => {
|
|
let commentThread = this._commentThread as modes.CommentThread2;
|
|
commentThread.input = {
|
|
uri: this._commentEditor.getModel()!.uri,
|
|
value: this._commentEditor.getValue()
|
|
};
|
|
}));
|
|
|
|
this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => {
|
|
let modelContent = this._commentEditor.getValue();
|
|
let thread = (this._commentThread as modes.CommentThread2);
|
|
if (thread.input && thread.input.uri === this._commentEditor.getModel()!.uri && thread.input.value !== modelContent) {
|
|
let newInput: modes.CommentInput = thread.input;
|
|
newInput.value = modelContent;
|
|
thread.input = newInput;
|
|
}
|
|
}));
|
|
|
|
this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => {
|
|
let thread = (this._commentThread as modes.CommentThread2);
|
|
|
|
if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) {
|
|
return;
|
|
}
|
|
if (!input) {
|
|
return;
|
|
}
|
|
|
|
if (this._commentEditor.getValue() !== input.value) {
|
|
this._commentEditor.setValue(input.value);
|
|
|
|
if (input.value === '') {
|
|
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._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => {
|
|
await this.update(this._commentThread);
|
|
}));
|
|
|
|
this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => {
|
|
this.createThreadLabel();
|
|
}));
|
|
}
|
|
|
|
private createCommentWidgetActionsListener(container: HTMLElement, model: ITextModel) {
|
|
this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => {
|
|
if (container) {
|
|
dom.clearNode(container);
|
|
this.createCommentWidgetActions2(container, model);
|
|
}
|
|
}));
|
|
|
|
this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => {
|
|
if (container) {
|
|
dom.clearNode(container);
|
|
this.createCommentWidgetActions2(container, model);
|
|
}
|
|
}));
|
|
|
|
this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => {
|
|
// Move comment glyph widget and show position if the line has changed.
|
|
const lineNumber = this._commentThread.range.startLineNumber;
|
|
let shouldMoveWidget = false;
|
|
if (this._commentGlyph) {
|
|
if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
|
|
shouldMoveWidget = true;
|
|
this._commentGlyph.setLineNumber(lineNumber);
|
|
}
|
|
}
|
|
|
|
if (shouldMoveWidget && this._isExpanded) {
|
|
this.show({ lineNumber, column: 1 }, 2);
|
|
}
|
|
}));
|
|
|
|
this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => {
|
|
if (state === modes.CommentThreadCollapsibleState.Expanded && !this._isExpanded) {
|
|
const lineNumber = this._commentThread.range.startLineNumber;
|
|
|
|
this.show({ lineNumber, column: 1 }, 2);
|
|
return;
|
|
}
|
|
|
|
if (state === modes.CommentThreadCollapsibleState.Collapsed && this._isExpanded) {
|
|
this.hide();
|
|
return;
|
|
}
|
|
}));
|
|
}
|
|
|
|
private handleError(e: Error) {
|
|
this._error.textContent = e.message;
|
|
this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`;
|
|
dom.removeClass(this._error, 'hidden');
|
|
}
|
|
|
|
private getActiveComment(): CommentNode | ReviewZoneWidget {
|
|
return this._commentElements.filter(node => node.isEditing)[0] || this;
|
|
}
|
|
|
|
private createCommentWidgetActions(container: HTMLElement, model: ITextModel) {
|
|
dispose(this._submitActionsDisposables);
|
|
|
|
const button = new Button(container);
|
|
this._submitActionsDisposables.push(attachButtonStyler(button, this.themeService));
|
|
button.label = 'Add comment';
|
|
|
|
button.enabled = model.getValueLength() > 0;
|
|
this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => {
|
|
if (this._commentEditor.getValue()) {
|
|
button.enabled = true;
|
|
} else {
|
|
button.enabled = false;
|
|
}
|
|
}));
|
|
|
|
button.onDidClick(async () => {
|
|
this.createComment();
|
|
});
|
|
|
|
if (this._draftMode === modes.DraftMode.NotSupported) {
|
|
return;
|
|
}
|
|
|
|
switch (this._draftMode) {
|
|
case modes.DraftMode.InDraft:
|
|
const deleteDraftLabel = this.commentService.getDeleteDraftLabel(this._owner);
|
|
if (deleteDraftLabel) {
|
|
const deletedraftButton = new Button(container);
|
|
this._submitActionsDisposables.push(attachButtonStyler(deletedraftButton, this.themeService));
|
|
deletedraftButton.label = deleteDraftLabel;
|
|
deletedraftButton.enabled = true;
|
|
|
|
this._disposables.push(deletedraftButton.onDidClick(async () => {
|
|
try {
|
|
await this.commentService.deleteDraft(this._owner, this.editor.getModel()!.uri);
|
|
} catch (e) {
|
|
this.handleError(e);
|
|
}
|
|
}));
|
|
}
|
|
|
|
const submitDraftLabel = this.commentService.getFinishDraftLabel(this._owner);
|
|
if (submitDraftLabel) {
|
|
const submitdraftButton = new Button(container);
|
|
this._submitActionsDisposables.push(attachButtonStyler(submitdraftButton, this.themeService));
|
|
submitdraftButton.label = this.commentService.getFinishDraftLabel(this._owner)!;
|
|
submitdraftButton.enabled = true;
|
|
|
|
submitdraftButton.onDidClick(async () => {
|
|
try {
|
|
if (this._commentEditor.getValue()) {
|
|
await this.createComment();
|
|
}
|
|
await this.commentService.finishDraft(this._owner, this.editor.getModel()!.uri);
|
|
} catch (e) {
|
|
this.handleError(e);
|
|
}
|
|
});
|
|
}
|
|
|
|
break;
|
|
case modes.DraftMode.NotInDraft:
|
|
const startDraftLabel = this.commentService.getStartDraftLabel(this._owner);
|
|
if (startDraftLabel) {
|
|
const draftButton = new Button(container);
|
|
this._disposables.push(attachButtonStyler(draftButton, this.themeService));
|
|
draftButton.label = this.commentService.getStartDraftLabel(this._owner)!;
|
|
|
|
draftButton.enabled = model.getValueLength() > 0;
|
|
this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => {
|
|
if (this._commentEditor.getValue()) {
|
|
draftButton.enabled = true;
|
|
} else {
|
|
draftButton.enabled = false;
|
|
}
|
|
}));
|
|
|
|
this._disposables.push(draftButton.onDidClick(async () => {
|
|
try {
|
|
await this.commentService.startDraft(this._owner, this.editor.getModel()!.uri);
|
|
await this.createComment();
|
|
} catch (e) {
|
|
this.handleError(e);
|
|
}
|
|
}));
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Command based actions.
|
|
*/
|
|
private createCommentWidgetActions2(container: HTMLElement, model: ITextModel) {
|
|
let commentThread = this._commentThread as modes.CommentThread2;
|
|
const { acceptInputCommand, additionalCommands } = commentThread;
|
|
|
|
if (acceptInputCommand) {
|
|
const button = new Button(container);
|
|
this._disposables.push(attachButtonStyler(button, this.themeService));
|
|
|
|
button.label = acceptInputCommand.title;
|
|
this._disposables.push(button.onDidClick(async () => {
|
|
commentThread.input = {
|
|
uri: this._commentEditor.getModel()!.uri,
|
|
value: this._commentEditor.getValue()
|
|
};
|
|
await this.commandService.executeCommand(acceptInputCommand.id, ...(acceptInputCommand.arguments || []));
|
|
}));
|
|
|
|
button.enabled = model.getValueLength() > 0;
|
|
this._disposables.push(this._commentEditor.onDidChangeModelContent(_ => {
|
|
if (this._commentEditor.getValue()) {
|
|
button.enabled = true;
|
|
} else {
|
|
button.enabled = false;
|
|
}
|
|
}));
|
|
}
|
|
|
|
if (additionalCommands) {
|
|
additionalCommands.reverse().forEach(command => {
|
|
const button = new Button(container);
|
|
this._disposables.push(attachButtonStyler(button, this.themeService));
|
|
|
|
button.label = command.title;
|
|
this._disposables.push(button.onDidClick(async () => {
|
|
commentThread.input = {
|
|
uri: this._commentEditor.getModel()!.uri,
|
|
value: this._commentEditor.getValue()
|
|
};
|
|
await this.commandService.executeCommand(command.id, ...(command.arguments || []));
|
|
}));
|
|
});
|
|
}
|
|
|
|
const menu = this._commentMenus.getCommentThreadActions(commentThread, this._contextKeyService);
|
|
|
|
this._disposables.push(menu);
|
|
this._disposables.push(menu.onDidChange(() => {
|
|
this._commentFormActions.setActions(menu);
|
|
}));
|
|
|
|
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 {
|
|
let newCommentNode = this.instantiationService.createInstance(CommentNode,
|
|
this._commentThread,
|
|
comment,
|
|
this.owner,
|
|
this.editor.getModel()!.uri,
|
|
this._parentEditor,
|
|
this,
|
|
this._markdownRenderer);
|
|
|
|
this._disposables.push(newCommentNode);
|
|
this._disposables.push(newCommentNode.onDidDelete(deletedNode => {
|
|
const deletedNodeId = deletedNode.comment.commentId;
|
|
const deletedElementIndex = arrays.firstIndex(this._commentElements, commentNode => commentNode.comment.commentId === deletedNodeId);
|
|
if (deletedElementIndex > -1) {
|
|
this._commentElements.splice(deletedElementIndex, 1);
|
|
}
|
|
|
|
const deletedCommentIndex = arrays.firstIndex(this._commentThread.comments!, comment => comment.commentId === deletedNodeId);
|
|
if (deletedCommentIndex > -1) {
|
|
this._commentThread.comments!.splice(deletedCommentIndex, 1);
|
|
}
|
|
|
|
this._commentsElement.removeChild(deletedNode.domNode);
|
|
deletedNode.dispose();
|
|
|
|
if (this._commentThread.comments!.length === 0) {
|
|
this.dispose();
|
|
}
|
|
}));
|
|
|
|
return newCommentNode;
|
|
}
|
|
|
|
async submitComment(): Promise<void> {
|
|
const activeComment = this.getActiveComment();
|
|
if (activeComment instanceof ReviewZoneWidget) {
|
|
if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
|
|
let commentThread = this._commentThread as modes.CommentThread2;
|
|
|
|
if (commentThread.acceptInputCommand) {
|
|
commentThread.input = {
|
|
uri: this._commentEditor.getModel()!.uri,
|
|
value: this._commentEditor.getValue()
|
|
};
|
|
let commandId = commentThread.acceptInputCommand.id;
|
|
let args = commentThread.acceptInputCommand.arguments || [];
|
|
|
|
await this.commandService.executeCommand(commandId, ...args);
|
|
return;
|
|
}
|
|
} else {
|
|
this.createComment();
|
|
}
|
|
}
|
|
|
|
if (activeComment instanceof CommentNode) {
|
|
activeComment.editComment();
|
|
}
|
|
}
|
|
|
|
async createComment(): Promise<void> {
|
|
try {
|
|
if (this._commentEditor.getModel()!.getValueLength() === 0) {
|
|
return;
|
|
}
|
|
if (!this._commentGlyph) {
|
|
return;
|
|
}
|
|
|
|
let newCommentThread;
|
|
const lineNumber = this._commentGlyph.getPosition().position!.lineNumber;
|
|
const isReply = this._commentThread.threadId !== null;
|
|
|
|
|
|
if (isReply) {
|
|
newCommentThread = await this.commentService.replyToCommentThread(
|
|
this._owner,
|
|
this.editor.getModel()!.uri,
|
|
new Range(lineNumber, 1, lineNumber, 1),
|
|
this._commentThread,
|
|
this._commentEditor.getValue()
|
|
);
|
|
} else {
|
|
newCommentThread = await this.commentService.createNewCommentThread(
|
|
this._owner,
|
|
this.editor.getModel()!.uri,
|
|
new Range(lineNumber, 1, lineNumber, 1),
|
|
this._commentEditor.getValue()
|
|
);
|
|
|
|
if (newCommentThread) {
|
|
this.createReplyButton();
|
|
}
|
|
}
|
|
|
|
if (newCommentThread) {
|
|
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');
|
|
this.update(newCommentThread);
|
|
|
|
if (!isReply) {
|
|
this._onDidCreateThread.fire(this);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
this._error.textContent = e.message
|
|
? nls.localize('commentCreationError', "Adding a comment failed: {0}.", e.message)
|
|
: nls.localize('commentCreationDefaultError', "Adding a comment failed. Please try again or report an issue with the extension if the problem persists.");
|
|
this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`;
|
|
dom.removeClass(this._error, 'hidden');
|
|
}
|
|
}
|
|
|
|
private createThreadLabel() {
|
|
let label: string | undefined;
|
|
if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
|
|
label = (this._commentThread as modes.CommentThread2).label;
|
|
}
|
|
|
|
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);
|
|
} else {
|
|
label = nls.localize('startThread', "Start discussion");
|
|
}
|
|
}
|
|
|
|
if (label) {
|
|
this._headingLabel.innerHTML = strings.escape(label);
|
|
this._headingLabel.setAttribute('aria-label', label);
|
|
}
|
|
}
|
|
|
|
private expandReplyArea() {
|
|
if (!dom.hasClass(this._commentForm, 'expand')) {
|
|
dom.addClass(this._commentForm, 'expand');
|
|
this._commentEditor.focus();
|
|
}
|
|
}
|
|
|
|
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 = <HTMLButtonElement>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._disposables.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea()));
|
|
this._disposables.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea()));
|
|
|
|
this._commentEditor.onDidBlurEditorWidget(() => {
|
|
if (this._commentEditor.getModel()!.getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) {
|
|
dom.removeClass(this._commentForm, 'expand');
|
|
}
|
|
});
|
|
}
|
|
|
|
_refresh() {
|
|
if (this._isExpanded && this._bodyElement) {
|
|
let dimensions = dom.getClientArea(this._bodyElement);
|
|
const headHeight = Math.ceil(this.editor.getConfiguration().lineHeight * 1.2);
|
|
const lineHeight = this.editor.getConfiguration().lineHeight;
|
|
const arrowHeight = Math.round(lineHeight / 3);
|
|
const frameThickness = Math.round(lineHeight / 9) * 2;
|
|
|
|
const computedLinesNumber = Math.ceil((headHeight + dimensions.height + arrowHeight + frameThickness + 8 /** margin bottom to avoid margin collapse */) / lineHeight);
|
|
this._relayout(computedLinesNumber);
|
|
}
|
|
}
|
|
|
|
private setCommentEditorDecorations() {
|
|
const model = this._commentEditor && this._commentEditor.getModel();
|
|
if (model) {
|
|
const valueLength = model.getValueLength();
|
|
const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
|
|
const placeholder = valueLength > 0
|
|
? ''
|
|
: hasExistingComments
|
|
? nls.localize('reply', "Reply...")
|
|
: nls.localize('newComment', "Type a new comment");
|
|
const decorations = [{
|
|
range: {
|
|
startLineNumber: 0,
|
|
endLineNumber: 0,
|
|
startColumn: 0,
|
|
endColumn: 1
|
|
},
|
|
renderOptions: {
|
|
after: {
|
|
contentText: placeholder,
|
|
color: `${transparent(editorForeground, 0.4)(this.themeService.getTheme())}`
|
|
}
|
|
}
|
|
}];
|
|
|
|
this._commentEditor.setDecorations(COMMENTEDITOR_DECORATION_KEY, decorations);
|
|
}
|
|
}
|
|
|
|
private mouseDownInfo: { lineNumber: number } | null;
|
|
|
|
private onEditorMouseDown(e: IEditorMouseEvent): void {
|
|
this.mouseDownInfo = null;
|
|
|
|
const range = e.target.range;
|
|
|
|
if (!range) {
|
|
return;
|
|
}
|
|
|
|
if (!e.event.leftButton) {
|
|
return;
|
|
}
|
|
|
|
if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
|
|
return;
|
|
}
|
|
|
|
const data = e.target.detail as IMarginData;
|
|
const gutterOffsetX = data.offsetX - data.glyphMarginWidth - data.lineNumbersWidth - data.glyphMarginLeft;
|
|
|
|
// don't collide with folding and git decorations
|
|
if (gutterOffsetX > 14) {
|
|
return;
|
|
}
|
|
|
|
this.mouseDownInfo = { lineNumber: range.startLineNumber };
|
|
}
|
|
|
|
private onEditorMouseUp(e: IEditorMouseEvent): void {
|
|
if (!this.mouseDownInfo) {
|
|
return;
|
|
}
|
|
|
|
const { lineNumber } = this.mouseDownInfo;
|
|
this.mouseDownInfo = null;
|
|
|
|
const range = e.target.range;
|
|
|
|
if (!range || range.startLineNumber !== lineNumber) {
|
|
return;
|
|
}
|
|
|
|
if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
|
|
return;
|
|
}
|
|
|
|
if (!e.target.element) {
|
|
return;
|
|
}
|
|
|
|
if (this._commentGlyph && this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
|
|
return;
|
|
}
|
|
|
|
if (e.target.element.className.indexOf('comment-thread') >= 0) {
|
|
this.toggleExpand(lineNumber);
|
|
}
|
|
}
|
|
|
|
private _applyTheme(theme: ITheme) {
|
|
const borderColor = theme.getColor(peekViewBorder) || Color.transparent;
|
|
this.style({
|
|
arrowColor: borderColor,
|
|
frameColor: borderColor
|
|
});
|
|
|
|
const content: string[] = [];
|
|
const linkColor = theme.getColor(textLinkForeground);
|
|
if (linkColor) {
|
|
content.push(`.monaco-editor .review-widget .body .comment-body a { color: ${linkColor} }`);
|
|
}
|
|
|
|
const linkActiveColor = theme.getColor(textLinkActiveForeground);
|
|
if (linkActiveColor) {
|
|
content.push(`.monaco-editor .review-widget .body .comment-body a:hover, a:active { color: ${linkActiveColor} }`);
|
|
}
|
|
|
|
const focusColor = theme.getColor(focusBorder);
|
|
if (focusColor) {
|
|
content.push(`.monaco-editor .review-widget .body .comment-body a:focus { outline: 1px solid ${focusColor}; }`);
|
|
content.push(`.monaco-editor .review-widget .body .monaco-editor.focused { outline: 1px solid ${focusColor}; }`);
|
|
}
|
|
|
|
const blockQuoteBackground = theme.getColor(textBlockQuoteBackground);
|
|
if (blockQuoteBackground) {
|
|
content.push(`.monaco-editor .review-widget .body .review-comment blockquote { background: ${blockQuoteBackground}; }`);
|
|
}
|
|
|
|
const blockQuoteBOrder = theme.getColor(textBlockQuoteBorder);
|
|
if (blockQuoteBOrder) {
|
|
content.push(`.monaco-editor .review-widget .body .review-comment blockquote { border-color: ${blockQuoteBOrder}; }`);
|
|
}
|
|
|
|
const hcBorder = theme.getColor(contrastBorder);
|
|
if (hcBorder) {
|
|
content.push(`.monaco-editor .review-widget .body .comment-form .review-thread-reply-button { outline-color: ${hcBorder}; }`);
|
|
content.push(`.monaco-editor .review-widget .body .monaco-editor { outline: 1px solid ${hcBorder}; }`);
|
|
}
|
|
|
|
const errorBorder = theme.getColor(inputValidationErrorBorder);
|
|
if (errorBorder) {
|
|
content.push(`.monaco-editor .review-widget .validation-error { border: 1px solid ${errorBorder}; }`);
|
|
}
|
|
|
|
const errorBackground = theme.getColor(inputValidationErrorBackground);
|
|
if (errorBackground) {
|
|
content.push(`.monaco-editor .review-widget .validation-error { background: ${errorBackground}; }`);
|
|
}
|
|
|
|
const errorForeground = theme.getColor(inputValidationErrorForeground);
|
|
if (errorForeground) {
|
|
content.push(`.monaco-editor .review-widget .body .comment-form .validation-error { color: ${errorForeground}; }`);
|
|
}
|
|
|
|
const fontInfo = this.editor.getConfiguration().fontInfo;
|
|
content.push(`.monaco-editor .review-widget .body code {
|
|
font-family: ${fontInfo.fontFamily};
|
|
font-size: ${fontInfo.fontSize}px;
|
|
font-weight: ${fontInfo.fontWeight};
|
|
}`);
|
|
|
|
this._styleElement.innerHTML = content.join('\n');
|
|
|
|
// Editor decorations should also be responsive to theme changes
|
|
this.setCommentEditorDecorations();
|
|
}
|
|
|
|
show(rangeOrPos: IRange | IPosition, heightInLines: number): void {
|
|
this._isExpanded = true;
|
|
super.show(rangeOrPos, heightInLines);
|
|
this._refresh();
|
|
}
|
|
|
|
hide() {
|
|
this._isExpanded = false;
|
|
// Focus the container so that the comment editor will be blurred before it is hidden
|
|
this.editor.focus();
|
|
super.hide();
|
|
}
|
|
|
|
dispose() {
|
|
super.dispose();
|
|
if (this._resizeObserver) {
|
|
this._resizeObserver.disconnect();
|
|
this._resizeObserver = null;
|
|
}
|
|
|
|
if (this._commentGlyph) {
|
|
this._commentGlyph.dispose();
|
|
this._commentGlyph = undefined;
|
|
}
|
|
|
|
this._globalToDispose.forEach(global => global.dispose());
|
|
this._commentThreadDisposables.forEach(global => global.dispose());
|
|
this._submitActionsDisposables.forEach(local => local.dispose());
|
|
this._onDidClose.fire(undefined);
|
|
}
|
|
}
|