| // Copyright 2018 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. | |
| Sources.GutterDiffPlugin = class extends Sources.UISourceCodeFrame.Plugin { | |
| /** | |
| * @param {!TextEditor.CodeMirrorTextEditor} textEditor | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| constructor(textEditor, uiSourceCode) { | |
| super(); | |
| this._textEditor = textEditor; | |
| this._uiSourceCode = uiSourceCode; | |
| /** @type {!Array<!Sources.GutterDiffPlugin.GutterDecoration>} */ | |
| this._decorations = []; | |
| this._textEditor.installGutter(Sources.GutterDiffPlugin.DiffGutterType, true); | |
| this._workspaceDiff = WorkspaceDiff.workspaceDiff(); | |
| this._workspaceDiff.subscribeToDiffChange(this._uiSourceCode, this._update, this); | |
| this._update(); | |
| } | |
| /** | |
| * @override | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @return {boolean} | |
| */ | |
| static accepts(uiSourceCode) { | |
| return uiSourceCode.project().type() === Workspace.projectTypes.Network; | |
| } | |
| /** | |
| * @param {!Array<!Sources.GutterDiffPlugin.GutterDecoration>} removed | |
| * @param {!Array<!Sources.GutterDiffPlugin.GutterDecoration>} added | |
| */ | |
| _updateDecorations(removed, added) { | |
| this._textEditor.operation(operation); | |
| function operation() { | |
| for (const decoration of removed) { | |
| decoration.remove(); | |
| } | |
| for (const decoration of added) { | |
| decoration.install(); | |
| } | |
| } | |
| } | |
| _update() { | |
| if (this._uiSourceCode) { | |
| this._workspaceDiff.requestDiff(this._uiSourceCode).then(this._innerUpdate.bind(this)); | |
| } else { | |
| this._innerUpdate(null); | |
| } | |
| } | |
| /** | |
| * @param {?Diff.Diff.DiffArray} lineDiff | |
| */ | |
| _innerUpdate(lineDiff) { | |
| if (!lineDiff) { | |
| this._updateDecorations(this._decorations, []); | |
| this._decorations = []; | |
| return; | |
| } | |
| /** @type {!Map<number, !Sources.GutterDiffPlugin.GutterDecoration>} */ | |
| const oldDecorations = new Map(); | |
| for (let i = 0; i < this._decorations.length; ++i) { | |
| const decoration = this._decorations[i]; | |
| const lineNumber = decoration.lineNumber(); | |
| if (lineNumber === -1) { | |
| continue; | |
| } | |
| oldDecorations.set(lineNumber, decoration); | |
| } | |
| const diff = SourceFrame.SourceCodeDiff.computeDiff(lineDiff); | |
| /** @type {!Map<number, !{lineNumber: number, type: !SourceFrame.SourceCodeDiff.EditType}>} */ | |
| const newDecorations = new Map(); | |
| for (let i = 0; i < diff.length; ++i) { | |
| const diffEntry = diff[i]; | |
| for (let lineNumber = diffEntry.from; lineNumber < diffEntry.to; ++lineNumber) { | |
| newDecorations.set(lineNumber, {lineNumber: lineNumber, type: diffEntry.type}); | |
| } | |
| } | |
| const decorationDiff = oldDecorations.diff(newDecorations, (e1, e2) => e1.type === e2.type); | |
| const addedDecorations = decorationDiff.added.map( | |
| entry => new Sources.GutterDiffPlugin.GutterDecoration(this._textEditor, entry.lineNumber, entry.type)); | |
| this._decorations = decorationDiff.equal.concat(addedDecorations); | |
| this._updateDecorations(decorationDiff.removed, addedDecorations); | |
| this._decorationsSetForTest(newDecorations); | |
| } | |
| /** | |
| * @param {!Map<number, !{lineNumber: number, type: !SourceFrame.SourceCodeDiff.EditType}>} decorations | |
| */ | |
| _decorationsSetForTest(decorations) { | |
| } | |
| /** | |
| * @override | |
| * @param {!UI.ContextMenu} contextMenu | |
| * @param {number} lineNumber | |
| * @return {!Promise} | |
| */ | |
| async populateLineGutterContextMenu(contextMenu, lineNumber) { | |
| Sources.GutterDiffPlugin._appendRevealDiffContextMenu(contextMenu, this._uiSourceCode); | |
| } | |
| /** | |
| * @override | |
| * @param {!UI.ContextMenu} contextMenu | |
| * @param {number} lineNumber | |
| * @param {number} columnNumber | |
| * @return {!Promise} | |
| */ | |
| async populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber) { | |
| Sources.GutterDiffPlugin._appendRevealDiffContextMenu(contextMenu, this._uiSourceCode); | |
| } | |
| static _appendRevealDiffContextMenu(contextMenu, uiSourceCode) { | |
| if (!WorkspaceDiff.workspaceDiff().isUISourceCodeModified(uiSourceCode)) { | |
| return; | |
| } | |
| contextMenu.revealSection().appendItem(ls`Local Modifications...`, () => { | |
| Common.Revealer.reveal(new WorkspaceDiff.DiffUILocation(uiSourceCode)); | |
| }); | |
| } | |
| /** | |
| * @override | |
| */ | |
| dispose() { | |
| for (const decoration of this._decorations) { | |
| decoration.remove(); | |
| } | |
| WorkspaceDiff.workspaceDiff().unsubscribeFromDiffChange(this._uiSourceCode, this._update, this); | |
| } | |
| }; | |
| Sources.GutterDiffPlugin.GutterDecoration = class { | |
| /** | |
| * @param {!TextEditor.CodeMirrorTextEditor} textEditor | |
| * @param {number} lineNumber | |
| * @param {!SourceFrame.SourceCodeDiff.EditType} type | |
| */ | |
| constructor(textEditor, lineNumber, type) { | |
| this._textEditor = textEditor; | |
| this._position = this._textEditor.textEditorPositionHandle(lineNumber, 0); | |
| this._className = ''; | |
| if (type === SourceFrame.SourceCodeDiff.EditType.Insert) { | |
| this._className = 'diff-entry-insert'; | |
| } else if (type === SourceFrame.SourceCodeDiff.EditType.Delete) { | |
| this._className = 'diff-entry-delete'; | |
| } else if (type === SourceFrame.SourceCodeDiff.EditType.Modify) { | |
| this._className = 'diff-entry-modify'; | |
| } | |
| this.type = type; | |
| } | |
| /** | |
| * @return {number} | |
| */ | |
| lineNumber() { | |
| const location = this._position.resolve(); | |
| if (!location) { | |
| return -1; | |
| } | |
| return location.lineNumber; | |
| } | |
| install() { | |
| const location = this._position.resolve(); | |
| if (!location) { | |
| return; | |
| } | |
| const element = createElementWithClass('div', 'diff-marker'); | |
| element.textContent = '\xA0'; | |
| this._textEditor.setGutterDecoration(location.lineNumber, Sources.GutterDiffPlugin.DiffGutterType, element); | |
| this._textEditor.toggleLineClass(location.lineNumber, this._className, true); | |
| } | |
| remove() { | |
| const location = this._position.resolve(); | |
| if (!location) { | |
| return; | |
| } | |
| this._textEditor.setGutterDecoration(location.lineNumber, Sources.GutterDiffPlugin.DiffGutterType, null); | |
| this._textEditor.toggleLineClass(location.lineNumber, this._className, false); | |
| } | |
| }; | |
| /** @type {string} */ | |
| Sources.GutterDiffPlugin.DiffGutterType = 'CodeMirror-gutter-diff'; | |
| /** | |
| * @implements {UI.ContextMenu.Provider} | |
| * @unrestricted | |
| */ | |
| Sources.GutterDiffPlugin.ContextMenuProvider = class { | |
| /** | |
| * @override | |
| * @param {!Event} event | |
| * @param {!UI.ContextMenu} contextMenu | |
| * @param {!Object} target | |
| */ | |
| appendApplicableItems(event, contextMenu, target) { | |
| let uiSourceCode = /** @type {!Workspace.UISourceCode} */ (target); | |
| const binding = Persistence.persistence.binding(uiSourceCode); | |
| if (binding) { | |
| uiSourceCode = binding.network; | |
| } | |
| Sources.GutterDiffPlugin._appendRevealDiffContextMenu(contextMenu, uiSourceCode); | |
| } | |
| }; |