1 /* 2 * Copyright (C) 2012 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 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 13 * distribution. 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. 17 * 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. 29 */ 30 31 /** 32 * @constructor 33 * @extends {WebInspector.VBox} 34 */ 35 WebInspector.RevisionHistoryView = function() 36 { 37 WebInspector.VBox.call(this); 38 this.registerRequiredCSS("revisionHistory.css"); 39 this.element.classList.add("revision-history-drawer"); 40 this.element.classList.add("outline-disclosure"); 41 this._uiSourceCodeItems = new Map(); 42 43 var olElement = this.element.createChild("ol"); 44 this._treeOutline = new TreeOutline(olElement); 45 46 /** 47 * @param {!WebInspector.UISourceCode} uiSourceCode 48 * @this {WebInspector.RevisionHistoryView} 49 */ 50 function populateRevisions(uiSourceCode) 51 { 52 if (uiSourceCode.history.length) 53 this._createUISourceCodeItem(uiSourceCode); 54 } 55 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); 60 } 61 62 /** 63 * @param {!WebInspector.UISourceCode} uiSourceCode 64 */ 65 WebInspector.RevisionHistoryView.showHistory = function(uiSourceCode) 66 { 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); 72 } 73 74 WebInspector.RevisionHistoryView.prototype = { 75 /** 76 * @param {!WebInspector.UISourceCode} uiSourceCode 77 */ 78 _createUISourceCodeItem: function(uiSourceCode) 79 { 80 var uiSourceCodeItem = new TreeElement(uiSourceCode.displayName(), null, true); 81 uiSourceCodeItem.selectable = false; 82 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); 87 break; 88 } 89 } 90 if (i === this._treeOutline.children.length) 91 this._treeOutline.appendChild(uiSourceCodeItem); 92 93 this._uiSourceCodeItems.put(uiSourceCode, uiSourceCodeItem); 94 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); 100 } 101 102 var linkItem = new TreeElement("", null, false); 103 linkItem.selectable = false; 104 uiSourceCodeItem.appendChild(linkItem); 105 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", uiSourceCode.revertToOriginal.bind(uiSourceCode)); 109 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; 114 }, 115 116 /** 117 * @param {!WebInspector.UISourceCode} uiSourceCode 118 */ 119 _clearHistory: function(uiSourceCode) 120 { 121 uiSourceCode.revertAndClearHistory(this._removeUISourceCode.bind(this)); 122 }, 123 124 _revisionAdded: function(event) 125 { 126 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode); 127 var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode); 128 if (!uiSourceCodeItem) { 129 uiSourceCodeItem = this._createUISourceCodeItem(uiSourceCode); 130 return; 131 } 132 133 var historyLength = uiSourceCode.history.length; 134 var historyItem = new WebInspector.RevisionHistoryTreeElement(uiSourceCode.history[historyLength - 1], uiSourceCode.history[historyLength - 2], false); 135 if (uiSourceCodeItem.children.length) 136 uiSourceCodeItem.children[0].allowRevert(); 137 uiSourceCodeItem.insertChild(historyItem, 0); 138 }, 139 140 /** 141 * @param {!WebInspector.UISourceCode} uiSourceCode 142 */ 143 _revealUISourceCode: function(uiSourceCode) 144 { 145 var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode); 146 if (uiSourceCodeItem) { 147 uiSourceCodeItem.reveal(); 148 uiSourceCodeItem.expand(); 149 } 150 }, 151 152 _uiSourceCodeRemoved: function(event) 153 { 154 var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data); 155 this._removeUISourceCode(uiSourceCode); 156 }, 157 158 /** 159 * @param {!WebInspector.UISourceCode} uiSourceCode 160 */ 161 _removeUISourceCode: function(uiSourceCode) 162 { 163 var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode); 164 if (!uiSourceCodeItem) 165 return; 166 this._treeOutline.removeChild(uiSourceCodeItem); 167 this._uiSourceCodeItems.remove(uiSourceCode); 168 }, 169 170 _projectRemoved: function(event) 171 { 172 var project = event.data; 173 project.uiSourceCodes().forEach(this._removeUISourceCode.bind(this)); 174 }, 175 176 __proto__: WebInspector.VBox.prototype 177 } 178 179 /** 180 * @constructor 181 * @extends {TreeElement} 182 * @param {!WebInspector.Revision} revision 183 * @param {!WebInspector.Revision} baseRevision 184 * @param {boolean} allowRevert 185 */ 186 WebInspector.RevisionHistoryTreeElement = function(revision, baseRevision, allowRevert) 187 { 188 TreeElement.call(this, revision.timestamp.toLocaleTimeString(), null, true); 189 this.selectable = false; 190 191 this._revision = revision; 192 this._baseRevision = baseRevision; 193 194 this._revertElement = document.createElement("span"); 195 this._revertElement.className = "revision-history-link"; 196 this._revertElement.textContent = WebInspector.UIString("apply revision content"); 197 this._revertElement.addEventListener("click", this._revision.revertToThis.bind(this._revision), false); 198 if (!allowRevert) 199 this._revertElement.classList.add("hidden"); 200 } 201 202 WebInspector.RevisionHistoryTreeElement.prototype = { 203 onattach: function() 204 { 205 this.listItemElement.classList.add("revision-history-revision"); 206 }, 207 208 onexpand: function() 209 { 210 this.listItemElement.appendChild(this._revertElement); 211 212 if (this._wasExpandedOnce) 213 return; 214 this._wasExpandedOnce = true; 215 216 this.childrenListElement.classList.add("source-code"); 217 if (this._baseRevision) 218 this._baseRevision.requestContent(step1.bind(this)); 219 else 220 this._revision.uiSourceCode.requestOriginalContent(step1.bind(this)); 221 222 /** 223 * @param {?string} baseContent 224 * @this {WebInspector.RevisionHistoryTreeElement} 225 */ 226 function step1(baseContent) 227 { 228 this._revision.requestContent(step2.bind(this, baseContent)); 229 } 230 231 /** 232 * @param {?string} baseContent 233 * @param {?string} newContent 234 * @this {WebInspector.RevisionHistoryTreeElement} 235 */ 236 function step2(baseContent, newContent) 237 { 238 var baseLines = difflib.stringAsLines(baseContent); 239 var newLines = difflib.stringAsLines(newContent); 240 var sm = new difflib.SequenceMatcher(baseLines, newLines); 241 var opcodes = sm.get_opcodes(); 242 var lastWasSeparator = false; 243 244 for (var idx = 0; idx < opcodes.length; idx++) { 245 var code = opcodes[idx]; 246 var change = code[0]; 247 var b = code[1]; 248 var be = code[2]; 249 var n = code[3]; 250 var ne = code[4]; 251 var rowCount = Math.max(be - b, ne - n); 252 var topRows = []; 253 var bottomRows = []; 254 for (var i = 0; i < rowCount; i++) { 255 if (change === "delete" || (change === "replace" && b < be)) { 256 var lineNumber = b++; 257 this._createLine(lineNumber, null, baseLines[lineNumber], "removed"); 258 lastWasSeparator = false; 259 } 260 261 if (change === "insert" || (change === "replace" && n < ne)) { 262 var lineNumber = n++; 263 this._createLine(null, lineNumber, newLines[lineNumber], "added"); 264 lastWasSeparator = false; 265 } 266 267 if (change === "equal") { 268 b++; 269 n++; 270 if (!lastWasSeparator) 271 this._createLine(null, null, " \u2026", "separator"); 272 lastWasSeparator = true; 273 } 274 } 275 } 276 } 277 }, 278 279 oncollapse: function() 280 { 281 this._revertElement.remove(); 282 }, 283 284 /** 285 * @param {?number} baseLineNumber 286 * @param {?number} newLineNumber 287 * @param {string} lineContent 288 * @param {string} changeType 289 */ 290 _createLine: function(baseLineNumber, newLineNumber, lineContent, changeType) 291 { 292 var child = new TreeElement("", null, false); 293 child.selectable = false; 294 this.appendChild(child); 295 var lineElement = document.createElement("span"); 296 297 function appendLineNumber(lineNumber) 298 { 299 var numberString = lineNumber !== null ? numberToStringWithSpacesPadding(lineNumber + 1, 4) : " "; 300 var lineNumberSpan = document.createElement("span"); 301 lineNumberSpan.classList.add("webkit-line-number"); 302 lineNumberSpan.textContent = numberString; 303 child.listItemElement.appendChild(lineNumberSpan); 304 } 305 306 appendLineNumber(baseLineNumber); 307 appendLineNumber(newLineNumber); 308 309 var contentSpan = document.createElement("span"); 310 contentSpan.textContent = lineContent; 311 child.listItemElement.appendChild(contentSpan); 312 child.listItemElement.classList.add("revision-history-line"); 313 child.listItemElement.classList.add("revision-history-line-" + changeType); 314 }, 315 316 allowRevert: function() 317 { 318 this._revertElement.classList.remove("hidden"); 319 }, 320 321 __proto__: TreeElement.prototype 322 } 323