commentThreadWidget.ts 42.7 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
		container.appendChild(this._bodyElement);
214 215 216 217

		dom.addDisposableListener(this._bodyElement, dom.EventType.FOCUS_IN, e => {
			this.commentService.setActiveCommentThread(this._commentThread);
		});
218 219 220
	}

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

223
		this._headingLabel = dom.append(titleElement, dom.$('span.filename'));
224
		this.createThreadLabel();
225

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

M
Matt Bierner 已提交
239
		this._disposables.add(this._actionbarWidget);
240

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

P
Peng Lyu 已提交
243
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
244 245 246
			const menu = this._commentMenus.getCommentThreadTitleActions(this._commentThread as modes.CommentThread2, this._contextKeyService);
			this.setActionBarActions(menu);

M
Matt Bierner 已提交
247 248
			this._disposables.add(menu);
			this._disposables.add(menu.onDidChange(e => {
249 250 251 252
				this.setActionBarActions(menu);
			}));
		} else {
			this._actionbarWidget.push([this._collapseAction], { label: false, icon: true });
P
Peng Lyu 已提交
253 254 255
		}

		this._actionbarWidget.context = this._commentThread;
256 257
	}

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

264
	public collapse(): Promise<void> {
265
		if (this._commentThread.comments && this._commentThread.comments.length === 0) {
266 267 268 269 270 271
			if ((this._commentThread as modes.CommentThread2).commentThreadHandle === undefined) {
				this.dispose();
				return Promise.resolve();
			} else {
				const deleteCommand = (this._commentThread as modes.CommentThread2).deleteCommand;
				if (deleteCommand) {
272
					this.commentService.setActiveCommentThread(this._commentThread);
273
					return this.commandService.executeCommand(deleteCommand.id, ...(deleteCommand.arguments || []));
P
Peng Lyu 已提交
274
				} else if (this._commentEditor.getValue() === '') {
275
					this.commentService.disposeCommentThread(this._owner, this._commentThread.threadId!);
P
Peng Lyu 已提交
276 277
					this.dispose();
					return Promise.resolve();
278
				}
279
			}
280
		}
281

282 283
		this.hide();
		return Promise.resolve();
284 285
	}

286
	public getGlyphPosition(): number {
M
Matt Bierner 已提交
287 288 289 290
		if (this._commentGlyph) {
			return this._commentGlyph.getPosition().position!.lineNumber;
		}
		return 0;
291 292 293
	}

	toggleExpand(lineNumber: number) {
294
		if (this._isExpanded) {
295 296 297 298
			this.hide();
			if (this._commentThread === null || this._commentThread.threadId === null) {
				this.dispose();
			}
299 300
		} else {
			this.show({ lineNumber: lineNumber, column: 1 }, 2);
301
		}
302 303
	}

P
Peng Lyu 已提交
304
	async update(commentThread: modes.CommentThread | modes.CommentThread2) {
305
		const oldCommentsLen = this._commentElements.length;
306
		const newCommentsLen = commentThread.comments ? commentThread.comments.length : 0;
307
		this._threadIsEmpty.set(!newCommentsLen);
308 309 310 311 312

		let commentElementsToDel: CommentNode[] = [];
		let commentElementsToDelIndex: number[] = [];
		for (let i = 0; i < oldCommentsLen; i++) {
			let comment = this._commentElements[i].comment;
313
			let newComment = commentThread.comments ? commentThread.comments.filter(c => c.commentId === comment.commentId) : [];
314 315 316 317 318 319 320 321 322 323 324

			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 已提交
325
			this._commentElements.splice(commentElementsToDelIndex[i], 1);
326 327 328
			this._commentsElement.removeChild(commentElementsToDel[i].domNode);
		}

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

341 342 343 344 345 346 347 348 349 350 351 352 353
				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 已提交
354
		this.createThreadLabel();
I
Ilya Biryukov 已提交
355

356 357 358 359 360 361
		if (this._formActions && this._commentEditor.hasModel()) {
			dom.clearNode(this._formActions);
			const model = this._commentEditor.getModel();
			this.createCommentWidgetActions2(this._formActions, model);
		}

I
Ilya Biryukov 已提交
362 363
		// Move comment glyph widget and show position if the line has changed.
		const lineNumber = this._commentThread.range.startLineNumber;
364
		let shouldMoveWidget = false;
M
Matt Bierner 已提交
365 366
		if (this._commentGlyph) {
			if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
367
				shouldMoveWidget = true;
M
Matt Bierner 已提交
368 369
				this._commentGlyph.setLineNumber(lineNumber);
			}
370 371
		}

