commentsEditorContribution.ts 24.4 KB
Newer Older
P
Peng Lyu 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';

P
Peng Lyu 已提交
7
import 'vs/css!./media/review';
8
import * as nls from 'vs/nls';
9
import * as arrays from 'vs/base/common/arrays';
M
Matt Bierner 已提交
10
import * as modes from 'vs/editor/common/modes';
P
Peng Lyu 已提交
11
import { IEditorContribution } from 'vs/editor/common/editorCommon';
12
import { ICodeEditor, IEditorMouseEvent, MouseTargetType, IViewZone } from 'vs/editor/browser/editorBrowser';
13
import { $ } from 'vs/base/browser/builder';
P
Peng Lyu 已提交
14 15 16
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
P
Peng Lyu 已提交
17
import { TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model';
P
Peng Lyu 已提交
18
import { renderMarkdown } from 'vs/base/browser/htmlContentRenderer';
M
Matt Bierner 已提交
19
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
P
Peng Lyu 已提交
20 21 22 23 24
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
25 26 27 28 29
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { Action } from 'vs/base/common/actions';
import { registerThemingParticipant, ITheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { peekViewEditorBackground, peekViewBorder, } from 'vs/editor/contrib/referenceSearch/referencesWidget';
import { Color } from 'vs/base/common/color';
P
Peng Lyu 已提交
30
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
31
import { ICommandService } from 'vs/platform/commands/common/commands';
32
import { Emitter, Event } from 'vs/base/common/event';
P
Peng Lyu 已提交
33
import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
P
Peng Lyu 已提交
34 35
import { ZoneWidget, IOptions } from 'vs/editor/contrib/zoneWidget/zoneWidget';
import { ReviewModel, ReviewStyle } from 'vs/workbench/parts/comments/common/reviewModel';
36
import { ICommentService } from '../../../services/comments/electron-browser/commentService';
P
Peng Lyu 已提交
37 38 39 40 41 42

export const ctxReviewPanelVisible = new RawContextKey<boolean>('reviewPanelVisible', false);
export const ID = 'editor.contrib.review';

declare var ResizeObserver: any;

P
Peng Lyu 已提交
43
const REVIEW_DECORATION = ModelDecorationOptions.register({
P
Peng Lyu 已提交
44
	stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
45
	glyphMarginClassName: 'review'
P
Peng Lyu 已提交
46 47
});

P
Peng Lyu 已提交
48 49 50 51 52
const NEW_COMMENT_DECORATION = ModelDecorationOptions.register({
	stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
	glyphMarginClassName: 'new-comment-hint',
});

P
Peng Lyu 已提交
53 54 55 56 57 58 59 60 61
export class ReviewViewZone implements IViewZone {
	public readonly afterLineNumber: number;
	public readonly domNode: HTMLElement;
	private callback: (top: number) => void;

	constructor(afterLineNumber: number, onDomNodeTop: (top: number) => void) {
		this.afterLineNumber = afterLineNumber;
		this.callback = onDomNodeTop;

P
use $.  
Peng Lyu 已提交
62
		this.domNode = $('.review-viewzone').getHTMLElement();
P
Peng Lyu 已提交
63 64 65 66 67 68 69
	}

	onDomNodeTop(top: number): void {
		this.callback(top);
	}
}

P
Peng Lyu 已提交
70 71
export class CommentNode {
	private _domNode: HTMLElement;
P
Peng Lyu 已提交
72 73
	private _body: HTMLElement;
	private _md: HTMLElement;
P
Peng Lyu 已提交
74 75 76
	public get domNode(): HTMLElement {
		return this._domNode;
	}
P
Peng Lyu 已提交
77
	constructor(public comment: modes.Comment, ) {
P
Peng Lyu 已提交
78
		this._domNode = $('div.review-comment').getHTMLElement();
P
Peng Lyu 已提交
79 80 81 82 83 84 85 86
		let avatar = $('span.float-left').appendTo(this._domNode).getHTMLElement();
		let img = <HTMLImageElement>$('img.avatar').appendTo(avatar).getHTMLElement();
		img.src = comment.gravatar;
		let commentDetailsContainer = $('.review-comment-contents').appendTo(this._domNode).getHTMLElement();

		let header = $('h4').appendTo(commentDetailsContainer).getHTMLElement();
		let author = $('strong.author').appendTo(header).getHTMLElement();
		author.innerText = comment.userName;
P
Peng Lyu 已提交
87 88 89 90 91 92 93 94 95 96 97
		this._body = $('comment-body').appendTo(commentDetailsContainer).getHTMLElement();
		this._md = renderMarkdown(comment.body);
		this._body.appendChild(this._md);
	}

	update(newComment: modes.Comment) {
		if (newComment.body !== this.comment.body) {
			this._body.removeChild(this._md);
			this._md = renderMarkdown(newComment.body);
			this._body.appendChild(this._md);
		}
P
Peng Lyu 已提交
98 99

		this.comment = newComment;
P
Peng Lyu 已提交
100 101 102
	}
}

P
Peng Lyu 已提交
103
export class ReviewZoneWidget extends ZoneWidget {
104 105 106 107 108 109
	private _headElement: HTMLElement;
	protected _primaryHeading: HTMLElement;
	protected _secondaryHeading: HTMLElement;
	protected _metaHeading: HTMLElement;
	protected _actionbarWidget: ActionBar;
	private _bodyElement: HTMLElement;
P
Peng Lyu 已提交
110
	private _commentsElement: HTMLElement;
P
Peng Lyu 已提交
111
	private _commentElements: CommentNode[];
P
Peng Lyu 已提交
112
	private _resizeObserver: any;
113
	private _onDidClose = new Emitter<ReviewZoneWidget>();
114 115
	private _isCollapsed = true;
	private _toggleAction: Action;
P
Peng Lyu 已提交
116 117 118 119
	private _commentThread: modes.CommentThread;
	public get commentThread(): modes.CommentThread {
		return this._commentThread;
	}
P
Peng Lyu 已提交
120

121
	constructor(
122
		editor: ICodeEditor,
P
Peng Lyu 已提交
123
		commentThread: modes.CommentThread,
124
		options: IOptions = {},
125
		private readonly themeService: IThemeService,
126 127
		private readonly commandService: ICommandService
	) {
P
Peng Lyu 已提交
128 129
		super(editor, options);
		this._resizeObserver = null;
P
Peng Lyu 已提交
130
		this._commentThread = commentThread;
P
Peng Lyu 已提交
131
		this.create();
132
		this.themeService.onThemeChange(this._applyTheme, this);
P
Peng Lyu 已提交
133 134
	}

135 136 137 138
	public get onDidClose(): Event<ReviewZoneWidget> {
		return this._onDidClose.event;
	}

139 140 141 142 143 144
	public reveal() {
		if (this._isCollapsed) {
			this._toggleAction.run();
		}
	}

P
Peng Lyu 已提交
145
	protected _fillContainer(container: HTMLElement): void {
146 147 148 149 150
		this.setCssClass('review-widget');
		this._headElement = <HTMLDivElement>$('.head').getHTMLElement();
		container.appendChild(this._headElement);
		this._fillHead(this._headElement);

P
Peng Lyu 已提交
151
		this._bodyElement = <HTMLDivElement>$('.body').getHTMLElement();
152 153 154 155 156 157 158 159 160 161 162 163
		container.appendChild(this._bodyElement);
	}

	protected _fillHead(container: HTMLElement): void {
		var titleElement = $('.review-title').
			appendTo(this._headElement).
			getHTMLElement();

		this._primaryHeading = $('span.filename').appendTo(titleElement).getHTMLElement();
		this._secondaryHeading = $('span.dirname').appendTo(titleElement).getHTMLElement();
		this._metaHeading = $('span.meta').appendTo(titleElement).getHTMLElement();

164
		let primaryHeading = 'Reviewers:';
165 166
		$(this._primaryHeading).safeInnerHtml(primaryHeading);
		this._primaryHeading.setAttribute('aria-label', primaryHeading);
167

P
Peng Lyu 已提交
168
		let secondaryHeading = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', ');
169 170 171
		$(this._secondaryHeading).safeInnerHtml(secondaryHeading);

		const actionsContainer = $('.review-actions').appendTo(this._headElement);
P
Peng Lyu 已提交
172
		this._actionbarWidget = new ActionBar(actionsContainer.getHTMLElement(), {});
173 174
		this._disposables.push(this._actionbarWidget);

175
		this._toggleAction = new Action('review.expand', nls.localize('label.expand', "Expand"), 'expand-review-action octicon octicon-chevron-down', true, () => {
P
Peng Lyu 已提交
176 177 178 179 180 181 182 183 184 185
			if (this._isCollapsed) {
				this._bodyElement.style.display = 'block';
				this._toggleAction.class = 'expand-review-action octicon octicon-chevron-up';
				this._isCollapsed = false;
			}
			else {
				this._bodyElement.style.display = 'none';
				this._toggleAction.class = 'expand-review-action octicon octicon-chevron-down';
				this._isCollapsed = true;
			}
186
			return null;
187
		});
188

189
		this._actionbarWidget.push(this._toggleAction, { label: false, icon: true });
P
use $.  
Peng Lyu 已提交
190
	}
191

P
use $.  
Peng Lyu 已提交
192
	toggleExpand() {
P
DRY  
Peng Lyu 已提交
193
		this._toggleAction.run();
P
Peng Lyu 已提交
194 195
	}

P
Peng Lyu 已提交
196 197 198 199 200 201 202 203
	update(commentThread: modes.CommentThread) {
		const oldCommentsLen = this._commentElements.length;
		const newCommentsLen = commentThread.comments.length;

		let commentElementsToDel: CommentNode[] = [];
		let commentElementsToDelIndex: number[] = [];
		for (let i = 0; i < oldCommentsLen; i++) {
			let comment = this._commentElements[i].comment;
P
Peng Lyu 已提交
204 205 206 207 208
			let newComment = commentThread.comments.filter(c => c.commentId === comment.commentId);

			if (newComment.length) {
				this._commentElements[i].update(newComment[0]);
			} else {
P
Peng Lyu 已提交
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
				commentElementsToDelIndex.push(i);
				commentElementsToDel.push(this._commentElements[i]);
			}
		}

		// del removed elements
		for (let i = commentElementsToDel.length - 1; i >= 0; i--) {
			this._commentElements.splice(commentElementsToDelIndex[i]);
			this._commentsElement.removeChild(commentElementsToDel[i].domNode);
		}

		if (this._commentElements.length === 0) {
			this._commentThread = commentThread;
			commentThread.comments.forEach(comment => {
				let newElement = new CommentNode(comment);
				this._commentElements.push(newElement);
				this._commentsElement.appendChild(newElement.domNode);
			});
			return;
		}

		let lastCommentElement: HTMLElement = null;
		let newCommentNodeList: CommentNode[] = [];
		for (let i = newCommentsLen - 1; i >= 0; i--) {
			let currentComment = commentThread.comments[i];
			let oldCommentNode = this._commentElements.filter(commentNode => commentNode.comment.commentId === currentComment.commentId);
			if (oldCommentNode.length) {
				lastCommentElement = oldCommentNode[0].domNode;
				newCommentNodeList.unshift(oldCommentNode[0]);
			} else {
				let newElement = new CommentNode(currentComment);
				newCommentNodeList.unshift(newElement);
				if (lastCommentElement) {
					this._commentsElement.insertBefore(newElement.domNode, lastCommentElement);
					lastCommentElement = newElement.domNode;
				} else {
					this._commentsElement.appendChild(newElement.domNode);
					lastCommentElement = newElement.domNode;
				}
			}
		}

		this._commentThread = commentThread;
		this._commentElements = newCommentNodeList;
	}

P
Peng Lyu 已提交
255
	display(lineNumber: number) {
P
Peng Lyu 已提交
256 257
		this.show({ lineNumber: lineNumber, column: 1 }, 2);

258 259 260 261
		var headHeight = Math.ceil(this.editor.getConfiguration().lineHeight * 1.2);
		this._headElement.style.height = `${headHeight}px`;
		this._headElement.style.lineHeight = this._headElement.style.height;

262
		this._bodyElement.style.display = 'none';
P
Peng Lyu 已提交
263
		this._commentsElement = $('div.comments-container').appendTo(this._bodyElement).getHTMLElement();
P
Peng Lyu 已提交
264
		this._commentElements = [];
P
Peng Lyu 已提交
265
		for (let i = 0; i < this._commentThread.comments.length; i++) {
P
Peng Lyu 已提交
266 267 268
			let newCommentNode = new CommentNode(this._commentThread.comments[i]);
			this._commentElements.push(newCommentNode);
			this._commentsElement.appendChild(newCommentNode.domNode);
P
Peng Lyu 已提交
269
		}
270

P
Peng Lyu 已提交
271 272 273 274
		if (this._commentThread.reply) {
			const commentForm = $('.comment-form').appendTo(this._bodyElement).getHTMLElement();
			const textArea = <HTMLTextAreaElement>$('textarea').appendTo(commentForm).getHTMLElement();
			const formActions = $('.form-actions').appendTo(commentForm).getHTMLElement();
275

P
use $.  
Peng Lyu 已提交
276
			const button = $('button').appendTo(formActions).getHTMLElement();
P
Peng Lyu 已提交
277
			button.onclick = async () => {
P
Peng Lyu 已提交
278
				let newComment = await this.commandService.executeCommand(this._commentThread.reply.id, this._commentThread.threadId, this.editor.getModel().uri, lineNumber, textArea.value);
P
Peng Lyu 已提交
279 280
				if (newComment) {
					textArea.value = '';
P
Peng Lyu 已提交
281
					this._commentThread.comments.push(newComment);
P
Peng Lyu 已提交
282
					let newCommentNode = new CommentNode(newComment);
P
Peng Lyu 已提交
283 284
					this._commentElements.push(newCommentNode);
					this._commentsElement.appendChild(newCommentNode.domNode);
P
Peng Lyu 已提交
285
					let secondaryHeading = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', ');
P
Peng Lyu 已提交
286
					$(this._secondaryHeading).safeInnerHtml(secondaryHeading);
P
Peng Lyu 已提交
287
				}
288
			};
P
Peng Lyu 已提交
289
			button.textContent = this._commentThread.reply.title;
290 291
		}

P
Peng Lyu 已提交
292
		this._resizeObserver = new ResizeObserver(entries => {
293
			if (entries[0].target === this._bodyElement) {
P
Peng Lyu 已提交
294 295
				const lineHeight = this.editor.getConfiguration().lineHeight;
				const arrowHeight = Math.round(lineHeight / 3);
296
				const computedLinesNumber = Math.ceil((headHeight + entries[0].contentRect.height + arrowHeight) / lineHeight);
P
Peng Lyu 已提交
297 298 299 300
				this._relayout(computedLinesNumber);
			}
		});

301 302 303 304 305 306 307 308 309
		this._resizeObserver.observe(this._bodyElement);
	}

	private _applyTheme(theme: ITheme) {
		let borderColor = theme.getColor(peekViewBorder) || Color.transparent;
		this.style({
			arrowColor: borderColor,
			frameColor: borderColor
		});
P
Peng Lyu 已提交
310 311 312 313
	}

	dispose() {
		super.dispose();
314 315 316 317
		if (this._resizeObserver) {
			this._resizeObserver.disconnect();
			this._resizeObserver = null;
		}
318 319 320 321 322
		this._onDidClose.fire();
	}

}

