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

P
Peng Lyu 已提交
6
import 'vs/css!./media/review';
P
Peng Lyu 已提交
7
import * as nls from 'vs/nls';
8
import { $ } from 'vs/base/browser/dom';
P
Peng Lyu 已提交
9
import { findFirstInSorted } from 'vs/base/common/arrays';
10
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
P
Peng Lyu 已提交
11
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
12
import { ICodeEditor, IEditorMouseEvent, IViewZone, MouseTargetType } from 'vs/editor/browser/editorBrowser';
P
Peng Lyu 已提交
13
import { registerEditorContribution, EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions';
P
Peng Lyu 已提交
14 15
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget';
R
rebornix 已提交
16
import { IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editorCommon';
P
Peng Lyu 已提交
17
import { IRange } from 'vs/editor/common/core/range';
18
import * as modes from 'vs/editor/common/modes';
M
Miguel Solorio 已提交
19
import { peekViewResultsBackground, peekViewResultsSelectionBackground, peekViewTitleBackground } from 'vs/editor/contrib/referenceSearch/referencesWidget';
20
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
P
Peng Lyu 已提交
21
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
22
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
23
import { editorForeground, registerColor } from 'vs/platform/theme/common/colorRegistry';
24
import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
25
import { CommentThreadCollapsibleState } from 'vs/workbench/api/node/extHostTypes';
P
Peng Lyu 已提交
26
import { ReviewZoneWidget, COMMENTEDITOR_DECORATION_KEY } from 'vs/workbench/parts/comments/electron-browser/commentThreadWidget';
27
import { ICommentService, ICommentInfo } from 'vs/workbench/parts/comments/electron-browser/commentService';
P
Peng Lyu 已提交
28 29
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
30
import { IOpenerService } from 'vs/platform/opener/common/opener';
31 32 33 34
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { IModelDecorationOptions } from 'vs/editor/common/model';
import { Color, RGBA } from 'vs/base/common/color';
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
35
import { INotificationService } from 'vs/platform/notification/common/notification';
36
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
37
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
P
Peng Lyu 已提交
38 39

export const ctxReviewPanelVisible = new RawContextKey<boolean>('reviewPanelVisible', false);
40

P
Peng Lyu 已提交
41 42 43 44 45 46 47 48 49 50 51
export const ID = 'editor.contrib.review';

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;

52
		this.domNode = $('.review-viewzone');
P
Peng Lyu 已提交
53 54 55 56 57 58 59
	}

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

P
Peng Lyu 已提交
60
const overviewRulerDefault = new Color(new RGBA(197, 197, 197, 1));
61

62
export const overviewRulerCommentingRangeForeground = registerColor('editorGutter.commentRangeForeground', { dark: overviewRulerDefault, light: overviewRulerDefault, hc: overviewRulerDefault }, nls.localize('editorGutterCommentRangeForeground', 'Editor gutter decoration color for commenting ranges.'));
63

P
Peng Lyu 已提交
64 65 66
class CommentingRangeDecoration {
	private _decorationId: string;

R
rebornix 已提交
67
	public get id(): string {
68 69 70
		return this._decorationId;
	}

71
	constructor(private _editor: ICodeEditor, private _ownerId: string, private _range: IRange, private _reply: modes.Command, commentingOptions: ModelDecorationOptions) {
P
Peng Lyu 已提交
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
		const startLineNumber = _range.startLineNumber;
		const endLineNumber = _range.endLineNumber;
		let commentingRangeDecorations = [{
			range: {
				startLineNumber: startLineNumber, startColumn: 1,
				endLineNumber: endLineNumber, endColumn: 1
			},
			options: commentingOptions
		}];

		let model = this._editor.getModel();
		if (model) {
			this._decorationId = model.deltaDecorations([this._decorationId], commentingRangeDecorations)[0];
		}
	}

88
	public getCommentAction(): { replyCommand: modes.Command, ownerId: string } {
P
Peng Lyu 已提交
89 90 91 92 93 94
		return {
			replyCommand: this._reply,
			ownerId: this._ownerId
		};
	}

R
rebornix 已提交
95
	public getOriginalRange() {
P
Peng Lyu 已提交
96 97 98
		return this._range;
	}

R
rebornix 已提交
99
	public getActiveRange() {
P
Peng Lyu 已提交
100 101 102
		return this._editor.getModel().getDecorationRange(this._decorationId);
	}
}
103 104 105 106 107 108 109
class CommentingRangeDecorator {

	static createDecoration(className: string, foregroundColor: string, options: { gutter: boolean, overview: boolean }): ModelDecorationOptions {
		const decorationOptions: IModelDecorationOptions = {
			isWholeLine: true,
		};

110
		decorationOptions.linesDecorationsClassName = `comment-range-glyph ${className}`;
111 112 113 114
		return ModelDecorationOptions.createDynamic(decorationOptions);
	}

	private commentingOptions: ModelDecorationOptions;
115
	public commentsOptions: ModelDecorationOptions;
P
Peng Lyu 已提交
116
	private commentingRangeDecorations: CommentingRangeDecoration[] = [];
117 118 119 120 121 122
	private disposables: IDisposable[] = [];

	constructor(
	) {
		const options = { gutter: true, overview: false };
		this.commentingOptions = CommentingRangeDecorator.createDecoration('comment-diff-added', overviewRulerCommentingRangeForeground, options);
123
		this.commentsOptions = CommentingRangeDecorator.createDecoration('comment-thread', overviewRulerCommentingRangeForeground, options);
124 125
	}

126
	public update(editor: ICodeEditor, commentInfos: ICommentInfo[]) {
127 128 129 130 131
		let model = editor.getModel();
		if (!model) {
			return;
		}

M
Matt Bierner 已提交
132
		let commentingRangeDecorations: CommentingRangeDecoration[] = [];
P
Peng Lyu 已提交
133 134 135 136 137 138
		for (let i = 0; i < commentInfos.length; i++) {
			let info = commentInfos[i];
			info.commentingRanges.forEach(range => {
				commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, range, info.reply, this.commentingOptions));
			});
		}
139

140 141 142
		let oldDecorations = this.commentingRangeDecorations.map(decoration => decoration.id);
		editor.deltaDecorations(oldDecorations, []);

P
Peng Lyu 已提交
143
		this.commentingRangeDecorations = commentingRangeDecorations;
144 145
	}

