Chromium Code Reviews| 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..d49c9154eaff675dbe1ffff65dc5875578e61267 |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/devtools/front_end/changes/ChangesView.js |
| @@ -0,0 +1,466 @@ |
| +// 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; |
|
luoe
2017/03/23 02:03:16
Not needed?
einbinder
2017/03/23 04:32:48
Done.
|
| + this.registerRequiredCSS('changes/changesView.css'); |
| + var splitWidget = new UI.SplitWidget(true, false); |
| + var mainWidget = new UI.Widget(); |
| + splitWidget.setMainWidget(mainWidget); |
| + splitWidget.show(this.contentElement); |
| + var diffArea = mainWidget.element.createChild('div', 'diff-area'); |
| + |
| + this._emptyWidget = new UI.EmptyWidget(Common.UIString('No changes')); |
| + this._emptyWidget.show(diffArea); |
| + |
| + this._workspaceDiff = WorkspaceDiff.workspaceDiff(); |
| + this._fileList = new Changes.FileList(this._workspaceDiff); |
|
luoe
2017/03/23 02:03:16
var filelist = ...?
einbinder
2017/03/23 04:32:47
Done.
|
| + this._fileList.on(Changes.FileList.SelectionChanged, this._fileListSelectionChanged, this); |
| + splitWidget.setSidebarWidget(this._fileList); |
| + |
| + /** @type {?Workspace.UISourceCode} */ |
| + this._uiSourceCode = null; |
| + |
| + /** @type {!Array<!Changes.ChangesView.Row>} */ |
| + this._rows = []; |
| + |
| + this._maxLineDigits = 1; |
| + |
| + this._editor = |
| + new TextEditor.CodeMirrorTextEditor({lineNumbers: true, lineWrapping: false, maxHighlightLength: Infinity}); |
| + this._editor.setReadOnly(true); |
| + this._editor.show(diffArea); |
| + this._editor.hideWidget(); |
| + this._editor.setLineNumberFormatter(() => ''); |
| + |
| + this._editor.element.addEventListener('mouseup', this._click.bind(this), false); |
|
luoe
2017/03/23 02:03:16
Why not click?
einbinder
2017/03/23 04:32:48
Done.
|
| + |
| + 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); |
| + } |
| + |
| + /** |
| + * @param {!Changes.FileList.SelectionChanged} event |
| + */ |
| + _fileListSelectionChanged(event) { |
| + this._revealUISourceCode(event.uiSourceCode); |
| + } |
| + |
| + _revert() { |
| + var uiSourceCode = this._uiSourceCode; |
| + if (!uiSourceCode) |
| + return; |
| + uiSourceCode.requestOriginalContent().then(original => uiSourceCode.addRevision(original || '')); |
| + } |
| + |
| + _click(event) { |
| + var selection = this._editor.selection(); |
| + if (!selection.isEmpty()) |
| + return; |
| + var row = this._rows[selection.startLine]; |
| + if (!row) |
| + return; |
| + Common.Revealer.reveal(this._uiSourceCode.uiLocation(row.current - 1, selection.startColumn), false); |
| + event.consume(true); |
| + } |
| + |
| + /** |
| + * @param {?Workspace.UISourceCode} uiSourceCode |
| + */ |
| + _revealUISourceCode(uiSourceCode) { |
| + if (this._uiSourceCode === uiSourceCode) |
| + return; |
| + |
| + if (this._uiSourceCode) |
| + this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._refreshDiff, this); |
| + if (uiSourceCode) |
| + this._workspaceDiff.subscribeToDiffChange(uiSourceCode, this._refreshDiff, this); |
| + |
| + this._uiSourceCode = uiSourceCode; |
| + this._refreshDiff(); |
| + } |
| + |
| + /** |
| + * @override |
| + */ |
| + wasShown() { |
| + if (this._uiSourceCode) |
| + this._workspaceDiff.subscribeToDiffChange(this._uiSourceCode, this._refreshDiff, this); |
| + this._refreshDiff(); |
| + } |
| + |
| + /** |
| + * @override |
| + */ |
| + willHide() { |
| + if (this._uiSourceCode) |
| + this._workspaceDiff.unsubscribeFromDiffChange(this._uiSourceCode, this._refreshDiff, this); |
| + } |
| + |
| + _refreshDiff() { |
| + if (!this._uiSourceCode) { |
| + this._renderRows(null); |
| + 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) { |
| + this._rows = []; |
| + |
| + if (!diff || (diff.length === 1 && diff[0][0] === Diff.Diff.Operation.Equal)) { |
| + this._diffStats.setText(''); |
| + this._toolbar.setEnabled(false); |
| + this._editor.hideWidget(); |
| + this._emptyWidget.showWidget(); |
| + return; |
| + } |
| + |
| + var insertions = 0; |
| + var deletions = 0; |
| + var currentLineNumber = 0; |
| + var baselineLineNumber = 0; |
| + var paddingLines = 3; |
| + var originalLines = []; |
| + var currentLines = []; |
| + |
| + 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) { |
| + i++; |
| + this._rows.pushAll(createModifyRows(token[1].join('\n'), diff[i][1].join('\n'))); |
| + currentLines.pushAll(diff[i][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' : '')); |
| + this._toolbar.setEnabled(true); |
| + this._emptyWidget.hideWidget(); |
| + this._editor.showWidget(); |
| + |
| + /** |
| + * @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; |
| + |
| +/** @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; |
| + } |
| + }; |
| +}); |