P
Peng Lyu 已提交
323 324 325 326 327
export class ReviewController implements IEditorContribution {
	private globalToDispose: IDisposable[];
	private localToDispose: IDisposable[];
	private editor: ICodeEditor;
	private decorationIDs: string[];
P
Peng Lyu 已提交
328
	private newCommentHintDecoration: string[];
P
Peng Lyu 已提交
329
	private _zoneWidget: ReviewZoneWidget;
330
	private _zoneWidgets: ReviewZoneWidget[];
P
Peng Lyu 已提交
331
	private _reviewPanelVisible: IContextKey<boolean>;
M
Matt Bierner 已提交
332
	private _commentThreads: modes.CommentThread[];
P
Peng Lyu 已提交
333
	private _newCommentActions: modes.NewCommentAction[];
334
	private _reviewModel: ReviewModel;
P
Peng Lyu 已提交
335 336 337

	constructor(
		editor: ICodeEditor,
338
		@IContextKeyService contextKeyService: IContextKeyService,
339 340
		@IThemeService private themeService: IThemeService,
		@ICommandService private commandService: ICommandService,
341
		@ICommentService private commentService: ICommentService
P
Peng Lyu 已提交
342 343 344 345 346
	) {
		this.editor = editor;
		this.globalToDispose = [];
		this.localToDispose = [];
		this.decorationIDs = [];
P
Peng Lyu 已提交
347
		this.newCommentHintDecoration = [];
P
Peng Lyu 已提交
348
		this.mouseDownInfo = null;
M
Matt Bierner 已提交
349
		this._commentThreads = [];
P
Peng Lyu 已提交
350
		this._newCommentActions = [];
351
		this._zoneWidgets = [];
352
		this._zoneWidget = null;
P
Peng Lyu 已提交
353 354

		this._reviewPanelVisible = ctxReviewPanelVisible.bindTo(contextKeyService);
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
		this._reviewModel = new ReviewModel();

		this._reviewModel.onDidChangeStyle(style => {
			if (style === ReviewStyle.Gutter) {
				this._zoneWidgets.forEach(zone => {
					zone.dispose();
				});
				this._zoneWidgets = [];

				this.editor.changeDecorations(accessor => {
					this.decorationIDs = accessor.deltaDecorations(this.decorationIDs, this._commentThreads.map(thread => ({
						range: thread.range,
						options: REVIEW_DECORATION
					})));
				});
			} else {
				this.editor.changeDecorations(accessor => {
					this.decorationIDs = accessor.deltaDecorations(this.decorationIDs, []);
				});

				if (this._zoneWidget) {
					this._zoneWidget.dispose();
					this._zoneWidget = null;
				}

				this._zoneWidgets.forEach(zone => {
					zone.dispose();
				});

				this._commentThreads.forEach(thread => {
P
Peng Lyu 已提交
385 386
					let zoneWidget = new ReviewZoneWidget(this.editor, thread, {}, this.themeService, this.commandService);
					zoneWidget.display(thread.range.startLineNumber);
387 388 389 390
					this._zoneWidgets.push(zoneWidget);
				});
			}
		});
P
Peng Lyu 已提交
391

P
Peng Lyu 已提交
392
		this.globalToDispose.push(this.commentService.onDidSetResourceCommentThreads(e => {
393 394 395 396
			const editorURI = this.editor && this.editor.getModel() && this.editor.getModel().uri;
			if (editorURI && editorURI.toString() === e.resource.toString()) {
				this.setComments(e.commentThreads);
			}
P
Peng Lyu 已提交
397 398
		}));

P
Peng Lyu 已提交
399 400 401 402 403 404 405
		this.globalToDispose.push(this.editor.onDidChangeModel(() => this.onModelChanged()));
	}

