commentThreadWidget.ts 41.4 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
P
Peng Lyu 已提交
8
import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
9
import { Button } from 'vs/base/browser/ui/button/button';
P
Peng Lyu 已提交
10
import { Action, IAction } from 'vs/base/common/actions';
11 12 13
import * as arrays from 'vs/base/common/arrays';
import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
14
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
15
import * as strings from 'vs/base/common/strings';
16 17 18
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';
19
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
20 21
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
22
import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/browser/commentGlyphWidget';
P
Peng Lyu 已提交
23 24 25
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IModelService } from 'vs/editor/common/services/modelService';
import { SimpleCommentEditor } from './simpleCommentEditor';
26
import { URI } from 'vs/base/common/uri';
27
import { transparent, editorForeground, textLinkActiveForeground, textLinkForeground, focusBorder, textBlockQuoteBackground, textBlockQuoteBorder, contrastBorder, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry';
P
Peng Lyu 已提交
28
import { IModeService } from 'vs/editor/common/services/modeService';
29
import { ICommentService } from 'vs/workbench/contrib/comments/browser/commentService';
30 31
import { Range, IRange } from 'vs/editor/common/core/range';
import { IPosition } from 'vs/editor/common/core/position';
32 33
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer';
34
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
35
import { CommentNode } from 'vs/workbench/contrib/comments/browser/commentNode';
R
rebornix 已提交
36
import { ITextModel } from 'vs/editor/common/model';
P
Peng Lyu 已提交
37
import { ICommandService } from 'vs/platform/commands/common/commands';
38
import { generateUuid } from 'vs/base/common/uuid';
39
import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget';
40
import { withNullAsUndefined } from 'vs/base/common/types';
P
Peng Lyu 已提交
41
import { CommentMenus } from 'vs/workbench/contrib/comments/browser/commentMenus';
42
import { MenuItemAction, IMenu } from 'vs/platform/actions/common/actions';
P
Peng Lyu 已提交
43 44 45
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
P
Peng Lyu 已提交
46
import { ContextAwareMenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
47 48
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { CommentContextKeys } from 'vs/workbench/contrib/comments/common/commentContextKeys';
49
import { CommentFormActions } from 'vs/workbench/contrib/comments/browser/commentFormActions';
50

P
Peng Lyu 已提交
51
export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration';
P
Peng Lyu 已提交
52
const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-chevron-up';
P
Peng Lyu 已提交
53
const COMMENT_SCHEME = 'comment';
54

55

56
let INMEM_MODEL_ID = 0;
57

58
export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget {
59
	private _headElement: HTMLElement;
60
	protected _headingLabel: HTMLElement;
61 62
	protected _actionbarWidget: ActionBar;
	private _bodyElement: HTMLElement;
63
	private _parentEditor: ICodeEditor;
P
Peng Lyu 已提交
64
	private _commentEditor: ICodeEditor;
65 66
	private _commentsElement: HTMLElement;
	private _commentElements: CommentNode[];
67 68
	private _commentForm: HTMLElement;
	private _reviewThreadReplyButton: HTMLElement;
69
	private _resizeObserver: any;
70
	private _onDidClose = new Emitter<ReviewZoneWidget | undefined>();
71
	private _onDidCreateThread = new Emitter<ReviewZoneWidget>();
72
	private _isExpanded?: boolean;
73
	private _collapseAction: Action;
74
	private _commentGlyph?: CommentGlyphWidget;
75
	private _submitActionsDisposables: IDisposable[];
76
	private _globalToDispose: IDisposable[];
P
Peng Lyu 已提交
77
	private _commentThreadDisposables: IDisposable[] = [];
78 79
	private _markdownRenderer: MarkdownRenderer;
	private _styleElement: HTMLStyleElement;
80
	private _formActions: HTMLElement | null;
81
	private _error: HTMLElement;
82 83
	private _contextKeyService: IContextKeyService;
	private _threadIsEmpty: IContextKey<boolean>;
P
Peng Lyu 已提交
84
	private _commentThreadContextValue: IContextKey<string>;
85
	private _commentEditorIsEmpty: IContextKey<boolean>;
86
	private _commentFormActions: CommentFormActions;
87

88
	public get owner(): string {
89 90
		return this._owner;
	}
91
	public get commentThread(): modes.CommentThread {
92
		return this._commentThread as modes.CommentThread;
93
	}
94

M
Matt Bierner 已提交
95
	public get extensionId(): string | undefined {
96 97 98
		return this._commentThread.extensionId;
	}

M
Matt Bierner 已提交
99
	public get draftMode(): modes.DraftMode | undefined {
100 101 102
		return this._draftMode;
	}

P
Peng Lyu 已提交
103 104
	private _commentMenus: CommentMenus;

105 106
	constructor(
		editor: ICodeEditor,
107 108 109
		private _owner: string,
		private _commentThread: modes.CommentThread | modes.CommentThread2,
		private _pendingComment: string,
M
Matt Bierner 已提交
110
		private _draftMode: modes.DraftMode | undefined,
111 112 113 114 115 116
		@IInstantiationService private instantiationService: IInstantiationService,
		@IModeService private modeService: IModeService,
		@ICommandService private commandService: ICommandService,
		@IModelService private modelService: IModelService,
		@IThemeService private themeService: IThemeService,
		@ICommentService private commentService: ICommentService,
P
Peng Lyu 已提交
117 118 119
		@IOpenerService private openerService: IOpenerService,
		@IKeybindingService private keybindingService: IKeybindingService,
		@INotificationService private notificationService: INotificationService,
120 121
		@IContextMenuService private contextMenuService: IContextMenuService,
		@IContextKeyService contextKeyService: IContextKeyService
122
	) {
123
		super(editor, { keepEditorSelection: true });
124 125 126
		this._contextKeyService = contextKeyService.createScoped(this.domNode);
		this._threadIsEmpty = CommentContextKeys.commentThreadIsEmpty.bindTo(this._contextKeyService);
		this._threadIsEmpty.set(!_commentThread.comments || !_commentThread.comments.length);
P
Peng Lyu 已提交
127
		this._commentThreadContextValue = contextKeyService.createKey('commentThread', _commentThread.contextValue);
128

129
		this._resizeObserver = null;
130
		this._isExpanded = _commentThread.collapsibleState ? _commentThread.collapsibleState === modes.CommentThreadCollapsibleState.Expanded : undefined;
131
		this._globalToDispose = [];
P
Peng Lyu 已提交
132
		this._commentThreadDisposables = [];
133
		this._submitActionsDisposables = [];
134
		this._formActions = null;
P
Peng Lyu 已提交
135
		this._commentMenus = this.commentService.getCommentMenus(this._owner);
136
		this.create();
137 138

		this._styleElement = dom.createStyleSheet(this.domNode);
139 140 141 142 143 144
		this._globalToDispose.push(this.themeService.onThemeChange(this._applyTheme, this));
		this._globalToDispose.push(this.editor.onDidChangeConfiguration(e => {
			if (e.fontInfo) {
				this._applyTheme(this.themeService.getTheme());
			}
		}));
145 146 147
		this._applyTheme(this.themeService.getTheme());

		this._markdownRenderer = new MarkdownRenderer(editor, this.modeService, this.openerService);
148
		this._parentEditor = editor;
149 150
	}

M
Matt Bierner 已提交
151
	public get onDidClose(): Event<ReviewZoneWidget | undefined> {
152 153 154
		return this._onDidClose.event;
	}

155 156 157 158
	public get onDidCreateThread(): Event<ReviewZoneWidget> {
		return this._onDidCreateThread.event;
	}

159
	public getPosition(): IPosition | undefined {
M
Matt Bierner 已提交
160 161
		if (this.position) {
			return this.position;
162 163
		}

M
Matt Bierner 已提交
164 165 166 167
		if (this._commentGlyph) {
			return withNullAsUndefined(this._commentGlyph.getPosition().position);
		}
		return undefined;
168 169
	}

P
Peng Lyu 已提交
170 171 172 173 174
	protected revealLine(lineNumber: number) {
		// we don't do anything here as we always do the reveal ourselves.
	}

	public reveal(commentId?: string) {
175
		if (!this._isExpanded) {
176
			this.show({ lineNumber: this._commentThread.range.startLineNumber, column: 1 }, 2);
177
		}
178

P
Peng Lyu 已提交
179 180 181 182 183 184 185 186 187 188 189 190
		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;
			}
		}

191
		this.editor.revealRangeInCenter(this._commentThread.range);
192 193
	}

194
	public getPendingComment(): string | null {
195 196 197
		if (this._commentEditor) {
			let model = this._commentEditor.getModel();

198
			if (model && model.getValueLength() > 0) { // checking length is cheap
199 200 201 202 203 204 205
				return model.getValue();
			}
		}

		return null;
	}

206 207
	protected _fillContainer(container: HTMLElement): void {
		this.setCssClass('review-widget');
208
		this._headElement = <HTMLDivElement>dom.$('.head');
209 210 211
		container.appendChild(this._headElement);
		this._fillHead(this._headElement);

212
		this._bodyElement = <HTMLDivElement>dom.$('.body');
213 214 215 216
		container.appendChild(this._bodyElement);
	}

	protected _fillHead(container: HTMLElement): void {
M
Matt Bierner 已提交
217
		let titleElement = dom.append(this._headElement, dom.$('.review-title'));
218

219
		this._headingLabel = dom.append(titleElement, dom.$('span.filename'));
220
		this.createThreadLabel();
221

222
		const actionsContainer = dom.append(this._headElement, dom.$('.review-actions'));
P
Peng Lyu 已提交
223 224 225
		this._actionbarWidget = new ActionBar(actionsContainer, {
			actionViewItemProvider: (action: IAction) => {
				if (action instanceof MenuItemAction) {
P
Peng Lyu 已提交
226
					let item = new ContextAwareMenuEntryActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
P
Peng Lyu 已提交
227 228 229 230 231 232 233 234
					return item;
				} else {
					let item = new ActionViewItem({}, action, { label: false, icon: true });
					return item;
				}
			}
		});

235 236
		this._disposables.push(this._actionbarWidget);

237 238
		this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => this.collapse());

P
Peng Lyu 已提交
239
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
240 241 242 243 244 245 246 247 248
			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 });