R
rebornix 已提交
146
	public getMatchedCommentAction(line: number) {
147
		for (let i = 0; i < this.commentingRangeDecorations.length; i++) {
P
Peng Lyu 已提交
148 149 150 151 152
			let range = this.commentingRangeDecorations[i].getActiveRange();

			if (range.startLineNumber <= line && line <= range.endLineNumber) {
				return this.commentingRangeDecorations[i].getCommentAction();
			}
153 154
		}

P
Peng Lyu 已提交
155
		return null;
156 157
	}

R
rebornix 已提交
158
	public dispose(): void {
159
		this.disposables = dispose(this.disposables);
160
		this.commentingRangeDecorations = [];
161 162 163
	}
}

P
Peng Lyu 已提交
164 165 166 167
export class ReviewController implements IEditorContribution {
	private globalToDispose: IDisposable[];
	private localToDispose: IDisposable[];
	private editor: ICodeEditor;
R
Rachel Macfarlane 已提交
168 169
	private _newCommentWidget: ReviewZoneWidget;
	private _commentWidgets: ReviewZoneWidget[];
P
Peng Lyu 已提交
170
	private _reviewPanelVisible: IContextKey<boolean>;
171
	private _commentInfos: ICommentInfo[];
172 173
	private _commentingRangeDecorator: CommentingRangeDecorator;
	private mouseDownInfo: { lineNumber: number } | null = null;
174
	private _commentingRangeSpaceReserved = false;
175
	private _computePromise: CancelablePromise<ICommentInfo[]> | null;
176

177
	private _pendingCommentCache: { [key: number]: { [key: string]: string } };
178
	private _pendingNewCommentCache: { [key: string]: { lineNumber: number, replyCommand: modes.Command, ownerId: string, pendingComment: string } };
P
Peng Lyu 已提交
179 180 181