	public static get(editor: ICodeEditor): ReviewController {
		return editor.getContribution<ReviewController>(ID);
	}

406
	public revealCommentThread(threadId: string): void {
P
Peng Lyu 已提交
407
		const commentThreadWidget = this._zoneWidgets.filter(widget => widget.commentThread.threadId === threadId);
408 409 410 411 412
		if (commentThreadWidget.length === 1) {
			commentThreadWidget[0].reveal();
		}
	}

P
Peng Lyu 已提交
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
	getId(): string {
		return ID;
	}
	dispose(): void {
		this.globalToDispose = dispose(this.globalToDispose);
		this.localToDispose = dispose(this.localToDispose);

		if (this._zoneWidget) {
			this._zoneWidget.dispose();
			this._zoneWidget = null;
		}
		this.editor = null;
	}

	public onModelChanged(): void {
		this.localToDispose = dispose(this.localToDispose);
P
Peng Lyu 已提交
429 430 431 432 433
		if (this._zoneWidget) {
			// todo store view state.
			this._zoneWidget.dispose();
			this._zoneWidget = null;
		}
434 435 436 437 438

		this._zoneWidgets.forEach(zone => {
			zone.dispose();
		});
		this._zoneWidgets = [];
439

P
Peng Lyu 已提交
440 441
		this.localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
		this.localToDispose.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
P
Peng Lyu 已提交
442
		this.localToDispose.push(this.editor.onMouseMove(e => this.onEditorMouseMove(e)));
P
Peng Lyu 已提交
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
		this.localToDispose.push(this.commentService.onDidUpdateCommentThreads(e => {
			const editorURI = this.editor && this.editor.getModel() && this.editor.getModel().uri;
			if (!editorURI) {
				return;
			}
			let added = e.added.filter(thread => thread.resource.toString() === editorURI.toString());
			let removed = e.removed.filter(thread => thread.resource.toString() === editorURI.toString());
			let changed = e.changed.filter(thread => thread.resource.toString() === editorURI.toString());

			removed.forEach(thread => {
				let matchedZones = this._zoneWidgets.filter(zoneWidget => zoneWidget.commentThread.threadId === thread.threadId);
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					let index = this._zoneWidgets.indexOf(matchedZone);
					this._zoneWidgets.splice(index, 1);
				}
			});

			changed.forEach(thread => {
				let matchedZones = this._zoneWidgets.filter(zoneWidget => zoneWidget.commentThread.threadId === thread.threadId);
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					matchedZone.update(thread);
				}
			});
			added.forEach(thread => {
				let zoneWidget = new ReviewZoneWidget(this.editor, thread, {}, this.themeService, this.commandService);
				zoneWidget.display(thread.range.startLineNumber);
				this._zoneWidgets.push(zoneWidget);
				this._commentThreads.push(thread);
			});
		}));