P
Peng Lyu 已提交
249 250 251
		}

		this._actionbarWidget.context = this._commentThread;
252 253
	}

254 255
	private setActionBarActions(menu: IMenu): void {
		const groups = menu.getActions({ shouldForwardArgs: true }).reduce((r, [, actions]) => [...r, ...actions], <MenuItemAction[]>[]);
256
		this._actionbarWidget.clear();
257 258 259
		this._actionbarWidget.push([...groups, this._collapseAction], { label: false, icon: true });
	}

260
	public collapse(): Promise<void> {
261
		if (this._commentThread.comments && this._commentThread.comments.length === 0) {
262 263 264 265 266 267 268
			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 || []));
P
Peng Lyu 已提交
269 270 271
				} else if (this._commentEditor.getValue() === '') {
					this.dispose();
					return Promise.resolve();
272
				}
273
			}
274
		}
275

276 277
		this.hide();
		return Promise.resolve();
278 279
	}

280
	public getGlyphPosition(): number {
M
Matt Bierner 已提交
281 282 283 284
		if (this._commentGlyph) {
			return this._commentGlyph.getPosition().position!.lineNumber;
		}
		return 0;
285 286 287
	}

	toggleExpand(lineNumber: number) {
288
		if (this._isExpanded) {
289 290 291 292
			this.hide();
			if (this._commentThread === null || this._commentThread.threadId === null) {
				this.dispose();
			}
293 294
		} else {
			this.show({ lineNumber: lineNumber, column: 1 }, 2);
295
		}
296 297
	}