	constructor(
		editor: ICodeEditor,
182
		@IContextKeyService contextKeyService: IContextKeyService,
183
		@IThemeService private themeService: IThemeService,
184
		@ICommentService private commentService: ICommentService,
185
		@INotificationService private notificationService: INotificationService,
P
Peng Lyu 已提交
186 187 188 189
		@IInstantiationService private instantiationService: IInstantiationService,
		@IModeService private modeService: IModeService,
		@IModelService private modelService: IModelService,
		@ICodeEditorService private codeEditorService: ICodeEditorService,
190 191
		@IOpenerService private openerService: IOpenerService,
		@IDialogService private dialogService: IDialogService
P
Peng Lyu 已提交
192 193 194 195
	) {
		this.editor = editor;
		this.globalToDispose = [];
		this.localToDispose = [];
196
		this._commentInfos = [];
R
Rachel Macfarlane 已提交
197
		this._commentWidgets = [];
198
		this._pendingCommentCache = {};
R
rebornix 已提交
199
		this._pendingNewCommentCache = {};
R
Rachel Macfarlane 已提交
200
		this._newCommentWidget = null;
201
		this._computePromise = null;
P
Peng Lyu 已提交
202 203

		this._reviewPanelVisible = ctxReviewPanelVisible.bindTo(contextKeyService);
204
		this._commentingRangeDecorator = new CommentingRangeDecorator();
205

206
		this.globalToDispose.push(this.commentService.onDidDeleteDataProvider(ownerId => {
207
			// Remove new comment widget and glyph, refresh comments
208
			if (this._newCommentWidget && this._newCommentWidget.owner === ownerId) {
209 210 211 212
				this._newCommentWidget.dispose();
				this._newCommentWidget = null;
			}

213
			delete this._pendingCommentCache[ownerId];
214
			this.beginCompute();
215
		}));
216
		this.globalToDispose.push(this.commentService.onDidSetDataProvider(_ => this.beginCompute()));
217

218
		this.globalToDispose.push(this.commentService.onDidSetResourceCommentInfos(e => {
219 220
			const editorURI = this.editor && this.editor.getModel() && this.editor.getModel().uri;
			if (editorURI && editorURI.toString() === e.resource.toString()) {
221
				this.setComments(e.commentInfos.filter(commentInfo => commentInfo !== null));
222
			}
P
Peng Lyu 已提交
223 224
		}));

R
rebornix 已提交
225
		this.globalToDispose.push(this.editor.onDidChangeModel(e => this.onModelChanged(e)));
P
Peng Lyu 已提交
226
		this.codeEditorService.registerDecorationType(COMMENTEDITOR_DECORATION_KEY, {});
227
		this.beginCompute();
P
Peng Lyu 已提交
228 229
	}

230 231 232
	private beginCompute(): Promise<void> {
		this._computePromise = createCancelablePromise(token => {
			const editorURI = this.editor && this.editor.getModel() && this.editor.getModel().uri;
233

234 235 236 237 238 239 240 241 242 243 244
			if (editorURI) {
				return this.commentService.getComments(editorURI);
			}

			return Promise.resolve([]);
		});

		return this._computePromise.then(commentInfos => {
			this.setComments(commentInfos.filter(commentInfo => commentInfo !== null));
			this._computePromise = null;
		}, error => console.log(error));
245 246
	}

P
Peng Lyu 已提交
247 248 249 250
	public static get(editor: ICodeEditor): ReviewController {
		return editor.getContribution<ReviewController>(ID);
	}

251
	public revealCommentThread(threadId: string, commentId: string, fetchOnceIfNotExist: boolean): void {
R
Rachel Macfarlane 已提交
252
		const commentThreadWidget = this._commentWidgets.filter(widget => widget.commentThread.threadId === threadId);
253
		if (commentThreadWidget.length === 1) {
P
Peng Lyu 已提交
254
			commentThreadWidget[0].reveal(commentId);
255 256 257 258
		} else if (fetchOnceIfNotExist) {
			this.beginCompute().then(_ => {
				this.revealCommentThread(threadId, commentId, false);
			});
259 260 261
		}
	}

P
Peng Lyu 已提交
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
	public nextCommentThread(): void {
		if (!this._commentWidgets.length) {
			return;
		}

		const after = this.editor.getSelection().getEndPosition();
		const sortedWidgets = this._commentWidgets.sort((a, b) => {
			if (a.commentThread.range.startLineNumber < b.commentThread.range.startLineNumber) {
				return -1;
			}

			if (a.commentThread.range.startLineNumber > b.commentThread.range.startLineNumber) {
				return 1;
			}

			if (a.commentThread.range.startColumn < b.commentThread.range.startColumn) {
				return -1;
			}

			if (a.commentThread.range.startColumn > b.commentThread.range.startColumn) {
				return 1;
			}

			return 0;
		});

		let idx = findFirstInSorted(sortedWidgets, widget => {
			if (widget.commentThread.range.startLineNumber > after.lineNumber) {
				return true;
			}

			if (widget.commentThread.range.startLineNumber < after.lineNumber) {
				return false;
			}

			if (widget.commentThread.range.startColumn > after.column) {
				return true;
			}
			return false;
		});

		if (idx === this._commentWidgets.length) {
			this._commentWidgets[0].reveal();
			this.editor.setSelection(this._commentWidgets[0].commentThread.range);
		} else {
			sortedWidgets[idx].reveal();
			this.editor.setSelection(sortedWidgets[idx].commentThread.range);
		}
	}

R
rebornix 已提交
312
	public getId(): string {
P
Peng Lyu 已提交
313 314
		return ID;
	}
315

R
rebornix 已提交
316
	public dispose(): void {
P
Peng Lyu 已提交
317 318 319
		this.globalToDispose = dispose(this.globalToDispose);
		this.localToDispose = dispose(this.localToDispose);

320 321
		this._commentWidgets.forEach(widget => widget.dispose());

R
Rachel Macfarlane 已提交
322 323 324
		if (this._newCommentWidget) {
			this._newCommentWidget.dispose();
			this._newCommentWidget = null;
P
Peng Lyu 已提交
325 326 327 328
		}
		this.editor = null;
	}

R
rebornix 已提交
329
	public onModelChanged(e: IModelChangedEvent): void {
P
Peng Lyu 已提交
330
		this.localToDispose = dispose(this.localToDispose);
R
Rachel Macfarlane 已提交
331
		if (this._newCommentWidget) {
R
rebornix 已提交
332 333
			let pendingNewComment = this._newCommentWidget.getPendingComment();

334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
			if (e.oldModelUrl) {
				if (pendingNewComment) {
					// we can't fetch zone widget's position as the model is already gone
					const position = this._newCommentWidget.getPosition();
					if (position) {
						this._pendingNewCommentCache[e.oldModelUrl.toString()] = {
							lineNumber: position.lineNumber,
							ownerId: this._newCommentWidget.owner,
							replyCommand: this._newCommentWidget.commentThread.reply,
							pendingComment: pendingNewComment
						};
					}
				} else {
					// clear cache if it is empty
					delete this._pendingNewCommentCache[e.oldModelUrl.toString()];
				}
R
rebornix 已提交
350 351
			}

R
Rachel Macfarlane 已提交
352 353
			this._newCommentWidget.dispose();
			this._newCommentWidget = null;
R
rebornix 已提交
354 355 356 357 358
		} else {
			if (e.oldModelUrl) {
				// remove pending new comment cache as there is no newCommentWidget anymore
				delete this._pendingNewCommentCache[e.oldModelUrl.toString()];
			}
P
Peng Lyu 已提交
359
		}
360

361
		this.removeCommentWidgetsAndStoreCache();
362

R
rebornix 已提交
363 364 365 366 367
		if (e.newModelUrl && this._pendingNewCommentCache[e.newModelUrl.toString()]) {
			let newCommentCache = this._pendingNewCommentCache[e.newModelUrl.toString()];
			this.addComment(newCommentCache.lineNumber, newCommentCache.replyCommand, newCommentCache.ownerId, newCommentCache.pendingComment);
		}

368 369
		this.localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e)));
		this.localToDispose.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e)));
