blob: b2b8e6e56fe0bf4a741c339c1903dad10c26eabc [file] [log] [blame]
/*
* Copyright (C) 2012 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @constructor
* @extends {WebInspector.VBox}
*/
WebInspector.RevisionHistoryView = function()
{
WebInspector.VBox.call(this);
this.registerRequiredCSS("sources/revisionHistory.css");
this.element.classList.add("revision-history-drawer");
this._uiSourceCodeItems = new Map();
this._treeOutline = new TreeOutline();
this._treeOutline.element.classList.add("outline-disclosure");
this.element.appendChild(this._treeOutline.element);
/**
* @param {!WebInspector.UISourceCode} uiSourceCode
* @this {WebInspector.RevisionHistoryView}
*/
function populateRevisions(uiSourceCode)
{
if (uiSourceCode.history.length)
this._createUISourceCodeItem(uiSourceCode);
}
WebInspector.workspace.uiSourceCodes().forEach(populateRevisions.bind(this));
WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, this._revisionAdded, this);
WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
}
/**
* @param {!WebInspector.UISourceCode} uiSourceCode
*/
WebInspector.RevisionHistoryView.showHistory = function(uiSourceCode)
{
if (!WebInspector.RevisionHistoryView._view)
WebInspector.RevisionHistoryView._view = new WebInspector.RevisionHistoryView();
var view = WebInspector.RevisionHistoryView._view;
WebInspector.inspectorView.showCloseableViewInDrawer("history", WebInspector.UIString("History"), view);
view._revealUISourceCode(uiSourceCode);
}
WebInspector.RevisionHistoryView.prototype = {
/**
* @param {!WebInspector.UISourceCode} uiSourceCode
*/
_createUISourceCodeItem: function(uiSourceCode)
{
var uiSourceCodeItem = new TreeElement(uiSourceCode.displayName(), true);
uiSourceCodeItem.selectable = false;
// Insert in sorted order
var rootElement = this._treeOutline.rootElement();
for (var i = 0; i < rootElement.childCount(); ++i) {
if (rootElement.childAt(i).title.localeCompare(uiSourceCode.displayName()) > 0) {
rootElement.insertChild(uiSourceCodeItem, i);
break;
}
}
if (i === rootElement.childCount())
rootElement.appendChild(uiSourceCodeItem);
this._uiSourceCodeItems.set(uiSourceCode, uiSourceCodeItem);
var revisionCount = uiSourceCode.history.length;
for (var i = revisionCount - 1; i >= 0; --i) {
var revision = uiSourceCode.history[i];
var historyItem = new WebInspector.RevisionHistoryTreeElement(revision, uiSourceCode.history[i - 1], i !== revisionCount - 1);
uiSourceCodeItem.appendChild(historyItem);
}
var linkItem = new TreeElement();
linkItem.selectable = false;
uiSourceCodeItem.appendChild(linkItem);
var revertToOriginal = linkItem.listItemElement.createChild("span", "revision-history-link revision-history-link-row");
revertToOriginal.textContent = WebInspector.UIString("apply original content");
revertToOriginal.addEventListener("click", this._revertToOriginal.bind(this, uiSourceCode));
var clearHistoryElement = uiSourceCodeItem.listItemElement.createChild("span", "revision-history-link");
clearHistoryElement.textContent = WebInspector.UIString("revert");
clearHistoryElement.addEventListener("click", this._clearHistory.bind(this, uiSourceCode));
return uiSourceCodeItem;
},
/**
* @param {!WebInspector.UISourceCode} uiSourceCode
*/
_revertToOriginal: function(uiSourceCode)
{
uiSourceCode.revertToOriginal();
},
/**
* @param {!WebInspector.UISourceCode} uiSourceCode
*/
_clearHistory: function(uiSourceCode)
{
uiSourceCode.revertAndClearHistory(this._removeUISourceCode.bind(this));
},
_revisionAdded: function(event)
{
var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode);
var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
if (!uiSourceCodeItem) {
uiSourceCodeItem = this._createUISourceCodeItem(uiSourceCode);
return;
}
var historyLength = uiSourceCode.history.length;
var historyItem = new WebInspector.RevisionHistoryTreeElement(uiSourceCode.history[historyLength - 1], uiSourceCode.history[historyLength - 2], false);
if (uiSourceCodeItem.firstChild())
uiSourceCodeItem.firstChild().allowRevert();
uiSourceCodeItem.insertChild(historyItem, 0);
},
/**
* @param {!WebInspector.UISourceCode} uiSourceCode
*/
_revealUISourceCode: function(uiSourceCode)
{
var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
if (uiSourceCodeItem) {
uiSourceCodeItem.reveal();
uiSourceCodeItem.expand();
}
},
_uiSourceCodeRemoved: function(event)
{
var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
this._removeUISourceCode(uiSourceCode);
},
/**
* @param {!WebInspector.UISourceCode} uiSourceCode
*/
_removeUISourceCode: function(uiSourceCode)
{
var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
if (!uiSourceCodeItem)
return;
this._treeOutline.removeChild(uiSourceCodeItem);
this._uiSourceCodeItems.remove(uiSourceCode);
},
_projectRemoved: function(event)
{
var project = event.data;
project.uiSourceCodes().forEach(this._removeUISourceCode.bind(this));
},
__proto__: WebInspector.VBox.prototype
}
/**
* @constructor
* @extends {TreeElement}
* @param {!WebInspector.Revision} revision
* @param {!WebInspector.Revision} baseRevision
* @param {boolean} allowRevert
*/
WebInspector.RevisionHistoryTreeElement = function(revision, baseRevision, allowRevert)
{
TreeElement.call(this, revision.timestamp.toLocaleTimeString(), true);
this.selectable = false;
this._revision = revision;
this._baseRevision = baseRevision;
this._revertElement = createElement("span");
this._revertElement.className = "revision-history-link";
this._revertElement.textContent = WebInspector.UIString("apply revision content");
this._revertElement.addEventListener("click", this._revision.revertToThis.bind(this._revision), false);
if (!allowRevert)
this._revertElement.classList.add("hidden");
}
WebInspector.RevisionHistoryTreeElement.prototype = {
onattach: function()
{
this.listItemElement.classList.add("revision-history-revision");
},
onpopulate: function()
{
this.listItemElement.appendChild(this._revertElement);
this.childrenListElement.classList.add("source-code");
if (this._baseRevision)
this._baseRevision.requestContent(step1.bind(this));
else
this._revision.uiSourceCode.requestOriginalContent(step1.bind(this));
/**
* @param {?string} baseContent
* @this {WebInspector.RevisionHistoryTreeElement}
*/
function step1(baseContent)
{
this._revision.requestContent(step2.bind(this, baseContent));
}
/**
* @param {?string} baseContent
* @param {?string} newContent
* @this {WebInspector.RevisionHistoryTreeElement}
*/
function step2(baseContent, newContent)
{
var baseLines = baseContent.split("\n");
var newLines = newContent.split("\n");
var opcodes = WebInspector.Diff.lineDiff(baseLines, newLines);
var lastWasSeparator = false;
var baseLineNumber = 0;
var newLineNumber = 0;
for (var idx = 0; idx < opcodes.length; idx++) {
var code = opcodes[idx][0];
var rowCount = opcodes[idx][1].length;
if (code === WebInspector.Diff.Operation.Equal) {
baseLineNumber += rowCount;
newLineNumber += rowCount;
if (!lastWasSeparator)
this._createLine(null, null, " \u2026", "separator");
lastWasSeparator = true;
} else if (code === WebInspector.Diff.Operation.Delete) {
lastWasSeparator = false;
for (var i = 0; i < rowCount; ++i)
this._createLine(baseLineNumber + i, null, baseLines[baseLineNumber + i], "removed");
baseLineNumber += rowCount;
} else if (code === WebInspector.Diff.Operation.Insert) {
lastWasSeparator = false;
for (var i = 0; i < rowCount; ++i)
this._createLine(null, newLineNumber + i, newLines[newLineNumber + i], "added");
newLineNumber += rowCount;
}
}
}
},
oncollapse: function()
{
this._revertElement.remove();
},
/**
* @param {?number} baseLineNumber
* @param {?number} newLineNumber
* @param {string} lineContent
* @param {string} changeType
*/
_createLine: function(baseLineNumber, newLineNumber, lineContent, changeType)
{
var child = new TreeElement();
child.selectable = false;
this.appendChild(child);
function appendLineNumber(lineNumber)
{
var numberString = lineNumber !== null ? numberToStringWithSpacesPadding(lineNumber + 1, 4) : spacesPadding(4);
var lineNumberSpan = createElement("span");
lineNumberSpan.classList.add("webkit-line-number");
lineNumberSpan.textContent = numberString;
child.listItemElement.appendChild(lineNumberSpan);
}
appendLineNumber(baseLineNumber);
appendLineNumber(newLineNumber);
var contentSpan = createElement("span");
contentSpan.textContent = lineContent;
child.listItemElement.appendChild(contentSpan);
child.listItemElement.classList.add("revision-history-line");
contentSpan.classList.add("revision-history-line-" + changeType);
},
allowRevert: function()
{
this._revertElement.classList.remove("hidden");
},
__proto__: TreeElement.prototype
}