P
Peng Lyu 已提交
372 373 374 375
		if (!this._reviewThreadReplyButton) {
			this.createReplyButton();
		}

376 377 378 379 380
		if (this._commentThread.comments && this._commentThread.comments.length === 0) {
			this.expandReplyArea();
		}

		if (shouldMoveWidget && this._isExpanded) {
381
			this.show({ lineNumber, column: 1 }, 2);
I
Ilya Biryukov 已提交
382
		}
383 384 385 386 387 388 389 390 391

		// 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 已提交
392 393 394 395 396 397

		if (this._commentThread.contextValue) {
			this._commentThreadContextValue.set(this._commentThread.contextValue);
		} else {
			this._commentThreadContextValue.reset();
		}
398 399
	}

M
Matt Bierner 已提交
400
	updateDraftMode(draftMode: modes.DraftMode | undefined) {
401 402
		if (this._draftMode !== draftMode) {
			this._draftMode = draftMode;
403

404 405
			if (this._formActions && this._commentEditor.hasModel()) {
				const model = this._commentEditor.getModel();
406 407 408
				dom.clearNode(this._formActions);
				this.createCommentWidgetActions(this._formActions, model);
			}
409 410 411
		}
	}

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

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

P
Peng Lyu 已提交
420
	display(lineNumber: number) {
421
		this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber);
422

M
Matt Bierner 已提交
423 424
		this._disposables.add(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
		this._disposables.add(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
425

M
Matt Bierner 已提交
426
		let headHeight = Math.ceil(this.editor.getConfiguration().lineHeight * 1.2);
427 428 429
		this._headElement.style.height = `${headHeight}px`;
		this._headElement.style.lineHeight = this._headElement.style.height;

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

433
		this._commentElements = [];
434 435 436
		if (this._commentThread.comments) {
			for (const comment of this._commentThread.comments) {
				const newCommentNode = this.createNewCommentNode(comment);
437

438 439 440
				this._commentElements.push(newCommentNode);
				this._commentsElement.appendChild(newCommentNode.domNode);
			}
441 442
		}

443
		const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
444
		this._commentForm = dom.append(this._bodyElement, dom.$('.comment-form'));
445
		this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this);
446 447
		this._commentEditorIsEmpty = CommentContextKeys.commentIsEmpty.bindTo(this._contextKeyService);
		this._commentEditorIsEmpty.set(!this._pendingComment);
448

449
		const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID);
450 451 452 453
		const params = JSON.stringify({
			extensionId: this.extensionId,
			commentThreadId: this.commentThread.threadId
		});
P
Peng Lyu 已提交
454 455 456 457 458 459 460

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

461
		const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource.path), resource, false);
M
Matt Bierner 已提交
462
		this._disposables.add(model);
463
		this._commentEditor.setModel(model);
M
Matt Bierner 已提交
464 465
		this._disposables.add(this._commentEditor);
		this._disposables.add(this._commentEditor.getModel()!.onDidChangeContent(() => {
466 467 468 469
			this.setCommentEditorDecorations();
			this._commentEditorIsEmpty.set(!this._commentEditor.getValue());
		}));

P
Peng Lyu 已提交
470
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
P
Peng Lyu 已提交
471
			this.createTextModelListener();
P
Peng Lyu 已提交
472 473
		}

