| // Copyright 2016 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. |
| |
| export class SourceCodeDiff { |
| /** |
| * @param {!TextEditor.CodeMirrorTextEditor} textEditor |
| */ |
| constructor(textEditor) { |
| this._textEditor = textEditor; |
| /** @type {!Array<!TextEditor.TextEditorPositionHandle>}*/ |
| this._animatedLines = []; |
| /** @type {?number} */ |
| this._animationTimeout = null; |
| } |
| |
| /** |
| * @param {?string} oldContent |
| * @param {?string} newContent |
| */ |
| highlightModifiedLines(oldContent, newContent) { |
| if (typeof oldContent !== 'string' || typeof newContent !== 'string') { |
| return; |
| } |
| |
| const diff = SourceCodeDiff.computeDiff(Diff.Diff.lineDiff(oldContent.split('\n'), newContent.split('\n'))); |
| const changedLines = []; |
| for (let i = 0; i < diff.length; ++i) { |
| const diffEntry = diff[i]; |
| if (diffEntry.type === EditType.Delete) { |
| continue; |
| } |
| for (let lineNumber = diffEntry.from; lineNumber < diffEntry.to; ++lineNumber) { |
| const position = this._textEditor.textEditorPositionHandle(lineNumber, 0); |
| if (position) { |
| changedLines.push(position); |
| } |
| } |
| } |
| this._updateHighlightedLines(changedLines); |
| this._animationTimeout = setTimeout( |
| this._updateHighlightedLines.bind(this, []), 400); // // Keep this timeout in sync with sourcesView.css. |
| } |
| |
| /** |
| * @param {!Array<!TextEditor.TextEditorPositionHandle>} newLines |
| */ |
| _updateHighlightedLines(newLines) { |
| if (this._animationTimeout) { |
| clearTimeout(this._animationTimeout); |
| } |
| this._animationTimeout = null; |
| this._textEditor.operation(operation.bind(this)); |
| |
| /** |
| * @this {SourceCodeDiff} |
| */ |
| function operation() { |
| toggleLines.call(this, false); |
| this._animatedLines = newLines; |
| toggleLines.call(this, true); |
| } |
| |
| /** |
| * @param {boolean} value |
| * @this {SourceCodeDiff} |
| */ |
| function toggleLines(value) { |
| for (let i = 0; i < this._animatedLines.length; ++i) { |
| const location = this._animatedLines[i].resolve(); |
| if (location) { |
| this._textEditor.toggleLineClass(location.lineNumber, 'highlight-line-modification', value); |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param {!Diff.Diff.DiffArray} diff |
| * @return {!Array<!{type: !EditType, from: number, to: number}>} |
| */ |
| static computeDiff(diff) { |
| const result = []; |
| let hasAdded = false; |
| let hasRemoved = false; |
| let blockStartLineNumber = 0; |
| let currentLineNumber = 0; |
| let isInsideBlock = false; |
| for (let i = 0; i < diff.length; ++i) { |
| const token = diff[i]; |
| if (token[0] === Diff.Diff.Operation.Equal) { |
| if (isInsideBlock) { |
| flush(); |
| } |
| currentLineNumber += token[1].length; |
| continue; |
| } |
| |
| if (!isInsideBlock) { |
| isInsideBlock = true; |
| blockStartLineNumber = currentLineNumber; |
| } |
| |
| if (token[0] === Diff.Diff.Operation.Delete) { |
| hasRemoved = true; |
| } else { |
| currentLineNumber += token[1].length; |
| hasAdded = true; |
| } |
| } |
| if (isInsideBlock) { |
| flush(); |
| } |
| if (result.length > 1 && result[0].from === 0 && result[1].from === 0) { |
| const merged = {type: EditType.Modify, from: 0, to: result[1].to}; |
| result.splice(0, 2, merged); |
| } |
| return result; |
| |
| function flush() { |
| let type = EditType.Insert; |
| let from = blockStartLineNumber; |
| let to = currentLineNumber; |
| if (hasAdded && hasRemoved) { |
| type = EditType.Modify; |
| } else if (!hasAdded && hasRemoved && from === 0 && to === 0) { |
| type = EditType.Modify; |
| to = 1; |
| } else if (!hasAdded && hasRemoved) { |
| type = EditType.Delete; |
| from -= 1; |
| } |
| result.push({type: type, from: from, to: to}); |
| isInsideBlock = false; |
| hasAdded = false; |
| hasRemoved = false; |
| } |
| } |
| } |
| |
| /** @enum {symbol} */ |
| export const EditType = { |
| Insert: Symbol('Insert'), |
| Delete: Symbol('Delete'), |
| Modify: Symbol('Modify'), |
| }; |
| |
| /* Legacy exported object */ |
| self.SourceFrame = self.SourceFrame || {}; |
| |
| /* Legacy exported object */ |
| SourceFrame = SourceFrame || {}; |
| |
| /** @constructor */ |
| SourceFrame.SourceCodeDiff = SourceCodeDiff; |
| |
| /** @enum {symbol} */ |
| SourceFrame.SourceCodeDiff.EditType = EditType; |