| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| /** |
| * @implements {Common.ContentProvider} |
| * @unrestricted |
| */ |
| export default class UISourceCode extends Common.Object { |
| /** |
| * @param {!Workspace.Project} project |
| * @param {string} url |
| * @param {!Common.ResourceType} contentType |
| */ |
| constructor(project, url, contentType) { |
| super(); |
| this._project = project; |
| this._url = url; |
| |
| const parsedURL = Common.ParsedURL.fromString(url); |
| if (parsedURL) { |
| this._origin = parsedURL.securityOrigin(); |
| this._parentURL = this._origin + parsedURL.folderPathComponents; |
| this._name = parsedURL.lastPathComponent; |
| if (parsedURL.queryParams) { |
| this._name += '?' + parsedURL.queryParams; |
| } |
| } else { |
| this._origin = ''; |
| this._parentURL = ''; |
| this._name = url; |
| } |
| |
| this._contentType = contentType; |
| /** @type {?Promise<!Common.DeferredContent>} */ |
| this._requestContentPromise = null; |
| /** @type {?Platform.Multimap<string, !LineMarker>} */ |
| this._decorations = null; |
| this._hasCommits = false; |
| /** @type {?Set<!Message>} */ |
| this._messages = null; |
| this._contentLoaded = false; |
| /** @type {?Common.DeferredContent} */ |
| this._content = null; |
| this._forceLoadOnCheckContent = false; |
| this._checkingContent = false; |
| /** @type {?string} */ |
| this._lastAcceptedContent = null; |
| /** @type {?string} */ |
| this._workingCopy = null; |
| /** @type {?function() : string} */ |
| this._workingCopyGetter = null; |
| } |
| |
| /** |
| * @return {!Promise<?UISourceCodeMetadata>} |
| */ |
| requestMetadata() { |
| return this._project.requestMetadata(this); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| name() { |
| return this._name; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| mimeType() { |
| return this._project.mimeType(this); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| url() { |
| return this._url; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| parentURL() { |
| return this._parentURL; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| origin() { |
| return this._origin; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| fullDisplayName() { |
| return this._project.fullDisplayName(this); |
| } |
| |
| /** |
| * @param {boolean=} skipTrim |
| * @return {string} |
| */ |
| displayName(skipTrim) { |
| if (!this._name) { |
| return Common.UIString('(index)'); |
| } |
| let name = this._name; |
| try { |
| if (this.project().type() === Workspace.projectTypes.FileSystem) { |
| name = unescape(name); |
| } else { |
| name = decodeURI(name); |
| } |
| } catch (e) { |
| } |
| return skipTrim ? name : name.trimEndWithMaxLength(100); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| canRename() { |
| return this._project.canRename(); |
| } |
| |
| /** |
| * @param {string} newName |
| * @return {!Promise<boolean>} |
| */ |
| rename(newName) { |
| let fulfill; |
| const promise = new Promise(x => fulfill = x); |
| this._project.rename(this, newName, innerCallback.bind(this)); |
| return promise; |
| |
| /** |
| * @param {boolean} success |
| * @param {string=} newName |
| * @param {string=} newURL |
| * @param {!Common.ResourceType=} newContentType |
| * @this {UISourceCode} |
| */ |
| function innerCallback(success, newName, newURL, newContentType) { |
| if (success) { |
| this._updateName( |
| /** @type {string} */ (newName), /** @type {string} */ (newURL), |
| /** @type {!Common.ResourceType} */ (newContentType)); |
| } |
| fulfill(success); |
| } |
| } |
| |
| remove() { |
| this._project.deleteFile(this); |
| } |
| |
| /** |
| * @param {string} name |
| * @param {string} url |
| * @param {!Common.ResourceType=} contentType |
| */ |
| _updateName(name, url, contentType) { |
| const oldURL = this._url; |
| this._url = this._url.substring(0, this._url.length - this._name.length) + name; |
| this._name = name; |
| if (url) { |
| this._url = url; |
| } |
| if (contentType) { |
| this._contentType = contentType; |
| } |
| this.dispatchEventToListeners(Events.TitleChanged, this); |
| this.project().workspace().dispatchEventToListeners( |
| Workspace.Workspace.Events.UISourceCodeRenamed, {oldURL: oldURL, uiSourceCode: this}); |
| } |
| |
| /** |
| * @override |
| * @return {string} |
| */ |
| contentURL() { |
| return this.url(); |
| } |
| |
| /** |
| * @override |
| * @return {!Common.ResourceType} |
| */ |
| contentType() { |
| return this._contentType; |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<boolean>} |
| */ |
| async contentEncoded() { |
| await this.requestContent(); |
| return this._contentEncoded || false; |
| } |
| |
| /** |
| * @return {!Workspace.Project} |
| */ |
| project() { |
| return this._project; |
| } |
| |
| /** |
| * @override |
| * @return {!Promise<!Common.DeferredContent>} |
| */ |
| requestContent() { |
| if (this._requestContentPromise) { |
| return this._requestContentPromise; |
| } |
| |
| if (this._contentLoaded) { |
| return Promise.resolve(/** @type {!Common.DeferredContent} */ (this._content)); |
| } |
| |
| |
| this._requestContentPromise = this._requestContentImpl(); |
| return this._requestContentPromise; |
| } |
| |
| /** |
| * @returns {!Promise<!UISourceCode>} |
| */ |
| async getFormatted() { |
| const formatData = await Sources.sourceFormatter.format(this); |
| return formatData.formattedSourceCode; |
| } |
| |
| /** |
| * @returns {!Promise<!Common.DeferredContent>} |
| */ |
| async _requestContentImpl() { |
| try { |
| const content = await this._project.requestFileContent(this); |
| if (!this._contentLoaded) { |
| this._contentLoaded = true; |
| this._content = content; |
| this._contentEncoded = content.isEncoded; |
| } |
| } catch (err) { |
| this._contentLoaded = true; |
| this._content = {error: err ? String(err) : '', isEncoded: false}; |
| } |
| |
| return /** @type {!Common.DeferredContent} */ (this._content); |
| } |
| |
| async checkContentUpdated() { |
| if (!this._contentLoaded && !this._forceLoadOnCheckContent) { |
| return; |
| } |
| |
| if (!this._project.canSetFileContent() || this._checkingContent) { |
| return; |
| } |
| |
| this._checkingContent = true; |
| const updatedContent = await this._project.requestFileContent(this); |
| this._checkingContent = false; |
| if (updatedContent.content === null) { |
| const workingCopy = this.workingCopy(); |
| this._contentCommitted('', false); |
| this.setWorkingCopy(workingCopy); |
| return; |
| } |
| if (this._lastAcceptedContent === updatedContent.content) { |
| return; |
| } |
| |
| if (this._content && this._content.content === updatedContent.content) { |
| this._lastAcceptedContent = null; |
| return; |
| } |
| |
| if (!this.isDirty() || this._workingCopy === updatedContent.content) { |
| this._contentCommitted(/** @type {string} */ (updatedContent.content), false); |
| return; |
| } |
| |
| await Common.Revealer.reveal(this); |
| |
| // Make sure we are in the next frame before stopping the world with confirm |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| |
| const shouldUpdate = window.confirm(ls`This file was changed externally. Would you like to reload it?`); |
| if (shouldUpdate) { |
| this._contentCommitted(/** @type {string} */ (updatedContent.content), false); |
| } else { |
| this._lastAcceptedContent = updatedContent.content; |
| } |
| } |
| |
| forceLoadOnCheckContent() { |
| this._forceLoadOnCheckContent = true; |
| } |
| |
| /** |
| * @param {string} content |
| */ |
| _commitContent(content) { |
| if (this._project.canSetFileContent()) { |
| this._project.setFileContent(this, content, false); |
| } |
| this._contentCommitted(content, true); |
| } |
| |
| /** |
| * @param {string} content |
| * @param {boolean} committedByUser |
| */ |
| _contentCommitted(content, committedByUser) { |
| this._lastAcceptedContent = null; |
| this._content = {content, isEncoded: false}; |
| this._contentLoaded = true; |
| this._requestContentPromise = null; |
| |
| this._hasCommits = true; |
| |
| this._innerResetWorkingCopy(); |
| const data = {uiSourceCode: this, content, encoded: this._contentEncoded}; |
| this.dispatchEventToListeners(Events.WorkingCopyCommitted, data); |
| this._project.workspace().dispatchEventToListeners(Workspace.Workspace.Events.WorkingCopyCommitted, data); |
| if (committedByUser) { |
| this._project.workspace().dispatchEventToListeners(Workspace.Workspace.Events.WorkingCopyCommittedByUser, data); |
| } |
| } |
| |
| /** |
| * @param {string} content |
| */ |
| addRevision(content) { |
| this._commitContent(content); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| hasCommits() { |
| return this._hasCommits; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| workingCopy() { |
| if (this._workingCopyGetter) { |
| this._workingCopy = this._workingCopyGetter(); |
| this._workingCopyGetter = null; |
| } |
| if (this.isDirty()) { |
| return /** @type {string} */ (this._workingCopy); |
| } |
| return (this._content && this._content.content) || ''; |
| } |
| |
| resetWorkingCopy() { |
| this._innerResetWorkingCopy(); |
| this._workingCopyChanged(); |
| } |
| |
| _innerResetWorkingCopy() { |
| this._workingCopy = null; |
| this._workingCopyGetter = null; |
| } |
| |
| /** |
| * @param {string} newWorkingCopy |
| */ |
| setWorkingCopy(newWorkingCopy) { |
| this._workingCopy = newWorkingCopy; |
| this._workingCopyGetter = null; |
| this._workingCopyChanged(); |
| } |
| |
| /** |
| * @param {string} content |
| * @param {boolean} isBase64 |
| */ |
| setContent(content, isBase64) { |
| this._contentEncoded = isBase64; |
| if (this._project.canSetFileContent()) { |
| this._project.setFileContent(this, content, isBase64); |
| } |
| this._contentCommitted(content, true); |
| } |
| |
| /** |
| * @param {function(): string } workingCopyGetter |
| */ |
| setWorkingCopyGetter(workingCopyGetter) { |
| this._workingCopyGetter = workingCopyGetter; |
| this._workingCopyChanged(); |
| } |
| |
| _workingCopyChanged() { |
| this._removeAllMessages(); |
| this.dispatchEventToListeners(Events.WorkingCopyChanged, this); |
| this._project.workspace().dispatchEventToListeners( |
| Workspace.Workspace.Events.WorkingCopyChanged, {uiSourceCode: this}); |
| } |
| |
| removeWorkingCopyGetter() { |
| if (!this._workingCopyGetter) { |
| return; |
| } |
| this._workingCopy = this._workingCopyGetter(); |
| this._workingCopyGetter = null; |
| } |
| |
| commitWorkingCopy() { |
| if (this.isDirty()) { |
| this._commitContent(this.workingCopy()); |
| } |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isDirty() { |
| return this._workingCopy !== null || this._workingCopyGetter !== null; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| extension() { |
| return Common.ParsedURL.extractExtension(this._name); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| content() { |
| return (this._content && this._content.content) || ''; |
| } |
| |
| /** |
| * @return {?string} |
| */ |
| loadError() { |
| return (this._content && this._content.error); |
| } |
| |
| /** |
| * @override |
| * @param {string} query |
| * @param {boolean} caseSensitive |
| * @param {boolean} isRegex |
| * @return {!Promise<!Array<!Common.ContentProvider.SearchMatch>>} |
| */ |
| searchInContent(query, caseSensitive, isRegex) { |
| const content = this.content(); |
| if (!content) { |
| return this._project.searchInFileContent(this, query, caseSensitive, isRegex); |
| } |
| return Promise.resolve(Common.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex)); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| contentLoaded() { |
| return this._contentLoaded; |
| } |
| |
| /** |
| * @param {number} lineNumber |
| * @param {number=} columnNumber |
| * @return {!UILocation} |
| */ |
| uiLocation(lineNumber, columnNumber) { |
| if (typeof columnNumber === 'undefined') { |
| columnNumber = 0; |
| } |
| return new UILocation(this, lineNumber, columnNumber); |
| } |
| |
| /** |
| * @return {!Set<!Message>} |
| */ |
| messages() { |
| return this._messages ? new Set(this._messages) : new Set(); |
| } |
| |
| /** |
| * @param {!Message.Level} level |
| * @param {string} text |
| * @param {number} lineNumber |
| * @param {number=} columnNumber |
| * @return {!Message} message |
| */ |
| addLineMessage(level, text, lineNumber, columnNumber) { |
| return this.addMessage( |
| level, text, new TextUtils.TextRange(lineNumber, columnNumber || 0, lineNumber, columnNumber || 0)); |
| } |
| |
| /** |
| * @param {!Message.Level} level |
| * @param {string} text |
| * @param {!TextUtils.TextRange} range |
| * @return {!Message} message |
| */ |
| addMessage(level, text, range) { |
| const message = new Message(this, level, text, range); |
| if (!this._messages) { |
| this._messages = new Set(); |
| } |
| this._messages.add(message); |
| this.dispatchEventToListeners(Events.MessageAdded, message); |
| return message; |
| } |
| |
| /** |
| * @param {!Message} message |
| */ |
| removeMessage(message) { |
| if (this._messages && this._messages.delete(message)) { |
| this.dispatchEventToListeners(Events.MessageRemoved, message); |
| } |
| } |
| |
| _removeAllMessages() { |
| if (!this._messages) { |
| return; |
| } |
| for (const message of this._messages) { |
| this.dispatchEventToListeners(Events.MessageRemoved, message); |
| } |
| this._messages = null; |
| } |
| |
| /** |
| * @param {number} lineNumber |
| * @param {string} type |
| * @param {?} data |
| */ |
| addLineDecoration(lineNumber, type, data) { |
| this.addDecoration(TextUtils.TextRange.createFromLocation(lineNumber, 0), type, data); |
| } |
| |
| /** |
| * @param {!TextUtils.TextRange} range |
| * @param {string} type |
| * @param {?} data |
| */ |
| addDecoration(range, type, data) { |
| const marker = new LineMarker(range, type, data); |
| if (!this._decorations) { |
| this._decorations = new Platform.Multimap(); |
| } |
| this._decorations.set(type, marker); |
| this.dispatchEventToListeners(Events.LineDecorationAdded, marker); |
| } |
| |
| /** |
| * @param {string} type |
| */ |
| removeDecorationsForType(type) { |
| if (!this._decorations) { |
| return; |
| } |
| const markers = this._decorations.get(type); |
| this._decorations.deleteAll(type); |
| markers.forEach(marker => { |
| this.dispatchEventToListeners(Events.LineDecorationRemoved, marker); |
| }); |
| } |
| |
| /** |
| * @return {!Array<!LineMarker>} |
| */ |
| allDecorations() { |
| return this._decorations ? this._decorations.valuesArray() : []; |
| } |
| |
| removeAllDecorations() { |
| if (!this._decorations) { |
| return; |
| } |
| const decorationList = this._decorations.valuesArray(); |
| this._decorations.clear(); |
| decorationList.forEach(marker => this.dispatchEventToListeners(Events.LineDecorationRemoved, marker)); |
| } |
| |
| /** |
| * @param {string} type |
| * @return {?Set<!LineMarker>} |
| */ |
| decorationsForType(type) { |
| return this._decorations ? this._decorations.get(type) : null; |
| } |
| } |
| |
| /** @enum {symbol} */ |
| export const Events = { |
| WorkingCopyChanged: Symbol('WorkingCopyChanged'), |
| WorkingCopyCommitted: Symbol('WorkingCopyCommitted'), |
| TitleChanged: Symbol('TitleChanged'), |
| MessageAdded: Symbol('MessageAdded'), |
| MessageRemoved: Symbol('MessageRemoved'), |
| LineDecorationAdded: Symbol('LineDecorationAdded'), |
| LineDecorationRemoved: Symbol('LineDecorationRemoved') |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| export class UILocation { |
| /** |
| * @param {!UISourceCode} uiSourceCode |
| * @param {number} lineNumber |
| * @param {number} columnNumber |
| */ |
| constructor(uiSourceCode, lineNumber, columnNumber) { |
| this.uiSourceCode = uiSourceCode; |
| this.lineNumber = lineNumber; |
| this.columnNumber = columnNumber; |
| } |
| |
| /** |
| * @param {boolean=} skipTrim |
| * @return {string} |
| */ |
| linkText(skipTrim) { |
| let linkText = this.uiSourceCode.displayName(skipTrim); |
| if (typeof this.lineNumber === 'number') { |
| linkText += ':' + (this.lineNumber + 1); |
| } |
| return linkText; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| id() { |
| return this.uiSourceCode.project().id() + ':' + this.uiSourceCode.url() + ':' + this.lineNumber + ':' + |
| this.columnNumber; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| toUIString() { |
| return this.uiSourceCode.url() + ':' + (this.lineNumber + 1); |
| } |
| |
| /** |
| * @param {!UILocation} location1 |
| * @param {!UILocation} location2 |
| * @return {number} |
| */ |
| static comparator(location1, location2) { |
| return location1.compareTo(location2); |
| } |
| |
| /** |
| * @param {!UILocation} other |
| * @return {number} |
| */ |
| compareTo(other) { |
| if (this.uiSourceCode.url() !== other.uiSourceCode.url()) { |
| return this.uiSourceCode.url() > other.uiSourceCode.url() ? 1 : -1; |
| } |
| if (this.lineNumber !== other.lineNumber) { |
| return this.lineNumber - other.lineNumber; |
| } |
| return this.columnNumber - other.columnNumber; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class Message { |
| /** |
| * @param {!UISourceCode} uiSourceCode |
| * @param {!Message.Level} level |
| * @param {string} text |
| * @param {!TextUtils.TextRange} range |
| */ |
| constructor(uiSourceCode, level, text, range) { |
| this._uiSourceCode = uiSourceCode; |
| this._level = level; |
| this._text = text; |
| this._range = range; |
| } |
| |
| /** |
| * @return {!UISourceCode} |
| */ |
| uiSourceCode() { |
| return this._uiSourceCode; |
| } |
| |
| /** |
| * @return {!Message.Level} |
| */ |
| level() { |
| return this._level; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| text() { |
| return this._text; |
| } |
| |
| /** |
| * @return {!TextUtils.TextRange} |
| */ |
| range() { |
| return this._range; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| lineNumber() { |
| return this._range.startLine; |
| } |
| |
| /** |
| * @return {(number|undefined)} |
| */ |
| columnNumber() { |
| return this._range.startColumn; |
| } |
| |
| /** |
| * @param {!Message} another |
| * @return {boolean} |
| */ |
| isEqual(another) { |
| return this._uiSourceCode === another._uiSourceCode && this.text() === another.text() && |
| this.level() === another.level() && this.range().equal(another.range()); |
| } |
| |
| remove() { |
| this._uiSourceCode.removeMessage(this); |
| } |
| } |
| |
| /** |
| * @enum {string} |
| */ |
| Message.Level = { |
| Error: 'Error', |
| Warning: 'Warning' |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| export class LineMarker { |
| /** |
| * @param {!TextUtils.TextRange} range |
| * @param {string} type |
| * @param {?} data |
| */ |
| constructor(range, type, data) { |
| this._range = range; |
| this._type = type; |
| this._data = data; |
| } |
| |
| /** |
| * @return {!TextUtils.TextRange} |
| */ |
| range() { |
| return this._range; |
| } |
| |
| /** |
| * @return {string} |
| */ |
| type() { |
| return this._type; |
| } |
| |
| /** |
| * @return {*} |
| */ |
| data() { |
| return this._data; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class UISourceCodeMetadata { |
| /** |
| * @param {?Date} modificationTime |
| * @param {?number} contentSize |
| */ |
| constructor(modificationTime, contentSize) { |
| this.modificationTime = modificationTime; |
| this.contentSize = contentSize; |
| } |
| } |
| |
| /* Legacy exported object */ |
| self.Workspace = self.Workspace || {}; |
| |
| /* Legacy exported object */ |
| Workspace = Workspace || {}; |
| |
| /** @constructor */ |
| Workspace.UISourceCode = UISourceCode; |
| |
| /** @enum {symbol} */ |
| Workspace.UISourceCode.Events = Events; |
| |
| /** @constructor */ |
| Workspace.UISourceCode.Message = Message; |
| |
| /** @constructor */ |
| Workspace.UISourceCode.LineMarker = LineMarker; |
| |
| /** @constructor */ |
| Workspace.UILocation = UILocation; |
| |
| /** @constructor */ |
| Workspace.UISourceCodeMetadata = UISourceCodeMetadata; |