P
Peng Lyu 已提交
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
	}

	private mouseDownInfo: { lineNumber: number, iconClicked: boolean };

	private onEditorMouseDown(e: IEditorMouseEvent): void {
		if (!e.event.leftButton) {
			return;
		}

		let range = e.target.range;
		if (!range) {
			return;
		}

		let iconClicked = false;
		switch (e.target.type) {
491
			case MouseTargetType.GUTTER_GLYPH_MARGIN:
P
Peng Lyu 已提交
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
				iconClicked = true;
				break;
			default:
				return;
		}

		this.mouseDownInfo = { lineNumber: range.startLineNumber, iconClicked };
	}

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

		let range = e.target.range;
		if (!range || range.startLineNumber !== lineNumber) {
			return;
		}

		if (iconClicked) {
514
			if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN) {
P
Peng Lyu 已提交
515 516 517 518 519 520 521 522
				return;
			}
		}

		if (this._zoneWidget && this._zoneWidget.position.lineNumber === lineNumber) {
			return;
		}

P
Peng Lyu 已提交
523
		if (this.marginFreeFromCommentHintDecorations(lineNumber)) {
P
Peng Lyu 已提交
524 525 526 527 528 529 530
			let newCommentAction = this.getNewCommentAction(lineNumber);
			if (!newCommentAction) {
				return;
			}

			// add new comment
			this._reviewPanelVisible.set(true);
P
Peng Lyu 已提交
531 532 533 534 535 536 537 538 539 540
			this._zoneWidget = new ReviewZoneWidget(this.editor, {
				threadId: null,
				resource: null,
				comments: [],
				range: {
					startLineNumber: lineNumber,
					startColumn: 0,
					endLineNumber: lineNumber,
					endColumn: 0
				},
P
Peng Lyu 已提交
541 542
				reply: null
				// actions: newCommentAction.actions
P
Peng Lyu 已提交
543
			}, {}, this.themeService, this.commandService);
P
Peng Lyu 已提交
544 545 546
			this._zoneWidget.onDidClose(e => {
				this._zoneWidget = null;
			});
P
Peng Lyu 已提交
547
			this._zoneWidget.display(lineNumber);
P
use $.  
Peng Lyu 已提交
548
			this._zoneWidget.toggleExpand();
549
		}