474 475 476
		this.setCommentEditorDecorations();

		// Only add the additional step of clicking a reply button to expand the textarea when there are existing comments
477 478 479
		if (hasExistingComments) {
			this.createReplyButton();
		} else {
480 481
			if (this._commentThread.comments && this._commentThread.comments.length === 0) {
				this.expandReplyArea();
482 483 484
			}
		}

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

487
		this._formActions = dom.append(this._commentForm, dom.$('.form-actions'));
P
Peng Lyu 已提交
488 489
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
			this.createCommentWidgetActions2(this._formActions, model);
P
Peng Lyu 已提交
490
			this.createCommentWidgetActionsListener(this._formActions, model);
P
Peng Lyu 已提交
491
		} else {
P
Peng Lyu 已提交
492
			this.createCommentWidgetActions(this._formActions, model);
P
Peng Lyu 已提交
493
		}
R
rebornix 已提交
494

495 496 497 498 499 500 501 502 503 504 505 506 507 508
		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.
509 510
		// 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) {
511
			this._commentEditor.focus();
512
		} else if (this._commentEditor.getModel()!.getValueLength() > 0) {
513
			this.expandReplyArea();
514 515 516
		}
	}

P
Peng Lyu 已提交
517
	private createTextModelListener() {
P
Peng Lyu 已提交
518
		this._commentThreadDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => {
P
Peng Lyu 已提交
519 520 521 522 523
			let commentThread = this._commentThread as modes.CommentThread2;
			commentThread.input = {
				uri: this._commentEditor.getModel()!.uri,
				value: this._commentEditor.getValue()
			};
524
			this.commentService.setActiveCommentThread(this._commentThread);
P
Peng Lyu 已提交
525 526
		}));

P
Peng Lyu 已提交
527
		this._commentThreadDisposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => {
P
Peng Lyu 已提交
528 529 530 531 532 533 534
			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;
			}
535
			this.commentService.setActiveCommentThread(this._commentThread);
P
Peng Lyu 已提交
536 537
		}));

P
Peng Lyu 已提交
538
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => {
P
Peng Lyu 已提交
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
			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 已提交
563
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => {
P
Peng Lyu 已提交
564 565 566
			await this.update(this._commentThread);
		}));

P
Peng Lyu 已提交
567
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => {
P
Peng Lyu 已提交
568 569 570 571 572
			this.createThreadLabel();
		}));
	}

	private createCommentWidgetActionsListener(container: HTMLElement, model: ITextModel) {
P
Peng Lyu 已提交
573
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => {
P
Peng Lyu 已提交
574 575 576 577 578 579
			if (container) {
				dom.clearNode(container);
				this.createCommentWidgetActions2(container, model);
			}
		}));

P
Peng Lyu 已提交
580
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => {
P
Peng Lyu 已提交
581 582 583 584 585 586
			if (container) {
				dom.clearNode(container);
				this.createCommentWidgetActions2(container, model);
			}
		}));

P
Peng Lyu 已提交
587
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => {
P
Peng Lyu 已提交
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
			// 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 已提交
603
		this._commentThreadDisposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => {
P
Peng Lyu 已提交
604 605 606 607 608 609 610 611 612 613 614 615 616 617
			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;
			}
		}));
	}

