2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @extends {WebInspector.VBox}
35 WebInspector.RevisionHistoryView = function()
37 WebInspector.VBox.call(this);
38 this.registerRequiredCSS("sources/revisionHistory.css");
39 this.element.classList.add("revision-history-drawer");
40 this.element.classList.add("outline-disclosure");
41 this._uiSourceCodeItems = new Map();
43 var olElement = this.element.createChild("ol");
44 this._treeOutline = new TreeOutline(olElement);
47 * @param {!WebInspector.UISourceCode} uiSourceCode
48 * @this {WebInspector.RevisionHistoryView}
50 function populateRevisions(uiSourceCode)
52 if (uiSourceCode.history.length)
53 this._createUISourceCodeItem(uiSourceCode);
56 WebInspector.workspace.uiSourceCodes().forEach(populateRevisions.bind(this));
57 WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeContentCommitted, this._revisionAdded, this);
58 WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
59 WebInspector.workspace.addEventListener(WebInspector.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
63 * @param {!WebInspector.UISourceCode} uiSourceCode
65 WebInspector.RevisionHistoryView.showHistory = function(uiSourceCode)
67 if (!WebInspector.RevisionHistoryView._view)
68 WebInspector.RevisionHistoryView._view = new WebInspector.RevisionHistoryView();
69 var view = WebInspector.RevisionHistoryView._view;
70 WebInspector.inspectorView.showCloseableViewInDrawer("history", WebInspector.UIString("History"), view);
71 view._revealUISourceCode(uiSourceCode);
74 WebInspector.RevisionHistoryView.prototype = {
76 * @param {!WebInspector.UISourceCode} uiSourceCode
78 _createUISourceCodeItem: function(uiSourceCode)
80 var uiSourceCodeItem = new TreeElement(uiSourceCode.displayName(), null, true);
81 uiSourceCodeItem.selectable = false;
83 // Insert in sorted order
84 for (var i = 0; i < this._treeOutline.children.length; ++i) {
85 if (this._treeOutline.children[i].title.localeCompare(uiSourceCode.displayName()) > 0) {
86 this._treeOutline.insertChild(uiSourceCodeItem, i);
90 if (i === this._treeOutline.children.length)
91 this._treeOutline.appendChild(uiSourceCodeItem);
93 this._uiSourceCodeItems.set(uiSourceCode, uiSourceCodeItem);
95 var revisionCount = uiSourceCode.history.length;
96 for (var i = revisionCount - 1; i >= 0; --i) {
97 var revision = uiSourceCode.history[i];
98 var historyItem = new WebInspector.RevisionHistoryTreeElement(revision, uiSourceCode.history[i - 1], i !== revisionCount - 1);
99 uiSourceCodeItem.appendChild(historyItem);
102 var linkItem = new TreeElement("", null, false);
103 linkItem.selectable = false;
104 uiSourceCodeItem.appendChild(linkItem);
106 var revertToOriginal = linkItem.listItemElement.createChild("span", "revision-history-link revision-history-link-row");
107 revertToOriginal.textContent = WebInspector.UIString("apply original content");
108 revertToOriginal.addEventListener("click", this._revertToOriginal.bind(this, uiSourceCode));
110 var clearHistoryElement = uiSourceCodeItem.listItemElement.createChild("span", "revision-history-link");
111 clearHistoryElement.textContent = WebInspector.UIString("revert");
112 clearHistoryElement.addEventListener("click", this._clearHistory.bind(this, uiSourceCode));
113 return uiSourceCodeItem;
117 * @param {!WebInspector.UISourceCode} uiSourceCode
119 _revertToOriginal: function(uiSourceCode)
121 uiSourceCode.revertToOriginal();
123 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
124 action: WebInspector.UserMetrics.UserActionNames.ApplyOriginalContent,
125 url: uiSourceCode.url
130 * @param {!WebInspector.UISourceCode} uiSourceCode
132 _clearHistory: function(uiSourceCode)
134 uiSourceCode.revertAndClearHistory(this._removeUISourceCode.bind(this));
136 WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
137 action: WebInspector.UserMetrics.UserActionNames.RevertRevision,
138 url: uiSourceCode.url
142 _revisionAdded: function(event)
144 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode);
145 var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
146 if (!uiSourceCodeItem) {
147 uiSourceCodeItem = this._createUISourceCodeItem(uiSourceCode);
151 var historyLength = uiSourceCode.history.length;
152 var historyItem = new WebInspector.RevisionHistoryTreeElement(uiSourceCode.history[historyLength - 1], uiSourceCode.history[historyLength - 2], false);
153 if (uiSourceCodeItem.children.length)
154 uiSourceCodeItem.children[0].allowRevert();
155 uiSourceCodeItem.insertChild(historyItem, 0);
159 * @param {!WebInspector.UISourceCode} uiSourceCode
161 _revealUISourceCode: function(uiSourceCode)
163 var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
164 if (uiSourceCodeItem) {
165 uiSourceCodeItem.reveal();
166 uiSourceCodeItem.expand();
170 _uiSourceCodeRemoved: function(event)
172 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
173 this._removeUISourceCode(uiSourceCode);
177 * @param {!WebInspector.UISourceCode} uiSourceCode
179 _removeUISourceCode: function(uiSourceCode)
181 var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
182 if (!uiSourceCodeItem)
184 this._treeOutline.removeChild(uiSourceCodeItem);
185 this._uiSourceCodeItems.remove(uiSourceCode);
188 _projectRemoved: function(event)
190 var project = event.data;
191 project.uiSourceCodes().forEach(this._removeUISourceCode.bind(this));
194 __proto__: WebInspector.VBox.prototype
199 * @extends {TreeElement}
200 * @param {!WebInspector.Revision} revision
201 * @param {!WebInspector.Revision} baseRevision
202 * @param {boolean} allowRevert
204 WebInspector.RevisionHistoryTreeElement = function(revision, baseRevision, allowRevert)
206 TreeElement.call(this, revision.timestamp.toLocaleTimeString(), null, true);
207 this.selectable = false;
209 this._revision = revision;
210 this._baseRevision = baseRevision;
212 this._revertElement = createElement("span");
213 this._revertElement.className = "revision-history-link";
214 this._revertElement.textContent = WebInspector.UIString("apply revision content");
215 this._revertElement.addEventListener("click", this._revision.revertToThis.bind(this._revision), false);
217 this._revertElement.classList.add("hidden");
220 WebInspector.RevisionHistoryTreeElement.prototype = {
223 this.listItemElement.classList.add("revision-history-revision");
228 this.listItemElement.appendChild(this._revertElement);
230 if (this._wasExpandedOnce)
232 this._wasExpandedOnce = true;
234 this.childrenListElement.classList.add("source-code");
235 if (this._baseRevision)
236 this._baseRevision.requestContent(step1.bind(this));
238 this._revision.uiSourceCode.requestOriginalContent(step1.bind(this));
241 * @param {?string} baseContent
242 * @this {WebInspector.RevisionHistoryTreeElement}
244 function step1(baseContent)
246 this._revision.requestContent(step2.bind(this, baseContent));
250 * @param {?string} baseContent
251 * @param {?string} newContent
252 * @this {WebInspector.RevisionHistoryTreeElement}
254 function step2(baseContent, newContent)
256 var baseLines = difflib.stringAsLines(baseContent);
257 var newLines = difflib.stringAsLines(newContent);
258 var sm = new difflib.SequenceMatcher(baseLines, newLines);
259 var opcodes = sm.get_opcodes();
260 var lastWasSeparator = false;
262 for (var idx = 0; idx < opcodes.length; idx++) {
263 var code = opcodes[idx];
264 var change = code[0];
269 var rowCount = Math.max(be - b, ne - n);
272 for (var i = 0; i < rowCount; i++) {
273 if (change === "delete" || (change === "replace" && b < be)) {
274 var lineNumber = b++;
275 this._createLine(lineNumber, null, baseLines[lineNumber], "removed");
276 lastWasSeparator = false;
279 if (change === "insert" || (change === "replace" && n < ne)) {
280 var lineNumber = n++;
281 this._createLine(null, lineNumber, newLines[lineNumber], "added");
282 lastWasSeparator = false;
285 if (change === "equal") {
288 if (!lastWasSeparator)
289 this._createLine(null, null, " \u2026", "separator");
290 lastWasSeparator = true;
297 oncollapse: function()
299 this._revertElement.remove();
303 * @param {?number} baseLineNumber
304 * @param {?number} newLineNumber
305 * @param {string} lineContent
306 * @param {string} changeType
308 _createLine: function(baseLineNumber, newLineNumber, lineContent, changeType)
310 var child = new TreeElement("", null, false);
311 child.selectable = false;
312 this.appendChild(child);
313 var lineElement = createElement("span");
315 function appendLineNumber(lineNumber)
317 var numberString = lineNumber !== null ? numberToStringWithSpacesPadding(lineNumber + 1, 4) : spacesPadding(4);
318 var lineNumberSpan = createElement("span");
319 lineNumberSpan.classList.add("webkit-line-number");
320 lineNumberSpan.textContent = numberString;
321 child.listItemElement.appendChild(lineNumberSpan);
324 appendLineNumber(baseLineNumber);
325 appendLineNumber(newLineNumber);
327 var contentSpan = createElement("span");
328 contentSpan.textContent = lineContent;
329 child.listItemElement.appendChild(contentSpan);
330 child.listItemElement.classList.add("revision-history-line");
331 contentSpan.classList.add("revision-history-line-" + changeType);
334 allowRevert: function()
336 this._revertElement.classList.remove("hidden");
339 __proto__: TreeElement.prototype