P
Peng Lyu 已提交
550 551
	}

P
Peng Lyu 已提交
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
	private onEditorMouseMove(e: IEditorMouseEvent): void {
		let showNewCommentHintAtLineNumber = -1;
		if (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN
			&& this.marginFreeFromCommentHintDecorations(e.target.position.lineNumber)) {
			const data = e.target.detail as IMarginData;
			if (!data.isAfterLines) {
				showNewCommentHintAtLineNumber = e.target.position.lineNumber;
			}
		}
		this.ensureNewCommentHintDecoration(showNewCommentHintAtLineNumber);
	}

	ensureNewCommentHintDecoration(showNewCommentHintAtLineNumber: number) {
		const newDecoration: IModelDeltaDecoration[] = [];
		if (showNewCommentHintAtLineNumber !== -1) {
			newDecoration.push({
				options: NEW_COMMENT_DECORATION,
				range: {
					startLineNumber: showNewCommentHintAtLineNumber,
					startColumn: 1,
					endLineNumber: showNewCommentHintAtLineNumber,
					endColumn: 1
				}
			});
		}

		this.newCommentHintDecoration = this.editor.deltaDecorations(this.newCommentHintDecoration, newDecoration);
	}

P
Peng Lyu 已提交
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
	getNewCommentAction(line: number): modes.NewCommentAction {
		let allowNewComment = false;

		for (let i = 0; i < this._newCommentActions.length; i++) {
			let newCommentAction = this._newCommentActions[i];

			for (let j = 0; j < newCommentAction.ranges.length; j++) {
				if (newCommentAction.ranges[j].startLineNumber <= line && newCommentAction.ranges[j].endLineNumber >= line) {
					allowNewComment = true;
					break;
				}
			}

			if (allowNewComment) {
				return newCommentAction;
			}
		}

		return null;
	}