618 619
	private handleError(e: Error) {
		this._error.textContent = e.message;
620
		this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`;
621 622 623
		dom.removeClass(this._error, 'hidden');
	}

624 625 626 627
	private getActiveComment(): CommentNode | ReviewZoneWidget {
		return this._commentElements.filter(node => node.isEditing)[0] || this;
	}

R
rebornix 已提交
628
	private createCommentWidgetActions(container: HTMLElement, model: ITextModel) {
629 630
		dispose(this._submitActionsDisposables);

R
rebornix 已提交
631
		const button = new Button(container);
632
		this._submitActionsDisposables.push(attachButtonStyler(button, this.themeService));
R
rebornix 已提交
633 634 635
		button.label = 'Add comment';

		button.enabled = model.getValueLength() > 0;
636
		this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => {
R
rebornix 已提交
637 638 639 640 641 642 643 644
			if (this._commentEditor.getValue()) {
				button.enabled = true;
			} else {
				button.enabled = false;
			}
		}));

		button.onDidClick(async () => {
645
			this.createComment();
R
rebornix 已提交
646 647 648 649 650 651 652 653
		});

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

		switch (this._draftMode) {
			case modes.DraftMode.InDraft:
R
rebornix 已提交
654 655 656
				const deleteDraftLabel = this.commentService.getDeleteDraftLabel(this._owner);
				if (deleteDraftLabel) {
					const deletedraftButton = new Button(container);
657
					this._submitActionsDisposables.push(attachButtonStyler(deletedraftButton, this.themeService));
R
rebornix 已提交
658 659 660
					deletedraftButton.label = deleteDraftLabel;
					deletedraftButton.enabled = true;

M
Matt Bierner 已提交
661
					this._disposables.add(deletedraftButton.onDidClick(async () => {
662
						try {
663
							await this.commentService.deleteDraft(this._owner, this.editor.getModel()!.uri);
664 665 666
						} catch (e) {
							this.handleError(e);
						}
667
					}));
R
rebornix 已提交
668
				}
R
rebornix 已提交
669

R
rebornix 已提交
670 671 672
				const submitDraftLabel = this.commentService.getFinishDraftLabel(this._owner);
				if (submitDraftLabel) {
					const submitdraftButton = new Button(container);
673
					this._submitActionsDisposables.push(attachButtonStyler(submitdraftButton, this.themeService));
M
Matt Bierner 已提交
674
					submitdraftButton.label = this.commentService.getFinishDraftLabel(this._owner)!;
R
rebornix 已提交
675 676 677
					submitdraftButton.enabled = true;

					submitdraftButton.onDidClick(async () => {
678
						try {
679
							if (this._commentEditor.getValue()) {
680
								await this.createComment();
681
							}
682
							await this.commentService.finishDraft(this._owner, this.editor.getModel()!.uri);
683 684 685
						} catch (e) {
							this.handleError(e);
						}
R
rebornix 已提交
686 687
					});
				}
R
rebornix 已提交
688 689 690

				break;
			case modes.DraftMode.NotInDraft:
R
rebornix 已提交
691 692 693
				const startDraftLabel = this.commentService.getStartDraftLabel(this._owner);
				if (startDraftLabel) {
					const draftButton = new Button(container);
M
Matt Bierner 已提交
694
					this._disposables.add(attachButtonStyler(draftButton, this.themeService));
M
Matt Bierner 已提交
695
					draftButton.label = this.commentService.getStartDraftLabel(this._owner)!;
R
rebornix 已提交
696 697

					draftButton.enabled = model.getValueLength() > 0;
698
					this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => {
R
rebornix 已提交
699 700 701 702 703 704 705
						if (this._commentEditor.getValue()) {
							draftButton.enabled = true;
						} else {
							draftButton.enabled = false;
						}
					}));

M
Matt Bierner 已提交
706
					this._disposables.add(draftButton.onDidClick(async () => {
707
						try {
708
							await this.commentService.startDraft(this._owner, this.editor.getModel()!.uri);
709
							await this.createComment();
710 711 712
						} catch (e) {
							this.handleError(e);
						}
713
					}));
R
rebornix 已提交
714
				}
R
rebornix 已提交
715 716 717 718 719

				break;
		}
	}

P
Peng Lyu 已提交
720 721 722 723 724
	/**
	 * Command based actions.
	 */
	private createCommentWidgetActions2(container: HTMLElement, model: ITextModel) {
		let commentThread = this._commentThread as modes.CommentThread2;
725
		const { acceptInputCommand, additionalCommands } = commentThread;
P
Peng Lyu 已提交
726

M
Matt Bierner 已提交
727
		if (acceptInputCommand) {
728
			const button = new Button(container);
M
Matt Bierner 已提交
729
			this._disposables.add(attachButtonStyler(button, this.themeService));
730

M
Matt Bierner 已提交
731
			button.label = acceptInputCommand.title;
M
Matt Bierner 已提交
732
			this._disposables.add(button.onDidClick(async () => {
733
				commentThread.input = {
734
					uri: this._commentEditor.getModel()!.uri,
735 736
					value: this._commentEditor.getValue()
				};
737
				this.commentService.setActiveCommentThread(this._commentThread);
M
Matt Bierner 已提交
738
				await this.commandService.executeCommand(acceptInputCommand.id, ...(acceptInputCommand.arguments || []));
739 740 741
			}));

			button.enabled = model.getValueLength() > 0;
M
Matt Bierner 已提交
742
			this._disposables.add(this._commentEditor.onDidChangeModelContent(_ => {
743 744 745 746 747 748 749
				if (this._commentEditor.getValue()) {
					button.enabled = true;
				} else {
					button.enabled = false;
				}
			}));
		}
750

751 752 753
		if (additionalCommands) {
			additionalCommands.reverse().forEach(command => {
				const button = new Button(container);
M
Matt Bierner 已提交
754
				this._disposables.add(attachButtonStyler(button, this.themeService));
P
Peng Lyu 已提交
755

756
				button.label = command.title;
M
Matt Bierner 已提交
757
				this._disposables.add(button.onDidClick(async () => {
758 759 760 761
					commentThread.input = {
						uri: this._commentEditor.getModel()!.uri,
						value: this._commentEditor.getValue()
					};
762
					this.commentService.setActiveCommentThread(this._commentThread);
763 764 765 766
					await this.commandService.executeCommand(command.id, ...(command.arguments || []));
				}));
			});
		}
P
Peng Lyu 已提交
767

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

M
Matt Bierner 已提交
770 771
		this._disposables.add(menu);
		this._disposables.add(menu.onDidChange(() => {
772
			this._commentFormActions.setActions(menu);
773
		}));
P
Peng Lyu 已提交
774

775 776 777 778 779 780 781 782
		this._commentFormActions = new CommentFormActions(container, async (action: IAction) => {
			if (!commentThread.comments || !commentThread.comments.length) {
				let newPosition = this.getPosition();

				if (newPosition) {
					this.commentService.updateCommentThreadTemplate(this.owner, commentThread.commentThreadHandle, new Range(newPosition.lineNumber, 1, newPosition.lineNumber, 1));
				}
			}
783 784 785 786 787
			action.run({
				thread: this._commentThread,
				text: this._commentEditor.getValue(),
				$mid: 8
			});
P
Peng Lyu 已提交
788

789 790
			this.hideReplyArea();
		}, this.themeService);
791

792
		this._commentFormActions.setActions(menu);
P
Peng Lyu 已提交
793 794
	}

795
	private createNewCommentNode(comment: modes.Comment): CommentNode {
796
		let newCommentNode = this.instantiationService.createInstance(CommentNode,
P
Peng Lyu 已提交
797
			this._commentThread,
798 799
			comment,
			this.owner,
800
			this.editor.getModel()!.uri,
801 802 803
			this._parentEditor,
			this,
			this._markdownRenderer);
804

M
Matt Bierner 已提交
805 806
		this._disposables.add(newCommentNode);
		this._disposables.add(newCommentNode.onDidDelete(deletedNode => {
807 808 809 810 811 812
			const deletedNodeId = deletedNode.comment.commentId;
			const deletedElementIndex = arrays.firstIndex(this._commentElements, commentNode => commentNode.comment.commentId === deletedNodeId);
			if (deletedElementIndex > -1) {
				this._commentElements.splice(deletedElementIndex, 1);
			}

813
			const deletedCommentIndex = arrays.firstIndex(this._commentThread.comments!, comment => comment.commentId === deletedNodeId);
814
			if (deletedCommentIndex > -1) {
815
				this._commentThread.comments!.splice(deletedCommentIndex, 1);
816 817 818 819 820
			}

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

821
			if (this._commentThread.comments!.length === 0) {
822 823 824 825 826 827 828
				this.dispose();
			}
		}));

		return newCommentNode;
	}

829 830 831
	async submitComment(): Promise<void> {
		const activeComment = this.getActiveComment();
		if (activeComment instanceof ReviewZoneWidget) {
832
			if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
833 834 835 836
				let commentThread = this._commentThread as modes.CommentThread2;

				if (commentThread.acceptInputCommand) {
					commentThread.input = {
M
Matt Bierner 已提交
837
						uri: this._commentEditor.getModel()!.uri,
838 839
						value: this._commentEditor.getValue()
					};
840
					this.commentService.setActiveCommentThread(this._commentThread);
841 842 843 844 845
					let commandId = commentThread.acceptInputCommand.id;
					let args = commentThread.acceptInputCommand.arguments || [];

					await this.commandService.executeCommand(commandId, ...args);
					return;
P
Peng Lyu 已提交
846 847
				} else if (this._commentFormActions) {
					this._commentFormActions.triggerDefaultAction();
848 849 850 851
				}
			} else {
				this.createComment();
			}
852 853 854 855 856 857 858 859
		}

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

	async createComment(): Promise<void> {
860
		try {
861
			if (this._commentEditor.getModel()!.getValueLength() === 0) {
862 863
				return;
			}
M
Matt Bierner 已提交
864 865 866
			if (!this._commentGlyph) {
				return;
			}
867

868
			let newCommentThread;
M
Matt Bierner 已提交
869
			const lineNumber = this._commentGlyph.getPosition().position!.lineNumber;
870
			const isReply = this._commentThread.threadId !== null;
871

872

873
			if (isReply) {
874 875
				newCommentThread = await this.commentService.replyToCommentThread(
					this._owner,
876
					this.editor.getModel()!.uri,
877 878 879 880
					new Range(lineNumber, 1, lineNumber, 1),
					this._commentThread,
					this._commentEditor.getValue()
				);
881
			} else {
882
				newCommentThread = await this.commentService.createNewCommentThread(
883
					this._owner,
884
					this.editor.getModel()!.uri,
885 886 887
					new Range(lineNumber, 1, lineNumber, 1),
					this._commentEditor.getValue()
				);
888

889 890 891
				if (newCommentThread) {
					this.createReplyButton();
				}
892 893
			}

894
			if (newCommentThread) {
895
				this._commentEditor.setValue('');
896
				this._pendingComment = '';
897 898 899
				if (dom.hasClass(this._commentForm, 'expand')) {
					dom.removeClass(this._commentForm, 'expand');
				}
900
				this._commentEditor.getDomNode()!.style.outline = '';
901 902
				this._error.textContent = '';
				dom.addClass(this._error, 'hidden');
903
				this.update(newCommentThread);
904 905 906 907

				if (!isReply) {
					this._onDidCreateThread.fire(this);
				}
908
			}
909 910 911 912
		} 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.");
913
			this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`;
914
			dom.removeClass(this._error, 'hidden');
915
		}
