UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

462 lines (407 loc) 14.8 kB
// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * @unrestricted */ Persistence.Persistence = class extends Common.Object { /** * @param {!Workspace.Workspace} workspace * @param {!Bindings.BreakpointManager} breakpointManager */ constructor(workspace, breakpointManager) { super(); this._workspace = workspace; this._breakpointManager = breakpointManager; /** @type {!Map<string, number>} */ this._filePathPrefixesToBindingCount = new Map(); /** @type {!Multimap<!Workspace.UISourceCode, function()>} */ this._subscribedBindingEventListeners = new Multimap(); const linkDecorator = new Persistence.PersistenceUtils.LinkDecorator(this); Components.Linkifier.setLinkDecorator(linkDecorator); this._mapping = null; this.setAutomappingEnabled(true); } /** * @param {boolean} enabled */ setAutomappingEnabled(enabled) { if (enabled === !!this._mapping) return; if (!enabled) { this._mapping.dispose(); this._mapping = null; } else { this._mapping = new Persistence.Automapping( this._workspace, this._onStatusAdded.bind(this), this._onStatusRemoved.bind(this)); } } /** * @param {!Persistence.PersistenceBinding} binding */ addBinding(binding) { this._innerAddBinding(binding); } /** * @param {!Persistence.PersistenceBinding} binding */ addBindingForTest(binding) { this._innerAddBinding(binding); } /** * @param {!Persistence.PersistenceBinding} binding */ removeBinding(binding) { this._innerRemoveBinding(binding); } /** * @param {!Persistence.PersistenceBinding} binding */ removeBindingForTest(binding) { this._innerRemoveBinding(binding); } /** * @param {!Persistence.PersistenceBinding} binding */ _innerAddBinding(binding) { binding.network[Persistence.Persistence._binding] = binding; binding.fileSystem[Persistence.Persistence._binding] = binding; binding.fileSystem.forceLoadOnCheckContent(); binding.network.addEventListener( Workspace.UISourceCode.Events.WorkingCopyCommitted, this._onWorkingCopyCommitted, this); binding.fileSystem.addEventListener( Workspace.UISourceCode.Events.WorkingCopyCommitted, this._onWorkingCopyCommitted, this); binding.network.addEventListener( Workspace.UISourceCode.Events.WorkingCopyChanged, this._onWorkingCopyChanged, this); binding.fileSystem.addEventListener( Workspace.UISourceCode.Events.WorkingCopyChanged, this._onWorkingCopyChanged, this); this._addFilePathBindingPrefixes(binding.fileSystem.url()); this._moveBreakpoints(binding.fileSystem, binding.network); console.assert(!binding.fileSystem.isDirty() || !binding.network.isDirty()); if (binding.fileSystem.isDirty()) { this._syncWorkingCopy(binding.fileSystem); } else if (binding.network.isDirty()) { this._syncWorkingCopy(binding.network); } else if (binding.network.hasCommits() && binding.network.content() !== binding.fileSystem.content()) { binding.network.setWorkingCopy(binding.network.content()); this._syncWorkingCopy(binding.network); } this._notifyBindingEvent(binding.network); this._notifyBindingEvent(binding.fileSystem); this.dispatchEventToListeners(Persistence.Persistence.Events.BindingCreated, binding); } /** * @param {!Persistence.PersistenceBinding} binding */ _innerRemoveBinding(binding) { if (binding.network[Persistence.Persistence._binding] !== binding) return; console.assert( binding.network[Persistence.Persistence._binding] === binding.fileSystem[Persistence.Persistence._binding], 'ERROR: inconsistent binding for networkURL ' + binding.network.url()); binding.network[Persistence.Persistence._binding] = null; binding.fileSystem[Persistence.Persistence._binding] = null; binding.network.removeEventListener( Workspace.UISourceCode.Events.WorkingCopyCommitted, this._onWorkingCopyCommitted, this); binding.fileSystem.removeEventListener( Workspace.UISourceCode.Events.WorkingCopyCommitted, this._onWorkingCopyCommitted, this); binding.network.removeEventListener( Workspace.UISourceCode.Events.WorkingCopyChanged, this._onWorkingCopyChanged, this); binding.fileSystem.removeEventListener( Workspace.UISourceCode.Events.WorkingCopyChanged, this._onWorkingCopyChanged, this); this._removeFilePathBindingPrefixes(binding.fileSystem.url()); this._breakpointManager.copyBreakpoints(binding.network.url(), binding.fileSystem); this._notifyBindingEvent(binding.network); this._notifyBindingEvent(binding.fileSystem); this.dispatchEventToListeners(Persistence.Persistence.Events.BindingRemoved, binding); } /** * @param {!Persistence.AutomappingStatus} status */ _onStatusAdded(status) { const binding = new Persistence.PersistenceBinding(status.network, status.fileSystem); status[Persistence.Persistence._binding] = binding; this._innerAddBinding(binding); } /** * @param {!Persistence.AutomappingStatus} status */ _onStatusRemoved(status) { const binding = /** @type {!Persistence.PersistenceBinding} */ (status[Persistence.Persistence._binding]); this._innerRemoveBinding(binding); } /** * @param {!Common.Event} event */ _onWorkingCopyChanged(event) { const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data); this._syncWorkingCopy(uiSourceCode); } /** * @param {!Workspace.UISourceCode} uiSourceCode */ _syncWorkingCopy(uiSourceCode) { const binding = uiSourceCode[Persistence.Persistence._binding]; if (!binding || binding[Persistence.Persistence._muteWorkingCopy]) return; const other = binding.network === uiSourceCode ? binding.fileSystem : binding.network; if (!uiSourceCode.isDirty()) { binding[Persistence.Persistence._muteWorkingCopy] = true; other.resetWorkingCopy(); binding[Persistence.Persistence._muteWorkingCopy] = false; this._contentSyncedForTest(); return; } const target = Bindings.NetworkProject.targetForUISourceCode(binding.network); if (target.isNodeJS()) { const newContent = uiSourceCode.workingCopy(); other.requestContent().then(() => { const nodeJSContent = Persistence.Persistence.rewrapNodeJSContent(other, other.workingCopy(), newContent); setWorkingCopy.call(this, () => nodeJSContent); }); return; } setWorkingCopy.call(this, () => uiSourceCode.workingCopy()); /** * @param {function():string} workingCopyGetter * @this {Persistence.Persistence} */ function setWorkingCopy(workingCopyGetter) { binding[Persistence.Persistence._muteWorkingCopy] = true; other.setWorkingCopyGetter(workingCopyGetter); binding[Persistence.Persistence._muteWorkingCopy] = false; this._contentSyncedForTest(); } } /** * @param {!Common.Event} event */ _onWorkingCopyCommitted(event) { const uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data.uiSourceCode); const newContent = /** @type {string} */ (event.data.content); this.syncContent(uiSourceCode, newContent, event.data.encoded); } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {string} newContent * @param {boolean} encoded */ syncContent(uiSourceCode, newContent, encoded) { const binding = uiSourceCode[Persistence.Persistence._binding]; if (!binding || binding[Persistence.Persistence._muteCommit]) return; const other = binding.network === uiSourceCode ? binding.fileSystem : binding.network; const target = Bindings.NetworkProject.targetForUISourceCode(binding.network); if (target.isNodeJS()) { other.requestContent().then(currentContent => { const nodeJSContent = Persistence.Persistence.rewrapNodeJSContent(other, currentContent, newContent); setContent.call(this, nodeJSContent); }); return; } setContent.call(this, newContent); /** * @param {string} newContent * @this {Persistence.Persistence} */ function setContent(newContent) { binding[Persistence.Persistence._muteCommit] = true; other.setContent(newContent, encoded); binding[Persistence.Persistence._muteCommit] = false; this._contentSyncedForTest(); } } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {string} currentContent * @param {string} newContent * @return {string} */ static rewrapNodeJSContent(uiSourceCode, currentContent, newContent) { if (uiSourceCode.project().type() === Workspace.projectTypes.FileSystem) { if (newContent.startsWith(Persistence.Persistence._NodePrefix) && newContent.endsWith(Persistence.Persistence._NodeSuffix)) { newContent = newContent.substring( Persistence.Persistence._NodePrefix.length, newContent.length - Persistence.Persistence._NodeSuffix.length); } if (currentContent.startsWith(Persistence.Persistence._NodeShebang)) newContent = Persistence.Persistence._NodeShebang + newContent; } else { if (newContent.startsWith(Persistence.Persistence._NodeShebang)) newContent = newContent.substring(Persistence.Persistence._NodeShebang.length); if (currentContent.startsWith(Persistence.Persistence._NodePrefix) && currentContent.endsWith(Persistence.Persistence._NodeSuffix)) newContent = Persistence.Persistence._NodePrefix + newContent + Persistence.Persistence._NodeSuffix; } return newContent; } _contentSyncedForTest() { } /** * @param {!Workspace.UISourceCode} from * @param {!Workspace.UISourceCode} to */ _moveBreakpoints(from, to) { const breakpoints = this._breakpointManager.breakpointsForUISourceCode(from); for (const breakpoint of breakpoints) { breakpoint.remove(false /* keepInStorage */); this._breakpointManager.setBreakpoint( to, breakpoint.lineNumber(), breakpoint.columnNumber(), breakpoint.condition(), breakpoint.enabled()); } } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {boolean} */ hasUnsavedCommittedChanges(uiSourceCode) { if (this._workspace.hasResourceContentTrackingExtensions()) return false; if (uiSourceCode.project().canSetFileContent()) return false; if (uiSourceCode[Persistence.Persistence._binding]) return false; return !!uiSourceCode.hasCommits(); } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {?Persistence.PersistenceBinding} */ binding(uiSourceCode) { return uiSourceCode[Persistence.Persistence._binding] || null; } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {function()} listener */ subscribeForBindingEvent(uiSourceCode, listener) { this._subscribedBindingEventListeners.set(uiSourceCode, listener); } /** * @param {!Workspace.UISourceCode} uiSourceCode * @param {function()} listener */ unsubscribeFromBindingEvent(uiSourceCode, listener) { this._subscribedBindingEventListeners.delete(uiSourceCode, listener); } /** * @param {!Workspace.UISourceCode} uiSourceCode */ _notifyBindingEvent(uiSourceCode) { if (!this._subscribedBindingEventListeners.has(uiSourceCode)) return; const listeners = Array.from(this._subscribedBindingEventListeners.get(uiSourceCode)); for (const listener of listeners) listener.call(null); } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {?Workspace.UISourceCode} */ fileSystem(uiSourceCode) { const binding = this.binding(uiSourceCode); return binding ? binding.fileSystem : null; } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {?Workspace.UISourceCode} */ network(uiSourceCode) { const binding = this.binding(uiSourceCode); return binding ? binding.network : null; } /** * @param {string} filePath */ _addFilePathBindingPrefixes(filePath) { let relative = ''; for (const token of filePath.split('/')) { relative += token + '/'; const count = this._filePathPrefixesToBindingCount.get(relative) || 0; this._filePathPrefixesToBindingCount.set(relative, count + 1); } } /** * @param {string} filePath */ _removeFilePathBindingPrefixes(filePath) { let relative = ''; for (const token of filePath.split('/')) { relative += token + '/'; const count = this._filePathPrefixesToBindingCount.get(relative); if (count === 1) this._filePathPrefixesToBindingCount.delete(relative); else this._filePathPrefixesToBindingCount.set(relative, count - 1); } } /** * @param {string} filePath * @return {boolean} */ filePathHasBindings(filePath) { if (!filePath.endsWith('/')) filePath += '/'; return this._filePathPrefixesToBindingCount.has(filePath); } dispose() { if (this._mapping) { this._mapping.dispose(); this._mapping = null; } } }; Persistence.Persistence._binding = Symbol('Persistence.Binding'); Persistence.Persistence._muteCommit = Symbol('Persistence.MuteCommit'); Persistence.Persistence._muteWorkingCopy = Symbol('Persistence.MuteWorkingCopy'); Persistence.Persistence._NodePrefix = '(function (exports, require, module, __filename, __dirname) { '; Persistence.Persistence._NodeSuffix = '\n});'; Persistence.Persistence._NodeShebang = '#!/usr/bin/env node'; Persistence.Persistence.Events = { BindingCreated: Symbol('BindingCreated'), BindingRemoved: Symbol('BindingRemoved') }; /** * @unrestricted */ Persistence.PathEncoder = class { constructor() { /** @type {!Common.CharacterIdMap<string>} */ this._encoder = new Common.CharacterIdMap(); } /** * @param {string} path * @return {string} */ encode(path) { return path.split('/').map(token => this._encoder.toChar(token)).join(''); } /** * @param {string} path * @return {string} */ decode(path) { return path.split('').map(token => this._encoder.fromChar(token)).join('/'); } }; /** * @unrestricted */ Persistence.PersistenceBinding = class { /** * @param {!Workspace.UISourceCode} network * @param {!Workspace.UISourceCode} fileSystem */ constructor(network, fileSystem) { this.network = network; this.fileSystem = fileSystem; } }; /** * @interface */ Persistence.MappingSystem = function() {}; Persistence.MappingSystem.prototype = { dispose: function() {} }; /** @type {!Persistence.Persistence} */ Persistence.persistence;