P
Peng Lyu 已提交
602 603 604
	marginFreeFromCommentHintDecorations(line: number): boolean {
		let allowNewComment = false;

P
Peng Lyu 已提交
605 606 607 608 609 610 611 612
		for (let i = 0; i < this._newCommentActions.length; i++) {
			let newCommentAction = this._newCommentActions[i];

			for (let j = 0; j < newCommentAction.ranges.length; j++) {
				if (newCommentAction.ranges[j].startLineNumber <= line && newCommentAction.ranges[j].endLineNumber >= line) {
					allowNewComment = true;
					break;
				}
P
Peng Lyu 已提交
613 614
			}

P
Peng Lyu 已提交
615 616 617
			if (allowNewComment) {
				break;
			}
P
Peng Lyu 已提交
618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635
		}

		if (!allowNewComment) {
			return false;
		}

		const decorations = this.editor.getLineDecorations(line);
		if (decorations) {
			for (const { options } of decorations) {
				if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('review') > -1) {
					return false;
				}
			}
		}

		return true;
	}

636
	getCommentThread(line: number): modes.CommentThread | undefined {
M
Matt Bierner 已提交
637 638
		for (let i = 0; i < this._commentThreads.length; i++) {
			if (this._commentThreads[i].range.startLineNumber === line) {
639
				return this._commentThreads[i];
M
Matt Bierner 已提交
640 641 642
			}
		}

643
		return undefined;
M
Matt Bierner 已提交
644 645
	}