370 371
		this.localToDispose.push(this.editor.onDidChangeModelContent(() => {
		}));
P
Peng Lyu 已提交
372 373 374 375 376
		this.localToDispose.push(this.commentService.onDidUpdateCommentThreads(e => {
			const editorURI = this.editor && this.editor.getModel() && this.editor.getModel().uri;
			if (!editorURI) {
				return;
			}
377

R
rebornix 已提交
378 379
			let commentInfo = this._commentInfos.filter(info => info.owner === e.owner);
			if (!commentInfo || !commentInfo.length) {
380 381 382
				return;
			}

P
Peng Lyu 已提交
383 384 385
			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());
386 387 388 389
			let draftMode = e.draftMode;

			commentInfo.forEach(info => info.draftMode = draftMode);
			this._commentWidgets.filter(ZoneWidget => ZoneWidget.owner === e.owner).forEach(widget => widget.updateDraftMode(draftMode));
P
Peng Lyu 已提交
390 391

			removed.forEach(thread => {
R
Rachel Macfarlane 已提交
392
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
P
Peng Lyu 已提交
393 394
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
R
Rachel Macfarlane 已提交
395 396
					let index = this._commentWidgets.indexOf(matchedZone);
					this._commentWidgets.splice(index, 1);
P
Peng Lyu 已提交
397 398 399 400
				}
			});

			changed.forEach(thread => {
R
Rachel Macfarlane 已提交
401
				let matchedZones = this._commentWidgets.filter(zoneWidget => zoneWidget.owner === e.owner && zoneWidget.commentThread.threadId === thread.threadId);
P
Peng Lyu 已提交
402 403 404 405 406 407
				if (matchedZones.length) {
					let matchedZone = matchedZones[0];
					matchedZone.update(thread);
				}
			});
			added.forEach(thread => {
R
rebornix 已提交
408
				let zoneWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.editor, e.owner, thread, null, draftMode, {});
409
				zoneWidget.display(thread.range.startLineNumber, this._commentingRangeDecorator.commentsOptions);
R
Rachel Macfarlane 已提交
410
				this._commentWidgets.push(zoneWidget);
411
				this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread);
P
Peng Lyu 已提交
412
			});