P
Peng Lyu 已提交
298
	async update(commentThread: modes.CommentThread | modes.CommentThread2) {
299
		const oldCommentsLen = this._commentElements.length;
300
		const newCommentsLen = commentThread.comments ? commentThread.comments.length : 0;
301
		this._threadIsEmpty.set(!newCommentsLen);
302 303 304 305 306

		let commentElementsToDel: CommentNode[] = [];
		let commentElementsToDelIndex: number[] = [];
		for (let i = 0; i < oldCommentsLen; i++) {
			let comment = this._commentElements[i].comment;
307
			let newComment = commentThread.comments ? commentThread.comments.filter(c => c.commentId === comment.commentId) : [];
308 309 310 311 312 313 314 315 316 317 318

			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--) {
I
Ilya Biryukov 已提交
319
			this._commentElements.splice(commentElementsToDelIndex[i], 1);
320 321 322
			this._commentsElement.removeChild(commentElementsToDel[i].domNode);
		}

323
		let lastCommentElement: HTMLElement | null = null;
324 325
		let newCommentNodeList: CommentNode[] = [];
		for (let i = newCommentsLen - 1; i >= 0; i--) {
326
			let currentComment = commentThread.comments![i];
327 328
			let oldCommentNode = this._commentElements.filter(commentNode => commentNode.comment.commentId === currentComment.commentId);
			if (oldCommentNode.length) {
R
rebornix 已提交
329
				oldCommentNode[0].update(currentComment);
330 331 332
				lastCommentElement = oldCommentNode[0].domNode;
				newCommentNodeList.unshift(oldCommentNode[0]);
			} else {
333
				const newElement = this.createNewCommentNode(currentComment);
334

335 336 337 338 339 340 341 342 343 344 345 346 347
				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;
P
Peng Lyu 已提交
348
		this.createThreadLabel();
I
Ilya Biryukov 已提交
349

350 351 352 353 354 355
		if (this._formActions && this._commentEditor.hasModel()) {
			dom.clearNode(this._formActions);
			const model = this._commentEditor.getModel();
			this.createCommentWidgetActions2(this._formActions, model);
		}

I
Ilya Biryukov 已提交
356 357
		// Move comment glyph widget and show position if the line has changed.
		const lineNumber = this._commentThread.range.startLineNumber;
358
		let shouldMoveWidget = false;
M
Matt Bierner 已提交
359 360
		if (this._commentGlyph) {
			if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
361
				shouldMoveWidget = true;
M
Matt Bierner 已提交
362 363
				this._commentGlyph.setLineNumber(lineNumber);
			}
364 365
		}

P
Peng Lyu 已提交
366 367 368 369
		if (!this._reviewThreadReplyButton) {
			this.createReplyButton();
		}

370 371 372 373 374
		if (this._commentThread.comments && this._commentThread.comments.length === 0) {
			this.expandReplyArea();
		}

		if (shouldMoveWidget && this._isExpanded) {
375
			this.show({ lineNumber, column: 1 }, 2);
I
Ilya Biryukov 已提交
376
		}
377 378 379 380 381 382 383 384 385

		// 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();
			}
		}