P
Peng Lyu 已提交
646 647 648 649
	setNewCommentActions(newCommentActions: modes.NewCommentAction[]) {
		this._newCommentActions = newCommentActions;
	}

M
Matt Bierner 已提交
650 651
	setComments(commentThreads: modes.CommentThread[]): void {
		this._commentThreads = commentThreads;
652

653 654 655 656
		if (this._commentThreads.length === 0) {
			return;
		}

657 658 659 660 661 662 663 664 665 666 667 668
		if (this._reviewModel.style === ReviewStyle.Gutter) {
			this.editor.changeDecorations(accessor => {
				this.decorationIDs = accessor.deltaDecorations(this.decorationIDs, commentThreads.map(thread => ({
					range: thread.range,
					options: REVIEW_DECORATION
				})));
			});
		} else {
			// create viewzones
			this._zoneWidgets.forEach(zone => {
				zone.dispose();
			});
669

670
			this._commentThreads.forEach(thread => {
P
Peng Lyu 已提交
671 672
				let zoneWidget = new ReviewZoneWidget(this.editor, thread, {}, this.themeService, this.commandService);
				zoneWidget.display(thread.range.startLineNumber);
673 674 675
				this._zoneWidgets.push(zoneWidget);
			});
		}
M
Matt Bierner 已提交
676 677 678
	}