916 917
	}

P
Peng Lyu 已提交
918
	private createThreadLabel() {
919 920 921 922 923
		let label: string | undefined;
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
			label = (this._commentThread as modes.CommentThread2).label;
		}

P
Peng Lyu 已提交
924
		if (label === undefined) {
925
			if (this._commentThread.comments && this._commentThread.comments.length) {
926 927 928 929 930
				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");
			}
931
		}
932

P
Peng Lyu 已提交
933 934 935 936
		if (label) {
			this._headingLabel.innerHTML = strings.escape(label);
			this._headingLabel.setAttribute('aria-label', label);
		}
937 938
	}

939 940 941 942 943 944 945
	private expandReplyArea() {
		if (!dom.hasClass(this._commentForm, 'expand')) {
			dom.addClass(this._commentForm, 'expand');
			this._commentEditor.focus();
		}
	}

P
Peng Lyu 已提交
946 947 948 949 950 951 952 953 954 955 956
	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');
	}

957
	private createReplyButton() {
958
		this._reviewThreadReplyButton = <HTMLButtonElement>dom.append(this._commentForm, dom.$('button.review-thread-reply-button'));
P
Peng Lyu 已提交
959 960 961
		if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) {
			// this._reviewThreadReplyButton.title = (this._commentThread as modes.CommentThread2).acceptInputCommands.title;
		} else {
P
Peng Lyu 已提交
962
			this._reviewThreadReplyButton.title = nls.localize('reply', "Reply...");
P
Peng Lyu 已提交
963
		}
