Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(295)

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/changes/ChangesView.js

Issue 2694923002: DevTools: Changes Drawer (Closed)
Patch Set: Remove event listener on willHide Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 Changes.ChangesView = class extends UI.VBox {
6 constructor() {
7 super(true);
8 Changes.ChangesView.SharedInstance = this;
9 this.registerRequiredCSS('changes/changesView.css');
10 var splitWidget = new UI.SplitWidget(true, false);
11 var mainWidget = new UI.Widget();
12 splitWidget.setMainWidget(mainWidget);
13
14 /** @type {?Workspace.UISourceCode} */
15 this._uiSourceCode = null;
16
17 /** @type {!Array<!Changes.ChangesView.Row>} */
18 this._rows = [];
19
20 this._maxLineDigits = 1;
21
22 var code = mainWidget.element.createChild('div', 'code');
23 this._editor =
24 new TextEditor.CodeMirrorTextEditor({lineNumbers: true, lineWrapping: fa lse, maxHighlightLength: Infinity});
25 this._editor.setReadOnly(true);
26 this._editor.show(code);
27 this._editor.hideWidget();
28 this._editor.setLineNumberFormatter(() => '');
29
30 this._emptyWidget = new UI.EmptyWidget(Common.UIString('No changes'));
31 this._emptyWidget.show(code);
32
33 this._editor.element.addEventListener('mouseup', e => {
34 var selection = this._editor.selection();
35 if (!selection.isEmpty())
36 return;
37 var row = this._rows[selection.startLine];
38 if (!row)
39 return;
40
41 setTimeout(
42 () => Common.Revealer.reveal(this._uiSourceCode.uiLocation(row.current - 1, selection.startColumn), false));
43 }, false);
44
45 this._toolbar = new UI.Toolbar('changes-toolbar', mainWidget.element);
46 var revertButton = new UI.ToolbarButton(Common.UIString('Revert all changes' ), 'largeicon-undo');
47 revertButton.addEventListener(UI.ToolbarButton.Events.Click, this._revert.bi nd(this));
48 this._toolbar.appendToolbarItem(revertButton);
49 this._diffStats = new UI.ToolbarText('');
50 this._toolbar.appendToolbarItem(this._diffStats);
51 this._toolbar.setEnabled(false);
52
53 this._navigator = new Changes.ChangesNavigator();
54 splitWidget.setSidebarWidget(this._navigator);
55 splitWidget.show(this.contentElement);
56
57 this._workspaceDiff = WorkspaceDiff.workspaceDiff();
58 Workspace.workspace.addEventListener(
59 Workspace.Workspace.Events.WorkingCopyCommittedByUser, this._refreshUISo urceCode, this);
60 Workspace.workspace.addEventListener(
61 Workspace.Workspace.Events.WorkingCopyChanged, this._refreshUISourceCode , this);
62 }
63
64 /**
65 * @param {!Workspace.UISourceCode} uiSourceCode
66 */
67 static showChanges(uiSourceCode) {
68 UI.viewManager.showView('changes.changes');
69 var changesView =
70 /** @type {!Changes.ChangesView} */ (self.runtime.sharedInstance(Changes .ChangesView));
71 changesView._revealUISourceCode(uiSourceCode);
72 }
73
74 _revert() {
75 var uiSourceCode = this._uiSourceCode;
76 if (!uiSourceCode)
77 return;
78 uiSourceCode.requestOriginalContent().then(original => uiSourceCode.addRevis ion(original || ''));
79 }
80
81 /**
82 * @param {!Common.Event} event
83 */
84 _refreshUISourceCode(event) {
85 var uiSourceCode = /** @type !Workspace.UISourceCode */ (event.data.uiSource Code);
86 this._navigator.refreshUISourceCode(uiSourceCode);
87 }
88
89 _refreshEmpty(empty) {
90 if (empty) {
91 this._diffStats.setText('');
92 this._toolbar.setEnabled(false);
93 this._editor.hideWidget();
94 this._emptyWidget.showWidget();
95 } else {
96 this._toolbar.setEnabled(true);
97 this._emptyWidget.hideWidget();
98 this._editor.showWidget();
99 }
100 }
101
102 /**
103 * @param {?Workspace.UISourceCode} uiSourceCode
104 * @param {boolean=} refresh
105 */
106 _revealUISourceCode(uiSourceCode, refresh) {
107 if (this._uiSourceCode === uiSourceCode && !refresh)
108 return;
109
110 if (this._uiSourceCode)
111 this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._re freshDiff, this);
112
113 this._diffStats.setText('');
114 this._editor.setText('');
115 this._uiSourceCode = uiSourceCode;
116 this._rows = [];
117 this._refreshEmpty(!uiSourceCode);
118
119 if (!uiSourceCode || !this.isShowing())
120 return;
121
122 this._workspaceDiff.subscribeToDiffChange(uiSourceCode, this._refreshDiff, t his);
123 this._navigator.revealUISourceCode(uiSourceCode, true);
124 this._refreshDiff();
125 }
126
127 /**
128 * @override
129 */
130 wasShown() {
131 this._revealUISourceCode(this._uiSourceCode, true);
132 }
133
134 /**
135 * @override
136 */
137 willHide() {
138 if (this._uiSourceCode)
139 this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._re freshDiff, this);
140 }
141
142 _refreshDiff() {
143 if (!this._uiSourceCode)
144 return;
145 var uiSourceCode = this._uiSourceCode;
146 this._workspaceDiff.requestDiff(uiSourceCode).then(diff => {
147 if (this._uiSourceCode !== uiSourceCode)
148 return;
149 this._renderRows(diff);
150 });
151 }
152
153 /**
154 * @param {?Diff.Diff.DiffArray} diff
155 */
156 _renderRows(diff) {
157 if (!diff || (diff.length === 1 && diff[0][0] === Diff.Diff.Operation.Equal) ) {
158 this._refreshEmpty(true);
159 return;
160 }
161 this._refreshEmpty(false);
162
163 var insertions = 0;
164 var deletions = 0;
165 var currentLineNumber = 0;
166 var baselineLineNumber = 0;
167 var paddingLines = 3;
168 var originalLines = [];
169 var currentLines = [];
170 this._rows = [];
171
172 for (var i = 0; i < diff.length; ++i) {
173 var token = diff[i];
174 switch (token[0]) {
175 case Diff.Diff.Operation.Equal:
176 this._rows.pushAll(createEqualRows(token[1], i === 0, i === diff.lengt h - 1));
177 originalLines.pushAll(token[1]);
178 currentLines.pushAll(token[1]);
179 break;
180 case Diff.Diff.Operation.Insert:
181 for (var line of token[1])
182 this._rows.push(createRow(line, 'addition'));
183 currentLines.pushAll(token[1]);
184 break;
185 case Diff.Diff.Operation.Delete:
186 originalLines.pushAll(token[1]);
187 if (diff[i + 1] && diff[i + 1][0] === Diff.Diff.Operation.Insert) {
188 this._rows.pushAll(createModifyRows(token[1].join('\n'), diff[i + 1] [1].join('\n')));
189 i++;
190 currentLines.pushAll(diff[i + 1][1]);
191 } else {
192 for (var line of token[1])
193 this._rows.push(createRow(line, 'deletion'));
194 }
195 break;
196 }
197 }
198
199 this._maxLineDigits = Math.max(Math.ceil(Math.log10(Math.max(currentLineNumb er, baselineLineNumber))), 1);
200
201 this._editor.operation(() => {
202 this._editor.setHighlightMode({
203 name: 'devtools-diff',
204 rows: this._rows,
205 mimeType:
206 Bindings.NetworkProject.uiSourceCodeMimeType(/** @type {!Workspace.U ISourceCode} */ (this._uiSourceCode)),
207 baselineLines: originalLines,
208 currentLines: currentLines
209 });
210 this._editor.setText(this._rows.map(row => row.content.map(t => t.text).jo in('')).join('\n'));
211 this._editor.setLineNumberFormatter(this._lineFormatter.bind(this));
212 });
213
214 this._diffStats.setText(Common.UIString(
215 '%d insertion%s (+), %d deletion%s (-)', insertions, insertions > 1 ? 's ' : '', deletions,
216 deletions > 1 ? 's' : ''));
217
218 /**
219 * @param {!Array<string>} lines
220 * @param {boolean} atStart
221 * @param {boolean} atEnd
222 * @return {!Array<!Changes.ChangesView.Row>}}
223 */
224 function createEqualRows(lines, atStart, atEnd) {
225 var equalRows = [];
226 if (!atStart) {
227 for (var i = 0; i < paddingLines && i < lines.length; i++)
228 equalRows.push(createRow(lines[i], 'equal'));
229 if (lines.length > paddingLines * 2 + 1 && !atEnd) {
230 equalRows.push(createRow(
231 Common.UIString('( \u2026 Skipping ') + (lines.length - paddingLin es * 2) +
232 Common.UIString(' matching lines \u2026 )'),
233 'spacer'));
234 }
235 }
236 if (!atEnd) {
237 var start = Math.max(lines.length - paddingLines - 1, atStart ? 0 : padd ingLines);
238 var skip = lines.length - paddingLines - 1;
239 if (!atStart)
240 skip -= paddingLines;
241 if (skip > 0) {
242 baselineLineNumber += skip;
243 currentLineNumber += skip;
244 }
245
246 for (var i = start; i < lines.length; i++)
247 equalRows.push(createRow(lines[i], 'equal'));
248 }
249 return equalRows;
250 }
251
252 /**
253 * @param {string} before
254 * @param {string} after
255 * @return {!Array<!Changes.ChangesView.Row>}}
256 */
257 function createModifyRows(before, after) {
258 var internalDiff = Diff.Diff.charDiff(before, after, true);
259 var deletionRows = [createRow('', 'deletion')];
260 var insertionRows = [createRow('', 'addition')];
261
262 for (var token of internalDiff) {
263 var text = token[1];
264 var type = token[0];
265 var className = type === Diff.Diff.Operation.Equal ? '' : 'double';
266 var first = true;
267 for (var line of text.split('\n')) {
268 if (first) {
269 first = false;
270 } else {
271 if (type !== Diff.Diff.Operation.Insert)
272 deletionRows.push(createRow('', 'deletion'));
273 if (type !== Diff.Diff.Operation.Delete)
274 insertionRows.push(createRow('', 'addition'));
275 }
276 if (line) {
277 if (type !== Diff.Diff.Operation.Insert)
278 deletionRows[deletionRows.length - 1].content.push({text: line, cl assName: className});
279
280 if (type !== Diff.Diff.Operation.Delete)
281 insertionRows[insertionRows.length - 1].content.push({text: line, className: className});
282 }
283 }
284 }
285 return deletionRows.concat(insertionRows);
286 }
287
288 /**
289 * @param {string} text
290 * @param {string} className
291 * @return {!Changes.ChangesView.Row}
292 */
293 function createRow(text, className) {
294 if (className === 'addition') {
295 currentLineNumber++;
296 insertions++;
297 }
298 if (className === 'deletion') {
299 baselineLineNumber++;
300 deletions++;
301 }
302 if (className === 'equal') {
303 baselineLineNumber++;
304 currentLineNumber++;
305 }
306 return {
307 base: baselineLineNumber,
308 current: currentLineNumber,
309 content: text ? [{text: text, className: 'double'}] : [],
310 className: className,
311 loc: currentLineNumber
312 };
313 }
314 }
315
316 /**
317 * @param {number} lineNumber
318 * @return {string}
319 */
320 _lineFormatter(lineNumber) {
321 var row = this._rows[lineNumber - 1];
322 if (!row)
323 return spacesPadding(this._maxLineDigits * 2 + 1);
324 var showBaseNumber = row.className === 'deletion';
325 var showCurrentNumber = row.className === 'addition';
326 if (row.className === 'equal') {
327 showBaseNumber = true;
328 showCurrentNumber = true;
329 }
330 var base = showBaseNumber ? numberToStringWithSpacesPadding(row.base, this._ maxLineDigits) :
331 spacesPadding(this._maxLineDigits);
332 var current = showCurrentNumber ? numberToStringWithSpacesPadding(row.curren t, this._maxLineDigits) :
333 spacesPadding(this._maxLineDigits);
334 return base + spacesPadding(1) + current;
335 }
336
337 /**
338 * @param {number} baselineLineNumber
339 * @param {number} currentLineNumber
340 * @param {string} text
341 * @param {string=} className
342 * @return {!Element}
343 */
344 _createRow(baselineLineNumber, currentLineNumber, text, className) {
345 var element = createElementWithClass('div', className);
346 element.createChild('span', 'number').textContent = baselineLineNumber ?
347 numberToStringWithSpacesPadding(baselineLineNumber, this._maxLineDigits) :
348 spacesPadding(this._maxLineDigits);
349 element.createChild('span', 'number').textContent = currentLineNumber ?
350 numberToStringWithSpacesPadding(currentLineNumber, this._maxLineDigits) :
351 spacesPadding(this._maxLineDigits);
352 element.createChild('span', 'double').textContent = text;
353 return element;
354 }
355 };
356
357 /** @typedef {!{base: number, current: number, content: !Array<!{text: string, c lassName: string}>, className: string, loc: number}} */
358 Changes.ChangesView.Row;
359
360 Changes.ChangesNavigator = class extends Sources.NavigatorView {
361 constructor() {
362 super();
363 this.dontGroup();
364 this.element.classList.add('changes-navigator');
365 }
366
367 /**
368 * @override
369 * @param {!Workspace.UISourceCode} uiSourceCode
370 * @return {boolean}
371 */
372 accept(uiSourceCode) {
373 if (uiSourceCode.project().type() !== Workspace.projectTypes.Network)
374 return false;
375
376 if (uiSourceCode.isDirty())
377 return true;
378
379 if (!uiSourceCode.mightHaveChanges)
380 return false;
381
382 uiSourceCode.hasChanges().then(hasChanges => {
383 if (!hasChanges)
384 this.refreshUISourceCode(uiSourceCode);
385 });
386 return true;
387 }
388
389 /**
390 * @override
391 * @param {?Workspace.UISourceCode} uiSourceCode
392 * @param {boolean} focusSource
393 */
394 revealSource(uiSourceCode, focusSource) {
395 Changes.ChangesView.SharedInstance._revealUISourceCode(uiSourceCode);
396 if (focusSource)
397 Changes.ChangesView.SharedInstance._editor.focus();
398 }
399
400 /**
401 * @override
402 * @param {!Workspace.UISourceCode} uiSourceCode
403 */
404 uiSourceCodeAdded(uiSourceCode) {
405 if (!Changes.ChangesView.SharedInstance._uiSourceCode)
406 this.revealSource(uiSourceCode, false);
407 }
408
409 /**
410 * @override
411 * @param {!Workspace.UISourceCode} uiSourceCode
412 */
413 uiSourceCodeRemoved(uiSourceCode) {
414 if (Changes.ChangesView.SharedInstance._uiSourceCode !== uiSourceCode)
415 return;
416 this.revealSource(this.anyUISourceCode(), false);
417 }
418 };
419
420 Changes.UISourceCodeChange = class {
421 /**
422 * @param {!Workspace.UISourceCode} uiSourceCode
423 */
424 constructor(uiSourceCode) {
425 this.uiSourceCode = uiSourceCode;
426 }
427 };
428
429 /**
430 * @implements {Common.Revealer}
431 */
432 Changes.UISourceCodeChangeRevealer = class {
433 /**
434 * @override
435 * @param {!Object} uiSourceCodeChange
436 * @param {boolean=} omitFocus
437 * @return {!Promise}
438 */
439 reveal(uiSourceCodeChange, omitFocus) {
440 if (!(uiSourceCodeChange instanceof Changes.UISourceCodeChange))
441 return Promise.reject(new Error('Changes.UISourceCodeChange'));
442 Changes.ChangesView.showChanges(uiSourceCodeChange.uiSourceCode);
443 return Promise.resolve();
444 }
445 };
446
447 /** @typedef {!{lineNumber: number, index: number}} */
448 Changes.ChangesView.DiffState;
449
450 CodeMirror.defineMode('devtools-diff', function(config, parserConfig) {
451 var rows = parserConfig.rows || [];
452 var baselineLines = parserConfig.baselineLines;
453 var currentLines = parserConfig.currentLines;
454 var innerMode = CodeMirror.getMode({indentUnit: 2}, parserConfig.mimeType);
455
456 function fastForward(state, baselineLineNumber, currentLineNumber) {
457 if (baselineLineNumber > state.baselineLineNumber) {
458 fastForwardState(state.baselineState, state.baselineLineNumber, baselineLi neNumber, baselineLines);
459 state.baselineLineNumber = baselineLineNumber;
460 }
461 if (currentLineNumber > state.currentLineNumber) {
462 fastForwardState(state.currentState, state.currentLineNumber, currentLineN umber, currentLines);
463 state.currentLineNumber = currentLineNumber;
464 }
465 }
466
467 function fastForwardState(state, from, to, lines) {
468 var lineNumber = from;
469 while (lineNumber < to && lineNumber < lines.length) {
470 var stream = new CodeMirror.StringStream(lines[lineNumber]);
471 while (!stream.eol()) {
472 innerMode.token(stream, state);
473 stream.start = stream.pos;
474 }
475 lineNumber++;
476 }
477 }
478
479 return {
480 /**
481 * @return {!Changes.ChangesView.DiffState}
482 */
483 startState: function() {
484 return {
485 lineNumber: 0,
486 index: 0,
487 currentLineNumber: 0,
488 baselineLineNumber: 0,
489 currentState: CodeMirror.startState(innerMode),
490 baselineState: CodeMirror.startState(innerMode),
491 mismatch: 0
492 };
493 },
494
495 /**
496 * @param {!{next: function()}} stream
497 * @param {!Changes.ChangesView.DiffState} state
498 * @return {string}
499 */
500 token: function(stream, state) {
501 var row = rows[state.lineNumber];
502 if (!row) {
503 stream.next();
504 return '';
505 }
506 fastForward(state, row.base - 1, row.current - 1);
507 var classes = '';
508 if (state.index === 0)
509 classes += ' line-background-' + row.className + ' line-' + row.classNam e;
510 var chars = row.content[state.index].text.length;
511 if (state.mismatch > 0)
512 chars = state.mismatch;
513 var pos = stream.pos;
514 var innerStyle = state.lastInnerStyle;
515 var innerPos = pos - state.mismatch;
516 if (state.mismatch >= 0) {
517 if (row.className === 'deletion' || row.className === 'addition' || row. className === 'equal') {
518 innerStyle = innerMode.token(stream, row.className === 'deletion' ? st ate.baselineState : state.currentState);
519 innerPos = stream.pos;
520 } else {
521 innerStyle = '';
522 innerPos = pos + chars;
523 }
524 }
525 stream.pos = Math.min(innerPos, pos + chars);
526 if (innerStyle)
527 classes += ' ' + innerStyle;
528 classes += ' ' + row.content[state.index].className;
529 state.mismatch = pos + chars - innerPos;
530 if (state.mismatch <= 0) {
531 state.index++;
532 state.lastInnerStyle = innerStyle;
533 }
534 if (state.index >= row.content.length) {
535 state.lineNumber++;
536 if (row.className === 'deletion')
537 state.baselineLineNumber++;
538 else
539 state.currentLineNumber++;
540 state.index = 0;
541 }
542 return classes;
543 },
544
545 /**
546 * @param {!Changes.ChangesView.DiffState} state
547 * @return {string}
548 */
549 blankLine: function(state) {
550 var row = rows[state.lineNumber];
551 state.lineNumber++;
552 state.mismatch = 0;
553 state.index = 0;
554 if (!row)
555 return '';
556 if (row.className === 'deletion')
557 state.baselineLineNumber++;
558 else
559 state.currentLineNumber++;
560 var style = '';
561 if (innerMode.blankLine)
562 style = innerMode.blankLine(row.className === 'deletion' ? state.baselin eState : state.currentState);
563 return style + ' line-background-' + row.className + ' line-' + row.classN ame;
564 },
565
566 /**
567 * @param {!Changes.ChangesView.DiffState} state
568 * @return {!Changes.ChangesView.DiffState}
569 */
570 copyState: function(state) {
571 var newState = /** @type {!Changes.ChangesView.DiffState} */ ({});
572 for (var i in state)
573 newState[i] = state[i];
574 newState.currentState = CodeMirror.copyState(innerMode, state.currentState );
575 newState.baselineState = CodeMirror.copyState(innerMode, state.baselineSta te);
576 return newState;
577 }
578 };
579 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698