P
Peng Lyu 已提交
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723
	public closeWidget(): void {
		this._reviewPanelVisible.reset();

		if (this._zoneWidget) {
			this._zoneWidget.dispose();
			this._zoneWidget = null;
		}

		this.editor.focus();
	}
}

registerEditorContribution(ReviewController);


KeybindingsRegistry.registerCommandAndKeybindingRule({
	id: 'closeReviewPanel',
	weight: KeybindingsRegistry.WEIGHT.editorContrib(),
	primary: KeyCode.Escape,
	secondary: [KeyMod.Shift | KeyCode.Escape],
	when: ctxReviewPanelVisible,
	handler: closeReviewPanel
});

export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor {
	let editor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
	if (editor instanceof EmbeddedCodeEditorWidget) {
		return editor.getParentEditor();
	}
	return editor;
}

function closeReviewPanel(accessor: ServicesAccessor, args: any) {
	var outerEditor = getOuterEditor(accessor);
	if (!outerEditor) {
		return;
	}

	let controller = ReviewController.get(outerEditor);

	if (!controller) {
		return;
	}

	controller.closeWidget();
724 725 726 727
}


registerThemingParticipant((theme, collector) => {
P
Peng Lyu 已提交
728 729
	let peekViewBackground = theme.getColor(peekViewEditorBackground);
	if (peekViewBackground) {
730 731 732
		collector.addRule(
			`.monaco-editor .review-widget,` +
			`.monaco-editor .review-widget {` +
P
Peng Lyu 已提交
733
			`	background-color: ${peekViewBackground};` +
734 735
			`}`);
	}
P
Peng Lyu 已提交
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753

	let monacoEditorBackground = theme.getColor(editorBackground);
	if (monacoEditorBackground) {
		collector.addRule(
			`.monaco-editor .review-widget .body textarea {` +
			`	background-color: ${monacoEditorBackground}` +
			`}`
		);
	}

	let monacoEditorForeground = theme.getColor(editorForeground);
	if (monacoEditorForeground) {
		collector.addRule(
			`.monaco-editor .review-widget .body textarea {` +
			`	color: ${monacoEditorForeground}` +
			`}`
		);
	}
754
});