P
Peng Lyu 已提交
386 387 388 389 390 391

		if (this._commentThread.contextValue) {
			this._commentThreadContextValue.set(this._commentThread.contextValue);
		} else {
			this._commentThreadContextValue.reset();
		}
392 393
	}

M
Matt Bierner 已提交
394
	updateDraftMode(draftMode: modes.DraftMode | undefined) {
395 396
		if (this._draftMode !== draftMode) {
			this._draftMode = draftMode;
397

398 399
			if (this._formActions && this._commentEditor.hasModel()) {
				const model = this._commentEditor.getModel();
400 401 402
				dom.clearNode(this._formActions);
				this.createCommentWidgetActions(this._formActions, model);
			}
403 404 405
		}
	}

406
	protected _onWidth(widthInPixel: number): void {
P
Peng Lyu 已提交
407
		this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
408 409
	}

P
Peng Lyu 已提交
410
	protected _doLayout(heightInPixel: number, widthInPixel: number): void {
P
Peng Lyu 已提交
411
		this._commentEditor.layout({ height: 5 * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ });
P
Peng Lyu 已提交
412 413
	}

P
Peng Lyu 已提交
414
	display(lineNumber: number) {
415
		this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber);
416

417 418
		this._disposables.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
		this._disposables.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
419

M
Matt Bierner 已提交
420
		let headHeight = Math.ceil(this.editor.getConfiguration().lineHeight * 1.2);
421 422 423
		this._headElement.style.height = `${headHeight}px`;
		this._headElement.style.lineHeight = this._headElement.style.height;

424
		this._commentsElement = dom.append(this._bodyElement, dom.$('div.comments-container'));
P
Peng Lyu 已提交
425 426
		this._commentsElement.setAttribute('role', 'presentation');

427
		this._commentElements = [];
428 429 430
		if (this._commentThread.comments) {
			for (const comment of this._commentThread.comments) {
				const newCommentNode = this.createNewCommentNode(comment);
431

432 433 434
				this._commentElements.push(newCommentNode);
				this._commentsElement.appendChild(newCommentNode.domNode);
			}
435 436
		}

437
		const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
438
		this._commentForm = dom.append(this._bodyElement, dom.$('.comment-form'));
439
		this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this);
440 441
		this._commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
		this._commentEditorIsEmpty.set(!this._pendingComment);
442

443
		const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID);
444 445 446 447
		const params = JSON.stringify({
			extensionId: this.extensionId,
			commentThreadId: this.commentThread.threadId
		});
P
Peng Lyu 已提交
448 449 450 451 452 453 454

		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 });
		}

455
		const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource.path), resource, false);
456
		this._disposables.push(model);
457
		this._commentEditor.setModel(model);
458
		this._disposables.push(this._commentEditor);
459 460 461 462 463
		this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => {
			this.setCommentEditorDecorations();
			this._commentEditorIsEmpty.set(!this._commentEditor.getValue());
		}));

P
Peng Lyu 已提交
464
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
P
Peng Lyu 已提交
465
			this.createTextModelListener();
P
Peng Lyu 已提交
466 467
		}

468 469 470
		this.setCommentEditorDecorations();

		// Only add the additional step of clicking a reply button to expand the textarea when there are existing comments
471 472 473
		if (hasExistingComments) {
			this.createReplyButton();
		} else {
474 475
			if (this._commentThread.comments && this._commentThread.comments.length === 0) {
				this.expandReplyArea();
476 477 478
			}
		}

479
		this._error = dom.append(this._commentForm, dom.$('.validation-error.hidden'));
480

481
		this._formActions = dom.append(this._commentForm, dom.$('.form-actions'));
P
Peng Lyu 已提交
482 483
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
			this.createCommentWidgetActions2(this._formActions, model);
P
Peng Lyu 已提交
484
			this.createCommentWidgetActionsListener(this._formActions, model);
P
Peng Lyu 已提交
485
		} else {
P
Peng Lyu 已提交
486
			this.createCommentWidgetActions(this._formActions, model);
P
Peng Lyu 已提交
487
		}
R
rebornix 已提交
488

489 490 491 492 493 494 495 496 497 498 499 500 501 502
		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.
503 504
		// 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) {
505
			this._commentEditor.focus();
506
		} else if (this._commentEditor.getModel()!.getValueLength() > 0) {
507
			this.expandReplyArea();
508 509 510
		}
	}

P
Peng Lyu 已提交
511
	private createTextModelListener() {
P
Peng Lyu 已提交
512
		this._commentThreadDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => {
P
Peng Lyu 已提交
513 514 515 516 517 518 519
			let commentThread = this._commentThread as modes.CommentThread2;
			commentThread.input = {
				uri: this._commentEditor.getModel()!.uri,
				value: this._commentEditor.getValue()
			};
		}));

P
Peng Lyu 已提交
520
		this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => {
P
Peng Lyu 已提交
521 522 523 524 525 526 527 528 529
			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;
			}
		}));

P
Peng Lyu 已提交
530
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => {
P
Peng Lyu 已提交
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
			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');
				}
			}
		}));

