Home | History | Annotate | Download | only in sources
      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