Home | History | Annotate | Download | only in workspace
      1 /*
      2  * Copyright (C) 2011 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 /**
     33  * @constructor
     34  * @extends {WebInspector.Object}
     35  * @implements {WebInspector.ContentProvider}
     36  * @param {!WebInspector.Project} project
     37  * @param {string} parentPath
     38  * @param {string} name
     39  * @param {string} originURL
     40  * @param {string} url
     41  * @param {!WebInspector.ResourceType} contentType
     42  */
     43 WebInspector.UISourceCode = function(project, parentPath, name, originURL, url, contentType)
     44 {
     45     this._project = project;
     46     this._parentPath = parentPath;
     47     this._name = name;
     48     this._originURL = originURL;
     49     this._url = url;
     50     this._contentType = contentType;
     51     /** @type {!Array.<function(?string)>} */
     52     this._requestContentCallbacks = [];
     53 
     54     /** @type {!Array.<!WebInspector.Revision>} */
     55     this.history = [];
     56 }
     57 
     58 /**
     59  * @enum {string}
     60  */
     61 WebInspector.UISourceCode.Events = {
     62     WorkingCopyChanged: "WorkingCopyChanged",
     63     WorkingCopyCommitted: "WorkingCopyCommitted",
     64     TitleChanged: "TitleChanged",
     65     SavedStateUpdated: "SavedStateUpdated",
     66     SourceMappingChanged: "SourceMappingChanged",
     67 }
     68 
     69 WebInspector.UISourceCode.prototype = {
     70     /**
     71      * @return {string}
     72      */
     73     get url()
     74     {
     75         return this._url;
     76     },
     77 
     78     /**
     79      * @return {string}
     80      */
     81     name: function()
     82     {
     83         return this._name;
     84     },
     85 
     86     /**
     87      * @return {string}
     88      */
     89     parentPath: function()
     90     {
     91         return this._parentPath;
     92     },
     93 
     94     /**
     95      * @return {string}
     96      */
     97     path: function()
     98     {
     99         return this._parentPath ? this._parentPath + "/" + this._name : this._name;
    100     },
    101 
    102     /**
    103      * @return {string}
    104      */
    105     fullDisplayName: function()
    106     {
    107         return this._project.displayName() + "/" + (this._parentPath ? this._parentPath + "/" : "") + this.displayName(true);
    108     },
    109 
    110     /**
    111      * @param {boolean=} skipTrim
    112      * @return {string}
    113      */
    114     displayName: function(skipTrim)
    115     {
    116         var displayName = this.name() || WebInspector.UIString("(index)");
    117         return skipTrim ? displayName : displayName.trimEnd(100);
    118     },
    119 
    120     /**
    121      * @return {string}
    122      */
    123     uri: function()
    124     {
    125         var path = this.path();
    126         if (!this._project.id())
    127             return path;
    128         if (!path)
    129             return this._project.id();
    130         return this._project.id() + "/" + path;
    131     },
    132 
    133     /**
    134      * @return {string}
    135      */
    136     originURL: function()
    137     {
    138         return this._originURL;
    139     },
    140 
    141     /**
    142      * @return {boolean}
    143      */
    144     canRename: function()
    145     {
    146         return this._project.canRename();
    147     },
    148 
    149     /**
    150      * @param {string} newName
    151      * @param {function(boolean)} callback
    152      */
    153     rename: function(newName, callback)
    154     {
    155         this._project.rename(this, newName, innerCallback.bind(this));
    156 
    157         /**
    158          * @param {boolean} success
    159          * @param {string=} newName
    160          * @param {string=} newURL
    161          * @param {string=} newOriginURL
    162          * @param {!WebInspector.ResourceType=} newContentType
    163          * @this {WebInspector.UISourceCode}
    164          */
    165         function innerCallback(success, newName, newURL, newOriginURL, newContentType)
    166         {
    167             if (success)
    168                 this._updateName(/** @type {string} */ (newName), /** @type {string} */ (newURL), /** @type {string} */ (newOriginURL), /** @type {!WebInspector.ResourceType} */ (newContentType));
    169             callback(success);
    170         }
    171     },
    172 
    173     remove: function()
    174     {
    175         this._project.deleteFile(this.path());
    176     },
    177 
    178     /**
    179      * @param {string} name
    180      * @param {string} url
    181      * @param {string} originURL
    182      * @param {!WebInspector.ResourceType=} contentType
    183      */
    184     _updateName: function(name, url, originURL, contentType)
    185     {
    186         var oldURI = this.uri();
    187         this._name = name;
    188         if (url)
    189             this._url = url;
    190         if (originURL)
    191             this._originURL = originURL;
    192         if (contentType)
    193             this._contentType = contentType;
    194         this.dispatchEventToListeners(WebInspector.UISourceCode.Events.TitleChanged, oldURI);
    195     },
    196 
    197     /**
    198      * @return {string}
    199      */
    200     contentURL: function()
    201     {
    202         return this.originURL();
    203     },
    204 
    205     /**
    206      * @return {!WebInspector.ResourceType}
    207      */
    208     contentType: function()
    209     {
    210         return this._contentType;
    211     },
    212 
    213     /**
    214      * @return {!WebInspector.Project}
    215      */
    216     project: function()
    217     {
    218         return this._project;
    219     },
    220 
    221     /**
    222      * @param {function(?Date, ?number)} callback
    223      */
    224     requestMetadata: function(callback)
    225     {
    226         this._project.requestMetadata(this, callback);
    227     },
    228 
    229     /**
    230      * @param {function(?string)} callback
    231      */
    232     requestContent: function(callback)
    233     {
    234         if (this._content || this._contentLoaded) {
    235             callback(this._content);
    236             return;
    237         }
    238         this._requestContentCallbacks.push(callback);
    239         if (this._requestContentCallbacks.length === 1)
    240             this._project.requestFileContent(this, this._fireContentAvailable.bind(this));
    241     },
    242 
    243     /**
    244      * @param {function()} callback
    245      */
    246     _pushCheckContentUpdatedCallback: function(callback)
    247     {
    248         if (!this._checkContentUpdatedCallbacks)
    249             this._checkContentUpdatedCallbacks = [];
    250         this._checkContentUpdatedCallbacks.push(callback);
    251     },
    252 
    253     _terminateContentCheck: function()
    254     {
    255         delete this._checkingContent;
    256         if (this._checkContentUpdatedCallbacks) {
    257             this._checkContentUpdatedCallbacks.forEach(function(callback) { callback(); });
    258             delete this._checkContentUpdatedCallbacks;
    259         }
    260     },
    261 
    262     /**
    263      * @param {function()=} callback
    264      */
    265     checkContentUpdated: function(callback)
    266     {
    267         callback = callback || function() {};
    268         if (!this._project.canSetFileContent()) {
    269             callback();
    270             return;
    271         }
    272         this._pushCheckContentUpdatedCallback(callback);
    273 
    274         if (this._checkingContent) {
    275             return;
    276         }
    277         this._checkingContent = true;
    278         this._project.requestFileContent(this, contentLoaded.bind(this));
    279 
    280         /**
    281          * @param {?string} updatedContent
    282          * @this {WebInspector.UISourceCode}
    283          */
    284         function contentLoaded(updatedContent)
    285         {
    286             if (updatedContent === null) {
    287                 var workingCopy = this.workingCopy();
    288                 this._commitContent("", false);
    289                 this.setWorkingCopy(workingCopy);
    290                 this._terminateContentCheck();
    291                 return;
    292             }
    293             if (typeof this._lastAcceptedContent === "string" && this._lastAcceptedContent === updatedContent) {
    294                 this._terminateContentCheck();
    295                 return;
    296             }
    297             if (this._content === updatedContent) {
    298                 delete this._lastAcceptedContent;
    299                 this._terminateContentCheck();
    300                 return;
    301             }
    302 
    303             if (!this.isDirty()) {
    304                 this._commitContent(updatedContent, false);
    305                 this._terminateContentCheck();
    306                 return;
    307             }
    308 
    309             var shouldUpdate = window.confirm(WebInspector.UIString("This file was changed externally. Would you like to reload it?"));
    310             if (shouldUpdate)
    311                 this._commitContent(updatedContent, false);
    312             else
    313                 this._lastAcceptedContent = updatedContent;
    314             this._terminateContentCheck();
    315         }
    316     },
    317 
    318     /**
    319      * @param {function(?string)} callback
    320      */
    321     requestOriginalContent: function(callback)
    322     {
    323         this._project.requestFileContent(this, callback);
    324     },
    325 
    326     /**
    327      * @param {string} content
    328      * @param {boolean} shouldSetContentInProject
    329      */
    330     _commitContent: function(content, shouldSetContentInProject)
    331     {
    332         delete this._lastAcceptedContent;
    333         this._content = content;
    334         this._contentLoaded = true;
    335 
    336         var lastRevision = this.history.length ? this.history[this.history.length - 1] : null;
    337         if (!lastRevision || lastRevision._content !== this._content) {
    338             var revision = new WebInspector.Revision(this, this._content, new Date());
    339             this.history.push(revision);
    340         }
    341 
    342         this._innerResetWorkingCopy();
    343         this._hasCommittedChanges = true;
    344         this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyCommitted);
    345         if (this._url && WebInspector.fileManager.isURLSaved(this._url))
    346             this._saveURLWithFileManager(false, this._content);
    347         if (shouldSetContentInProject)
    348             this._project.setFileContent(this, this._content, function() { });
    349     },
    350 
    351     /**
    352      * @param {boolean} forceSaveAs
    353      * @param {?string} content
    354      */
    355     _saveURLWithFileManager: function(forceSaveAs, content)
    356     {
    357         WebInspector.fileManager.save(this._url, /** @type {string} */ (content), forceSaveAs, callback.bind(this));
    358         WebInspector.fileManager.close(this._url);
    359 
    360         /**
    361          * @param {boolean} accepted
    362          * @this {WebInspector.UISourceCode}
    363          */
    364         function callback(accepted)
    365         {
    366             if (!accepted)
    367                 return;
    368             this._savedWithFileManager = true;
    369             this.dispatchEventToListeners(WebInspector.UISourceCode.Events.SavedStateUpdated);
    370         }
    371     },
    372 
    373     /**
    374      * @param {boolean} forceSaveAs
    375      */
    376     save: function(forceSaveAs)
    377     {
    378         if (this.project().type() === WebInspector.projectTypes.FileSystem || this.project().type() === WebInspector.projectTypes.Snippets) {
    379             this.commitWorkingCopy();
    380             return;
    381         }
    382         if (this.isDirty()) {
    383             this._saveURLWithFileManager(forceSaveAs, this.workingCopy());
    384             this.commitWorkingCopy();
    385             return;
    386         }
    387         this.requestContent(this._saveURLWithFileManager.bind(this, forceSaveAs));
    388     },
    389 
    390     /**
    391      * @return {boolean}
    392      */
    393     hasUnsavedCommittedChanges: function()
    394     {
    395         if (this._savedWithFileManager || this.project().canSetFileContent() || this._project.isServiceProject())
    396             return false;
    397         if (this._project.workspace().hasResourceContentTrackingExtensions())
    398             return false;
    399         return !!this._hasCommittedChanges;
    400     },
    401 
    402     /**
    403      * @param {string} content
    404      */
    405     addRevision: function(content)
    406     {
    407         this._commitContent(content, true);
    408     },
    409 
    410     revertToOriginal: function()
    411     {
    412         /**
    413          * @this {WebInspector.UISourceCode}
    414          * @param {?string} content
    415          */
    416         function callback(content)
    417         {
    418             if (typeof content !== "string")
    419                 return;
    420 
    421             this.addRevision(content);
    422         }
    423 
    424         this.requestOriginalContent(callback.bind(this));
    425     },
    426 
    427     /**
    428      * @param {function(!WebInspector.UISourceCode)} callback
    429      */
    430     revertAndClearHistory: function(callback)
    431     {
    432         /**
    433          * @this {WebInspector.UISourceCode}
    434          * @param {?string} content
    435          */
    436         function revert(content)
    437         {
    438             if (typeof content !== "string")
    439                 return;
    440 
    441             this.addRevision(content);
    442             this.history = [];
    443             callback(this);
    444         }
    445 
    446         this.requestOriginalContent(revert.bind(this));
    447     },
    448 
    449     /**
    450      * @return {string}
    451      */
    452     workingCopy: function()
    453     {
    454         if (this._workingCopyGetter) {
    455             this._workingCopy = this._workingCopyGetter();
    456             delete this._workingCopyGetter;
    457         }
    458         if (this.isDirty())
    459             return this._workingCopy;
    460         return this._content;
    461     },
    462 
    463     resetWorkingCopy: function()
    464     {
    465         this._innerResetWorkingCopy();
    466         this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged);
    467     },
    468 
    469     _innerResetWorkingCopy: function()
    470     {
    471         delete this._workingCopy;
    472         delete this._workingCopyGetter;
    473     },
    474 
    475     /**
    476      * @param {string} newWorkingCopy
    477      */
    478     setWorkingCopy: function(newWorkingCopy)
    479     {
    480         this._workingCopy = newWorkingCopy;
    481         delete this._workingCopyGetter;
    482         this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged);
    483     },
    484 
    485     setWorkingCopyGetter: function(workingCopyGetter)
    486     {
    487         this._workingCopyGetter = workingCopyGetter;
    488         this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged);
    489     },
    490 
    491     removeWorkingCopyGetter: function()
    492     {
    493         if (!this._workingCopyGetter)
    494             return;
    495         this._workingCopy = this._workingCopyGetter();
    496         delete this._workingCopyGetter;
    497     },
    498 
    499     commitWorkingCopy: function()
    500     {
    501         if (this.isDirty())
    502             this._commitContent(this.workingCopy(), true);
    503     },
    504 
    505     /**
    506      * @return {boolean}
    507      */
    508     isDirty: function()
    509     {
    510         return typeof this._workingCopy !== "undefined" || typeof this._workingCopyGetter !== "undefined";
    511     },
    512 
    513     /**
    514      * @return {string}
    515      */
    516     highlighterType: function()
    517     {
    518         if (this._project.type() === WebInspector.projectTypes.Network)
    519             return this.contentType().canonicalMimeType();
    520         var lastIndexOfDot = this._name.lastIndexOf(".");
    521         var extension = lastIndexOfDot !== -1 ? this._name.substr(lastIndexOfDot + 1) : "";
    522         var indexOfQuestionMark = extension.indexOf("?");
    523         if (indexOfQuestionMark !== -1)
    524             extension = extension.substr(0, indexOfQuestionMark);
    525         var mimeType = WebInspector.ResourceType.mimeTypesForExtensions[extension.toLowerCase()];
    526         return mimeType || this.contentType().canonicalMimeType();
    527     },
    528 
    529     /**
    530      * @return {?string}
    531      */
    532     content: function()
    533     {
    534         return this._content;
    535     },
    536 
    537     /**
    538      * @param {string} query
    539      * @param {boolean} caseSensitive
    540      * @param {boolean} isRegex
    541      * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
    542      */
    543     searchInContent: function(query, caseSensitive, isRegex, callback)
    544     {
    545         var content = this.content();
    546         if (content) {
    547             var provider = new WebInspector.StaticContentProvider(this.contentType(), content);
    548             provider.searchInContent(query, caseSensitive, isRegex, callback);
    549             return;
    550         }
    551 
    552         this._project.searchInFileContent(this, query, caseSensitive, isRegex, callback);
    553     },
    554 
    555     /**
    556      * @param {?string} content
    557      */
    558     _fireContentAvailable: function(content)
    559     {
    560         this._contentLoaded = true;
    561         this._content = content;
    562 
    563         var callbacks = this._requestContentCallbacks.slice();
    564         this._requestContentCallbacks = [];
    565         for (var i = 0; i < callbacks.length; ++i)
    566             callbacks[i](content);
    567     },
    568 
    569     /**
    570      * @return {boolean}
    571      */
    572     contentLoaded: function()
    573     {
    574         return this._contentLoaded;
    575     },
    576 
    577     /**
    578      * @param {number} lineNumber
    579      * @param {number=} columnNumber
    580      * @return {!WebInspector.UILocation}
    581      */
    582     uiLocation: function(lineNumber, columnNumber)
    583     {
    584         if (typeof columnNumber === "undefined")
    585             columnNumber = 0;
    586         return new WebInspector.UILocation(this, lineNumber, columnNumber);
    587     },
    588 
    589     __proto__: WebInspector.Object.prototype
    590 }
    591 
    592 /**
    593  * @constructor
    594  * @param {!WebInspector.UISourceCode} uiSourceCode
    595  * @param {number} lineNumber
    596  * @param {number} columnNumber
    597  */
    598 WebInspector.UILocation = function(uiSourceCode, lineNumber, columnNumber)
    599 {
    600     this.uiSourceCode = uiSourceCode;
    601     this.lineNumber = lineNumber;
    602     this.columnNumber = columnNumber;
    603 }
    604 
    605 WebInspector.UILocation.prototype = {
    606     /**
    607      * @return {string}
    608      */
    609     linkText: function()
    610     {
    611         var linkText = this.uiSourceCode.displayName();
    612         if (typeof this.lineNumber === "number")
    613             linkText += ":" + (this.lineNumber + 1);
    614         return linkText;
    615     },
    616 
    617     /**
    618      * @return {string}
    619      */
    620     id: function()
    621     {
    622         return this.uiSourceCode.uri() + ":" + this.lineNumber + ":" + this.columnNumber;
    623     },
    624 }
    625 
    626 /**
    627  * @constructor
    628  * @implements {WebInspector.ContentProvider}
    629  * @param {!WebInspector.UISourceCode} uiSourceCode
    630  * @param {?string|undefined} content
    631  * @param {!Date} timestamp
    632  */
    633 WebInspector.Revision = function(uiSourceCode, content, timestamp)
    634 {
    635     this._uiSourceCode = uiSourceCode;
    636     this._content = content;
    637     this._timestamp = timestamp;
    638 }
    639 
    640 WebInspector.Revision.prototype = {
    641     /**
    642      * @return {!WebInspector.UISourceCode}
    643      */
    644     get uiSourceCode()
    645     {
    646         return this._uiSourceCode;
    647     },
    648 
    649     /**
    650      * @return {!Date}
    651      */
    652     get timestamp()
    653     {
    654         return this._timestamp;
    655     },
    656 
    657     /**
    658      * @return {?string}
    659      */
    660     get content()
    661     {
    662         return this._content || null;
    663     },
    664 
    665     revertToThis: function()
    666     {
    667         /**
    668          * @param {string} content
    669          * @this {WebInspector.Revision}
    670          */
    671         function revert(content)
    672         {
    673             if (this._uiSourceCode._content !== content)
    674                 this._uiSourceCode.addRevision(content);
    675         }
    676         this.requestContent(revert.bind(this));
    677     },
    678 
    679     /**
    680      * @return {string}
    681      */
    682     contentURL: function()
    683     {
    684         return this._uiSourceCode.originURL();
    685     },
    686 
    687     /**
    688      * @return {!WebInspector.ResourceType}
    689      */
    690     contentType: function()
    691     {
    692         return this._uiSourceCode.contentType();
    693     },
    694 
    695     /**
    696      * @param {function(string)} callback
    697      */
    698     requestContent: function(callback)
    699     {
    700         callback(this._content || "");
    701     },
    702 
    703     /**
    704      * @param {string} query
    705      * @param {boolean} caseSensitive
    706      * @param {boolean} isRegex
    707      * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
    708      */
    709     searchInContent: function(query, caseSensitive, isRegex, callback)
    710     {
    711         callback([]);
    712     }
    713 }
    714