P
Peng Lyu 已提交
555
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => {
P
Peng Lyu 已提交
556 557 558
			await this.update(this._commentThread);
		}));

P
Peng Lyu 已提交
559
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => {
P
Peng Lyu 已提交
560 561 562 563 564
			this.createThreadLabel();
		}));
	}

	private createCommentWidgetActionsListener(container: HTMLElement, model: ITextModel) {
P
Peng Lyu 已提交
565
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => {
P
Peng Lyu 已提交
566 567 568 569 570 571
			if (container) {
				dom.clearNode(container);
				this.createCommentWidgetActions2(container, model);
			}
		}));

P
Peng Lyu 已提交
572
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => {
P
Peng Lyu 已提交
573 574 575 576 577 578
			if (container) {
				dom.clearNode(container);
				this.createCommentWidgetActions2(container, model);
			}
		}));

P
Peng Lyu 已提交
579
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => {
P
Peng Lyu 已提交
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594
			// 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);
			}
		}));

P
Peng Lyu 已提交
595
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => {
P
Peng Lyu 已提交
596 597 598 599 600 601 602 603 604 605 606 607 608 609
			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;
			}
		}));
	}

610 611
	private handleError(e: Error) {
		this._error.textContent = e.message;
612
		this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`;
613 614 615
		dom.removeClass(this._error, 'hidden');
	}

616 617 618 619
	private getActiveComment(): CommentNode | ReviewZoneWidget {
		return this._commentElements.filter(node => node.isEditing)[0] || this;
	}

R
rebornix 已提交
620
	private createCommentWidgetActions(container: HTMLElement, model: ITextModel) {
621 622
		dispose(this._submitActionsDisposables);

R
rebornix 已提交
623
		const button = new Button(container);
624
		this._submitActionsDisposables.push(attachButtonStyler(button, this.themeService));
R
rebornix 已提交
625 626 627
		button.label = 'Add comment';

		button.enabled = model.getValueLength() > 0;
628
		this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => {
R
rebornix 已提交
629 630 631 632 633 634 635 636
			if (this._commentEditor.getValue()) {
				button.enabled = true;
			} else {
				button.enabled = false;
			}
		}));

		button.onDidClick(async () => {
637
			this.createComment();
R
rebornix 已提交
638 639 640 641 642 643 644 645
		});

		if (this._draftMode === modes.DraftMode.NotSupported) {
			return;
		}

		switch (this._draftMode) {
			case modes.DraftMode.InDraft:
R
rebornix 已提交
646 647 648
				const deleteDraftLabel = this.commentService.getDeleteDraftLabel(this._owner);
				if (deleteDraftLabel) {
					const deletedraftButton = new Button(container);
649
					this._submitActionsDisposables.push(attachButtonStyler(deletedraftButton, this.themeService));
R
rebornix 已提交
650 651 652
					deletedraftButton.label = deleteDraftLabel;
					deletedraftButton.enabled = true;

653
					this._disposables.push(deletedraftButton.onDidClick(async () => {
654
						try {
655
							await this.commentService.deleteDraft(this._owner, this.editor.getModel()!.uri);
656 657 658
						} catch (e) {
							this.handleError(e);
						}
659
					}));
R
rebornix 已提交
660
				}
R
rebornix 已提交
661

R
rebornix 已提交
662 663 664
				const submitDraftLabel = this.commentService.getFinishDraftLabel(this._owner);
				if (submitDraftLabel) {
					const submitdraftButton = new Button(container);
665
					this._submitActionsDisposables.push(attachButtonStyler(submitdraftButton, this.themeService));
M
Matt Bierner 已提交
666
					submitdraftButton.label = this.commentService.getFinishDraftLabel(this._owner)!;
R
rebornix 已提交
667 668 669
					submitdraftButton.enabled = true;

					submitdraftButton.onDidClick(async () => {
670
						try {
671
							if (this._commentEditor.getValue()) {
672
								await this.createComment();
673
							}
674
							await this.commentService.finishDraft(this._owner, this.editor.getModel()!.uri);
675 676 677
						} catch (e) {
							this.handleError(e);
						}
R
rebornix 已提交
678 679
					});
				}
R
rebornix 已提交
680 681 682

				break;
			case modes.DraftMode.NotInDraft:
R
rebornix 已提交
683 684 685
				const startDraftLabel = this.commentService.getStartDraftLabel(this._owner);
				if (startDraftLabel) {
					const draftButton = new Button(container);
686
					this._disposables.push(attachButtonStyler(draftButton, this.themeService));
M
Matt Bierner 已提交
687
					draftButton.label = this.commentService.getStartDraftLabel(this._owner)!;
R
rebornix 已提交
688 689

					draftButton.enabled = model.getValueLength() > 0;
690
					this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => {
R
rebornix 已提交
691 692 693 694 695 696 697
						if (this._commentEditor.getValue()) {
							draftButton.enabled = true;
						} else {
							draftButton.enabled = false;
						}
					}));

698
					this._disposables.push(draftButton.onDidClick(async () => {
699
						try {
700
							await this.commentService.startDraft(this._owner, this.editor.getModel()!.uri);
701
							await this.createComment();
702 703 704
						} catch (e) {
							this.handleError(e);
						}
705
					}));
R
rebornix 已提交
706
				}
R
rebornix 已提交
707 708 709 710 711

				break;
		}
	}

P
Peng Lyu 已提交
712 713 714 715 716
	/**
	 * Command based actions.
	 */
	private createCommentWidgetActions2(container: HTMLElement, model: ITextModel) {
		let commentThread = this._commentThread as modes.CommentThread2;
717
		const { acceptInputCommand, additionalCommands } = commentThread;
P
Peng Lyu 已提交
718

M
Matt Bierner 已提交
719
		if (acceptInputCommand) {
720 721 722
			const button = new Button(container);
			this._disposables.push(attachButtonStyler(button, this.themeService));

M
Matt Bierner 已提交
723
			button.label = acceptInputCommand.title;
724 725
			this._disposables.push(button.onDidClick(async () => {
				commentThread.input = {
726
					uri: this._commentEditor.getModel()!.uri,
727 728
					value: this._commentEditor.getValue()
				};
M
Matt Bierner 已提交
729
				await this.commandService.executeCommand(acceptInputCommand.id, ...(acceptInputCommand.arguments || []));
730 731 732 733 734 735 736 737 738 739 740
			}));

			button.enabled = model.getValueLength() > 0;
			this._disposables.push(this._commentEditor.onDidChangeModelContent(_ => {
				if (this._commentEditor.getValue()) {
					button.enabled = true;
				} else {
					button.enabled = false;
				}
			}));
		}
741

742 743 744 745
		if (additionalCommands) {
			additionalCommands.reverse().forEach(command => {
				const button = new Button(container);
				this._disposables.push(attachButtonStyler(button, this.themeService));
P
Peng Lyu 已提交
746

747 748 749 750 751 752 753 754 755 756
				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 || []));
				}));
			});
		}
P
Peng Lyu 已提交
757

758
		const menu = this._commentMenus.getCommentThreadActions(commentThread, this._contextKeyService);
P
Peng Lyu 已提交
759

760 761
		this._disposables.push(menu);
		this._disposables.push(menu.onDidChange(() => {
762
			this._commentFormActions.setActions(menu);
763
		}));
P
Peng Lyu 已提交
764

765 766 767 768 769 770
		this._commentFormActions = new CommentFormActions(container, (action: IAction) => {
			action.run({
				thread: this._commentThread,
				text: this._commentEditor.getValue(),
				$mid: 8
			});
P
Peng Lyu 已提交
771

772 773
			this.hideReplyArea();
		}, this.themeService);
774

775
		this._commentFormActions.setActions(menu);
P
Peng Lyu 已提交
776 777
	}

778
	private createNewCommentNode(comment: modes.Comment): CommentNode {
779
		let newCommentNode = this.instantiationService.createInstance(CommentNode,
P
Peng Lyu 已提交
780
			this._commentThread,
781 782
			comment,
			this.owner,
783
			this.editor.getModel()!.uri,
784 785 786
			this._parentEditor,
			this,
			this._markdownRenderer);
787 788 789 790 791 792 793 794 795

		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);
			}

796
			const deletedCommentIndex = arrays.firstIndex(this._commentThread.comments!, comment => comment.commentId === deletedNodeId);
797
			if (deletedCommentIndex > -1) {
798
				this._commentThread.comments!.splice(deletedCommentIndex, 1);
799 800 801 802 803
			}

			this._commentsElement.removeChild(deletedNode.domNode);
			deletedNode.dispose();

804
			if (this._commentThread.comments!.length === 0) {
805 806 807 808 809 810 811
				this.dispose();
			}
		}));

		return newCommentNode;
	}

812 813 814
	async submitComment(): Promise<void> {
		const activeComment = this.getActiveComment();
		if (activeComment instanceof ReviewZoneWidget) {
815
			if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
816 817 818 819
				let commentThread = this._commentThread as modes.CommentThread2;

				if (commentThread.acceptInputCommand) {
					commentThread.input = {
M
Matt Bierner 已提交
820
						uri: this._commentEditor.getModel()!.uri,
821 822 823 824 825 826 827 828 829 830 831
						value: this._commentEditor.getValue()
					};
					let commandId = commentThread.acceptInputCommand.id;
					let args = commentThread.acceptInputCommand.arguments || [];

					await this.commandService.executeCommand(commandId, ...args);
					return;
				}
			} else {
				this.createComment();
			}
832 833 834 835 836 837 838 839
		}

		if (activeComment instanceof CommentNode) {
			activeComment.editComment();
		}
	}

	async createComment(): Promise<void> {
840
		try {
841
			if (this._commentEditor.getModel()!.getValueLength() === 0) {
842 843
				return;
			}
M
Matt Bierner 已提交
844 845 846
			if (!this._commentGlyph) {
				return;
			}
847

848
			let newCommentThread;
M
Matt Bierner 已提交
849
			const lineNumber = this._commentGlyph.getPosition().position!.lineNumber;
850
			const isReply = this._commentThread.threadId !== null;
851

852

853
			if (isReply) {
854 855
				newCommentThread = await this.commentService.replyToCommentThread(
					this._owner,
856
					this.editor.getModel()!.uri,
857 858 859 860
					new Range(lineNumber, 1, lineNumber, 1),
					this._commentThread,
					this._commentEditor.getValue()
				);
861
			} else {
862
				newCommentThread = await this.commentService.createNewCommentThread(
863
					this._owner,
864
					this.editor.getModel()!.uri,
865 866 867
					new Range(lineNumber, 1, lineNumber, 1),
					this._commentEditor.getValue()
				);
868

869 870 871
				if (newCommentThread) {
					this.createReplyButton();
				}
872 873
			}

874
			if (newCommentThread) {
875
				this._commentEditor.setValue('');
876
				this._pendingComment = '';
877 878 879
				if (dom.hasClass(this._commentForm, 'expand')) {
					dom.removeClass(this._commentForm, 'expand');
				}
880
				this._commentEditor.getDomNode()!.style.outline = '';
881 882
				this._error.textContent = '';
				dom.addClass(this._error, 'hidden');
883
				this.update(newCommentThread);
884 885 886 887

				if (!isReply) {
					this._onDidCreateThread.fire(this);
				}
888
			}
889 890 891 892
		} 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.");
893
			this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`;
894
			dom.removeClass(this._error, 'hidden');
895
		}