964
		this._reviewThreadReplyButton.textContent = nls.localize('reply', "Reply...");
965
		// bind click/escape actions for reviewThreadReplyButton and textArea
M
Matt Bierner 已提交
966 967
		this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea()));
		this._disposables.add(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea()));
968 969

		this._commentEditor.onDidBlurEditorWidget(() => {
970
			if (this._commentEditor.getModel()!.getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) {
971 972 973
				dom.removeClass(this._commentForm, 'expand');
			}
		});
974 975
	}

976
	_refresh() {
977
		if (this._isExpanded && this._bodyElement) {
978 979 980 981 982 983
			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;

984
			const computedLinesNumber = Math.ceil((headHeight + dimensions.height + arrowHeight + frameThickness + 8 /** margin bottom to avoid margin collapse */) / lineHeight);
985 986 987 988 989 990 991

			let currentPosition = this.getPosition();

			if (this._viewZone && currentPosition && currentPosition.lineNumber !== this._viewZone.afterLineNumber) {
				this._viewZone.afterLineNumber = currentPosition.lineNumber;
			}

992 993 994 995
			this._relayout(computedLinesNumber);
		}
	}

P
Peng Lyu 已提交
996
	private setCommentEditorDecorations() {
997 998
		const model = this._commentEditor && this._commentEditor.getModel();
		if (model) {
999
			const valueLength = model.getValueLength();
1000
			const hasExistingComments = this._commentThread.comments && this._commentThread.comments.length > 0;
1001
			const placeholder = valueLength > 0
1002
				? ''
1003 1004 1005
				: hasExistingComments
					? nls.localize('reply', "Reply...")
					: nls.localize('newComment', "Type a new comment");
1006 1007 1008 1009 1010 1011 1012 1013 1014 1015
			const decorations = [{
				range: {
					startLineNumber: 0,
					endLineNumber: 0,
					startColumn: 0,
					endColumn: 1
				},
				renderOptions: {
					after: {
						contentText: placeholder,
1016
						color: `${transparent(editorForeground, 0.4)(this.themeService.getTheme())}`
1017
					}
P
Peng Lyu 已提交
1018
				}
1019
			}];
P
Peng Lyu 已提交
1020

1021 1022
			this._commentEditor.setDecorations(COMMENTEDITOR_DECORATION_KEY, decorations);
		}
P
Peng Lyu 已提交
1023 1024
	}

