| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Common from '../../core/common/common.js'; |
| import type * as Platform from '../../core/platform/platform.js'; |
| import * as Root from '../../core/root/root.js'; |
| import type * as TextUtils from '../text_utils/text_utils.js'; |
| |
| import type {SearchConfig} from './SearchConfig.js'; |
| import {UISourceCode, type UISourceCodeMetadata} from './UISourceCode.js'; |
| |
| export interface Project { |
| workspace(): WorkspaceImpl; |
| id(): string; |
| type(): projectTypes; |
| isServiceProject(): boolean; |
| displayName(): string; |
| requestMetadata(uiSourceCode: UISourceCode): Promise<UISourceCodeMetadata|null>; |
| requestFileContent(uiSourceCode: UISourceCode): Promise<TextUtils.ContentData.ContentDataOrError>; |
| canSetFileContent(): boolean; |
| setFileContent(uiSourceCode: UISourceCode, newContent: string, isBase64: boolean): Promise<void>; |
| fullDisplayName(uiSourceCode: UISourceCode): string; |
| mimeType(uiSourceCode: UISourceCode): string; |
| canRename(): boolean; |
| rename( |
| uiSourceCode: UISourceCode, newName: Platform.DevToolsPath.RawPathString, |
| callback: |
| (arg0: boolean, arg1?: string, arg2?: Platform.DevToolsPath.UrlString, |
| arg3?: Common.ResourceType.ResourceType) => void): void; |
| excludeFolder(path: Platform.DevToolsPath.UrlString): void; |
| canExcludeFolder(path: Platform.DevToolsPath.EncodedPathString): boolean; |
| createFile(path: Platform.DevToolsPath.EncodedPathString, name: string|null, content: string, isBase64?: boolean): |
| Promise<UISourceCode|null>; |
| canCreateFile(): boolean; |
| deleteFile(uiSourceCode: UISourceCode): void; |
| deleteDirectoryRecursively(path: Platform.DevToolsPath.EncodedPathString): Promise<boolean>; |
| remove(): void; |
| removeUISourceCode(url: Platform.DevToolsPath.UrlString): void; |
| searchInFileContent(uiSourceCode: UISourceCode, query: string, caseSensitive: boolean, isRegex: boolean): |
| Promise<TextUtils.ContentProvider.SearchMatch[]>; |
| findFilesMatchingSearchRequest( |
| searchConfig: SearchConfig, filesMatchingFileQuery: UISourceCode[], |
| progress: Common.Progress.Progress): Promise<Map<UISourceCode, TextUtils.ContentProvider.SearchMatch[]|null>>; |
| indexContent(progress: Common.Progress.Progress): void; |
| uiSourceCodeForURL(url: Platform.DevToolsPath.UrlString): UISourceCode|null; |
| |
| /** |
| * Returns an iterator for the currently registered {@link UISourceCode}s for this project. When |
| * new {@link UISourceCode}s are added while iterating, they might show up already. When removing |
| * {@link UISourceCode}s while iterating, these will no longer show up, and will have no effect |
| * on the other entries. |
| * |
| * @returns an iterator for the sources provided by this project. |
| */ |
| uiSourceCodes(): Iterable<UISourceCode>; |
| } |
| |
| /* eslint-disable @typescript-eslint/naming-convention -- Used by web_tests. */ |
| export enum projectTypes { |
| Debugger = 'debugger', |
| Formatter = 'formatter', |
| Network = 'network', |
| FileSystem = 'filesystem', |
| ConnectableFileSystem = 'connectablefilesystem', |
| ContentScripts = 'contentscripts', |
| Service = 'service', |
| } |
| /* eslint-enable @typescript-eslint/naming-convention */ |
| |
| export abstract class ProjectStore implements Project { |
| readonly #workspace: WorkspaceImpl; |
| readonly #id: string; |
| readonly #type: projectTypes; |
| readonly #displayName: string; |
| readonly #uiSourceCodes = new Map<Platform.DevToolsPath.UrlString, UISourceCode>(); |
| |
| constructor(workspace: WorkspaceImpl, id: string, type: projectTypes, displayName: string) { |
| this.#workspace = workspace; |
| this.#id = id; |
| this.#type = type; |
| this.#displayName = displayName; |
| } |
| |
| id(): string { |
| return this.#id; |
| } |
| |
| type(): projectTypes { |
| return this.#type; |
| } |
| |
| displayName(): string { |
| return this.#displayName; |
| } |
| |
| workspace(): WorkspaceImpl { |
| return this.#workspace; |
| } |
| |
| createUISourceCode(url: Platform.DevToolsPath.UrlString, contentType: Common.ResourceType.ResourceType): |
| UISourceCode { |
| return new UISourceCode(this, url, contentType); |
| } |
| |
| addUISourceCode(uiSourceCode: UISourceCode): boolean { |
| const url = uiSourceCode.url(); |
| if (this.uiSourceCodeForURL(url)) { |
| return false; |
| } |
| this.#uiSourceCodes.set(url, uiSourceCode); |
| this.#workspace.dispatchEventToListeners(Events.UISourceCodeAdded, uiSourceCode); |
| return true; |
| } |
| |
| removeUISourceCode(url: Platform.DevToolsPath.UrlString): void { |
| const uiSourceCode = this.#uiSourceCodes.get(url); |
| if (uiSourceCode === undefined) { |
| return; |
| } |
| this.#uiSourceCodes.delete(url); |
| this.#workspace.dispatchEventToListeners(Events.UISourceCodeRemoved, uiSourceCode); |
| } |
| |
| removeProject(): void { |
| this.#workspace.removeProject(this); |
| this.#uiSourceCodes.clear(); |
| } |
| |
| uiSourceCodeForURL(url: Platform.DevToolsPath.UrlString): UISourceCode|null { |
| return this.#uiSourceCodes.get(url) ?? null; |
| } |
| |
| uiSourceCodes(): Iterable<UISourceCode> { |
| return this.#uiSourceCodes.values(); |
| } |
| |
| renameUISourceCode(uiSourceCode: UISourceCode, newName: string): void { |
| const oldPath = uiSourceCode.url(); |
| const newPath = uiSourceCode.parentURL() ? |
| Common.ParsedURL.ParsedURL.urlFromParentUrlAndName(uiSourceCode.parentURL(), newName) : |
| Common.ParsedURL.ParsedURL.preEncodeSpecialCharactersInPath(newName) as Platform.DevToolsPath.UrlString; |
| this.#uiSourceCodes.set(newPath, uiSourceCode); |
| this.#uiSourceCodes.delete(oldPath); |
| } |
| |
| // No-op implementation for a handful of interface methods. |
| |
| rename( |
| _uiSourceCode: UISourceCode, _newName: string, |
| _callback: |
| (arg0: boolean, arg1?: string, arg2?: Platform.DevToolsPath.UrlString, |
| arg3?: Common.ResourceType.ResourceType) => void): void { |
| } |
| excludeFolder(_path: Platform.DevToolsPath.UrlString): void { |
| } |
| deleteFile(_uiSourceCode: UISourceCode): void { |
| } |
| deleteDirectoryRecursively(_path: Platform.DevToolsPath.EncodedPathString): Promise<boolean> { |
| return Promise.resolve(false); |
| } |
| remove(): void { |
| } |
| indexContent(_progress: Common.Progress.Progress): void { |
| } |
| |
| abstract isServiceProject(): boolean; |
| abstract requestMetadata(uiSourceCode: UISourceCode): Promise<UISourceCodeMetadata|null>; |
| abstract requestFileContent(uiSourceCode: UISourceCode): Promise<TextUtils.ContentData.ContentDataOrError>; |
| abstract canSetFileContent(): boolean; |
| abstract setFileContent(uiSourceCode: UISourceCode, newContent: string, isBase64: boolean): Promise<void>; |
| abstract fullDisplayName(uiSourceCode: UISourceCode): string; |
| abstract mimeType(uiSourceCode: UISourceCode): string; |
| abstract canRename(): boolean; |
| abstract canExcludeFolder(path: Platform.DevToolsPath.EncodedPathString): boolean; |
| abstract createFile( |
| path: Platform.DevToolsPath.EncodedPathString, name: string|null, content: string, |
| isBase64?: boolean): Promise<UISourceCode|null>; |
| abstract canCreateFile(): boolean; |
| abstract searchInFileContent(uiSourceCode: UISourceCode, query: string, caseSensitive: boolean, isRegex: boolean): |
| Promise<TextUtils.ContentProvider.SearchMatch[]>; |
| abstract findFilesMatchingSearchRequest( |
| searchConfig: SearchConfig, filesMatchingFileQuery: UISourceCode[], |
| progress: Common.Progress.Progress): Promise<Map<UISourceCode, TextUtils.ContentProvider.SearchMatch[]|null>>; |
| } |
| |
| export class WorkspaceImpl extends Common.ObjectWrapper.ObjectWrapper<EventTypes> { |
| #projects = new Map<string, Project>(); |
| #hasResourceContentTrackingExtensions = false; |
| |
| static instance(opts: {forceNew: boolean|null} = {forceNew: null}): WorkspaceImpl { |
| const {forceNew} = opts; |
| if (!Root.DevToolsContext.globalInstance().has(WorkspaceImpl) || forceNew) { |
| Root.DevToolsContext.globalInstance().set(WorkspaceImpl, new WorkspaceImpl()); |
| } |
| |
| return Root.DevToolsContext.globalInstance().get(WorkspaceImpl); |
| } |
| |
| static removeInstance(): void { |
| Root.DevToolsContext.globalInstance().delete(WorkspaceImpl); |
| } |
| |
| uiSourceCode(projectId: string, url: Platform.DevToolsPath.UrlString): UISourceCode|null { |
| const project = this.#projects.get(projectId); |
| return project ? project.uiSourceCodeForURL(url) : null; |
| } |
| |
| uiSourceCodeForURL(url: Platform.DevToolsPath.UrlString): UISourceCode|null { |
| for (const project of this.#projects.values()) { |
| const uiSourceCode = project.uiSourceCodeForURL(url); |
| if (uiSourceCode) { |
| return uiSourceCode; |
| } |
| } |
| return null; |
| } |
| |
| findCompatibleUISourceCodes(uiSourceCode: UISourceCode): UISourceCode[] { |
| const url = uiSourceCode.url(); |
| const contentType = uiSourceCode.contentType(); |
| const result: UISourceCode[] = []; |
| for (const project of this.#projects.values()) { |
| if (uiSourceCode.project().type() !== project.type()) { |
| continue; |
| } |
| const candidate = project.uiSourceCodeForURL(url); |
| if (candidate && candidate.url() === url && candidate.contentType() === contentType) { |
| result.push(candidate); |
| } |
| } |
| return result; |
| } |
| |
| uiSourceCodesForProjectType(type: projectTypes): UISourceCode[] { |
| const result: UISourceCode[] = []; |
| for (const project of this.#projects.values()) { |
| if (project.type() === type) { |
| for (const uiSourceCode of project.uiSourceCodes()) { |
| result.push(uiSourceCode); |
| } |
| } |
| } |
| return result; |
| } |
| |
| addProject(project: Project): void { |
| console.assert(!this.#projects.has(project.id()), `A project with id ${project.id()} already exists!`); |
| this.#projects.set(project.id(), project); |
| this.dispatchEventToListeners(Events.ProjectAdded, project); |
| } |
| |
| removeProject(project: Project): void { |
| this.#projects.delete(project.id()); |
| this.dispatchEventToListeners(Events.ProjectRemoved, project); |
| } |
| |
| project(projectId: string): Project|null { |
| return this.#projects.get(projectId) || null; |
| } |
| |
| projectForFileSystemRoot(root: Platform.DevToolsPath.RawPathString): Project|null { |
| const projectId = Common.ParsedURL.ParsedURL.rawPathToUrlString(root); |
| return this.project(projectId); |
| } |
| |
| projects(): Project[] { |
| return [...this.#projects.values()]; |
| } |
| |
| projectsForType(type: string): Project[] { |
| function filterByType(project: Project): boolean { |
| return project.type() === type; |
| } |
| return this.projects().filter(filterByType); |
| } |
| |
| uiSourceCodes(): UISourceCode[] { |
| const result: UISourceCode[] = []; |
| for (const project of this.#projects.values()) { |
| for (const uiSourceCode of project.uiSourceCodes()) { |
| result.push(uiSourceCode); |
| } |
| } |
| return result; |
| } |
| |
| setHasResourceContentTrackingExtensions(hasExtensions: boolean): void { |
| this.#hasResourceContentTrackingExtensions = hasExtensions; |
| } |
| |
| hasResourceContentTrackingExtensions(): boolean { |
| return this.#hasResourceContentTrackingExtensions; |
| } |
| } |
| |
| export enum Events { |
| /* eslint-disable @typescript-eslint/naming-convention -- Used by web_tests. */ |
| UISourceCodeAdded = 'UISourceCodeAdded', |
| UISourceCodeRemoved = 'UISourceCodeRemoved', |
| UISourceCodeRenamed = 'UISourceCodeRenamed', |
| WorkingCopyChanged = 'WorkingCopyChanged', |
| WorkingCopyCommitted = 'WorkingCopyCommitted', |
| WorkingCopyCommittedByUser = 'WorkingCopyCommittedByUser', |
| ProjectAdded = 'ProjectAdded', |
| ProjectRemoved = 'ProjectRemoved', |
| /* eslint-enable @typescript-eslint/naming-convention */ |
| } |
| |
| export interface UISourceCodeRenamedEvent { |
| oldURL: Platform.DevToolsPath.UrlString; |
| uiSourceCode: UISourceCode; |
| } |
| |
| export interface WorkingCopyChangedEvent { |
| uiSourceCode: UISourceCode; |
| } |
| |
| export interface WorkingCopyCommittedEvent { |
| uiSourceCode: UISourceCode; |
| content: string; |
| encoded?: boolean; |
| } |
| |
| export interface EventTypes { |
| [Events.UISourceCodeAdded]: UISourceCode; |
| [Events.UISourceCodeRemoved]: UISourceCode; |
| [Events.UISourceCodeRenamed]: UISourceCodeRenamedEvent; |
| [Events.WorkingCopyChanged]: WorkingCopyChangedEvent; |
| [Events.WorkingCopyCommitted]: WorkingCopyCommittedEvent; |
| [Events.WorkingCopyCommittedByUser]: WorkingCopyCommittedEvent; |
| [Events.ProjectAdded]: Project; |
| [Events.ProjectRemoved]: Project; |
| } |