| Index: third_party/WebKit/Source/devtools/front_end/changes/ChangesView.js
|
| diff --git a/third_party/WebKit/Source/devtools/front_end/changes/ChangesView.js b/third_party/WebKit/Source/devtools/front_end/changes/ChangesView.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9e1770ef2caeb69e68d4baf35c3530502bc123de
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/devtools/front_end/changes/ChangesView.js
|
| @@ -0,0 +1,579 @@
|
| +// Copyright 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +Changes.ChangesView = class extends UI.VBox {
|
| + constructor() {
|
| + super(true);
|
| + Changes.ChangesView.SharedInstance = this;
|
| + this.registerRequiredCSS('changes/changesView.css');
|
| + var splitWidget = new UI.SplitWidget(true, false);
|
| + var mainWidget = new UI.Widget();
|
| + splitWidget.setMainWidget(mainWidget);
|
| +
|
| + /** @type {?Workspace.UISourceCode} */
|
| + this._uiSourceCode = null;
|
| +
|
| + /** @type {!Array<!Changes.ChangesView.Row>} */
|
| + this._rows = [];
|
| +
|
| + this._maxLineDigits = 1;
|
| +
|
| + var code = mainWidget.element.createChild('div', 'code');
|
| + this._editor =
|
| + new TextEditor.CodeMirrorTextEditor({lineNumbers: true, lineWrapping: false, maxHighlightLength: Infinity});
|
| + this._editor.setReadOnly(true);
|
| + this._editor.show(code);
|
| + this._editor.hideWidget();
|
| + this._editor.setLineNumberFormatter(() => '');
|
| +
|
| + this._emptyWidget = new UI.EmptyWidget(Common.UIString('No changes'));
|
| + this._emptyWidget.show(code);
|
| +
|
| + this._editor.element.addEventListener('mouseup', e => {
|
| + var selection = this._editor.selection();
|
| + if (!selection.isEmpty())
|
| + return;
|
| + var row = this._rows[selection.startLine];
|
| + if (!row)
|
| + return;
|
| +
|
| + setTimeout(
|
| + () => Common.Revealer.reveal(this._uiSourceCode.uiLocation(row.current - 1, selection.startColumn), false));
|
| + }, false);
|
| +
|
| + this._toolbar = new UI.Toolbar('changes-toolbar', mainWidget.element);
|
| + var revertButton = new UI.ToolbarButton(Common.UIString('Revert all changes'), 'largeicon-undo');
|
| + revertButton.addEventListener(UI.ToolbarButton.Events.Click, this._revert.bind(this));
|
| + this._toolbar.appendToolbarItem(revertButton);
|
| + this._diffStats = new UI.ToolbarText('');
|
| + this._toolbar.appendToolbarItem(this._diffStats);
|
| + this._toolbar.setEnabled(false);
|
| +
|
| + this._navigator = new Changes.ChangesNavigator();
|
| + splitWidget.setSidebarWidget(this._navigator);
|
| + splitWidget.show(this.contentElement);
|
| +
|
| + this._workspaceDiff = WorkspaceDiff.workspaceDiff();
|
| + Workspace.workspace.addEventListener(
|
| + Workspace.Workspace.Events.WorkingCopyCommittedByUser, this._refreshUISourceCode, this);
|
| + Workspace.workspace.addEventListener(
|
| + Workspace.Workspace.Events.WorkingCopyChanged, this._refreshUISourceCode, this);
|
| + }
|
| +
|
| + /**
|
| + * @param {!Workspace.UISourceCode} uiSourceCode
|
| + */
|
| + static showChanges(uiSourceCode) {
|
| + UI.viewManager.showView('changes.changes');
|
| + var changesView =
|
| + /** @type {!Changes.ChangesView} */ (self.runtime.sharedInstance(Changes.ChangesView));
|
| + changesView._revealUISourceCode(uiSourceCode);
|
| + }
|
| +
|
| + _revert() {
|
| + var uiSourceCode = this._uiSourceCode;
|
| + if (!uiSourceCode)
|
| + return;
|
| + uiSourceCode.requestOriginalContent().then(original => uiSourceCode.addRevision(original || ''));
|
| + }
|
| +
|
| + /**
|
| + * @param {!Common.Event} event
|
| + */
|
| + _refreshUISourceCode(event) {
|
| + var uiSourceCode = /** @type !Workspace.UISourceCode */ (event.data.uiSourceCode);
|
| + this._navigator.refreshUISourceCode(uiSourceCode);
|
| + }
|
| +
|
| + _refreshEmpty(empty) {
|
| + if (empty) {
|
| + this._diffStats.setText('');
|
| + this._toolbar.setEnabled(false);
|
| + this._editor.hideWidget();
|
| + this._emptyWidget.showWidget();
|
| + } else {
|
| + this._toolbar.setEnabled(true);
|
| + this._emptyWidget.hideWidget();
|
| + this._editor.showWidget();
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * @param {?Workspace.UISourceCode} uiSourceCode
|
| + * @param {boolean=} refresh
|
| + */
|
| + _revealUISourceCode(uiSourceCode, refresh) {
|
| + if (this._uiSourceCode === uiSourceCode && !refresh)
|
| + return;
|
| +
|
| + if (this._uiSourceCode)
|
| + this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._refreshDiff, this);
|
| +
|
| + this._diffStats.setText('');
|
| + this._editor.setText('');
|
| + this._uiSourceCode = uiSourceCode;
|
| + this._rows = [];
|
| + this._refreshEmpty(!uiSourceCode);
|
| +
|
| + if (!uiSourceCode || !this.isShowing())
|
| + return;
|
| +
|
| + this._workspaceDiff.subscribeToDiffChange(uiSourceCode, this._refreshDiff, this);
|
| + this._navigator.revealUISourceCode(uiSourceCode, true);
|
| + this._refreshDiff();
|
| + }
|
| +
|
| + /**
|
| + * @override
|
| + */
|
| + wasShown() {
|
| + this._revealUISourceCode(this._uiSourceCode, true);
|
| + }
|
| +
|
| + /**
|
| + * @override
|
| + */
|
| + willHide() {
|
| + if (this._uiSourceCode)
|
| + this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._refreshDiff, this);
|
| + }
|
| +
|
| + _refreshDiff() {
|
| + if (!this._uiSourceCode)
|
| + return;
|
| + var uiSourceCode = this._uiSourceCode;
|
| + this._workspaceDiff.requestDiff(uiSourceCode).then(diff => {
|
| + if (this._uiSourceCode !== uiSourceCode)
|
| + return;
|
| + this._renderRows(diff);
|
| + });
|
| + }
|
| +
|
| + /**
|
| + * @param {?Diff.Diff.DiffArray} diff
|
| + */
|
| + _renderRows(diff) {
|
| + if (!diff || (diff.length === 1 && diff[0][0] === Diff.Diff.Operation.Equal)) {
|
| + this._refreshEmpty(true);
|
| + return;
|
| + }
|
| + this._refreshEmpty(false);
|
| +
|
| + var insertions = 0;
|
| + var deletions = 0;
|
| + var currentLineNumber = 0;
|
| + var baselineLineNumber = 0;
|
| + var paddingLines = 3;
|
| + var originalLines = [];
|
| + var currentLines = [];
|
| + this._rows = [];
|
| +
|
| + for (var i = 0; i < diff.length; ++i) {
|
| + var token = diff[i];
|
| + switch (token[0]) {
|
| + case Diff.Diff.Operation.Equal:
|
| + this._rows.pushAll(createEqualRows(token[1], i === 0, i === diff.length - 1));
|
| + originalLines.pushAll(token[1]);
|
| + currentLines.pushAll(token[1]);
|
| + break;
|
| + case Diff.Diff.Operation.Insert:
|
| + for (var line of token[1])
|
| + this._rows.push(createRow(line, 'addition'));
|
| + currentLines.pushAll(token[1]);
|
| + break;
|
| + case Diff.Diff.Operation.Delete:
|
| + originalLines.pushAll(token[1]);
|
| + if (diff[i + 1] && diff[i + 1][0] === Diff.Diff.Operation.Insert) {
|
| + this._rows.pushAll(createModifyRows(token[1].join('\n'), diff[i + 1][1].join('\n')));
|
| + i++;
|
| + currentLines.pushAll(diff[i + 1][1]);
|
| + } else {
|
| + for (var line of token[1])
|
| + this._rows.push(createRow(line, 'deletion'));
|
| + }
|
| + break;
|
| + }
|
| + }
|
| +
|
| + this._maxLineDigits = Math.max(Math.ceil(Math.log10(Math.max(currentLineNumber, baselineLineNumber))), 1);
|
| +
|
| + this._editor.operation(() => {
|
| + this._editor.setHighlightMode({
|
| + name: 'devtools-diff',
|
| + rows: this._rows,
|
| + mimeType:
|
| + Bindings.NetworkProject.uiSourceCodeMimeType(/** @type {!Workspace.UISourceCode} */ (this._uiSourceCode)),
|
| + baselineLines: originalLines,
|
| + currentLines: currentLines
|
| + });
|
| + this._editor.setText(this._rows.map(row => row.content.map(t => t.text).join('')).join('\n'));
|
| + this._editor.setLineNumberFormatter(this._lineFormatter.bind(this));
|
| + });
|
| +
|
| + this._diffStats.setText(Common.UIString(
|
| + '%d insertion%s (+), %d deletion%s (-)', insertions, insertions > 1 ? 's' : '', deletions,
|
| + deletions > 1 ? 's' : ''));
|
| +
|
| + /**
|
| + * @param {!Array<string>} lines
|
| + * @param {boolean} atStart
|
| + * @param {boolean} atEnd
|
| + * @return {!Array<!Changes.ChangesView.Row>}}
|
| + */
|
| + function createEqualRows(lines, atStart, atEnd) {
|
| + var equalRows = [];
|
| + if (!atStart) {
|
| + for (var i = 0; i < paddingLines && i < lines.length; i++)
|
| + equalRows.push(createRow(lines[i], 'equal'));
|
| + if (lines.length > paddingLines * 2 + 1 && !atEnd) {
|
| + equalRows.push(createRow(
|
| + Common.UIString('( \u2026 Skipping ') + (lines.length - paddingLines * 2) +
|
| + Common.UIString(' matching lines \u2026 )'),
|
| + 'spacer'));
|
| + }
|
| + }
|
| + if (!atEnd) {
|
| + var start = Math.max(lines.length - paddingLines - 1, atStart ? 0 : paddingLines);
|
| + var skip = lines.length - paddingLines - 1;
|
| + if (!atStart)
|
| + skip -= paddingLines;
|
| + if (skip > 0) {
|
| + baselineLineNumber += skip;
|
| + currentLineNumber += skip;
|
| + }
|
| +
|
| + for (var i = start; i < lines.length; i++)
|
| + equalRows.push(createRow(lines[i], 'equal'));
|
| + }
|
| + return equalRows;
|
| + }
|
| +
|
| + /**
|
| + * @param {string} before
|
| + * @param {string} after
|
| + * @return {!Array<!Changes.ChangesView.Row>}}
|
| + */
|
| + function createModifyRows(before, after) {
|
| + var internalDiff = Diff.Diff.charDiff(before, after, true);
|
| + var deletionRows = [createRow('', 'deletion')];
|
| + var insertionRows = [createRow('', 'addition')];
|
| +
|
| + for (var token of internalDiff) {
|
| + var text = token[1];
|
| + var type = token[0];
|
| + var className = type === Diff.Diff.Operation.Equal ? '' : 'double';
|
| + var first = true;
|
| + for (var line of text.split('\n')) {
|
| + if (first) {
|
| + first = false;
|
| + } else {
|
| + if (type !== Diff.Diff.Operation.Insert)
|
| + deletionRows.push(createRow('', 'deletion'));
|
| + if (type !== Diff.Diff.Operation.Delete)
|
| + insertionRows.push(createRow('', 'addition'));
|
| + }
|
| + if (line) {
|
| + if (type !== Diff.Diff.Operation.Insert)
|
| + deletionRows[deletionRows.length - 1].content.push({text: line, className: className});
|
| +
|
| + if (type !== Diff.Diff.Operation.Delete)
|
| + insertionRows[insertionRows.length - 1].content.push({text: line, className: className});
|
| + }
|
| + }
|
| + }
|
| + return deletionRows.concat(insertionRows);
|
| + }
|
| +
|
| + /**
|
| + * @param {string} text
|
| + * @param {string} className
|
| + * @return {!Changes.ChangesView.Row}
|
| + */
|
| + function createRow(text, className) {
|
| + if (className === 'addition') {
|
| + currentLineNumber++;
|
| + insertions++;
|
| + }
|
| + if (className === 'deletion') {
|
| + baselineLineNumber++;
|
| + deletions++;
|
| + }
|
| + if (className === 'equal') {
|
| + baselineLineNumber++;
|
| + currentLineNumber++;
|
| + }
|
| + return {
|
| + base: baselineLineNumber,
|
| + current: currentLineNumber,
|
| + content: text ? [{text: text, className: 'double'}] : [],
|
| + className: className,
|
| + loc: currentLineNumber
|
| + };
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * @param {number} lineNumber
|
| + * @return {string}
|
| + */
|
| + _lineFormatter(lineNumber) {
|
| + var row = this._rows[lineNumber - 1];
|
| + if (!row)
|
| + return spacesPadding(this._maxLineDigits * 2 + 1);
|
| + var showBaseNumber = row.className === 'deletion';
|
| + var showCurrentNumber = row.className === 'addition';
|
| + if (row.className === 'equal') {
|
| + showBaseNumber = true;
|
| + showCurrentNumber = true;
|
| + }
|
| + var base = showBaseNumber ? numberToStringWithSpacesPadding(row.base, this._maxLineDigits) :
|
| + spacesPadding(this._maxLineDigits);
|
| + var current = showCurrentNumber ? numberToStringWithSpacesPadding(row.current, this._maxLineDigits) :
|
| + spacesPadding(this._maxLineDigits);
|
| + return base + spacesPadding(1) + current;
|
| + }
|
| +
|
| + /**
|
| + * @param {number} baselineLineNumber
|
| + * @param {number} currentLineNumber
|
| + * @param {string} text
|
| + * @param {string=} className
|
| + * @return {!Element}
|
| + */
|
| + _createRow(baselineLineNumber, currentLineNumber, text, className) {
|
| + var element = createElementWithClass('div', className);
|
| + element.createChild('span', 'number').textContent = baselineLineNumber ?
|
| + numberToStringWithSpacesPadding(baselineLineNumber, this._maxLineDigits) :
|
| + spacesPadding(this._maxLineDigits);
|
| + element.createChild('span', 'number').textContent = currentLineNumber ?
|
| + numberToStringWithSpacesPadding(currentLineNumber, this._maxLineDigits) :
|
| + spacesPadding(this._maxLineDigits);
|
| + element.createChild('span', 'double').textContent = text;
|
| + return element;
|
| + }
|
| +};
|
| +
|
| +/** @typedef {!{base: number, current: number, content: !Array<!{text: string, className: string}>, className: string, loc: number}} */
|
| +Changes.ChangesView.Row;
|
| +
|
| +Changes.ChangesNavigator = class extends Sources.NavigatorView {
|
| + constructor() {
|
| + super();
|
| + this.dontGroup();
|
| + this.element.classList.add('changes-navigator');
|
| + }
|
| +
|
| + /**
|
| + * @override
|
| + * @param {!Workspace.UISourceCode} uiSourceCode
|
| + * @return {boolean}
|
| + */
|
| + accept(uiSourceCode) {
|
| + if (uiSourceCode.project().type() !== Workspace.projectTypes.Network)
|
| + return false;
|
| +
|
| + if (uiSourceCode.isDirty())
|
| + return true;
|
| +
|
| + if (!uiSourceCode.mightHaveChanges)
|
| + return false;
|
| +
|
| + uiSourceCode.hasChanges().then(hasChanges => {
|
| + if (!hasChanges)
|
| + this.refreshUISourceCode(uiSourceCode);
|
| + });
|
| + return true;
|
| + }
|
| +
|
| + /**
|
| + * @override
|
| + * @param {?Workspace.UISourceCode} uiSourceCode
|
| + * @param {boolean} focusSource
|
| + */
|
| + revealSource(uiSourceCode, focusSource) {
|
| + Changes.ChangesView.SharedInstance._revealUISourceCode(uiSourceCode);
|
| + if (focusSource)
|
| + Changes.ChangesView.SharedInstance._editor.focus();
|
| + }
|
| +
|
| + /**
|
| + * @override
|
| + * @param {!Workspace.UISourceCode} uiSourceCode
|
| + */
|
| + uiSourceCodeAdded(uiSourceCode) {
|
| + if (!Changes.ChangesView.SharedInstance._uiSourceCode)
|
| + this.revealSource(uiSourceCode, false);
|
| + }
|
| +
|
| + /**
|
| + * @override
|
| + * @param {!Workspace.UISourceCode} uiSourceCode
|
| + */
|
| + uiSourceCodeRemoved(uiSourceCode) {
|
| + if (Changes.ChangesView.SharedInstance._uiSourceCode !== uiSourceCode)
|
| + return;
|
| + this.revealSource(this.anyUISourceCode(), false);
|
| + }
|
| +};
|
| +
|
| +Changes.UISourceCodeChange = class {
|
| + /**
|
| + * @param {!Workspace.UISourceCode} uiSourceCode
|
| + */
|
| + constructor(uiSourceCode) {
|
| + this.uiSourceCode = uiSourceCode;
|
| + }
|
| +};
|
| +
|
| +/**
|
| + * @implements {Common.Revealer}
|
| + */
|
| +Changes.UISourceCodeChangeRevealer = class {
|
| + /**
|
| + * @override
|
| + * @param {!Object} uiSourceCodeChange
|
| + * @param {boolean=} omitFocus
|
| + * @return {!Promise}
|
| + */
|
| + reveal(uiSourceCodeChange, omitFocus) {
|
| + if (!(uiSourceCodeChange instanceof Changes.UISourceCodeChange))
|
| + return Promise.reject(new Error('Changes.UISourceCodeChange'));
|
| + Changes.ChangesView.showChanges(uiSourceCodeChange.uiSourceCode);
|
| + return Promise.resolve();
|
| + }
|
| +};
|
| +
|
| +/** @typedef {!{lineNumber: number, index: number}} */
|
| +Changes.ChangesView.DiffState;
|
| +
|
| +CodeMirror.defineMode('devtools-diff', function(config, parserConfig) {
|
| + var rows = parserConfig.rows || [];
|
| + var baselineLines = parserConfig.baselineLines;
|
| + var currentLines = parserConfig.currentLines;
|
| + var innerMode = CodeMirror.getMode({indentUnit: 2}, parserConfig.mimeType);
|
| +
|
| + function fastForward(state, baselineLineNumber, currentLineNumber) {
|
| + if (baselineLineNumber > state.baselineLineNumber) {
|
| + fastForwardState(state.baselineState, state.baselineLineNumber, baselineLineNumber, baselineLines);
|
| + state.baselineLineNumber = baselineLineNumber;
|
| + }
|
| + if (currentLineNumber > state.currentLineNumber) {
|
| + fastForwardState(state.currentState, state.currentLineNumber, currentLineNumber, currentLines);
|
| + state.currentLineNumber = currentLineNumber;
|
| + }
|
| + }
|
| +
|
| + function fastForwardState(state, from, to, lines) {
|
| + var lineNumber = from;
|
| + while (lineNumber < to && lineNumber < lines.length) {
|
| + var stream = new CodeMirror.StringStream(lines[lineNumber]);
|
| + while (!stream.eol()) {
|
| + innerMode.token(stream, state);
|
| + stream.start = stream.pos;
|
| + }
|
| + lineNumber++;
|
| + }
|
| + }
|
| +
|
| + return {
|
| + /**
|
| + * @return {!Changes.ChangesView.DiffState}
|
| + */
|
| + startState: function() {
|
| + return {
|
| + lineNumber: 0,
|
| + index: 0,
|
| + currentLineNumber: 0,
|
| + baselineLineNumber: 0,
|
| + currentState: CodeMirror.startState(innerMode),
|
| + baselineState: CodeMirror.startState(innerMode),
|
| + mismatch: 0
|
| + };
|
| + },
|
| +
|
| + /**
|
| + * @param {!{next: function()}} stream
|
| + * @param {!Changes.ChangesView.DiffState} state
|
| + * @return {string}
|
| + */
|
| + token: function(stream, state) {
|
| + var row = rows[state.lineNumber];
|
| + if (!row) {
|
| + stream.next();
|
| + return '';
|
| + }
|
| + fastForward(state, row.base - 1, row.current - 1);
|
| + var classes = '';
|
| + if (state.index === 0)
|
| + classes += ' line-background-' + row.className + ' line-' + row.className;
|
| + var chars = row.content[state.index].text.length;
|
| + if (state.mismatch > 0)
|
| + chars = state.mismatch;
|
| + var pos = stream.pos;
|
| + var innerStyle = state.lastInnerStyle;
|
| + var innerPos = pos - state.mismatch;
|
| + if (state.mismatch >= 0) {
|
| + if (row.className === 'deletion' || row.className === 'addition' || row.className === 'equal') {
|
| + innerStyle = innerMode.token(stream, row.className === 'deletion' ? state.baselineState : state.currentState);
|
| + innerPos = stream.pos;
|
| + } else {
|
| + innerStyle = '';
|
| + innerPos = pos + chars;
|
| + }
|
| + }
|
| + stream.pos = Math.min(innerPos, pos + chars);
|
| + if (innerStyle)
|
| + classes += ' ' + innerStyle;
|
| + classes += ' ' + row.content[state.index].className;
|
| + state.mismatch = pos + chars - innerPos;
|
| + if (state.mismatch <= 0) {
|
| + state.index++;
|
| + state.lastInnerStyle = innerStyle;
|
| + }
|
| + if (state.index >= row.content.length) {
|
| + state.lineNumber++;
|
| + if (row.className === 'deletion')
|
| + state.baselineLineNumber++;
|
| + else
|
| + state.currentLineNumber++;
|
| + state.index = 0;
|
| + }
|
| + return classes;
|
| + },
|
| +
|
| + /**
|
| + * @param {!Changes.ChangesView.DiffState} state
|
| + * @return {string}
|
| + */
|
| + blankLine: function(state) {
|
| + var row = rows[state.lineNumber];
|
| + state.lineNumber++;
|
| + state.mismatch = 0;
|
| + state.index = 0;
|
| + if (!row)
|
| + return '';
|
| + if (row.className === 'deletion')
|
| + state.baselineLineNumber++;
|
| + else
|
| + state.currentLineNumber++;
|
| + var style = '';
|
| + if (innerMode.blankLine)
|
| + style = innerMode.blankLine(row.className === 'deletion' ? state.baselineState : state.currentState);
|
| + return style + ' line-background-' + row.className + ' line-' + row.className;
|
| + },
|
| +
|
| + /**
|
| + * @param {!Changes.ChangesView.DiffState} state
|
| + * @return {!Changes.ChangesView.DiffState}
|
| + */
|
| + copyState: function(state) {
|
| + var newState = /** @type {!Changes.ChangesView.DiffState} */ ({});
|
| + for (var i in state)
|
| + newState[i] = state[i];
|
| + newState.currentState = CodeMirror.copyState(innerMode, state.currentState);
|
| + newState.baselineState = CodeMirror.copyState(innerMode, state.baselineState);
|
| + return newState;
|
| + }
|
| + };
|
| +});
|
|
|