UNPKG

@dcloudio/uni-debugger

Version:

uni-app debugger

586 lines (525 loc) 19.1 kB
/* * Copyright (C) 2013 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. */ /** * @unrestricted */ Persistence.FileSystemWorkspaceBinding = class { /** * @param {!Persistence.IsolatedFileSystemManager} isolatedFileSystemManager * @param {!Workspace.Workspace} workspace */ constructor(isolatedFileSystemManager, workspace) { this._isolatedFileSystemManager = isolatedFileSystemManager; this._workspace = workspace; this._eventListeners = [ this._isolatedFileSystemManager.addEventListener( Persistence.IsolatedFileSystemManager.Events.FileSystemAdded, this._onFileSystemAdded, this), this._isolatedFileSystemManager.addEventListener( Persistence.IsolatedFileSystemManager.Events.FileSystemRemoved, this._onFileSystemRemoved, this), this._isolatedFileSystemManager.addEventListener( Persistence.IsolatedFileSystemManager.Events.FileSystemFilesChanged, this._fileSystemFilesChanged, this) ]; /** @type {!Map.<string, !Persistence.FileSystemWorkspaceBinding.FileSystem>} */ this._boundFileSystems = new Map(); this._isolatedFileSystemManager.waitForFileSystems().then(this._onFileSystemsLoaded.bind(this)); } /** * @param {string} fileSystemPath * @return {string} */ static projectId(fileSystemPath) { return fileSystemPath; } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {!Array<string>} */ static relativePath(uiSourceCode) { const baseURL = /** @type {!Persistence.FileSystemWorkspaceBinding.FileSystem}*/ (uiSourceCode.project())._fileSystemBaseURL; return uiSourceCode.url().substring(baseURL.length).split('/'); } /** * @param {!Workspace.Project} project * @return {string} */ static fileSystemType(project) { const fileSystem = /** @type {!Persistence.FileSystemWorkspaceBinding.FileSystem}*/ (project)._fileSystem; return fileSystem.type(); } /** * @param {!Workspace.Project} project * @param {string} relativePath * @return {string} */ static completeURL(project, relativePath) { const fsProject = /** @type {!Persistence.FileSystemWorkspaceBinding.FileSystem}*/ (project); return fsProject._fileSystemBaseURL + relativePath; } /** * @param {string} extension * @return {!Common.ResourceType} */ static _contentTypeForExtension(extension) { if (Persistence.FileSystemWorkspaceBinding._styleSheetExtensions.has(extension)) return Common.resourceTypes.Stylesheet; if (Persistence.FileSystemWorkspaceBinding._documentExtensions.has(extension)) return Common.resourceTypes.Document; if (Persistence.FileSystemWorkspaceBinding._imageExtensions.has(extension)) return Common.resourceTypes.Image; if (Persistence.FileSystemWorkspaceBinding._scriptExtensions.has(extension)) return Common.resourceTypes.Script; return Persistence.FileSystemWorkspaceBinding._binaryExtensions.has(extension) ? Common.resourceTypes.Other : Common.resourceTypes.Document; } /** * @param {string} projectId * @return {string} */ static fileSystemPath(projectId) { return projectId; } /** * @return {!Persistence.IsolatedFileSystemManager} */ fileSystemManager() { return this._isolatedFileSystemManager; } /** * @param {!Array<!Persistence.IsolatedFileSystem>} fileSystems */ _onFileSystemsLoaded(fileSystems) { for (const fileSystem of fileSystems) this._addFileSystem(fileSystem); } /** * @param {!Common.Event} event */ _onFileSystemAdded(event) { const fileSystem = /** @type {!Persistence.IsolatedFileSystem} */ (event.data); this._addFileSystem(fileSystem); } /** * @param {!Persistence.IsolatedFileSystem} fileSystem */ _addFileSystem(fileSystem) { const boundFileSystem = new Persistence.FileSystemWorkspaceBinding.FileSystem(this, fileSystem, this._workspace); this._boundFileSystems.set(fileSystem.path(), boundFileSystem); } /** * @param {!Common.Event} event */ _onFileSystemRemoved(event) { const fileSystem = /** @type {!Persistence.IsolatedFileSystem} */ (event.data); const boundFileSystem = this._boundFileSystems.get(fileSystem.path()); boundFileSystem.dispose(); this._boundFileSystems.remove(fileSystem.path()); } /** * @param {!Common.Event} event */ _fileSystemFilesChanged(event) { const paths = /** @type {!Persistence.IsolatedFileSystemManager.FilesChangedData} */ (event.data); for (const fileSystemPath of paths.changed.keysArray()) { const fileSystem = this._boundFileSystems.get(fileSystemPath); if (!fileSystem) continue; paths.changed.get(fileSystemPath).forEach(path => fileSystem._fileChanged(path)); } for (const fileSystemPath of paths.added.keysArray()) { const fileSystem = this._boundFileSystems.get(fileSystemPath); if (!fileSystem) continue; paths.added.get(fileSystemPath).forEach(path => fileSystem._fileChanged(path)); } for (const fileSystemPath of paths.removed.keysArray()) { const fileSystem = this._boundFileSystems.get(fileSystemPath); if (!fileSystem) continue; paths.removed.get(fileSystemPath).forEach(path => fileSystem.removeUISourceCode(path)); } } dispose() { Common.EventTarget.removeEventListeners(this._eventListeners); for (const fileSystem of this._boundFileSystems.values()) { fileSystem.dispose(); this._boundFileSystems.remove(fileSystem._fileSystem.path()); } } }; Persistence.FileSystemWorkspaceBinding._styleSheetExtensions = new Set(['css', 'scss', 'sass', 'less']); Persistence.FileSystemWorkspaceBinding._documentExtensions = new Set(['htm', 'html', 'asp', 'aspx', 'phtml', 'jsp']); Persistence.FileSystemWorkspaceBinding._scriptExtensions = new Set([ 'asp', 'aspx', 'c', 'cc', 'cljs', 'coffee', 'cpp', 'cs', 'dart', 'java', 'js', 'jsp', 'jsx', 'h', 'm', 'mjs', 'mm', 'py', 'sh', 'ts', 'tsx', 'ls' ]); Persistence.FileSystemWorkspaceBinding._imageExtensions = Persistence.IsolatedFileSystem.ImageExtensions; Persistence.FileSystemWorkspaceBinding._binaryExtensions = Persistence.IsolatedFileSystem.BinaryExtensions; /** * @implements {Workspace.Project} * @unrestricted */ Persistence.FileSystemWorkspaceBinding.FileSystem = class extends Workspace.ProjectStore { /** * @param {!Persistence.FileSystemWorkspaceBinding} fileSystemWorkspaceBinding * @param {!Persistence.IsolatedFileSystem} isolatedFileSystem * @param {!Workspace.Workspace} workspace */ constructor(fileSystemWorkspaceBinding, isolatedFileSystem, workspace) { const fileSystemPath = isolatedFileSystem.path(); const id = Persistence.FileSystemWorkspaceBinding.projectId(fileSystemPath); console.assert(!workspace.project(id)); const displayName = fileSystemPath.substr(fileSystemPath.lastIndexOf('/') + 1); super(workspace, id, Workspace.projectTypes.FileSystem, displayName); this._fileSystem = isolatedFileSystem; this._fileSystemBaseURL = this._fileSystem.path() + '/'; this._fileSystemParentURL = this._fileSystemBaseURL.substr(0, fileSystemPath.lastIndexOf('/') + 1); this._fileSystemWorkspaceBinding = fileSystemWorkspaceBinding; this._fileSystemPath = fileSystemPath; /** @type {!Set<string>} */ this._creatingFilesGuard = new Set(); workspace.addProject(this); this.populate(); } /** * @return {string} */ fileSystemPath() { return this._fileSystemPath; } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode * @return {string} */ mimeType(uiSourceCode) { return Common.ResourceType.mimeFromURL(uiSourceCode.url()) || 'text/plain'; } /** * @return {!Array<string>} */ initialGitFolders() { return this._fileSystem.initialGitFolders().map(folder => this._fileSystemPath + '/' + folder); } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {string} */ _filePathForUISourceCode(uiSourceCode) { return uiSourceCode.url().substring(this._fileSystemPath.length); } /** * @override * @return {boolean} */ isServiceProject() { return false; } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode * @return {!Promise<?Workspace.UISourceCodeMetadata>} */ requestMetadata(uiSourceCode) { if (uiSourceCode[Persistence.FileSystemWorkspaceBinding._metadata]) return uiSourceCode[Persistence.FileSystemWorkspaceBinding._metadata]; const relativePath = this._filePathForUISourceCode(uiSourceCode); const promise = this._fileSystem.getMetadata(relativePath).then(onMetadata); uiSourceCode[Persistence.FileSystemWorkspaceBinding._metadata] = promise; return promise; /** * @param {?{modificationTime: !Date, size: number}} metadata * @return {?Workspace.UISourceCodeMetadata} */ function onMetadata(metadata) { if (!metadata) return null; return new Workspace.UISourceCodeMetadata(metadata.modificationTime, metadata.size); } } /** * @param {!Workspace.UISourceCode} uiSourceCode * @return {!Promise<?Blob>} */ requestFileBlob(uiSourceCode) { return this._fileSystem.requestFileBlob(this._filePathForUISourceCode(uiSourceCode)); } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode * @param {function(?string, boolean)} callback */ requestFileContent(uiSourceCode, callback) { const filePath = this._filePathForUISourceCode(uiSourceCode); this._fileSystem.requestFileContent(filePath, callback); } /** * @override * @return {boolean} */ canSetFileContent() { return true; } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode * @param {string} newContent * @param {boolean} isBase64 * @return {!Promise} */ async setFileContent(uiSourceCode, newContent, isBase64) { const filePath = this._filePathForUISourceCode(uiSourceCode); await this._fileSystem.setFileContent(filePath, newContent, isBase64); } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode * @return {string} */ fullDisplayName(uiSourceCode) { const baseURL = /** @type {!Persistence.FileSystemWorkspaceBinding.FileSystem}*/ (uiSourceCode.project())._fileSystemParentURL; return uiSourceCode.url().substring(baseURL.length); } /** * @override * @return {boolean} */ canRename() { return true; } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode * @param {string} newName * @param {function(boolean, string=, string=, !Common.ResourceType=)} callback */ rename(uiSourceCode, newName, callback) { if (newName === uiSourceCode.name()) { callback(true, uiSourceCode.name(), uiSourceCode.url(), uiSourceCode.contentType()); return; } let filePath = this._filePathForUISourceCode(uiSourceCode); this._fileSystem.renameFile(filePath, newName, innerCallback.bind(this)); /** * @param {boolean} success * @param {string=} newName * @this {Persistence.FileSystemWorkspaceBinding.FileSystem} */ function innerCallback(success, newName) { if (!success || !newName) { callback(false, newName); return; } console.assert(newName); const slash = filePath.lastIndexOf('/'); const parentPath = filePath.substring(0, slash); filePath = parentPath + '/' + newName; filePath = filePath.substr(1); const extension = this._extensionForPath(newName); const newURL = this._fileSystemBaseURL + filePath; const newContentType = Persistence.FileSystemWorkspaceBinding._contentTypeForExtension(extension); this.renameUISourceCode(uiSourceCode, newName); callback(true, newName, newURL, newContentType); } } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode * @param {string} query * @param {boolean} caseSensitive * @param {boolean} isRegex * @return {!Promise<!Array<!Common.ContentProvider.SearchMatch>>} */ searchInFileContent(uiSourceCode, query, caseSensitive, isRegex) { return new Promise(resolve => { const filePath = this._filePathForUISourceCode(uiSourceCode); this._fileSystem.requestFileContent(filePath, contentCallback); /** * @param {?string} content */ function contentCallback(content) { resolve(content ? Common.ContentProvider.performSearchInContent(content, query, caseSensitive, isRegex) : []); } }); } /** * @override * @param {!Workspace.ProjectSearchConfig} searchConfig * @param {!Array.<string>} filesMathingFileQuery * @param {!Common.Progress} progress * @return {!Promise<!Array<string>>} */ async findFilesMatchingSearchRequest(searchConfig, filesMathingFileQuery, progress) { let result = filesMathingFileQuery; const queriesToRun = searchConfig.queries().slice(); if (!queriesToRun.length) queriesToRun.push(''); progress.setTotalWork(queriesToRun.length); for (const query of queriesToRun) { const files = await this._fileSystem.searchInPath(searchConfig.isRegex() ? '' : query, progress); result = result.intersectOrdered(files.sort(), String.naturalOrderComparator); progress.worked(1); } progress.done(); return result; } /** * @override * @param {!Common.Progress} progress */ indexContent(progress) { this._fileSystem.indexContent(progress); } /** * @param {string} path * @return {string} */ _extensionForPath(path) { return Common.ParsedURL.extractExtension(path); } populate() { const chunkSize = 1000; const filePaths = this._fileSystem.initialFilePaths(); reportFileChunk.call(this, 0); /** * @param {number} from * @this {Persistence.FileSystemWorkspaceBinding.FileSystem} */ function reportFileChunk(from) { const to = Math.min(from + chunkSize, filePaths.length); for (let i = from; i < to; ++i) this._addFile(filePaths[i]); if (to < filePaths.length) setTimeout(reportFileChunk.bind(this, to), 100); } } /** * @override * @param {string} url */ excludeFolder(url) { let relativeFolder = url.substring(this._fileSystemBaseURL.length); if (!relativeFolder.startsWith('/')) relativeFolder = '/' + relativeFolder; if (!relativeFolder.endsWith('/')) relativeFolder += '/'; this._fileSystem.addExcludedFolder(relativeFolder); const uiSourceCodes = this.uiSourceCodes().slice(); for (let i = 0; i < uiSourceCodes.length; ++i) { const uiSourceCode = uiSourceCodes[i]; if (uiSourceCode.url().startsWith(url)) this.removeUISourceCode(uiSourceCode.url()); } } /** * @override * @param {string} path * @return {boolean} */ canExcludeFolder(path) { return !!path && Persistence.FileSystemWorkspaceBinding.fileSystemType(this) !== 'overrides'; } /** * @override * @return {boolean} */ canCreateFile() { return true; } /** * @override * @param {string} path * @param {?string} name * @param {string} content * @param {boolean=} isBase64 * @return {!Promise<?Workspace.UISourceCode>} */ async createFile(path, name, content, isBase64) { const guardFileName = this._fileSystemPath + path + (!path.endsWith('/') ? '/' : '') + name; this._creatingFilesGuard.add(guardFileName); const filePath = await this._fileSystem.createFile(path, name); if (!filePath) return null; const uiSourceCode = this._addFile(filePath); uiSourceCode.setContent(content, !!isBase64); this._creatingFilesGuard.delete(guardFileName); return uiSourceCode; } /** * @override * @param {!Workspace.UISourceCode} uiSourceCode */ deleteFile(uiSourceCode) { const relativePath = this._filePathForUISourceCode(uiSourceCode); this._fileSystem.deleteFile(relativePath).then(success => { if (success) this.removeUISourceCode(uiSourceCode.url()); }); } /** * @override */ remove() { this._fileSystemWorkspaceBinding._isolatedFileSystemManager.removeFileSystem(this._fileSystem); } /** * @param {string} filePath * @return {!Workspace.UISourceCode} */ _addFile(filePath) { const extension = this._extensionForPath(filePath); const contentType = Persistence.FileSystemWorkspaceBinding._contentTypeForExtension(extension); const uiSourceCode = this.createUISourceCode(this._fileSystemBaseURL + filePath, contentType); this.addUISourceCode(uiSourceCode); return uiSourceCode; } /** * @param {string} path */ _fileChanged(path) { // Ignore files that are being created but do not have content yet. if (this._creatingFilesGuard.has(path)) return; const uiSourceCode = this.uiSourceCodeForURL(path); if (!uiSourceCode) { const contentType = Persistence.FileSystemWorkspaceBinding._contentTypeForExtension(this._extensionForPath(path)); this.addUISourceCode(this.createUISourceCode(path, contentType)); return; } uiSourceCode[Persistence.FileSystemWorkspaceBinding._metadata] = null; uiSourceCode.checkContentUpdated(); } dispose() { this.removeProject(); } }; Persistence.FileSystemWorkspaceBinding._metadata = Symbol('FileSystemWorkspaceBinding.Metadata');