| // Copyright 2018 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 * as i18n from '../../core/i18n/i18n.js'; |
| import * as Platform from '../../core/platform/platform.js'; |
| import * as SDK from '../../core/sdk/sdk.js'; |
| import * as Protocol from '../../generated/protocol.js'; |
| import * as Persistence from '../../models/persistence/persistence.js'; |
| import * as TextUtils from '../../models/text_utils/text_utils.js'; |
| import * as Workspace from '../../models/workspace/workspace.js'; |
| import * as UI from '../../ui/legacy/legacy.js'; |
| |
| const UIStrings = { |
| /** |
| * @description Default snippet name when a new snippet is created in the Sources panel |
| * @example {1} PH1 |
| */ |
| scriptSnippet: 'Script snippet #{PH1}', |
| /** |
| * @description Text to show something is linked to another |
| * @example {example.url} PH1 |
| */ |
| linkedTo: 'Linked to {PH1}', |
| } as const; |
| const str_ = i18n.i18n.registerUIStrings('panels/snippets/ScriptSnippetFileSystem.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| |
| function escapeSnippetName(name: Platform.DevToolsPath.RawPathString): Platform.DevToolsPath.EncodedPathString { |
| return Common.ParsedURL.ParsedURL.rawPathToEncodedPathString(name); |
| } |
| |
| function unescapeSnippetName(name: Platform.DevToolsPath.EncodedPathString): Platform.DevToolsPath.RawPathString { |
| return Common.ParsedURL.ParsedURL.encodedPathToRawPathString(name); |
| } |
| |
| export class SnippetFileSystem extends Persistence.PlatformFileSystem.PlatformFileSystem { |
| private readonly lastSnippetIdentifierSetting: Common.Settings.Setting<number>; |
| private readonly snippetsSetting: Common.Settings.Setting<Snippet[]>; |
| constructor() { |
| super( |
| 'snippet://' as Platform.DevToolsPath.UrlString, Persistence.PlatformFileSystem.PlatformFileSystemType.SNIPPETS, |
| false); |
| this.lastSnippetIdentifierSetting = |
| Common.Settings.Settings.instance().createSetting('script-snippets-last-identifier', 0); |
| this.snippetsSetting = Common.Settings.Settings.instance().createSetting('script-snippets', []); |
| } |
| |
| override initialFilePaths(): Platform.DevToolsPath.EncodedPathString[] { |
| const savedSnippets: Snippet[] = this.snippetsSetting.get(); |
| return savedSnippets.map(snippet => escapeSnippetName(snippet.name)); |
| } |
| |
| override async createFile( |
| _path: Platform.DevToolsPath.EncodedPathString, |
| _name: Platform.DevToolsPath.RawPathString|null): Promise<Platform.DevToolsPath.EncodedPathString|null> { |
| const nextId = this.lastSnippetIdentifierSetting.get() + 1; |
| this.lastSnippetIdentifierSetting.set(nextId); |
| |
| const snippetName = |
| i18nString(UIStrings.scriptSnippet, {PH1: nextId}) as string as Platform.DevToolsPath.RawPathString; |
| const snippets = this.snippetsSetting.get(); |
| snippets.push({name: snippetName, content: ''}); |
| this.snippetsSetting.set(snippets); |
| |
| return escapeSnippetName(snippetName); |
| } |
| |
| override async deleteFile(path: Platform.DevToolsPath.EncodedPathString): Promise<boolean> { |
| const name = unescapeSnippetName(Common.ParsedURL.ParsedURL.substring(path, 1)); |
| const allSnippets: Snippet[] = this.snippetsSetting.get(); |
| const snippets = allSnippets.filter(snippet => snippet.name !== name); |
| if (allSnippets.length !== snippets.length) { |
| this.snippetsSetting.set(snippets); |
| return true; |
| } |
| return false; |
| } |
| |
| override async requestFileContent(path: Platform.DevToolsPath.EncodedPathString): |
| Promise<TextUtils.ContentData.ContentDataOrError> { |
| const name = unescapeSnippetName(Common.ParsedURL.ParsedURL.substring(path, 1)); |
| const snippets: Snippet[] = this.snippetsSetting.get(); |
| const snippet = snippets.find(snippet => snippet.name === name); |
| if (snippet) { |
| return new TextUtils.ContentData.ContentData(snippet.content, /* isBase64 */ false, 'text/javascript'); |
| } |
| return {error: `A snippet with name '${name}' was not found`}; |
| } |
| |
| override async setFileContent(path: Platform.DevToolsPath.EncodedPathString, content: string, _isBase64: boolean): |
| Promise<boolean> { |
| const name = unescapeSnippetName(Common.ParsedURL.ParsedURL.substring(path, 1)); |
| const snippets = this.snippetsSetting.get(); |
| const snippet = snippets.find(snippet => snippet.name === name); |
| if (snippet) { |
| snippet.content = content; |
| this.snippetsSetting.set(snippets); |
| return true; |
| } |
| return false; |
| } |
| |
| override renameFile( |
| path: Platform.DevToolsPath.EncodedPathString, newName: Platform.DevToolsPath.RawPathString, |
| callback: (arg0: boolean, arg1?: string|undefined) => void): void { |
| const name = unescapeSnippetName(Common.ParsedURL.ParsedURL.substring(path, 1)); |
| const snippets: Snippet[] = this.snippetsSetting.get(); |
| const snippet = snippets.find(snippet => snippet.name === name); |
| newName = Common.ParsedURL.ParsedURL.trim(newName); |
| if (!snippet || newName.length === 0 || snippets.find(snippet => snippet.name === newName)) { |
| callback(false); |
| return; |
| } |
| snippet.name = newName; |
| this.snippetsSetting.set(snippets); |
| callback(true, newName); |
| } |
| |
| override async searchInPath(query: string, _progress: Common.Progress.Progress): Promise<string[]> { |
| const re = new RegExp(Platform.StringUtilities.escapeForRegExp(query), 'i'); |
| const allSnippets: Snippet[] = this.snippetsSetting.get(); |
| const matchedSnippets = allSnippets.filter(snippet => snippet.content.match(re)); |
| return matchedSnippets.map(snippet => `snippet:///${escapeSnippetName(snippet.name)}`); |
| } |
| |
| override mimeFromPath(_path: Platform.DevToolsPath.UrlString): string { |
| return 'text/javascript'; |
| } |
| |
| override contentType(_path: string): Common.ResourceType.ResourceType { |
| return Common.ResourceType.resourceTypes.Script; |
| } |
| |
| override tooltipForURL(url: Platform.DevToolsPath.UrlString): string { |
| return i18nString( |
| UIStrings.linkedTo, |
| {PH1: unescapeSnippetName(Common.ParsedURL.ParsedURL.sliceUrlToEncodedPathString(url, this.path().length))}); |
| } |
| |
| override supportsAutomapping(): boolean { |
| return true; |
| } |
| } |
| |
| export async function evaluateScriptSnippet(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> { |
| if (!Common.ParsedURL.schemeIs(uiSourceCode.url(), 'snippet:')) { |
| return; |
| } |
| |
| const executionContext = UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext); |
| if (!executionContext) { |
| return; |
| } |
| |
| const runtimeModel = executionContext.runtimeModel; |
| const consoleModel = executionContext.target().model(SDK.ConsoleModel.ConsoleModel); |
| await uiSourceCode.requestContentData(); |
| uiSourceCode.commitWorkingCopy(); |
| const expression = uiSourceCode.workingCopy(); |
| Common.Console.Console.instance().show(); |
| |
| const url = uiSourceCode.url(); |
| |
| const result = await executionContext.evaluate( |
| { |
| expression: `${expression}\n//# sourceURL=${url}`, |
| objectGroup: 'console', |
| silent: false, |
| includeCommandLineAPI: true, |
| returnByValue: false, |
| generatePreview: true, |
| replMode: true, |
| } as SDK.RuntimeModel.EvaluationOptions, |
| true, true); |
| |
| if ('exceptionDetails' in result && result.exceptionDetails) { |
| consoleModel?.addMessage(SDK.ConsoleModel.ConsoleMessage.fromException( |
| runtimeModel, result.exceptionDetails, /* messageType */ undefined, /* timestamp */ undefined, url)); |
| return; |
| } |
| if (!('object' in result) || !result.object) { |
| return; |
| } |
| |
| const scripts = executionContext.debuggerModel.scriptsForSourceURL(url); |
| if (scripts.length < 1) { |
| return; |
| } |
| const scriptId = scripts[scripts.length - 1].scriptId; |
| const details = { |
| type: SDK.ConsoleModel.FrontendMessageType.Result, |
| url, |
| parameters: [result.object], |
| executionContextId: executionContext.id, |
| scriptId, |
| }; |
| consoleModel?.addMessage(new SDK.ConsoleModel.ConsoleMessage( |
| runtimeModel, Protocol.Log.LogEntrySource.Javascript, Protocol.Log.LogEntryLevel.Info, '', details)); |
| } |
| |
| export function isSnippetsUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean { |
| return Common.ParsedURL.schemeIs(uiSourceCode.url(), 'snippet:'); |
| } |
| |
| export function isSnippetsProject(project: Workspace.Workspace.Project): boolean { |
| return project.type() === Workspace.Workspace.projectTypes.FileSystem && |
| Persistence.FileSystemWorkspaceBinding.FileSystemWorkspaceBinding.fileSystemType(project) === |
| Persistence.PlatformFileSystem.PlatformFileSystemType.SNIPPETS; |
| } |
| |
| export function findSnippetsProject(): Workspace.Workspace.Project { |
| const workspaceProject = |
| Workspace.Workspace.WorkspaceImpl.instance() |
| .projectsForType(Workspace.Workspace.projectTypes.FileSystem) |
| .find( |
| project => Persistence.FileSystemWorkspaceBinding.FileSystemWorkspaceBinding.fileSystemType(project) === |
| 'snippets'); |
| |
| if (!workspaceProject) { |
| throw new Error('Unable to find workspace project for the snippets file system'); |
| } |
| |
| return workspaceProject; |
| } |
| export interface Snippet { |
| name: Platform.DevToolsPath.RawPathString; |
| content: string; |
| } |