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.set(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", this._revertToOriginal.bind(this, 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     _revertToOriginal: function(uiSourceCode)
    120     {
    121         uiSourceCode.revertToOriginal();
    122 
    123         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
    124             action: WebInspector.UserMetrics.UserActionNames.ApplyOriginalContent,
    125             url: uiSourceCode.url
    126         });
    127     },
    128 
    129     /**
    130      * @param {!WebInspector.UISourceCode} uiSourceCode
    131      */
    132     _clearHistory: function(uiSourceCode)
    133     {
    134         uiSourceCode.revertAndClearHistory(this._removeUISourceCode.bind(this));
    135 
    136         WebInspector.notifications.dispatchEventToListeners(WebInspector.UserMetrics.UserAction, {
    137             action: WebInspector.UserMetrics.UserActionNames.RevertRevision,
    138             url: uiSourceCode.url
    139         });
    140     },
    141 
    142     _revisionAdded: function(event)
    143     {
    144         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data.uiSourceCode);
    145         var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
    146         if (!uiSourceCodeItem) {
    147             uiSourceCodeItem = this._createUISourceCodeItem(uiSourceCode);
    148             return;
    149         }
    150 
    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);
    156     },
    157 
    158     /**
    159      * @param {!WebInspector.UISourceCode} uiSourceCode
    160      */
    161     _revealUISourceCode: function(uiSourceCode)
    162     {
    163         var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
    164         if (uiSourceCodeItem) {
    165             uiSourceCodeItem.reveal();
    166             uiSourceCodeItem.expand();
    167         }
    168     },
    169 
    170     _uiSourceCodeRemoved: function(event)
    171     {
    172         var uiSourceCode = /** @type {!WebInspector.UISourceCode} */ (event.data);
    173         this._removeUISourceCode(uiSourceCode);
    174     },
    175 
    176     /**
    177      * @param {!WebInspector.UISourceCode} uiSourceCode
    178      */
    179     _removeUISourceCode: function(uiSourceCode)
    180     {
    181         var uiSourceCodeItem = this._uiSourceCodeItems.get(uiSourceCode);
    182         if (!uiSourceCodeItem)
    183             return;
    184         this._treeOutline.removeChild(uiSourceCodeItem);
    185         this._uiSourceCodeItems.remove(uiSourceCode);
    186     },
    187 
    188     _projectRemoved: function(event)
    189     {
    190         var project = event.data;
    191         project.uiSourceCodes().forEach(this._removeUISourceCode.bind(this));
    192     },
    193 
    194     __proto__: WebInspector.VBox.prototype
    195 }
    196 
    197 /**
    198  * @constructor
    199  * @extends {TreeElement}
    200  * @param {!WebInspector.Revision} revision
    201  * @param {!WebInspector.Revision} baseRevision
    202  * @param {boolean} allowRevert
    203  */
    204 WebInspector.RevisionHistoryTreeElement = function(revision, baseRevision, allowRevert)
    205 {
    206     TreeElement.call(this, revision.timestamp.toLocaleTimeString(), null, true);
    207     this.selectable = false;
    208 
    209     this._revision = revision;
    210     this._baseRevision = baseRevision;
    211 
    212     this._revertElement = document.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);
    216     if (!allowRevert)
    217         this._revertElement.classList.add("hidden");
    218 }
    219 
    220 WebInspector.RevisionHistoryTreeElement.prototype = {
    221     onattach: function()
    222     {
    223         this.listItemElement.classList.add("revision-history-revision");
    224     },
    225 
    226     onexpand: function()
    227     {
    228         this.listItemElement.appendChild(this._revertElement);
    229 
    230         if (this._wasExpandedOnce)
    231             return;
    232         this._wasExpandedOnce = true;
    233 
    234         this.childrenListElement.classList.add("source-code");
    235         if (this._baseRevision)
    236             this._baseRevision.requestContent(step1.bind(this));
    237         else
    238             this._revision.uiSourceCode.requestOriginalContent(step1.bind(this));
    239 
    240         /**
    241          * @param {?string} baseContent
    242          * @this {WebInspector.RevisionHistoryTreeElement}
    243          */
    244         function step1(baseContent)
    245         {
    246             this._revision.requestContent(step2.bind(this, baseContent));
    247         }
    248 
    249         /**
    250          * @param {?string} baseContent
    251          * @param {?string} newContent
    252          * @this {WebInspector.RevisionHistoryTreeElement}
    253          */
    254         function step2(baseContent, newContent)
    255         {
    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;
    261 
    262             for (var idx = 0; idx < opcodes.length; idx++) {
    263                 var code = opcodes[idx];
    264                 var change = code[0];
    265                 var b = code[1];
    266                 var be = code[2];
    267                 var n = code[3];
    268                 var ne = code[4];
    269                 var rowCount = Math.max(be - b, ne - n);
    270                 var topRows = [];
    271                 var bottomRows = [];
    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;
    277                     }
    278 
    279                     if (change === "insert" || (change === "replace" && n < ne)) {
    280                         var lineNumber = n++;
    281                         this._createLine(null, lineNumber, newLines[lineNumber], "added");
    282                         lastWasSeparator = false;
    283                     }
    284 
    285                     if (change === "equal") {
    286                         b++;
    287                         n++;
    288                         if (!lastWasSeparator)
    289                             this._createLine(null, null, "    \u2026", "separator");
    290                         lastWasSeparator = true;
    291                     }
    292                 }
    293             }
    294         }
    295     },
    296 
    297     oncollapse: function()
    298     {
    299         this._revertElement.remove();
    300     },
    301 
    302     /**
    303      * @param {?number} baseLineNumber
    304      * @param {?number} newLineNumber
    305      * @param {string} lineContent
    306      * @param {string} changeType
    307      */
    308     _createLine: function(baseLineNumber, newLineNumber, lineContent, changeType)
    309     {
    310         var child = new TreeElement("", null, false);
    311         child.selectable = false;
    312         this.appendChild(child);
    313         var lineElement = document.createElement("span");
    314 
    315         function appendLineNumber(lineNumber)
    316         {
    317             var numberString = lineNumber !== null ? numberToStringWithSpacesPadding(lineNumber + 1, 4) : spacesPadding(4);
    318             var lineNumberSpan = document.createElement("span");
    319             lineNumberSpan.classList.add("webkit-line-number");
    320             lineNumberSpan.textContent = numberString;
    321             child.listItemElement.appendChild(lineNumberSpan);
    322         }
    323 
    324         appendLineNumber(baseLineNumber);
    325         appendLineNumber(newLineNumber);
    326 
    327         var contentSpan = document.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);
    332     },
    333 
    334     allowRevert: function()
    335     {
    336         this._revertElement.classList.remove("hidden");
    337     },
    338 
    339     __proto__: TreeElement.prototype
    340 }
    341