2 * Copyright (C) 2011 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.
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
41 * @param {!WebInspector.ResourceType} contentType
43 WebInspector.UISourceCode = function(project, parentPath, name, originURL, url, contentType)
45 this._project = project;
46 this._parentPath = parentPath;
48 this._originURL = originURL;
50 this._contentType = contentType;
51 /** @type {!Array.<function(?string)>} */
52 this._requestContentCallbacks = [];
54 /** @type {!Array.<!WebInspector.Revision>} */
61 WebInspector.UISourceCode.Events = {
62 WorkingCopyChanged: "WorkingCopyChanged",
63 WorkingCopyCommitted: "WorkingCopyCommitted",
64 TitleChanged: "TitleChanged",
65 SavedStateUpdated: "SavedStateUpdated",
66 SourceMappingChanged: "SourceMappingChanged",
69 WebInspector.UISourceCode.prototype = {
73 networkURL: function()
89 parentPath: function()
91 return this._parentPath;
99 return this._parentPath ? this._parentPath + "/" + this._name : this._name;
105 fullDisplayName: function()
107 return this._project.displayName() + "/" + (this._parentPath ? this._parentPath + "/" : "") + this.displayName(true);
111 * @param {boolean=} skipTrim
114 displayName: function(skipTrim)
116 var displayName = this.name() || WebInspector.UIString("(index)");
117 return skipTrim ? displayName : displayName.trimEnd(100);
125 var path = this.path();
126 if (!this._project.url())
129 return this._project.url();
130 return this._project.url() + "/" + path;
136 originURL: function()
138 return this._originURL;
144 canRename: function()
146 return this._project.canRename();
150 * @param {string} newName
151 * @param {function(boolean)} callback
153 rename: function(newName, callback)
155 this._project.rename(this, newName, innerCallback.bind(this));
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}
165 function innerCallback(success, newName, newURL, newOriginURL, newContentType)
168 this._updateName(/** @type {string} */ (newName), /** @type {string} */ (newURL), /** @type {string} */ (newOriginURL), /** @type {!WebInspector.ResourceType} */ (newContentType));
175 this._project.deleteFile(this.path());
179 * @param {string} name
180 * @param {string} url
181 * @param {string} originURL
182 * @param {!WebInspector.ResourceType=} contentType
184 _updateName: function(name, url, originURL, contentType)
186 var oldURI = this.uri();
191 this._originURL = originURL;
193 this._contentType = contentType;
194 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.TitleChanged, oldURI);
201 contentURL: function()
203 return this.originURL();
208 * @return {!WebInspector.ResourceType}
210 contentType: function()
212 return this._contentType;
216 * @return {!WebInspector.Project}
220 return this._project;
224 * @param {function(?Date, ?number)} callback
226 requestMetadata: function(callback)
228 this._project.requestMetadata(this, callback);
233 * @param {function(?string)} callback
235 requestContent: function(callback)
237 if (this._content || this._contentLoaded) {
238 callback(this._content);
241 this._requestContentCallbacks.push(callback);
242 if (this._requestContentCallbacks.length === 1)
243 this._project.requestFileContent(this, this._fireContentAvailable.bind(this));
247 * @param {function()} callback
249 _pushCheckContentUpdatedCallback: function(callback)
251 if (!this._checkContentUpdatedCallbacks)
252 this._checkContentUpdatedCallbacks = [];
253 this._checkContentUpdatedCallbacks.push(callback);
256 _terminateContentCheck: function()
258 delete this._checkingContent;
259 if (this._checkContentUpdatedCallbacks) {
260 this._checkContentUpdatedCallbacks.forEach(function(callback) { callback(); });
261 delete this._checkContentUpdatedCallbacks;
266 * @param {function()=} callback
268 checkContentUpdated: function(callback)
270 callback = callback || function() {};
271 if (!this._project.canSetFileContent()) {
275 this._pushCheckContentUpdatedCallback(callback);
277 if (this._checkingContent) {
280 this._checkingContent = true;
281 this._project.requestFileContent(this, contentLoaded.bind(this));
284 * @param {?string} updatedContent
285 * @this {WebInspector.UISourceCode}
287 function contentLoaded(updatedContent)
289 if (updatedContent === null) {
290 var workingCopy = this.workingCopy();
291 this._commitContent("", false);
292 this.setWorkingCopy(workingCopy);
293 this._terminateContentCheck();
296 if (typeof this._lastAcceptedContent === "string" && this._lastAcceptedContent === updatedContent) {
297 this._terminateContentCheck();
301 if (this._content === updatedContent) {
302 delete this._lastAcceptedContent;
303 this._terminateContentCheck();
307 if (!this.isDirty() || this._workingCopy === updatedContent) {
308 this._commitContent(updatedContent, false);
309 this._terminateContentCheck();
313 var shouldUpdate = window.confirm(WebInspector.UIString("This file was changed externally. Would you like to reload it?"));
315 this._commitContent(updatedContent, false);
317 this._lastAcceptedContent = updatedContent;
318 this._terminateContentCheck();
323 * @param {function(?string)} callback
325 requestOriginalContent: function(callback)
327 this._project.requestFileContent(this, callback);
331 * @param {string} content
332 * @param {boolean} shouldSetContentInProject
334 _commitContent: function(content, shouldSetContentInProject)
336 delete this._lastAcceptedContent;
337 this._content = content;
338 this._contentLoaded = true;
340 var lastRevision = this.history.length ? this.history[this.history.length - 1] : null;
341 if (!lastRevision || lastRevision._content !== this._content) {
342 var revision = new WebInspector.Revision(this, this._content, new Date());
343 this.history.push(revision);
346 this._innerResetWorkingCopy();
347 this._hasCommittedChanges = true;
348 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyCommitted);
349 if (this._url && WebInspector.fileManager.isURLSaved(this._url))
350 this._saveURLWithFileManager(false, this._content);
351 if (shouldSetContentInProject)
352 this._project.setFileContent(this, this._content, function() { });
356 * @param {boolean} forceSaveAs
357 * @param {?string} content
359 _saveURLWithFileManager: function(forceSaveAs, content)
361 WebInspector.fileManager.save(this._url, /** @type {string} */ (content), forceSaveAs, callback.bind(this));
362 WebInspector.fileManager.close(this._url);
365 * @param {boolean} accepted
366 * @this {WebInspector.UISourceCode}
368 function callback(accepted)
370 this._savedWithFileManager = accepted;
372 this._hasCommittedChanges = false;
373 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.SavedStateUpdated);
378 * @param {boolean} forceSaveAs
380 save: function(forceSaveAs)
382 if (this.project().type() === WebInspector.projectTypes.FileSystem || this.project().type() === WebInspector.projectTypes.Snippets) {
383 this.commitWorkingCopy();
386 if (this.isDirty()) {
387 this._saveURLWithFileManager(forceSaveAs, this.workingCopy());
388 this.commitWorkingCopy();
391 this.requestContent(this._saveURLWithFileManager.bind(this, forceSaveAs));
397 hasUnsavedCommittedChanges: function()
399 if (this._savedWithFileManager || this.project().canSetFileContent() || this._project.isServiceProject())
401 if (this._project.workspace().hasResourceContentTrackingExtensions())
403 return !!this._hasCommittedChanges;
407 * @param {string} content
409 addRevision: function(content)
411 this._commitContent(content, true);
414 revertToOriginal: function()
417 * @this {WebInspector.UISourceCode}
418 * @param {?string} content
420 function callback(content)
422 if (typeof content !== "string")
425 this.addRevision(content);
428 WebInspector.userMetrics.RevisionApplied.record();
429 this.requestOriginalContent(callback.bind(this));
433 * @param {function(!WebInspector.UISourceCode)} callback
435 revertAndClearHistory: function(callback)
438 * @this {WebInspector.UISourceCode}
439 * @param {?string} content
441 function revert(content)
443 if (typeof content !== "string")
446 this.addRevision(content);
451 WebInspector.userMetrics.RevisionApplied.record();
452 this.requestOriginalContent(revert.bind(this));
458 workingCopy: function()
460 if (this._workingCopyGetter) {
461 this._workingCopy = this._workingCopyGetter();
462 delete this._workingCopyGetter;
465 return this._workingCopy;
466 return this._content;
469 resetWorkingCopy: function()
471 this._innerResetWorkingCopy();
472 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged);
475 _innerResetWorkingCopy: function()
477 delete this._workingCopy;
478 delete this._workingCopyGetter;
482 * @param {string} newWorkingCopy
484 setWorkingCopy: function(newWorkingCopy)
486 this._workingCopy = newWorkingCopy;
487 delete this._workingCopyGetter;
488 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged);
489 this._project.workspace().dispatchEventToListeners(WebInspector.Workspace.Events.UISourceCodeWorkingCopyChanged, { uiSourceCode: this });
492 setWorkingCopyGetter: function(workingCopyGetter)
494 this._workingCopyGetter = workingCopyGetter;
495 this.dispatchEventToListeners(WebInspector.UISourceCode.Events.WorkingCopyChanged);
496 this._project.workspace().dispatchEventToListeners(WebInspector.Workspace.Events.UISourceCodeWorkingCopyChanged, { uiSourceCode: this });
499 removeWorkingCopyGetter: function()
501 if (!this._workingCopyGetter)
503 this._workingCopy = this._workingCopyGetter();
504 delete this._workingCopyGetter;
507 commitWorkingCopy: function()
510 this._commitContent(this.workingCopy(), true);
518 return typeof this._workingCopy !== "undefined" || typeof this._workingCopyGetter !== "undefined";
524 extension: function()
526 var lastIndexOfDot = this._name.lastIndexOf(".");
527 var extension = lastIndexOfDot !== -1 ? this._name.substr(lastIndexOfDot + 1) : "";
528 var indexOfQuestionMark = extension.indexOf("?");
529 if (indexOfQuestionMark !== -1)
530 extension = extension.substr(0, indexOfQuestionMark);
539 return this._content;
544 * @param {string} query
545 * @param {boolean} caseSensitive
546 * @param {boolean} isRegex
547 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
549 searchInContent: function(query, caseSensitive, isRegex, callback)
551 var content = this.content();
553 WebInspector.StaticContentProvider.searchInContent(content, query, caseSensitive, isRegex, callback);
557 this._project.searchInFileContent(this, query, caseSensitive, isRegex, callback);
561 * @param {?string} content
563 _fireContentAvailable: function(content)
565 this._contentLoaded = true;
566 this._content = content;
568 var callbacks = this._requestContentCallbacks.slice();
569 this._requestContentCallbacks = [];
570 for (var i = 0; i < callbacks.length; ++i)
571 callbacks[i](content);
577 contentLoaded: function()
579 return this._contentLoaded;
583 * @param {number} lineNumber
584 * @param {number=} columnNumber
585 * @return {!WebInspector.UILocation}
587 uiLocation: function(lineNumber, columnNumber)
589 if (typeof columnNumber === "undefined")
591 return new WebInspector.UILocation(this, lineNumber, columnNumber);
594 __proto__: WebInspector.Object.prototype
599 * @param {!WebInspector.UISourceCode} uiSourceCode
600 * @param {number} lineNumber
601 * @param {number} columnNumber
603 WebInspector.UILocation = function(uiSourceCode, lineNumber, columnNumber)
605 this.uiSourceCode = uiSourceCode;
606 this.lineNumber = lineNumber;
607 this.columnNumber = columnNumber;
610 WebInspector.UILocation.prototype = {
616 var linkText = this.uiSourceCode.displayName();
617 if (typeof this.lineNumber === "number")
618 linkText += ":" + (this.lineNumber + 1);
627 return this.uiSourceCode.project().id() + ":" + this.uiSourceCode.uri() + ":" + this.lineNumber + ":" + this.columnNumber;
633 toUIString: function()
635 return this.uiSourceCode.uri() + ":" + (this.lineNumber + 1);
641 * @implements {WebInspector.ContentProvider}
642 * @param {!WebInspector.UISourceCode} uiSourceCode
643 * @param {?string|undefined} content
644 * @param {!Date} timestamp
646 WebInspector.Revision = function(uiSourceCode, content, timestamp)
648 this._uiSourceCode = uiSourceCode;
649 this._content = content;
650 this._timestamp = timestamp;
653 WebInspector.Revision.prototype = {
655 * @return {!WebInspector.UISourceCode}
659 return this._uiSourceCode;
667 return this._timestamp;
675 return this._content || null;
678 revertToThis: function()
681 * @param {string} content
682 * @this {WebInspector.Revision}
684 function revert(content)
686 if (this._uiSourceCode._content !== content)
687 this._uiSourceCode.addRevision(content);
689 WebInspector.userMetrics.RevisionApplied.record();
690 this.requestContent(revert.bind(this));
697 contentURL: function()
699 return this._uiSourceCode.originURL();
704 * @return {!WebInspector.ResourceType}
706 contentType: function()
708 return this._uiSourceCode.contentType();
713 * @param {function(string)} callback
715 requestContent: function(callback)
717 callback(this._content || "");
722 * @param {string} query
723 * @param {boolean} caseSensitive
724 * @param {boolean} isRegex
725 * @param {function(!Array.<!WebInspector.ContentProvider.SearchMatch>)} callback
727 searchInContent: function(query, caseSensitive, isRegex, callback)