413

P
Peng Lyu 已提交
414
		}));
415 416

		this.beginCompute();
P
Peng Lyu 已提交
417 418
	}

419
	private addComment(lineNumber: number, replyCommand: modes.Command, ownerId: string, pendingComment: string) {
420 421 422 423 424
		if (this._newCommentWidget !== null) {
			this.notificationService.warn(`Please submit the comment at line ${this._newCommentWidget.position.lineNumber} before creating a new one.`);
			return;
		}

R
rebornix 已提交
425 426 427 428 429 430 431
		let commentInfo = this._commentInfos.filter(info => info.owner === ownerId);
		if (!commentInfo || !commentInfo.length) {
			return;
		}

		let draftMode = commentInfo[0].draftMode;

432 433
		// add new comment
		this._reviewPanelVisible.set(true);
434
		this._newCommentWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.editor, ownerId, {
435 436 437 438 439 440 441 442 443
			threadId: null,
			resource: null,
			comments: [],
			range: {
				startLineNumber: lineNumber,
				startColumn: 0,
				endLineNumber: lineNumber,
				endColumn: 0
			},
444
			reply: replyCommand,
445
			collapsibleState: CommentThreadCollapsibleState.Expanded,
R
rebornix 已提交
446
		}, pendingComment, draftMode, {});
447

448
		this.localToDispose.push(this._newCommentWidget.onDidClose(e => {
R
Rachel Macfarlane 已提交
449
			this._newCommentWidget = null;
450 451 452 453 454 455 456 457 458
		}));

		this.localToDispose.push(this._newCommentWidget.onDidCreateThread(commentWidget => {
			const thread = commentWidget.commentThread;
			this._commentWidgets.push(commentWidget);
			this._commentInfos.filter(info => info.owner === commentWidget.owner)[0].threads.push(thread);
			this._newCommentWidget = null;
		}));

459
		this._newCommentWidget.display(lineNumber, this._commentingRangeDecorator.commentsOptions);
P
Peng Lyu 已提交
460 461
	}

462 463 464 465 466 467
	private onEditorMouseDown(e: IEditorMouseEvent): void {
		this.mouseDownInfo = null;

		const range = e.target.range;

		if (!range) {
468 469 470
			return;
		}

471 472 473 474 475 476 477 478 479 480 481
		if (!e.event.leftButton) {
			return;
		}

		if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
			return;
		}

		const data = e.target.detail as IMarginData;
		const gutterOffsetX = data.offsetX - data.glyphMarginWidth - data.lineNumbersWidth - data.glyphMarginLeft;

482
		// don't collide with folding and git decorations
483
		if (gutterOffsetX > 14) {
484 485 486 487 488 489 490 491 492 493
			return;
		}

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

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

495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
		const { lineNumber } = this.mouseDownInfo;
		this.mouseDownInfo = null;

		const range = e.target.range;

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

		if (e.target.type !== MouseTargetType.GUTTER_LINE_DECORATIONS) {
			return;
		}

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

512
		if (e.target.element.className.indexOf('comment-diff-added') >= 0) {
R
Rachel Macfarlane 已提交
513
			const lineNumber = e.target.position.lineNumber;
R
rebornix 已提交
514 515 516 517 518 519
			let newCommentInfo = this._commentingRangeDecorator.getMatchedCommentAction(lineNumber);
			if (!newCommentInfo) {
				return;
			}
			const { replyCommand, ownerId } = newCommentInfo;
			this.addComment(lineNumber, replyCommand, ownerId, null);
520
		}