1025
	private mouseDownInfo: { lineNumber: number } | null;
1026 1027

	private onEditorMouseDown(e: IEditorMouseEvent): void {
1028 1029 1030 1031 1032 1033 1034 1035
		this.mouseDownInfo = null;

		const range = e.target.range;

		if (!range) {
			return;
		}

1036 1037 1038 1039
		if (!e.event.leftButton) {
			return;
		}

1040
		if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
1041 1042 1043
			return;
		}

1044 1045 1046 1047
		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
1048
		if (gutterOffsetX > 14) {
1049
			return;
1050 1051
		}

1052
		this.mouseDownInfo = { lineNumber: range.startLineNumber };
1053 1054 1055 1056 1057 1058 1059
	}

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

1060 1061 1062 1063 1064
		const { lineNumber } = this.mouseDownInfo;
		this.mouseDownInfo = null;

		const range = e.target.range;

1065 1066 1067 1068
		if (!range || range.startLineNumber !== lineNumber) {
			return;
		}

1069 1070 1071 1072 1073 1074 1075 1076
		if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
			return;
		}

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

M
Matt Bierner 已提交
1077
		if (this._commentGlyph && this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) {
1078 1079 1080
			return;
		}

1081
		if (e.target.element.className.indexOf('comment-thread') >= 0) {
1082
			this.toggleExpand(lineNumber);
1083 1084 1085 1086
		}
	}

	private _applyTheme(theme: ITheme) {
M
Matt Bierner 已提交
1087
		const borderColor = theme.getColor(peekViewBorder) || Color.transparent;
1088 1089 1090 1091
		this.style({
			arrowColor: borderColor,
			frameColor: borderColor
		});
1092 1093 1094 1095

		const content: string[] = [];
		const linkColor = theme.getColor(textLinkForeground);
		if (linkColor) {
1096
			content.push(`.monaco-editor .review-widget .body .comment-body a { color: ${linkColor} }`);
1097 1098 1099 1100
		}

		const linkActiveColor = theme.getColor(textLinkActiveForeground);
		if (linkActiveColor) {
1101
			content.push(`.monaco-editor .review-widget .body .comment-body a:hover, a:active { color: ${linkActiveColor} }`);
1102 1103 1104 1105
		}

		const focusColor = theme.getColor(focusBorder);
		if (focusColor) {
1106 1107
			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}; }`);
1108 1109
		}

1110 1111 1112 1113 1114 1115 1116 1117 1118 1119
		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}; }`);
		}