896 897
	}

P
Peng Lyu 已提交
898
	private createThreadLabel() {
899 900 901 902 903
		let label: string | undefined;
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
			label = (this._commentThread as modes.CommentThread2).label;
		}

P
Peng Lyu 已提交
904
		if (label === undefined) {
905
			if (this._commentThread.comments && this._commentThread.comments.length) {
906 907 908 909 910
				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");
			}
911
		}
912

P
Peng Lyu 已提交
913 914 915 916
		if (label) {
			this._headingLabel.innerHTML = strings.escape(label);
			this._headingLabel.setAttribute('aria-label', label);
		}
917 918
	}

919 920 921 922 923 924 925
	private expandReplyArea() {
		if (!dom.hasClass(this._commentForm, 'expand')) {
			dom.addClass(this._commentForm, 'expand');
			this._commentEditor.focus();
		}
	}

P
Peng Lyu 已提交
926 927 928 929 930 931 932 933 934 935 936
	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');
	}

937
	private createReplyButton() {
938
		this._reviewThreadReplyButton = <HTMLButtonElement>dom.append(this._commentForm, dom.$('button.review-thread-reply-button'));
P
Peng Lyu 已提交
939 940 941
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
			// this._reviewThreadReplyButton.title = (this._commentThread as modes.CommentThread2).acceptInputCommands.title;
		} else {
P
Peng Lyu 已提交
942
			this._reviewThreadReplyButton.title = nls.localize('reply', "Reply...");
P
Peng Lyu 已提交
943
		}