P
Peng Lyu 已提交
521 522
	}

R
Rachel Macfarlane 已提交
523

524
	private setComments(commentInfos: ICommentInfo[]): void {
R
Rachel Macfarlane 已提交
525 526 527 528
		if (!this.editor) {
			return;
		}

529
		this._commentInfos = commentInfos;
530 531 532 533 534
		let lineDecorationsWidth: number = this.editor.getConfiguration().layoutInfo.decorationsWidth;

		if (this._commentInfos.some(info => Boolean(info.commentingRanges && info.commentingRanges.length))) {
			if (!this._commentingRangeSpaceReserved) {
				this._commentingRangeSpaceReserved = true;
M
Matt Bierner 已提交
535
				let extraEditorClassName: string[] = [];
536 537 538 539 540 541 542 543 544 545 546 547 548
				if (this.editor.getRawConfiguration().extraEditorClassName) {
					extraEditorClassName = this.editor.getRawConfiguration().extraEditorClassName.split(' ');
				}

				if (this.editor.getConfiguration().contribInfo.folding) {
					lineDecorationsWidth -= 16;
				}
				lineDecorationsWidth += 9;
				extraEditorClassName.push('inline-comment');
				this.editor.updateOptions({
					extraEditorClassName: extraEditorClassName.join(' '),
					lineDecorationsWidth: lineDecorationsWidth
				});
549 550 551 552 553 554 555 556

				// we only update the lineDecorationsWidth property but keep the width of the whole editor.
				const originalLayoutInfo = this.editor.getLayoutInfo();

				this.editor.layout({
					width: originalLayoutInfo.width,
					height: originalLayoutInfo.height
				});
557 558 559
			}
		}

560
		// create viewzones
561
		this.removeCommentWidgetsAndStoreCache();
562

563
		this._commentInfos.forEach(info => {
564
			let providerCacheStore = this._pendingCommentCache[info.owner];
565
			info.threads.forEach(thread => {
566 567 568
				let pendingComment: string = null;
				if (providerCacheStore) {
					pendingComment = providerCacheStore[thread.threadId];
R
rebornix 已提交
569 570 571
				}

				if (pendingComment) {
572
					thread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded;
573
				}
R
rebornix 已提交
574

R
rebornix 已提交
575
				let zoneWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.editor, info.owner, thread, pendingComment, info.draftMode, {});
576
				zoneWidget.display(thread.range.startLineNumber, this._commentingRangeDecorator.commentsOptions);
R
Rachel Macfarlane 已提交
577
				this._commentWidgets.push(zoneWidget);
578 579
			});
		});
580

M
Matt Bierner 已提交
581
		const commentingRanges: IRange[] = [];
582 583 584
		this._commentInfos.forEach(info => {
			commentingRanges.push(...info.commentingRanges);
		});
P
Peng Lyu 已提交
585
		this._commentingRangeDecorator.update(this.editor, this._commentInfos);
M
Matt Bierner 已提交
586 587
	}

P
Peng Lyu 已提交
588 589 590
	public closeWidget(): void {
		this._reviewPanelVisible.reset();

R
Rachel Macfarlane 已提交
591 592 593
		if (this._newCommentWidget) {
			this._newCommentWidget.dispose();
			this._newCommentWidget = null;
P
Peng Lyu 已提交
594 595
		}

R
Rachel Macfarlane 已提交
596
		if (this._commentWidgets) {
597
			this._commentWidgets.forEach(widget => widget.hide());
598 599
		}

P
Peng Lyu 已提交
600
		this.editor.focus();
601
		this.editor.revealRangeInCenter(this.editor.getSelection());
P
Peng Lyu 已提交
602
	}
603

R
rebornix 已提交
604
	private removeCommentWidgetsAndStoreCache() {
605 606 607
		if (this._commentWidgets) {
			this._commentWidgets.forEach(zone => {
				let pendingComment = zone.getPendingComment();
R
rebornix 已提交
608
				let providerCacheStore = this._pendingCommentCache[zone.owner];
609

R
rebornix 已提交
610
				if (pendingComment) {
611 612 613 614
					if (!providerCacheStore) {
						this._pendingCommentCache[zone.owner] = {};
					}

R
rebornix 已提交
615 616 617 618 619
					this._pendingCommentCache[zone.owner][zone.commentThread.threadId] = pendingComment;
				} else {
					if (providerCacheStore) {
						delete providerCacheStore[zone.commentThread.threadId];
					}
620 621 622 623 624 625 626 627
				}

				zone.dispose();
			});
		}

		this._commentWidgets = [];
	}