1120 1121 1122
		const hcBorder = theme.getColor(contrastBorder);
		if (hcBorder) {
			content.push(`.monaco-editor .review-widget .body .comment-form .review-thread-reply-button { outline-color: ${hcBorder}; }`);
1123
			content.push(`.monaco-editor .review-widget .body .monaco-editor { outline: 1px solid ${hcBorder}; }`);
1124 1125
		}

1126 1127
		const errorBorder = theme.getColor(inputValidationErrorBorder);
		if (errorBorder) {
1128
			content.push(`.monaco-editor .review-widget .validation-error { border: 1px solid ${errorBorder}; }`);
1129 1130 1131 1132
		}

		const errorBackground = theme.getColor(inputValidationErrorBackground);
		if (errorBackground) {
1133
			content.push(`.monaco-editor .review-widget .validation-error { background: ${errorBackground}; }`);
1134 1135 1136 1137 1138
		}

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

1141 1142 1143 1144 1145 1146 1147
		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};
		}`);

1148
		this._styleElement.innerHTML = content.join('\n');
1149 1150 1151

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

1154
	show(rangeOrPos: IRange | IPosition, heightInLines: number): void {
1155
		this._isExpanded = true;
1156
		super.show(rangeOrPos, heightInLines);
1157
		this._refresh();
1158 1159 1160
	}

	hide() {
1161
		this._isExpanded = false;
1162 1163
		// Focus the container so that the comment editor will be blurred before it is hidden
		this.editor.focus();
1164 1165 1166
		super.hide();
	}

1167 1168 1169 1170 1171 1172
	dispose() {
		super.dispose();
		if (this._resizeObserver) {
			this._resizeObserver.disconnect();
			this._resizeObserver = null;
		}
1173

1174
		if (this._commentGlyph) {
1175
			this._commentGlyph.dispose();
1176
			this._commentGlyph = undefined;
1177
		}
1178

1179
		this._globalToDispose.forEach(global => global.dispose());
P
Peng Lyu 已提交
1180
		this._commentThreadDisposables.forEach(global => global.dispose());
1181
		this._submitActionsDisposables.forEach(local => local.dispose());
1182
		this._onDidClose.fire(undefined);
1183 1184
	}
}