mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-06 20:11:38 +08:00
Bug: 1299780 Change-Id: I1dffe6edc834cf5914d48c4e331510b1866f78b8 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3484200 Reviewed-by: Eric Leese <leese@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Commit-Queue: Kateryna Prokopenko <kprokopenko@chromium.org>
788 lines
28 KiB
TypeScript
788 lines
28 KiB
TypeScript
// Copyright 2014 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.
|
|
|
|
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 Root from '../../core/root/root.js';
|
|
import * as Persistence from '../../models/persistence/persistence.js';
|
|
import * as Workspace from '../../models/workspace/workspace.js';
|
|
import * as QuickOpen from '../../ui/legacy/components/quick_open/quick_open.js';
|
|
import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
|
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
|
|
import * as Components from './components/components.js';
|
|
import {EditingLocationHistoryManager} from './EditingLocationHistoryManager.js';
|
|
import sourcesViewStyles from './sourcesView.css.js';
|
|
|
|
import type {EditorSelectedEvent, TabbedEditorContainerDelegate} from './TabbedEditorContainer.js';
|
|
import {Events as TabbedEditorContainerEvents, TabbedEditorContainer} from './TabbedEditorContainer.js';
|
|
import {Events as UISourceCodeFrameEvents, UISourceCodeFrame} from './UISourceCodeFrame.js';
|
|
|
|
const UIStrings = {
|
|
/**
|
|
*@description Text to open a file
|
|
*/
|
|
openFile: 'Open file',
|
|
/**
|
|
*@description Text to run commands
|
|
*/
|
|
runCommand: 'Run command',
|
|
/**
|
|
*@description Text in Sources View of the Sources panel
|
|
*/
|
|
dropInAFolderToAddToWorkspace: 'Drop in a folder to add to workspace',
|
|
/**
|
|
*@description Accessible label for Sources placeholder view actions list
|
|
*/
|
|
sourceViewActions: 'Source View Actions',
|
|
};
|
|
const str_ = i18n.i18n.registerUIStrings('panels/sources/SourcesView.ts', UIStrings);
|
|
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
|
|
export class SourcesView extends Common.ObjectWrapper.eventMixin<EventTypes, typeof UI.Widget.VBox>(UI.Widget.VBox)
|
|
implements TabbedEditorContainerDelegate, UI.SearchableView.Searchable, UI.SearchableView.Replaceable {
|
|
private placeholderOptionArray: {
|
|
element: HTMLElement,
|
|
handler: Function,
|
|
}[];
|
|
private selectedIndex: number;
|
|
private readonly searchableViewInternal: UI.SearchableView.SearchableView;
|
|
private readonly sourceViewByUISourceCode: Map<Workspace.UISourceCode.UISourceCode, UI.Widget.Widget>;
|
|
editorContainer: TabbedEditorContainer;
|
|
private readonly historyManager: EditingLocationHistoryManager;
|
|
private readonly toolbarContainerElementInternal: HTMLElement;
|
|
private readonly scriptViewToolbar: UI.Toolbar.Toolbar;
|
|
private readonly bottomToolbarInternal: UI.Toolbar.Toolbar;
|
|
private toolbarChangedListener: Common.EventTarget.EventDescriptor|null;
|
|
private readonly shortcuts: Map<number, () => boolean>;
|
|
private readonly focusedPlaceholderElement?: HTMLElement;
|
|
private searchView?: UISourceCodeFrame;
|
|
private searchConfig?: UI.SearchableView.SearchConfig;
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
this.element.id = 'sources-panel-sources-view';
|
|
this.setMinimumAndPreferredSizes(250, 52, 250, 100);
|
|
|
|
this.placeholderOptionArray = [];
|
|
this.selectedIndex = 0;
|
|
|
|
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
|
|
|
|
this.searchableViewInternal = new UI.SearchableView.SearchableView(this, this, 'sourcesViewSearchConfig');
|
|
this.searchableViewInternal.setMinimalSearchQuerySize(0);
|
|
this.searchableViewInternal.show(this.element);
|
|
|
|
this.sourceViewByUISourceCode = new Map();
|
|
|
|
this.editorContainer = new TabbedEditorContainer(
|
|
this, Common.Settings.Settings.instance().createLocalSetting('previouslyViewedFiles', []),
|
|
this.placeholderElement(), this.focusedPlaceholderElement);
|
|
this.editorContainer.show(this.searchableViewInternal.element);
|
|
this.editorContainer.addEventListener(TabbedEditorContainerEvents.EditorSelected, this.editorSelected, this);
|
|
this.editorContainer.addEventListener(TabbedEditorContainerEvents.EditorClosed, this.editorClosed, this);
|
|
|
|
this.historyManager = new EditingLocationHistoryManager(this);
|
|
|
|
this.toolbarContainerElementInternal = this.element.createChild('div', 'sources-toolbar');
|
|
if (!Root.Runtime.experiments.isEnabled('sourcesPrettyPrint')) {
|
|
const toolbarEditorActions = new UI.Toolbar.Toolbar('', this.toolbarContainerElementInternal);
|
|
for (const action of getRegisteredEditorActions()) {
|
|
toolbarEditorActions.appendToolbarItem(action.getOrCreateButton(this));
|
|
}
|
|
}
|
|
this.scriptViewToolbar = new UI.Toolbar.Toolbar('', this.toolbarContainerElementInternal);
|
|
this.scriptViewToolbar.element.style.flex = 'auto';
|
|
this.bottomToolbarInternal = new UI.Toolbar.Toolbar('', this.toolbarContainerElementInternal);
|
|
|
|
this.toolbarChangedListener = null;
|
|
|
|
UI.UIUtils.startBatchUpdate();
|
|
workspace.uiSourceCodes().forEach(this.addUISourceCode.bind(this));
|
|
UI.UIUtils.endBatchUpdate();
|
|
|
|
workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded, this.uiSourceCodeAdded, this);
|
|
workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRemoved, this.uiSourceCodeRemoved, this);
|
|
workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, this.projectRemoved.bind(this), this);
|
|
|
|
function handleBeforeUnload(event: Event): void {
|
|
if (event.returnValue) {
|
|
return;
|
|
}
|
|
|
|
const unsavedSourceCodes: Workspace.UISourceCode.UISourceCode[] = [];
|
|
const projects =
|
|
Workspace.Workspace.WorkspaceImpl.instance().projectsForType(Workspace.Workspace.projectTypes.FileSystem);
|
|
for (const project of projects) {
|
|
unsavedSourceCodes.push(...project.uiSourceCodes().filter(sourceCode => sourceCode.isDirty()));
|
|
}
|
|
|
|
if (!unsavedSourceCodes.length) {
|
|
return;
|
|
}
|
|
|
|
event.returnValue = true;
|
|
void UI.ViewManager.ViewManager.instance().showView('sources');
|
|
for (const sourceCode of unsavedSourceCodes) {
|
|
void Common.Revealer.reveal(sourceCode);
|
|
}
|
|
}
|
|
|
|
if (!window.opener) {
|
|
window.addEventListener('beforeunload', handleBeforeUnload, true);
|
|
}
|
|
|
|
this.shortcuts = new Map();
|
|
this.element.addEventListener('keydown', this.handleKeyDown.bind(this), false);
|
|
}
|
|
|
|
private placeholderElement(): Element {
|
|
this.placeholderOptionArray = [];
|
|
|
|
const shortcuts = [
|
|
{actionId: 'quickOpen.show', description: i18nString(UIStrings.openFile)},
|
|
{actionId: 'commandMenu.show', description: i18nString(UIStrings.runCommand)},
|
|
{actionId: 'sources.add-folder-to-workspace', description: i18nString(UIStrings.dropInAFolderToAddToWorkspace)},
|
|
];
|
|
|
|
const element = document.createElement('div');
|
|
const list = element.createChild('div', 'tabbed-pane-placeholder');
|
|
list.addEventListener('keydown', this.placeholderOnKeyDown.bind(this), false);
|
|
UI.ARIAUtils.markAsList(list);
|
|
UI.ARIAUtils.setAccessibleName(list, i18nString(UIStrings.sourceViewActions));
|
|
|
|
for (let i = 0; i < shortcuts.length; i++) {
|
|
const shortcut = shortcuts[i];
|
|
const shortcutKeyText = UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutTitleForAction(shortcut.actionId);
|
|
const listItemElement = list.createChild('div');
|
|
UI.ARIAUtils.markAsListitem(listItemElement);
|
|
const row = (listItemElement.createChild('div', 'tabbed-pane-placeholder-row') as HTMLElement);
|
|
row.tabIndex = -1;
|
|
UI.ARIAUtils.markAsButton(row);
|
|
if (shortcutKeyText) {
|
|
row.createChild('div', 'tabbed-pane-placeholder-key').textContent = shortcutKeyText;
|
|
row.createChild('div', 'tabbed-pane-placeholder-value').textContent = shortcut.description;
|
|
} else {
|
|
row.createChild('div', 'tabbed-pane-no-shortcut').textContent = shortcut.description;
|
|
}
|
|
const action = UI.ActionRegistry.ActionRegistry.instance().action(shortcut.actionId);
|
|
if (action) {
|
|
this.placeholderOptionArray.push({
|
|
element: row,
|
|
handler(): void {
|
|
void action.execute();
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
element.appendChild(
|
|
UI.XLink.XLink.create('https://developer.chrome.com/docs/devtools/workspaces/', 'Learn more about Workspaces'));
|
|
|
|
return element;
|
|
}
|
|
|
|
private placeholderOnKeyDown(event: Event): void {
|
|
const keyboardEvent = (event as KeyboardEvent);
|
|
if (isEnterOrSpaceKey(keyboardEvent)) {
|
|
this.placeholderOptionArray[this.selectedIndex].handler();
|
|
return;
|
|
}
|
|
|
|
let offset = 0;
|
|
if (keyboardEvent.key === 'ArrowDown') {
|
|
offset = 1;
|
|
} else if (keyboardEvent.key === 'ArrowUp') {
|
|
offset = -1;
|
|
}
|
|
|
|
const newIndex = Math.max(Math.min(this.placeholderOptionArray.length - 1, this.selectedIndex + offset), 0);
|
|
const newElement = this.placeholderOptionArray[newIndex].element;
|
|
const oldElement = this.placeholderOptionArray[this.selectedIndex].element;
|
|
if (newElement !== oldElement) {
|
|
oldElement.tabIndex = -1;
|
|
newElement.tabIndex = 0;
|
|
UI.ARIAUtils.setSelected(oldElement, false);
|
|
UI.ARIAUtils.setSelected(newElement, true);
|
|
this.selectedIndex = newIndex;
|
|
newElement.focus();
|
|
}
|
|
}
|
|
|
|
static defaultUISourceCodeScores(): Map<Workspace.UISourceCode.UISourceCode, number> {
|
|
const defaultScores = new Map<Workspace.UISourceCode.UISourceCode, number>();
|
|
const sourcesView = UI.Context.Context.instance().flavor(SourcesView);
|
|
if (sourcesView) {
|
|
const uiSourceCodes = sourcesView.editorContainer.historyUISourceCodes();
|
|
for (let i = 1; i < uiSourceCodes.length; ++i) // Skip current element
|
|
{
|
|
defaultScores.set(uiSourceCodes[i], uiSourceCodes.length - i);
|
|
}
|
|
}
|
|
return defaultScores;
|
|
}
|
|
|
|
leftToolbar(): UI.Toolbar.Toolbar {
|
|
return this.editorContainer.leftToolbar();
|
|
}
|
|
|
|
rightToolbar(): UI.Toolbar.Toolbar {
|
|
return this.editorContainer.rightToolbar();
|
|
}
|
|
|
|
bottomToolbar(): UI.Toolbar.Toolbar {
|
|
return this.bottomToolbarInternal;
|
|
}
|
|
|
|
private registerShortcuts(keys: UI.KeyboardShortcut.Descriptor[], handler: (arg0?: Event|undefined) => boolean):
|
|
void {
|
|
for (let i = 0; i < keys.length; ++i) {
|
|
this.shortcuts.set(keys[i].key, handler);
|
|
}
|
|
}
|
|
|
|
private handleKeyDown(event: Event): void {
|
|
const shortcutKey = UI.KeyboardShortcut.KeyboardShortcut.makeKeyFromEvent((event as KeyboardEvent));
|
|
const handler = this.shortcuts.get(shortcutKey);
|
|
if (handler && handler()) {
|
|
event.consume(true);
|
|
}
|
|
}
|
|
|
|
wasShown(): void {
|
|
super.wasShown();
|
|
this.registerCSSFiles([sourcesViewStyles]);
|
|
UI.Context.Context.instance().setFlavor(SourcesView, this);
|
|
}
|
|
|
|
willHide(): void {
|
|
UI.Context.Context.instance().setFlavor(SourcesView, null);
|
|
super.willHide();
|
|
}
|
|
|
|
toolbarContainerElement(): Element {
|
|
return this.toolbarContainerElementInternal;
|
|
}
|
|
|
|
searchableView(): UI.SearchableView.SearchableView {
|
|
return this.searchableViewInternal;
|
|
}
|
|
|
|
visibleView(): UI.Widget.Widget|null {
|
|
return this.editorContainer.visibleView;
|
|
}
|
|
|
|
currentSourceFrame(): UISourceCodeFrame|null {
|
|
const view = this.visibleView();
|
|
if (!(view instanceof UISourceCodeFrame)) {
|
|
return null;
|
|
}
|
|
return (view as UISourceCodeFrame);
|
|
}
|
|
|
|
currentUISourceCode(): Workspace.UISourceCode.UISourceCode|null {
|
|
return this.editorContainer.currentFile();
|
|
}
|
|
|
|
onCloseEditorTab(): boolean {
|
|
const uiSourceCode = this.editorContainer.currentFile();
|
|
if (!uiSourceCode) {
|
|
return false;
|
|
}
|
|
this.editorContainer.closeFile(uiSourceCode);
|
|
return true;
|
|
}
|
|
|
|
onJumpToPreviousLocation(): void {
|
|
this.historyManager.rollback();
|
|
}
|
|
|
|
onJumpToNextLocation(): void {
|
|
this.historyManager.rollover();
|
|
}
|
|
|
|
private uiSourceCodeAdded(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
|
|
const uiSourceCode = event.data;
|
|
this.addUISourceCode(uiSourceCode);
|
|
}
|
|
|
|
private addUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
|
|
if (uiSourceCode.project().isServiceProject()) {
|
|
return;
|
|
}
|
|
if (uiSourceCode.project().type() === Workspace.Workspace.projectTypes.FileSystem &&
|
|
Persistence.FileSystemWorkspaceBinding.FileSystemWorkspaceBinding.fileSystemType(uiSourceCode.project()) ===
|
|
'overrides') {
|
|
return;
|
|
}
|
|
this.editorContainer.addUISourceCode(uiSourceCode);
|
|
}
|
|
|
|
private uiSourceCodeRemoved(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
|
|
const uiSourceCode = event.data;
|
|
this.removeUISourceCodes([uiSourceCode]);
|
|
}
|
|
|
|
private removeUISourceCodes(uiSourceCodes: Workspace.UISourceCode.UISourceCode[]): void {
|
|
this.editorContainer.removeUISourceCodes(uiSourceCodes);
|
|
for (let i = 0; i < uiSourceCodes.length; ++i) {
|
|
this.removeSourceFrame(uiSourceCodes[i]);
|
|
this.historyManager.removeHistoryForSourceCode(uiSourceCodes[i]);
|
|
}
|
|
}
|
|
|
|
private projectRemoved(event: Common.EventTarget.EventTargetEvent<Workspace.Workspace.Project>): void {
|
|
const project = event.data;
|
|
const uiSourceCodes = project.uiSourceCodes();
|
|
this.removeUISourceCodes(uiSourceCodes);
|
|
}
|
|
|
|
private updateScriptViewToolbarItems(): void {
|
|
const view = this.visibleView();
|
|
if (view instanceof UI.View.SimpleView) {
|
|
void view.toolbarItems().then(items => {
|
|
this.scriptViewToolbar.removeToolbarItems();
|
|
items.map(item => this.scriptViewToolbar.appendToolbarItem(item));
|
|
});
|
|
}
|
|
}
|
|
|
|
showSourceLocation(
|
|
uiSourceCode: Workspace.UISourceCode.UISourceCode, location?: {lineNumber: number, columnNumber?: number}|number,
|
|
omitFocus?: boolean, omitHighlight?: boolean): void {
|
|
const currentFrame = this.currentSourceFrame();
|
|
if (currentFrame) {
|
|
this.historyManager.updateCurrentState(
|
|
currentFrame.uiSourceCode(), currentFrame.textEditor.state.selection.main.head);
|
|
}
|
|
this.editorContainer.showFile(uiSourceCode);
|
|
const currentSourceFrame = this.currentSourceFrame();
|
|
if (currentSourceFrame && location) {
|
|
currentSourceFrame.revealPosition(location, !omitHighlight);
|
|
}
|
|
const visibleView = this.visibleView();
|
|
if (!omitFocus && visibleView) {
|
|
visibleView.focus();
|
|
}
|
|
}
|
|
|
|
private createSourceView(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget {
|
|
let sourceFrame;
|
|
let sourceView;
|
|
const contentType = uiSourceCode.contentType();
|
|
|
|
if (contentType === Common.ResourceType.resourceTypes.Image) {
|
|
sourceView = new SourceFrame.ImageView.ImageView(uiSourceCode.mimeType(), uiSourceCode);
|
|
} else if (contentType === Common.ResourceType.resourceTypes.Font) {
|
|
sourceView = new SourceFrame.FontView.FontView(uiSourceCode.mimeType(), uiSourceCode);
|
|
} else if (
|
|
uiSourceCode.name() === HEADER_OVERRIDES_FILENAME &&
|
|
Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.HEADER_OVERRIDES)) {
|
|
sourceView = new Components.HeadersView.HeadersView(uiSourceCode);
|
|
} else {
|
|
sourceFrame = new UISourceCodeFrame(uiSourceCode);
|
|
}
|
|
|
|
if (sourceFrame) {
|
|
this.historyManager.trackSourceFrameCursorJumps(sourceFrame);
|
|
}
|
|
|
|
uiSourceCode.addEventListener(Workspace.UISourceCode.Events.TitleChanged, this.#uiSourceCodeTitleChanged, this);
|
|
|
|
const widget = (sourceFrame || sourceView as UI.Widget.Widget);
|
|
this.sourceViewByUISourceCode.set(uiSourceCode, widget);
|
|
return widget;
|
|
}
|
|
|
|
#sourceViewTypeForWidget(widget: UI.Widget.Widget): SourceViewType {
|
|
if (widget instanceof SourceFrame.ImageView.ImageView) {
|
|
return SourceViewType.ImageView;
|
|
}
|
|
if (widget instanceof SourceFrame.FontView.FontView) {
|
|
return SourceViewType.FontView;
|
|
}
|
|
if (widget instanceof Components.HeadersView.HeadersView) {
|
|
return SourceViewType.HeadersView;
|
|
}
|
|
return SourceViewType.SourceView;
|
|
}
|
|
|
|
#sourceViewTypeForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): SourceViewType {
|
|
if (uiSourceCode.name() === HEADER_OVERRIDES_FILENAME &&
|
|
Root.Runtime.experiments.isEnabled(Root.Runtime.ExperimentName.HEADER_OVERRIDES)) {
|
|
return SourceViewType.HeadersView;
|
|
}
|
|
const contentType = uiSourceCode.contentType();
|
|
switch (contentType) {
|
|
case Common.ResourceType.resourceTypes.Image:
|
|
return SourceViewType.ImageView;
|
|
case Common.ResourceType.resourceTypes.Font:
|
|
return SourceViewType.FontView;
|
|
default:
|
|
return SourceViewType.SourceView;
|
|
}
|
|
}
|
|
|
|
#uiSourceCodeTitleChanged(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
|
|
const uiSourceCode = event.data;
|
|
const widget = this.sourceViewByUISourceCode.get(uiSourceCode);
|
|
if (widget) {
|
|
if (this.#sourceViewTypeForWidget(widget) !== this.#sourceViewTypeForUISourceCode(uiSourceCode)) {
|
|
// Remove the exisiting editor tab and create a new one of the correct type.
|
|
this.removeUISourceCodes([uiSourceCode]);
|
|
this.showSourceLocation(uiSourceCode);
|
|
}
|
|
}
|
|
}
|
|
|
|
getSourceView(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget|undefined {
|
|
return this.sourceViewByUISourceCode.get(uiSourceCode);
|
|
}
|
|
|
|
private getOrCreateSourceView(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget {
|
|
return this.sourceViewByUISourceCode.get(uiSourceCode) || this.createSourceView(uiSourceCode);
|
|
}
|
|
|
|
recycleUISourceCodeFrame(sourceFrame: UISourceCodeFrame, uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
|
|
sourceFrame.uiSourceCode().removeEventListener(
|
|
Workspace.UISourceCode.Events.TitleChanged, this.#uiSourceCodeTitleChanged, this);
|
|
this.sourceViewByUISourceCode.delete(sourceFrame.uiSourceCode());
|
|
sourceFrame.setUISourceCode(uiSourceCode);
|
|
this.sourceViewByUISourceCode.set(uiSourceCode, sourceFrame);
|
|
uiSourceCode.addEventListener(Workspace.UISourceCode.Events.TitleChanged, this.#uiSourceCodeTitleChanged, this);
|
|
}
|
|
|
|
viewForFile(uiSourceCode: Workspace.UISourceCode.UISourceCode): UI.Widget.Widget {
|
|
return this.getOrCreateSourceView(uiSourceCode);
|
|
}
|
|
|
|
private removeSourceFrame(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
|
|
const sourceView = this.sourceViewByUISourceCode.get(uiSourceCode);
|
|
this.sourceViewByUISourceCode.delete(uiSourceCode);
|
|
if (sourceView && sourceView instanceof UISourceCodeFrame) {
|
|
(sourceView as UISourceCodeFrame).dispose();
|
|
}
|
|
uiSourceCode.removeEventListener(Workspace.UISourceCode.Events.TitleChanged, this.#uiSourceCodeTitleChanged, this);
|
|
}
|
|
|
|
private editorClosed(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
|
|
const uiSourceCode = event.data;
|
|
this.historyManager.removeHistoryForSourceCode(uiSourceCode);
|
|
|
|
let wasSelected = false;
|
|
if (!this.editorContainer.currentFile()) {
|
|
wasSelected = true;
|
|
}
|
|
|
|
// SourcesNavigator does not need to update on EditorClosed.
|
|
this.removeToolbarChangedListener();
|
|
this.updateScriptViewToolbarItems();
|
|
this.searchableViewInternal.resetSearch();
|
|
|
|
const data = {
|
|
uiSourceCode: uiSourceCode,
|
|
wasSelected: wasSelected,
|
|
};
|
|
this.dispatchEventToListeners(Events.EditorClosed, data);
|
|
}
|
|
|
|
private editorSelected(event: Common.EventTarget.EventTargetEvent<EditorSelectedEvent>): void {
|
|
const previousSourceFrame = event.data.previousView instanceof UISourceCodeFrame ? event.data.previousView : null;
|
|
if (previousSourceFrame) {
|
|
previousSourceFrame.setSearchableView(null);
|
|
}
|
|
const currentSourceFrame = event.data.currentView instanceof UISourceCodeFrame ? event.data.currentView : null;
|
|
if (currentSourceFrame) {
|
|
currentSourceFrame.setSearchableView(this.searchableViewInternal);
|
|
}
|
|
|
|
this.searchableViewInternal.setReplaceable(Boolean(currentSourceFrame?.canEditSource()));
|
|
this.searchableViewInternal.refreshSearch();
|
|
this.updateToolbarChangedListener();
|
|
this.updateScriptViewToolbarItems();
|
|
|
|
const currentFile = this.editorContainer.currentFile();
|
|
if (currentFile) {
|
|
this.dispatchEventToListeners(Events.EditorSelected, currentFile);
|
|
}
|
|
}
|
|
|
|
private removeToolbarChangedListener(): void {
|
|
if (this.toolbarChangedListener) {
|
|
Common.EventTarget.removeEventListeners([this.toolbarChangedListener]);
|
|
}
|
|
this.toolbarChangedListener = null;
|
|
}
|
|
|
|
private updateToolbarChangedListener(): void {
|
|
this.removeToolbarChangedListener();
|
|
const sourceFrame = this.currentSourceFrame();
|
|
if (!sourceFrame) {
|
|
return;
|
|
}
|
|
this.toolbarChangedListener = sourceFrame.addEventListener(
|
|
UISourceCodeFrameEvents.ToolbarItemsChanged, this.updateScriptViewToolbarItems, this);
|
|
}
|
|
|
|
searchCanceled(): void {
|
|
if (this.searchView) {
|
|
this.searchView.searchCanceled();
|
|
}
|
|
|
|
delete this.searchView;
|
|
delete this.searchConfig;
|
|
}
|
|
|
|
performSearch(searchConfig: UI.SearchableView.SearchConfig, shouldJump: boolean, jumpBackwards?: boolean): void {
|
|
const sourceFrame = this.currentSourceFrame();
|
|
if (!sourceFrame) {
|
|
return;
|
|
}
|
|
|
|
this.searchView = sourceFrame;
|
|
this.searchConfig = searchConfig;
|
|
|
|
this.searchView.performSearch(this.searchConfig, shouldJump, jumpBackwards);
|
|
}
|
|
|
|
jumpToNextSearchResult(): void {
|
|
if (!this.searchView) {
|
|
return;
|
|
}
|
|
|
|
if (this.searchConfig && this.searchView !== this.currentSourceFrame()) {
|
|
this.performSearch(this.searchConfig, true);
|
|
return;
|
|
}
|
|
|
|
this.searchView.jumpToNextSearchResult();
|
|
}
|
|
|
|
jumpToPreviousSearchResult(): void {
|
|
if (!this.searchView) {
|
|
return;
|
|
}
|
|
|
|
if (this.searchConfig && this.searchView !== this.currentSourceFrame()) {
|
|
this.performSearch(this.searchConfig, true);
|
|
if (this.searchView) {
|
|
this.searchView.jumpToLastSearchResult();
|
|
}
|
|
return;
|
|
}
|
|
|
|
this.searchView.jumpToPreviousSearchResult();
|
|
}
|
|
|
|
supportsCaseSensitiveSearch(): boolean {
|
|
return true;
|
|
}
|
|
|
|
supportsRegexSearch(): boolean {
|
|
return true;
|
|
}
|
|
|
|
replaceSelectionWith(searchConfig: UI.SearchableView.SearchConfig, replacement: string): void {
|
|
const sourceFrame = this.currentSourceFrame();
|
|
if (!sourceFrame) {
|
|
console.assert(Boolean(sourceFrame));
|
|
return;
|
|
}
|
|
sourceFrame.replaceSelectionWith(searchConfig, replacement);
|
|
}
|
|
|
|
replaceAllWith(searchConfig: UI.SearchableView.SearchConfig, replacement: string): void {
|
|
const sourceFrame = this.currentSourceFrame();
|
|
if (!sourceFrame) {
|
|
console.assert(Boolean(sourceFrame));
|
|
return;
|
|
}
|
|
sourceFrame.replaceAllWith(searchConfig, replacement);
|
|
}
|
|
|
|
showOutlineQuickOpen(): void {
|
|
QuickOpen.QuickOpen.QuickOpenImpl.show('@');
|
|
}
|
|
|
|
showGoToLineQuickOpen(): void {
|
|
if (this.editorContainer.currentFile()) {
|
|
QuickOpen.QuickOpen.QuickOpenImpl.show(':');
|
|
}
|
|
}
|
|
|
|
save(): void {
|
|
this.saveSourceView(this.visibleView());
|
|
}
|
|
|
|
saveAll(): void {
|
|
const sourceFrames = this.editorContainer.fileViews();
|
|
sourceFrames.forEach(this.saveSourceView.bind(this));
|
|
}
|
|
|
|
private saveSourceView(sourceView: UI.Widget.Widget|null): void {
|
|
if (sourceView instanceof UISourceCodeFrame || sourceView instanceof Components.HeadersView.HeadersView) {
|
|
sourceView.commitEditing();
|
|
}
|
|
}
|
|
|
|
toggleBreakpointsActiveState(active: boolean): void {
|
|
this.editorContainer.view.element.classList.toggle('breakpoints-deactivated', !active);
|
|
}
|
|
}
|
|
|
|
export // TODO(crbug.com/1167717): Make this a const enum again
|
|
// eslint-disable-next-line rulesdir/const_enum
|
|
enum Events {
|
|
EditorClosed = 'EditorClosed',
|
|
EditorSelected = 'EditorSelected',
|
|
}
|
|
|
|
export interface EditorClosedEvent {
|
|
uiSourceCode: Workspace.UISourceCode.UISourceCode;
|
|
wasSelected: boolean;
|
|
}
|
|
|
|
export type EventTypes = {
|
|
[Events.EditorClosed]: EditorClosedEvent,
|
|
[Events.EditorSelected]: Workspace.UISourceCode.UISourceCode,
|
|
};
|
|
|
|
export interface EditorAction {
|
|
getOrCreateButton(sourcesView: SourcesView): UI.Toolbar.ToolbarButton;
|
|
}
|
|
|
|
const registeredEditorActions: (() => EditorAction)[] = [];
|
|
|
|
export function registerEditorAction(editorAction: () => EditorAction): void {
|
|
registeredEditorActions.push(editorAction);
|
|
}
|
|
|
|
export function getRegisteredEditorActions(): EditorAction[] {
|
|
return registeredEditorActions.map(editorAction => editorAction());
|
|
}
|
|
|
|
let switchFileActionDelegateInstance: SwitchFileActionDelegate;
|
|
|
|
export class SwitchFileActionDelegate implements UI.ActionRegistration.ActionDelegate {
|
|
static instance(opts: {
|
|
forceNew: boolean|null,
|
|
} = {forceNew: null}): SwitchFileActionDelegate {
|
|
const {forceNew} = opts;
|
|
if (!switchFileActionDelegateInstance || forceNew) {
|
|
switchFileActionDelegateInstance = new SwitchFileActionDelegate();
|
|
}
|
|
|
|
return switchFileActionDelegateInstance;
|
|
}
|
|
|
|
private static nextFile(currentUISourceCode: Workspace.UISourceCode.UISourceCode): Workspace.UISourceCode.UISourceCode
|
|
|null {
|
|
function fileNamePrefix(name: string): string {
|
|
const lastDotIndex = name.lastIndexOf('.');
|
|
const namePrefix = name.substr(0, lastDotIndex !== -1 ? lastDotIndex : name.length);
|
|
return namePrefix.toLowerCase();
|
|
}
|
|
|
|
const uiSourceCodes = currentUISourceCode.project().uiSourceCodes();
|
|
const candidates = [];
|
|
const url = currentUISourceCode.parentURL();
|
|
const name = currentUISourceCode.name();
|
|
const namePrefix = fileNamePrefix(name);
|
|
for (let i = 0; i < uiSourceCodes.length; ++i) {
|
|
const uiSourceCode = uiSourceCodes[i];
|
|
if (url !== uiSourceCode.parentURL()) {
|
|
continue;
|
|
}
|
|
if (fileNamePrefix(uiSourceCode.name()) === namePrefix) {
|
|
candidates.push(uiSourceCode.name());
|
|
}
|
|
}
|
|
candidates.sort(Platform.StringUtilities.naturalOrderComparator);
|
|
const index = Platform.NumberUtilities.mod(candidates.indexOf(name) + 1, candidates.length);
|
|
const fullURL = Common.ParsedURL.ParsedURL.concatenate(
|
|
(url ? Common.ParsedURL.ParsedURL.concatenate(url, '/') : '' as Platform.DevToolsPath.UrlString),
|
|
candidates[index]);
|
|
const nextUISourceCode = currentUISourceCode.project().uiSourceCodeForURL(fullURL);
|
|
return nextUISourceCode !== currentUISourceCode ? nextUISourceCode : null;
|
|
}
|
|
|
|
handleAction(_context: UI.Context.Context, _actionId: string): boolean {
|
|
const sourcesView = UI.Context.Context.instance().flavor(SourcesView);
|
|
if (!sourcesView) {
|
|
return false;
|
|
}
|
|
const currentUISourceCode = sourcesView.currentUISourceCode();
|
|
if (!currentUISourceCode) {
|
|
return false;
|
|
}
|
|
const nextUISourceCode = SwitchFileActionDelegate.nextFile(currentUISourceCode);
|
|
if (!nextUISourceCode) {
|
|
return false;
|
|
}
|
|
sourcesView.showSourceLocation(nextUISourceCode);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
let actionDelegateInstance: ActionDelegate;
|
|
export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
|
|
static instance(opts: {
|
|
forceNew: boolean|null,
|
|
}|undefined = {forceNew: null}): ActionDelegate {
|
|
const {forceNew} = opts;
|
|
if (!actionDelegateInstance || forceNew) {
|
|
actionDelegateInstance = new ActionDelegate();
|
|
}
|
|
|
|
return actionDelegateInstance;
|
|
}
|
|
|
|
handleAction(context: UI.Context.Context, actionId: string): boolean {
|
|
const sourcesView = UI.Context.Context.instance().flavor(SourcesView);
|
|
if (!sourcesView) {
|
|
return false;
|
|
}
|
|
|
|
switch (actionId) {
|
|
case 'sources.close-all':
|
|
sourcesView.editorContainer.closeAllFiles();
|
|
return true;
|
|
case 'sources.jump-to-previous-location':
|
|
sourcesView.onJumpToPreviousLocation();
|
|
return true;
|
|
case 'sources.jump-to-next-location':
|
|
sourcesView.onJumpToNextLocation();
|
|
return true;
|
|
case 'sources.close-editor-tab':
|
|
return sourcesView.onCloseEditorTab();
|
|
case 'sources.go-to-line':
|
|
sourcesView.showGoToLineQuickOpen();
|
|
return true;
|
|
case 'sources.go-to-member':
|
|
sourcesView.showOutlineQuickOpen();
|
|
return true;
|
|
case 'sources.save':
|
|
sourcesView.save();
|
|
return true;
|
|
case 'sources.save-all':
|
|
sourcesView.saveAll();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const HEADER_OVERRIDES_FILENAME = '.headers';
|
|
|
|
// eslint-disable-next-line rulesdir/const_enum
|
|
enum SourceViewType {
|
|
ImageView = 'ImageView',
|
|
FontView = 'FontView',
|
|
HeadersView = 'HeadersView',
|
|
SourceView = 'SourceView',
|
|
}
|