P
Peng Lyu 已提交
628 629
}

P
Peng Lyu 已提交
630
export class NextCommentThreadAction extends EditorAction {
P
Peng Lyu 已提交
631

P
Peng Lyu 已提交
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
	constructor() {
		super({
			id: 'editor.action.nextCommentThreadAction',
			label: nls.localize('nextCommentThreadAction', "Go to Next Comment Thread"),
			alias: 'Go to Next Comment Thread',
			precondition: null,
		});
	}

	public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
		let controller = ReviewController.get(editor);
		if (controller) {
			controller.nextCommentThread();
		}
	}
}

registerEditorContribution(ReviewController);
registerEditorAction(NextCommentThreadAction);
P
Peng Lyu 已提交
651 652 653

KeybindingsRegistry.registerCommandAndKeybindingRule({
	id: 'closeReviewPanel',
654
	weight: KeybindingWeight.EditorContrib,
P
Peng Lyu 已提交
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
	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();
682 683 684 685
}


registerThemingParticipant((theme, collector) => {
M
Matt Bierner 已提交
686
	const peekViewBackground = theme.getColor(peekViewResultsBackground);
P
Peng Lyu 已提交
687
	if (peekViewBackground) {
688 689 690
		collector.addRule(
			`.monaco-editor .review-widget,` +
			`.monaco-editor .review-widget {` +
P
Peng Lyu 已提交
691
			`	background-color: ${peekViewBackground};` +
692 693
			`}`);
	}
P
Peng Lyu 已提交
694

M
Matt Bierner 已提交
695
	const monacoEditorBackground = theme.getColor(peekViewTitleBackground);
P
Peng Lyu 已提交
696 697
	if (monacoEditorBackground) {
		collector.addRule(
P
Peng Lyu 已提交
698 699
			`.monaco-editor .review-widget .body .comment-form .review-thread-reply-button {` +
			`	background-color: ${monacoEditorBackground}` +
P
Peng Lyu 已提交
700 701 702 703
			`}`
		);
	}

M
Matt Bierner 已提交
704
	const monacoEditorForeground = theme.getColor(editorForeground);
P
Peng Lyu 已提交
705 706
	if (monacoEditorForeground) {
		collector.addRule(
P
Peng Lyu 已提交
707
			`.monaco-editor .review-widget .body .monaco-editor {` +
P
Peng Lyu 已提交
708
			`	color: ${monacoEditorForeground}` +
P
Peng Lyu 已提交
709 710 711
			`}` +
			`.monaco-editor .review-widget .body .comment-form .review-thread-reply-button {` +
			`	color: ${monacoEditorForeground}` +
P
Peng Lyu 已提交
712 713 714
			`}`
		);
	}
P
Peng Lyu 已提交
715

M
Matt Bierner 已提交
716
	const selectionBackground = theme.getColor(peekViewResultsSelectionBackground);
P
Peng Lyu 已提交
717 718 719 720 721 722 723 724 725 726 727 728

	if (selectionBackground) {
		collector.addRule(
			`@keyframes monaco-review-widget-focus {` +
			`	0% { background: ${selectionBackground}; }` +
			`	100% { background: transparent; }` +
			`}` +
			`.monaco-editor .review-widget .body .review-comment.focus {` +
			`	animation: monaco-review-widget-focus 3s ease 0s;` +
			`}`
		);
	}
729 730 731 732 733 734 735 736 737 738

	const commentingRangeForeground = theme.getColor(overviewRulerCommentingRangeForeground);
	if (commentingRangeForeground) {
		collector.addRule(`
			.monaco-editor .comment-diff-added {
				border-left: 3px solid ${commentingRangeForeground};
			}
			.monaco-editor .comment-diff-added:before {
				background: ${commentingRangeForeground};
			}
739 740 741 742 743 744
			.monaco-editor .comment-thread {
				border-left: 3px solid ${commentingRangeForeground};
			}
			.monaco-editor .comment-thread:before {
				background: ${commentingRangeForeground};
			}
745 746
		`);
	}
747
});