| /* | |
| * Copyright (C) 2012 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: | |
| * | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * | |
| * 2. 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. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS 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 GOOGLE INC. | |
| * OR ITS 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. | |
| */ | |
| Sources.UISourceCodeFrame = class extends SourceFrame.SourceFrame { | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| constructor(uiSourceCode) { | |
| super(workingCopy); | |
| this._uiSourceCode = uiSourceCode; | |
| if (Root.Runtime.experiments.isEnabled('sourceDiff')) { | |
| this._diff = new SourceFrame.SourceCodeDiff(this.textEditor); | |
| } | |
| this._muteSourceCodeEvents = false; | |
| this._isSettingContent = false; | |
| /** @type {?Persistence.PersistenceBinding} */ | |
| this._persistenceBinding = Persistence.persistence.binding(uiSourceCode); | |
| /** @type {!Map<number, !Sources.UISourceCodeFrame.RowMessageBucket>} */ | |
| this._rowMessageBuckets = new Map(); | |
| /** @type {!Set<string>} */ | |
| this._typeDecorationsPending = new Set(); | |
| this._uiSourceCodeEventListeners = []; | |
| this._messageAndDecorationListeners = []; | |
| this._boundOnBindingChanged = this._onBindingChanged.bind(this); | |
| this.textEditor.addEventListener( | |
| SourceFrame.SourcesTextEditor.Events.EditorBlurred, | |
| () => UI.context.setFlavor(Sources.UISourceCodeFrame, null)); | |
| this.textEditor.addEventListener( | |
| SourceFrame.SourcesTextEditor.Events.EditorFocused, | |
| () => UI.context.setFlavor(Sources.UISourceCodeFrame, this)); | |
| Common.settings.moduleSetting('persistenceNetworkOverridesEnabled') | |
| .addChangeListener(this._onNetworkPersistenceChanged, this); | |
| this._errorPopoverHelper = new UI.PopoverHelper(this.element, this._getErrorPopoverContent.bind(this)); | |
| this._errorPopoverHelper.setHasPadding(true); | |
| this._errorPopoverHelper.setTimeout(100, 100); | |
| /** @type {!Array<!Sources.UISourceCodeFrame.Plugin>} */ | |
| this._plugins = []; | |
| this._initializeUISourceCode(); | |
| /** | |
| * @return {!Promise<!Common.DeferredContent>} | |
| */ | |
| function workingCopy() { | |
| if (uiSourceCode.isDirty()) { | |
| return Promise.resolve({content: uiSourceCode.workingCopy(), isEncoded: false}); | |
| } | |
| return uiSourceCode.requestContent(); | |
| } | |
| } | |
| _installMessageAndDecorationListeners() { | |
| if (this._persistenceBinding) { | |
| const networkSourceCode = this._persistenceBinding.network; | |
| const fileSystemSourceCode = this._persistenceBinding.fileSystem; | |
| this._messageAndDecorationListeners = [ | |
| networkSourceCode.addEventListener(Workspace.UISourceCode.Events.MessageAdded, this._onMessageAdded, this), | |
| networkSourceCode.addEventListener(Workspace.UISourceCode.Events.MessageRemoved, this._onMessageRemoved, this), | |
| networkSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.LineDecorationAdded, this._onLineDecorationAdded, this), | |
| networkSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.LineDecorationRemoved, this._onLineDecorationRemoved, this), | |
| fileSystemSourceCode.addEventListener(Workspace.UISourceCode.Events.MessageAdded, this._onMessageAdded, this), | |
| fileSystemSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.MessageRemoved, this._onMessageRemoved, this), | |
| ]; | |
| } else { | |
| this._messageAndDecorationListeners = [ | |
| this._uiSourceCode.addEventListener(Workspace.UISourceCode.Events.MessageAdded, this._onMessageAdded, this), | |
| this._uiSourceCode.addEventListener(Workspace.UISourceCode.Events.MessageRemoved, this._onMessageRemoved, this), | |
| this._uiSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.LineDecorationAdded, this._onLineDecorationAdded, this), | |
| this._uiSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.LineDecorationRemoved, this._onLineDecorationRemoved, this) | |
| ]; | |
| } | |
| } | |
| /** | |
| * @return {!Workspace.UISourceCode} | |
| */ | |
| uiSourceCode() { | |
| return this._uiSourceCode; | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| */ | |
| setUISourceCode(uiSourceCode) { | |
| this._unloadUISourceCode(); | |
| this._uiSourceCode = uiSourceCode; | |
| if (uiSourceCode.contentLoaded()) { | |
| if (uiSourceCode.workingCopy() !== this.textEditor.text()) { | |
| this._innerSetContent(uiSourceCode.workingCopy()); | |
| } | |
| } else { | |
| uiSourceCode.requestContent().then(() => { | |
| if (this._uiSourceCode !== uiSourceCode) { | |
| return; | |
| } | |
| if (uiSourceCode.workingCopy() !== this.textEditor.text()) { | |
| this._innerSetContent(uiSourceCode.workingCopy()); | |
| } | |
| }); | |
| } | |
| this._initializeUISourceCode(); | |
| } | |
| _unloadUISourceCode() { | |
| this._disposePlugins(); | |
| for (const message of this._allMessages()) { | |
| this._removeMessageFromSource(message); | |
| } | |
| Common.EventTarget.removeEventListeners(this._messageAndDecorationListeners); | |
| Common.EventTarget.removeEventListeners(this._uiSourceCodeEventListeners); | |
| this._uiSourceCode.removeWorkingCopyGetter(); | |
| Persistence.persistence.unsubscribeFromBindingEvent(this._uiSourceCode, this._boundOnBindingChanged); | |
| } | |
| _initializeUISourceCode() { | |
| this._uiSourceCodeEventListeners = [ | |
| this._uiSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.WorkingCopyChanged, this._onWorkingCopyChanged, this), | |
| this._uiSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.WorkingCopyCommitted, this._onWorkingCopyCommitted, this), | |
| this._uiSourceCode.addEventListener( | |
| Workspace.UISourceCode.Events.TitleChanged, this._refreshHighlighterType, this) | |
| ]; | |
| Persistence.persistence.subscribeForBindingEvent(this._uiSourceCode, this._boundOnBindingChanged); | |
| for (const message of this._allMessages()) { | |
| this._addMessageToSource(message); | |
| } | |
| this._installMessageAndDecorationListeners(); | |
| this._updateStyle(); | |
| this._decorateAllTypes(); | |
| this._refreshHighlighterType(); | |
| if (Root.Runtime.experiments.isEnabled('sourcesPrettyPrint')) { | |
| const supportedPrettyTypes = new Set(['text/html', 'text/css', 'text/javascript']); | |
| this.setCanPrettyPrint(supportedPrettyTypes.has(this.highlighterType()), true); | |
| } | |
| this._ensurePluginsLoaded(); | |
| } | |
| /** | |
| * @override | |
| */ | |
| wasShown() { | |
| super.wasShown(); | |
| // We need CodeMirrorTextEditor to be initialized prior to this call as it calls |cursorPositionToCoordinates| internally. @see crbug.com/506566 | |
| setImmediate(this._updateBucketDecorations.bind(this)); | |
| this.setEditable(this._canEditSource()); | |
| for (const plugin of this._plugins) { | |
| plugin.wasShown(); | |
| } | |
| } | |
| /** | |
| * @override | |
| */ | |
| willHide() { | |
| for (const plugin of this._plugins) { | |
| plugin.willHide(); | |
| } | |
| super.willHide(); | |
| UI.context.setFlavor(Sources.UISourceCodeFrame, null); | |
| this._uiSourceCode.removeWorkingCopyGetter(); | |
| } | |
| _refreshHighlighterType() { | |
| const binding = Persistence.persistence.binding(this._uiSourceCode); | |
| const highlighterType = binding ? binding.network.mimeType() : this._uiSourceCode.mimeType(); | |
| if (this.highlighterType() === highlighterType) { | |
| return; | |
| } | |
| this._disposePlugins(); | |
| this.setHighlighterType(highlighterType); | |
| this._ensurePluginsLoaded(); | |
| } | |
| /** | |
| * @return {boolean} | |
| */ | |
| _canEditSource() { | |
| if (this.hasLoadError()) { | |
| return false; | |
| } | |
| if (Persistence.persistence.binding(this._uiSourceCode)) { | |
| return true; | |
| } | |
| if (this._uiSourceCode.project().canSetFileContent()) { | |
| return true; | |
| } | |
| if (this._uiSourceCode.project().isServiceProject()) { | |
| return false; | |
| } | |
| if (this._uiSourceCode.project().type() === Workspace.projectTypes.Network && | |
| Persistence.networkPersistenceManager.active()) { | |
| return true; | |
| } | |
| // Because live edit fails on large whitespace changes, pretty printed scripts are not editable. | |
| if (this.pretty && this._uiSourceCode.contentType().hasScripts()) { | |
| return false; | |
| } | |
| return this._uiSourceCode.contentType() !== Common.resourceTypes.Document; | |
| } | |
| _onNetworkPersistenceChanged() { | |
| this.setEditable(this._canEditSource()); | |
| } | |
| commitEditing() { | |
| if (!this._uiSourceCode.isDirty()) { | |
| return; | |
| } | |
| this._muteSourceCodeEvents = true; | |
| this._uiSourceCode.commitWorkingCopy(); | |
| this._muteSourceCodeEvents = false; | |
| } | |
| /** | |
| * @override | |
| * @param {?string} content | |
| * @param {?string} loadError | |
| */ | |
| setContent(content, loadError) { | |
| this._disposePlugins(); | |
| this._rowMessageBuckets.clear(); | |
| super.setContent(content, loadError); | |
| for (const message of this._allMessages()) { | |
| this._addMessageToSource(message); | |
| } | |
| this._decorateAllTypes(); | |
| this._ensurePluginsLoaded(); | |
| } | |
| /** | |
| * @return {!Set<!Workspace.UISourceCode.Message>} | |
| */ | |
| _allMessages() { | |
| if (this._persistenceBinding) { | |
| const combinedSet = this._persistenceBinding.network.messages(); | |
| combinedSet.addAll(this._persistenceBinding.fileSystem.messages()); | |
| return combinedSet; | |
| } | |
| return this._uiSourceCode.messages(); | |
| } | |
| /** | |
| * @override | |
| * @param {!TextUtils.TextRange} oldRange | |
| * @param {!TextUtils.TextRange} newRange | |
| */ | |
| onTextChanged(oldRange, newRange) { | |
| const wasPretty = this.pretty; | |
| super.onTextChanged(oldRange, newRange); | |
| this._errorPopoverHelper.hidePopover(); | |
| if (this._isSettingContent) { | |
| return; | |
| } | |
| Sources.SourcesPanel.instance().updateLastModificationTime(); | |
| this._muteSourceCodeEvents = true; | |
| if (this.isClean()) { | |
| this._uiSourceCode.resetWorkingCopy(); | |
| } else { | |
| this._uiSourceCode.setWorkingCopyGetter(this.textEditor.text.bind(this.textEditor)); | |
| } | |
| this._muteSourceCodeEvents = false; | |
| if (wasPretty !== this.pretty) { | |
| this._updateStyle(); | |
| this._disposePlugins(); | |
| this._ensurePluginsLoaded(); | |
| } | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _onWorkingCopyChanged(event) { | |
| if (this._muteSourceCodeEvents) { | |
| return; | |
| } | |
| this._innerSetContent(this._uiSourceCode.workingCopy()); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _onWorkingCopyCommitted(event) { | |
| if (!this._muteSourceCodeEvents) { | |
| this._innerSetContent(this._uiSourceCode.workingCopy()); | |
| } | |
| this.contentCommitted(); | |
| this._updateStyle(); | |
| } | |
| _ensurePluginsLoaded() { | |
| if (!this.loaded || this._plugins.length) { | |
| return; | |
| } | |
| const binding = Persistence.persistence.binding(this._uiSourceCode); | |
| const pluginUISourceCode = binding ? binding.network : this._uiSourceCode; | |
| // The order of these plugins matters for toolbar items | |
| if (Sources.DebuggerPlugin.accepts(pluginUISourceCode)) { | |
| this._plugins.push(new Sources.DebuggerPlugin(this.textEditor, pluginUISourceCode, this.transformer())); | |
| } | |
| if (Sources.CSSPlugin.accepts(pluginUISourceCode)) { | |
| this._plugins.push(new Sources.CSSPlugin(this.textEditor)); | |
| } | |
| if (!this.pretty && Sources.JavaScriptCompilerPlugin.accepts(pluginUISourceCode)) { | |
| this._plugins.push(new Sources.JavaScriptCompilerPlugin(this.textEditor, pluginUISourceCode)); | |
| } | |
| if (Sources.SnippetsPlugin.accepts(pluginUISourceCode)) { | |
| this._plugins.push(new Sources.SnippetsPlugin(this.textEditor, pluginUISourceCode)); | |
| } | |
| if (Sources.ScriptOriginPlugin.accepts(pluginUISourceCode)) { | |
| this._plugins.push(new Sources.ScriptOriginPlugin(this.textEditor, pluginUISourceCode)); | |
| } | |
| if (!this.pretty && Root.Runtime.experiments.isEnabled('sourceDiff') && | |
| Sources.GutterDiffPlugin.accepts(pluginUISourceCode)) { | |
| this._plugins.push(new Sources.GutterDiffPlugin(this.textEditor, pluginUISourceCode)); | |
| } | |
| if (Sources.CoveragePlugin.accepts(pluginUISourceCode)) { | |
| this._plugins.push(new Sources.CoveragePlugin(this.textEditor, pluginUISourceCode)); | |
| } | |
| this.dispatchEventToListeners(Sources.UISourceCodeFrame.Events.ToolbarItemsChanged); | |
| for (const plugin of this._plugins) { | |
| plugin.wasShown(); | |
| } | |
| } | |
| _disposePlugins() { | |
| this.textEditor.operation(() => { | |
| for (const plugin of this._plugins) { | |
| plugin.dispose(); | |
| } | |
| }); | |
| this._plugins = []; | |
| } | |
| _onBindingChanged() { | |
| const binding = Persistence.persistence.binding(this._uiSourceCode); | |
| if (binding === this._persistenceBinding) { | |
| return; | |
| } | |
| this._unloadUISourceCode(); | |
| this._persistenceBinding = binding; | |
| this._initializeUISourceCode(); | |
| } | |
| _updateStyle() { | |
| this.setEditable(this._canEditSource()); | |
| } | |
| /** | |
| * @param {string} content | |
| */ | |
| _innerSetContent(content) { | |
| this._isSettingContent = true; | |
| const oldContent = this.textEditor.text(); | |
| if (this._diff) { | |
| this._diff.highlightModifiedLines(oldContent, content); | |
| } | |
| if (oldContent !== content) { | |
| this.setContent(content, null); | |
| } | |
| this._isSettingContent = false; | |
| } | |
| /** | |
| * @override | |
| * @return {!Promise} | |
| */ | |
| async populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber) { | |
| await super.populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber); | |
| contextMenu.appendApplicableItems(this._uiSourceCode); | |
| const location = this.transformer().editorToRawLocation(editorLineNumber, editorColumnNumber); | |
| contextMenu.appendApplicableItems(new Workspace.UILocation(this._uiSourceCode, location[0], location[1])); | |
| contextMenu.appendApplicableItems(this); | |
| for (const plugin of this._plugins) { | |
| await plugin.populateTextAreaContextMenu(contextMenu, editorLineNumber, editorColumnNumber); | |
| } | |
| } | |
| dispose() { | |
| this._errorPopoverHelper.dispose(); | |
| this._unloadUISourceCode(); | |
| this.textEditor.dispose(); | |
| this.detach(); | |
| Common.settings.moduleSetting('persistenceNetworkOverridesEnabled') | |
| .removeChangeListener(this._onNetworkPersistenceChanged, this); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _onMessageAdded(event) { | |
| const message = /** @type {!Workspace.UISourceCode.Message} */ (event.data); | |
| this._addMessageToSource(message); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode.Message} message | |
| */ | |
| _addMessageToSource(message) { | |
| if (!this.loaded) { | |
| return; | |
| } | |
| const editorLocation = this.transformer().rawToEditorLocation(message.lineNumber(), message.columnNumber()); | |
| let editorLineNumber = editorLocation[0]; | |
| if (editorLineNumber >= this.textEditor.linesCount) { | |
| editorLineNumber = this.textEditor.linesCount - 1; | |
| } | |
| if (editorLineNumber < 0) { | |
| editorLineNumber = 0; | |
| } | |
| let messageBucket = this._rowMessageBuckets.get(editorLineNumber); | |
| if (!messageBucket) { | |
| messageBucket = new Sources.UISourceCodeFrame.RowMessageBucket(this, this.textEditor, editorLineNumber); | |
| this._rowMessageBuckets.set(editorLineNumber, messageBucket); | |
| } | |
| messageBucket.addMessage(message); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _onMessageRemoved(event) { | |
| const message = /** @type {!Workspace.UISourceCode.Message} */ (event.data); | |
| this._removeMessageFromSource(message); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode.Message} message | |
| */ | |
| _removeMessageFromSource(message) { | |
| if (!this.loaded) { | |
| return; | |
| } | |
| const editorLocation = this.transformer().rawToEditorLocation(message.lineNumber(), message.columnNumber()); | |
| let editorLineNumber = editorLocation[0]; | |
| if (editorLineNumber >= this.textEditor.linesCount) { | |
| editorLineNumber = this.textEditor.linesCount - 1; | |
| } | |
| if (editorLineNumber < 0) { | |
| editorLineNumber = 0; | |
| } | |
| const messageBucket = this._rowMessageBuckets.get(editorLineNumber); | |
| if (!messageBucket) { | |
| return; | |
| } | |
| messageBucket.removeMessage(message); | |
| if (!messageBucket.uniqueMessagesCount()) { | |
| messageBucket.detachFromEditor(); | |
| this._rowMessageBuckets.delete(editorLineNumber); | |
| } | |
| } | |
| /** | |
| * @param {!Event} event | |
| * @return {?UI.PopoverRequest} | |
| */ | |
| _getErrorPopoverContent(event) { | |
| const element = event.target.enclosingNodeOrSelfWithClass('text-editor-line-decoration-icon') || | |
| event.target.enclosingNodeOrSelfWithClass('text-editor-line-decoration-wave'); | |
| if (!element) { | |
| return null; | |
| } | |
| const anchor = element.enclosingNodeOrSelfWithClass('text-editor-line-decoration-icon') ? | |
| element.boxInWindow() : | |
| new AnchorBox(event.clientX, event.clientY, 1, 1); | |
| return { | |
| box: anchor, | |
| show: popover => { | |
| const messageBucket = element.enclosingNodeOrSelfWithClass('text-editor-line-decoration')._messageBucket; | |
| const messagesOutline = messageBucket.messagesDescription(); | |
| popover.contentElement.appendChild(messagesOutline); | |
| return Promise.resolve(true); | |
| } | |
| }; | |
| } | |
| _updateBucketDecorations() { | |
| for (const bucket of this._rowMessageBuckets.values()) { | |
| bucket._updateDecoration(); | |
| } | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _onLineDecorationAdded(event) { | |
| const marker = /** @type {!Workspace.UISourceCode.LineMarker} */ (event.data); | |
| this._decorateTypeThrottled(marker.type()); | |
| } | |
| /** | |
| * @param {!Common.Event} event | |
| */ | |
| _onLineDecorationRemoved(event) { | |
| const marker = /** @type {!Workspace.UISourceCode.LineMarker} */ (event.data); | |
| this._decorateTypeThrottled(marker.type()); | |
| } | |
| /** | |
| * @param {string} type | |
| */ | |
| async _decorateTypeThrottled(type) { | |
| if (this._typeDecorationsPending.has(type)) { | |
| return; | |
| } | |
| this._typeDecorationsPending.add(type); | |
| const decorator = await self.runtime.extensions(SourceFrame.LineDecorator) | |
| .find(extension => extension.descriptor()['decoratorType'] === type) | |
| .instance(); | |
| this._typeDecorationsPending.delete(type); | |
| this.textEditor.codeMirror().operation(() => { | |
| decorator.decorate( | |
| this._persistenceBinding ? this._persistenceBinding.network : this.uiSourceCode(), this.textEditor, type); | |
| }); | |
| } | |
| _decorateAllTypes() { | |
| if (!this.loaded) { | |
| return; | |
| } | |
| for (const extension of self.runtime.extensions(SourceFrame.LineDecorator)) { | |
| const type = extension.descriptor()['decoratorType']; | |
| if (this._uiSourceCode.decorationsForType(type)) { | |
| this._decorateTypeThrottled(type); | |
| } | |
| } | |
| } | |
| /** | |
| * @override | |
| * @return {!Array<!UI.ToolbarItem>} | |
| */ | |
| syncToolbarItems() { | |
| const leftToolbarItems = super.syncToolbarItems(); | |
| const rightToolbarItems = []; | |
| for (const plugin of this._plugins) { | |
| leftToolbarItems.pushAll(plugin.leftToolbarItems()); | |
| rightToolbarItems.pushAll(plugin.rightToolbarItems()); | |
| } | |
| if (!rightToolbarItems.length) { | |
| return leftToolbarItems; | |
| } | |
| return [...leftToolbarItems, new UI.ToolbarSeparator(true), ...rightToolbarItems]; | |
| } | |
| /** | |
| * @override | |
| * @param {!UI.ContextMenu} contextMenu | |
| * @param {number} lineNumber | |
| * @return {!Promise} | |
| */ | |
| async populateLineGutterContextMenu(contextMenu, lineNumber) { | |
| await super.populateLineGutterContextMenu(contextMenu, lineNumber); | |
| for (const plugin of this._plugins) { | |
| await plugin.populateLineGutterContextMenu(contextMenu, lineNumber); | |
| } | |
| } | |
| }; | |
| Sources.UISourceCodeFrame._iconClassPerLevel = {}; | |
| Sources.UISourceCodeFrame._iconClassPerLevel[Workspace.UISourceCode.Message.Level.Error] = 'smallicon-error'; | |
| Sources.UISourceCodeFrame._iconClassPerLevel[Workspace.UISourceCode.Message.Level.Warning] = 'smallicon-warning'; | |
| Sources.UISourceCodeFrame._bubbleTypePerLevel = {}; | |
| Sources.UISourceCodeFrame._bubbleTypePerLevel[Workspace.UISourceCode.Message.Level.Error] = 'error'; | |
| Sources.UISourceCodeFrame._bubbleTypePerLevel[Workspace.UISourceCode.Message.Level.Warning] = 'warning'; | |
| Sources.UISourceCodeFrame._lineClassPerLevel = {}; | |
| Sources.UISourceCodeFrame._lineClassPerLevel[Workspace.UISourceCode.Message.Level.Error] = | |
| 'text-editor-line-with-error'; | |
| Sources.UISourceCodeFrame._lineClassPerLevel[Workspace.UISourceCode.Message.Level.Warning] = | |
| 'text-editor-line-with-warning'; | |
| /** | |
| * @unrestricted | |
| */ | |
| Sources.UISourceCodeFrame.RowMessage = class { | |
| /** | |
| * @param {!Workspace.UISourceCode.Message} message | |
| */ | |
| constructor(message) { | |
| this._message = message; | |
| this._repeatCount = 1; | |
| this.element = createElementWithClass('div', 'text-editor-row-message'); | |
| this._icon = this.element.createChild('label', '', 'dt-icon-label'); | |
| this._icon.type = Sources.UISourceCodeFrame._iconClassPerLevel[message.level()]; | |
| this._repeatCountElement = | |
| this.element.createChild('span', 'text-editor-row-message-repeat-count hidden', 'dt-small-bubble'); | |
| this._repeatCountElement.type = Sources.UISourceCodeFrame._bubbleTypePerLevel[message.level()]; | |
| const linesContainer = this.element.createChild('div'); | |
| const lines = this._message.text().split('\n'); | |
| for (let i = 0; i < lines.length; ++i) { | |
| const messageLine = linesContainer.createChild('div'); | |
| messageLine.textContent = lines[i]; | |
| } | |
| } | |
| /** | |
| * @return {!Workspace.UISourceCode.Message} | |
| */ | |
| message() { | |
| return this._message; | |
| } | |
| /** | |
| * @return {number} | |
| */ | |
| repeatCount() { | |
| return this._repeatCount; | |
| } | |
| setRepeatCount(repeatCount) { | |
| if (this._repeatCount === repeatCount) { | |
| return; | |
| } | |
| this._repeatCount = repeatCount; | |
| this._updateMessageRepeatCount(); | |
| } | |
| _updateMessageRepeatCount() { | |
| this._repeatCountElement.textContent = this._repeatCount; | |
| const showRepeatCount = this._repeatCount > 1; | |
| this._repeatCountElement.classList.toggle('hidden', !showRepeatCount); | |
| this._icon.classList.toggle('hidden', showRepeatCount); | |
| } | |
| }; | |
| Sources.UISourceCodeFrame.RowMessageBucket = class { | |
| /** | |
| * @param {!Sources.UISourceCodeFrame} sourceFrame | |
| * @param {!TextEditor.CodeMirrorTextEditor} textEditor | |
| * @param {number} editorLineNumber | |
| */ | |
| constructor(sourceFrame, textEditor, editorLineNumber) { | |
| this._sourceFrame = sourceFrame; | |
| this.textEditor = textEditor; | |
| this._lineHandle = textEditor.textEditorPositionHandle(editorLineNumber, 0); | |
| this._decoration = createElementWithClass('div', 'text-editor-line-decoration'); | |
| this._decoration._messageBucket = this; | |
| this._wave = this._decoration.createChild('div', 'text-editor-line-decoration-wave'); | |
| this._icon = this._wave.createChild('span', 'text-editor-line-decoration-icon', 'dt-icon-label'); | |
| /** @type {?number} */ | |
| this._decorationStartColumn = null; | |
| this._messagesDescriptionElement = createElementWithClass('div', 'text-editor-messages-description-container'); | |
| /** @type {!Array.<!Sources.UISourceCodeFrame.RowMessage>} */ | |
| this._messages = []; | |
| this._level = null; | |
| } | |
| /** | |
| * @param {number} editorLineNumber | |
| * @param {number} columnNumber | |
| */ | |
| _updateWavePosition(editorLineNumber, columnNumber) { | |
| editorLineNumber = Math.min(editorLineNumber, this.textEditor.linesCount - 1); | |
| const lineText = this.textEditor.line(editorLineNumber); | |
| columnNumber = Math.min(columnNumber, lineText.length); | |
| const lineIndent = TextUtils.TextUtils.lineIndent(lineText).length; | |
| const startColumn = Math.max(columnNumber - 1, lineIndent); | |
| if (this._decorationStartColumn === startColumn) { | |
| return; | |
| } | |
| if (this._decorationStartColumn !== null) { | |
| this.textEditor.removeDecoration(this._decoration, editorLineNumber); | |
| } | |
| this.textEditor.addDecoration(this._decoration, editorLineNumber, startColumn); | |
| this._decorationStartColumn = startColumn; | |
| } | |
| /** | |
| * @return {!Element} | |
| */ | |
| messagesDescription() { | |
| this._messagesDescriptionElement.removeChildren(); | |
| UI.appendStyle(this._messagesDescriptionElement, 'source_frame/messagesPopover.css'); | |
| for (let i = 0; i < this._messages.length; ++i) { | |
| this._messagesDescriptionElement.appendChild(this._messages[i].element); | |
| } | |
| return this._messagesDescriptionElement; | |
| } | |
| detachFromEditor() { | |
| const position = this._lineHandle.resolve(); | |
| if (!position) { | |
| return; | |
| } | |
| const editorLineNumber = position.lineNumber; | |
| if (this._level) { | |
| this.textEditor.toggleLineClass( | |
| editorLineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], false); | |
| } | |
| if (this._decorationStartColumn !== null) { | |
| this.textEditor.removeDecoration(this._decoration, editorLineNumber); | |
| this._decorationStartColumn = null; | |
| } | |
| } | |
| /** | |
| * @return {number} | |
| */ | |
| uniqueMessagesCount() { | |
| return this._messages.length; | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode.Message} message | |
| */ | |
| addMessage(message) { | |
| for (let i = 0; i < this._messages.length; ++i) { | |
| const rowMessage = this._messages[i]; | |
| if (rowMessage.message().isEqual(message)) { | |
| rowMessage.setRepeatCount(rowMessage.repeatCount() + 1); | |
| return; | |
| } | |
| } | |
| const rowMessage = new Sources.UISourceCodeFrame.RowMessage(message); | |
| this._messages.push(rowMessage); | |
| this._updateDecoration(); | |
| } | |
| /** | |
| * @param {!Workspace.UISourceCode.Message} message | |
| */ | |
| removeMessage(message) { | |
| for (let i = 0; i < this._messages.length; ++i) { | |
| const rowMessage = this._messages[i]; | |
| if (!rowMessage.message().isEqual(message)) { | |
| continue; | |
| } | |
| rowMessage.setRepeatCount(rowMessage.repeatCount() - 1); | |
| if (!rowMessage.repeatCount()) { | |
| this._messages.splice(i, 1); | |
| } | |
| this._updateDecoration(); | |
| return; | |
| } | |
| } | |
| _updateDecoration() { | |
| if (!this._sourceFrame.isShowing()) { | |
| return; | |
| } | |
| if (!this._messages.length) { | |
| return; | |
| } | |
| const position = this._lineHandle.resolve(); | |
| if (!position) { | |
| return; | |
| } | |
| const editorLineNumber = position.lineNumber; | |
| let columnNumber = Number.MAX_VALUE; | |
| let maxMessage = null; | |
| for (let i = 0; i < this._messages.length; ++i) { | |
| const message = this._messages[i].message(); | |
| const editorLocation = | |
| this._sourceFrame.transformer().rawToEditorLocation(editorLineNumber, message.columnNumber()); | |
| columnNumber = Math.min(columnNumber, editorLocation[1]); | |
| if (!maxMessage || Workspace.UISourceCode.Message.messageLevelComparator(maxMessage, message) < 0) { | |
| maxMessage = message; | |
| } | |
| } | |
| this._updateWavePosition(editorLineNumber, columnNumber); | |
| if (this._level === maxMessage.level()) { | |
| return; | |
| } | |
| if (this._level) { | |
| this.textEditor.toggleLineClass( | |
| editorLineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], false); | |
| this._icon.type = ''; | |
| } | |
| this._level = maxMessage.level(); | |
| if (!this._level) { | |
| return; | |
| } | |
| this.textEditor.toggleLineClass(editorLineNumber, Sources.UISourceCodeFrame._lineClassPerLevel[this._level], true); | |
| this._icon.type = Sources.UISourceCodeFrame._iconClassPerLevel[this._level]; | |
| } | |
| }; | |
| Workspace.UISourceCode.Message._messageLevelPriority = { | |
| 'Warning': 3, | |
| 'Error': 4 | |
| }; | |
| /** | |
| * @param {!Workspace.UISourceCode.Message} a | |
| * @param {!Workspace.UISourceCode.Message} b | |
| * @return {number} | |
| */ | |
| Workspace.UISourceCode.Message.messageLevelComparator = function(a, b) { | |
| return Workspace.UISourceCode.Message._messageLevelPriority[a.level()] - | |
| Workspace.UISourceCode.Message._messageLevelPriority[b.level()]; | |
| }; | |
| Sources.UISourceCodeFrame.Plugin = class { | |
| /** | |
| * @param {!Workspace.UISourceCode} uiSourceCode | |
| * @return {boolean} | |
| */ | |
| static accepts(uiSourceCode) { | |
| return false; | |
| } | |
| wasShown() { | |
| } | |
| willHide() { | |
| } | |
| /** | |
| * @return {!Array<!UI.ToolbarItem>} | |
| */ | |
| rightToolbarItems() { | |
| return []; | |
| } | |
| /** | |
| * @return {!Array<!UI.ToolbarItem>} | |
| */ | |
| leftToolbarItems() { | |
| return []; | |
| } | |
| /** | |
| * @param {!UI.ContextMenu} contextMenu | |
| * @param {number} lineNumber | |
| * @return {!Promise} | |
| */ | |
| populateLineGutterContextMenu(contextMenu, lineNumber) { | |
| return Promise.resolve(); | |
| } | |
| /** | |
| * @param {!UI.ContextMenu} contextMenu | |
| * @param {number} lineNumber | |
| * @param {number} columnNumber | |
| * @return {!Promise} | |
| */ | |
| populateTextAreaContextMenu(contextMenu, lineNumber, columnNumber) { | |
| return Promise.resolve(); | |
| } | |
| dispose() { | |
| } | |
| }; | |
| /** @enum {symbol} */ | |
| Sources.UISourceCodeFrame.Events = { | |
| ToolbarItemsChanged: Symbol('ToolbarItemsChanged') | |
| }; |