944
		this._reviewThreadReplyButton.textContent = nls.localize('reply', "Reply...");
945
		// bind click/escape actions for reviewThreadReplyButton and textArea
946 947
		this._disposables.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea()));
		this._disposables.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea()));
948 949

		this._commentEditor.onDidBlurEditorWidget(() => {
950
			if (this._commentEditor.getModel()!.getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) {
951 952 953
				dom.removeClass(this._commentForm, 'expand');
			}
		});
954 955
	}

956
	_refresh() {
957
		if (this._isExpanded && this._bodyElement) {
958 959 960 961 962 963
			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;

964
			const computedLinesNumber = Math.ceil((headHeight + dimensions.height + arrowHeight + frameThickness + 8 /** margin bottom to avoid margin collapse */) / lineHeight);
965 966 967 968
			this._relayout(computedLinesNumber);
		}
	}

P
Peng Lyu 已提交
969
	private setCommentEditorDecorations() {
970 971
		const model = this._commentEditor && this._commentEditor.getModel();
		if (model) {
972
			const valueLength = model.getValueLength();
973
			const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
974
			const placeholder = valueLength > 0
975
				? ''
976 977 978
				: hasExistingComments
					? nls.localize('reply', "Reply...")
					: nls.localize('newComment', "Type a new comment");
979 980 981 982 983 984 985 986 987 988
			const decorations = [{
				range: {
					startLineNumber: 0,
					endLineNumber: 0,
					startColumn: 0,
					endColumn: 1
				},
				renderOptions: {
					after: {
						contentText: placeholder,
989
						color: `${transparent(editorForeground, 0.4)(this.themeService.getTheme())}`
990
					}
P
Peng Lyu 已提交
991
				}
992
			}];
P
Peng Lyu 已提交
993

994 995
			this._commentEditor.setDecorations(COMMENTEDITOR_DECORATION_KEY, decorations);
		}
P
Peng Lyu 已提交
996 997
	}

998
	private mouseDownInfo: { lineNumber: number } | null;
999 1000

	private onEditorMouseDown(e: IEditorMouseEvent): void {
1001 1002 1003 1004 1005 1006 1007 1008
		this.mouseDownInfo = null;

		const range = e.target.range;

		if (!range) {
			return;
		}

1009 1010 1011 1012
		if (!e.event.leftButton) {
			return;
		}

1013
		if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
1014 1015 1016
			return;
		}

1017 1018 1019 1020
		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
1021
		if (gutterOffsetX > 14) {
1022
			return;
1023 1024
		}

1025
		this.mouseDownInfo = { lineNumber: range.startLineNumber };
1026 1027 1028 1029 1030 1031 1032
	}

	private onEditorMouseUp(e: IEditorMouseEvent): void {
		if (!this.mouseDownInfo) {
			return;
		}

1033 1034 1035 1036 1037
		const { lineNumber } = this.mouseDownInfo;
		this.mouseDownInfo = null;

		const range = e.target.range;

1038 1039 1040 1041
		if (!range || range.startLineNumber !== lineNumber) {
			return;
		}

1042 1043 1044 1045 1046 1047 1048 1049
		if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
			return;
		}

		if (!e.target.element) {
			return;
		}

M
Matt Bierner 已提交
1050
		if (this._commentGlyph && this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
1051 1052 1053
			return;
		}

1054
		if (e.target.element.className.indexOf('comment-thread') >= 0) {
1055
			this.toggleExpand(lineNumber);
1056 1057 1058 1059
		}
	}

	private _applyTheme(theme: ITheme) {
M
Matt Bierner 已提交
1060
		const borderColor = theme.getColor(peekViewBorder) || Color.transparent;
1061 1062 1063 1064
		this.style({
			arrowColor: borderColor,
			frameColor: borderColor
		});
1065 1066 1067 1068

		const content: string[] = [];
		const linkColor = theme.getColor(textLinkForeground);
		if (linkColor) {
1069
			content.push(`.monaco-editor .review-widget .body .comment-body a { color: ${linkColor} }`);
1070 1071 1072 1073
		}

		const linkActiveColor = theme.getColor(textLinkActiveForeground);
		if (linkActiveColor) {
1074
			content.push(`.monaco-editor .review-widget .body .comment-body a:hover, a:active { color: ${linkActiveColor} }`);
1075 1076 1077 1078
		}

		const focusColor = theme.getColor(focusBorder);
		if (focusColor) {
1079 1080
			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}; }`);
1081 1082
		}

1083 1084 1085 1086 1087 1088 1089 1090 1091 1092
		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}; }`);
		}

1093 1094 1095
		const hcBorder = theme.getColor(contrastBorder);
		if (hcBorder) {
			content.push(`.monaco-editor .review-widget .body .comment-form .review-thread-reply-button { outline-color: ${hcBorder}; }`);
1096
			content.push(`.monaco-editor .review-widget .body .monaco-editor { outline: 1px solid ${hcBorder}; }`);
1097 1098
		}

1099 1100
		const errorBorder = theme.getColor(inputValidationErrorBorder);
		if (errorBorder) {
1101
			content.push(`.monaco-editor .review-widget .validation-error { border: 1px solid ${errorBorder}; }`);
1102 1103 1104 1105
		}

		const errorBackground = theme.getColor(inputValidationErrorBackground);
		if (errorBackground) {
1106
			content.push(`.monaco-editor .review-widget .validation-error { background: ${errorBackground}; }`);
1107 1108 1109 1110 1111
		}

		const errorForeground = theme.getColor(inputValidationErrorForeground);
		if (errorForeground) {
			content.push(`.monaco-editor .review-widget .body .comment-form .validation-error { color: ${errorForeground}; }`);
1112 1113
		}

1114 1115 1116 1117 1118 1119 1120
		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};
		}`);

1121
		this._styleElement.innerHTML = content.join('\n');
1122 1123 1124

		// Editor decorations should also be responsive to theme changes
		this.setCommentEditorDecorations();
1125 1126
	}

1127
	show(rangeOrPos: IRange | IPosition, heightInLines: number): void {
1128
		this._isExpanded = true;
1129
		super.show(rangeOrPos, heightInLines);
1130
		this._refresh();
1131 1132 1133
	}

	hide() {
1134
		this._isExpanded = false;
1135 1136
		// Focus the container so that the comment editor will be blurred before it is hidden
		this.editor.focus();
1137 1138 1139
		super.hide();
	}

1140 1141 1142 1143 1144 1145
	dispose() {
		super.dispose();
		if (this._resizeObserver) {
			this._resizeObserver.disconnect();
			this._resizeObserver = null;
		}
1146

1147
		if (this._commentGlyph) {
1148
			this._commentGlyph.dispose();
1149
			this._commentGlyph = undefined;
1150
		}
1151

1152
		this._globalToDispose.forEach(global => global.dispose());
P
Peng Lyu 已提交
1153
		this._commentThreadDisposables.forEach(global => global.dispose());
1154
		this._submitActionsDisposables.forEach(local => local.dispose());
1155
		this._onDidClose.fire(undefined);
1156 1157
	}
}