mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-03 02:21:05 +08:00
Note that this only migrates the code, but doesn't yet change the ESLint configuration, since other folders are still pending. Bug: 361720369 Change-Id: I3ffef5a9a16710dfcf922ec709eb8ce187d78301 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5803050 Reviewed-by: Nikolay Vitkov <nvitkov@chromium.org> Auto-Submit: Benedikt Meurer <bmeurer@chromium.org> Commit-Queue: Nikolay Vitkov <nvitkov@chromium.org>
319 lines
11 KiB
TypeScript
319 lines
11 KiB
TypeScript
// Copyright 2017 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 Host from '../../core/host/host.js';
|
|
import * as Diff from '../../third_party/diff/diff.js';
|
|
import * as FormatterModule from '../formatter/formatter.js';
|
|
import * as Persistence from '../persistence/persistence.js';
|
|
import * as TextUtils from '../text_utils/text_utils.js';
|
|
import * as Workspace from '../workspace/workspace.js';
|
|
|
|
interface DiffRequestOptions {
|
|
shouldFormatDiff: boolean;
|
|
}
|
|
|
|
interface DiffResponse {
|
|
diff: Diff.Diff.DiffArray;
|
|
formattedCurrentMapping?: FormatterModule.ScriptFormatter.FormatterSourceMapping;
|
|
}
|
|
|
|
export class WorkspaceDiffImpl extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
|
|
private readonly uiSourceCodeDiffs: WeakMap<Workspace.UISourceCode.UISourceCode, UISourceCodeDiff>;
|
|
private readonly loadingUISourceCodes:
|
|
Map<Workspace.UISourceCode.UISourceCode, Promise<[string | null, string|null]>>;
|
|
private readonly modifiedUISourceCodesInternal: Set<Workspace.UISourceCode.UISourceCode>;
|
|
|
|
constructor(workspace: Workspace.Workspace.WorkspaceImpl) {
|
|
super();
|
|
this.uiSourceCodeDiffs = new WeakMap();
|
|
|
|
this.loadingUISourceCodes = new Map();
|
|
|
|
this.modifiedUISourceCodesInternal = new Set();
|
|
workspace.addEventListener(Workspace.Workspace.Events.WorkingCopyChanged, this.uiSourceCodeChanged, this);
|
|
workspace.addEventListener(Workspace.Workspace.Events.WorkingCopyCommitted, this.uiSourceCodeChanged, this);
|
|
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, this);
|
|
workspace.uiSourceCodes().forEach(this.updateModifiedState.bind(this));
|
|
}
|
|
|
|
requestDiff(uiSourceCode: Workspace.UISourceCode.UISourceCode, diffRequestOptions: DiffRequestOptions):
|
|
Promise<DiffResponse|null> {
|
|
return this.uiSourceCodeDiff(uiSourceCode).requestDiff(diffRequestOptions);
|
|
}
|
|
|
|
subscribeToDiffChange(uiSourceCode: Workspace.UISourceCode.UISourceCode, callback: () => void, thisObj?: Object):
|
|
void {
|
|
this.uiSourceCodeDiff(uiSourceCode).addEventListener(UISourceCodeDiffEvents.DIFF_CHANGED, callback, thisObj);
|
|
}
|
|
|
|
unsubscribeFromDiffChange(uiSourceCode: Workspace.UISourceCode.UISourceCode, callback: () => void, thisObj?: Object):
|
|
void {
|
|
this.uiSourceCodeDiff(uiSourceCode).removeEventListener(UISourceCodeDiffEvents.DIFF_CHANGED, callback, thisObj);
|
|
}
|
|
|
|
modifiedUISourceCodes(): Workspace.UISourceCode.UISourceCode[] {
|
|
return Array.from(this.modifiedUISourceCodesInternal);
|
|
}
|
|
|
|
isUISourceCodeModified(uiSourceCode: Workspace.UISourceCode.UISourceCode): boolean {
|
|
return this.modifiedUISourceCodesInternal.has(uiSourceCode) || this.loadingUISourceCodes.has(uiSourceCode);
|
|
}
|
|
|
|
private uiSourceCodeDiff(uiSourceCode: Workspace.UISourceCode.UISourceCode): UISourceCodeDiff {
|
|
let diff = this.uiSourceCodeDiffs.get(uiSourceCode);
|
|
if (!diff) {
|
|
diff = new UISourceCodeDiff(uiSourceCode);
|
|
this.uiSourceCodeDiffs.set(uiSourceCode, diff);
|
|
}
|
|
return diff;
|
|
}
|
|
|
|
private uiSourceCodeChanged(
|
|
event: Common.EventTarget.EventTargetEvent<{uiSourceCode: Workspace.UISourceCode.UISourceCode}>): void {
|
|
const uiSourceCode = event.data.uiSourceCode;
|
|
void this.updateModifiedState(uiSourceCode);
|
|
}
|
|
|
|
private uiSourceCodeAdded(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
|
|
const uiSourceCode = event.data;
|
|
void this.updateModifiedState(uiSourceCode);
|
|
}
|
|
|
|
private uiSourceCodeRemoved(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
|
|
const uiSourceCode = event.data;
|
|
this.removeUISourceCode(uiSourceCode);
|
|
}
|
|
|
|
private projectRemoved(event: Common.EventTarget.EventTargetEvent<Workspace.Workspace.Project>): void {
|
|
const project = event.data;
|
|
for (const uiSourceCode of project.uiSourceCodes()) {
|
|
this.removeUISourceCode(uiSourceCode);
|
|
}
|
|
}
|
|
|
|
private removeUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
|
|
this.loadingUISourceCodes.delete(uiSourceCode);
|
|
const uiSourceCodeDiff = this.uiSourceCodeDiffs.get(uiSourceCode);
|
|
if (uiSourceCodeDiff) {
|
|
uiSourceCodeDiff.dispose = true;
|
|
}
|
|
this.markAsUnmodified(uiSourceCode);
|
|
}
|
|
|
|
private markAsUnmodified(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
|
|
this.uiSourceCodeProcessedForTest();
|
|
if (this.modifiedUISourceCodesInternal.delete(uiSourceCode)) {
|
|
this.dispatchEventToListeners(Events.MODIFIED_STATUS_CHANGED, {uiSourceCode, isModified: false});
|
|
}
|
|
}
|
|
|
|
private markAsModified(uiSourceCode: Workspace.UISourceCode.UISourceCode): void {
|
|
this.uiSourceCodeProcessedForTest();
|
|
if (this.modifiedUISourceCodesInternal.has(uiSourceCode)) {
|
|
return;
|
|
}
|
|
this.modifiedUISourceCodesInternal.add(uiSourceCode);
|
|
this.dispatchEventToListeners(Events.MODIFIED_STATUS_CHANGED, {uiSourceCode, isModified: true});
|
|
}
|
|
|
|
private uiSourceCodeProcessedForTest(): void {
|
|
}
|
|
|
|
private async updateModifiedState(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
|
this.loadingUISourceCodes.delete(uiSourceCode);
|
|
|
|
if (uiSourceCode.project().type() !== Workspace.Workspace.projectTypes.Network) {
|
|
this.markAsUnmodified(uiSourceCode);
|
|
return;
|
|
}
|
|
if (uiSourceCode.isDirty()) {
|
|
this.markAsModified(uiSourceCode);
|
|
return;
|
|
}
|
|
if (!uiSourceCode.hasCommits()) {
|
|
this.markAsUnmodified(uiSourceCode);
|
|
return;
|
|
}
|
|
|
|
const contentsPromise = Promise.all([
|
|
this.requestOriginalContentForUISourceCode(uiSourceCode),
|
|
uiSourceCode.requestContent().then(deferredContent => deferredContent.content),
|
|
]);
|
|
|
|
this.loadingUISourceCodes.set(uiSourceCode, contentsPromise);
|
|
const contents = await contentsPromise;
|
|
if (this.loadingUISourceCodes.get(uiSourceCode) !== contentsPromise) {
|
|
return;
|
|
}
|
|
this.loadingUISourceCodes.delete(uiSourceCode);
|
|
|
|
if (contents[0] !== null && contents[1] !== null && contents[0] !== contents[1]) {
|
|
this.markAsModified(uiSourceCode);
|
|
} else {
|
|
this.markAsUnmodified(uiSourceCode);
|
|
}
|
|
}
|
|
|
|
requestOriginalContentForUISourceCode(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<string|null> {
|
|
return this.uiSourceCodeDiff(uiSourceCode).originalContent();
|
|
}
|
|
|
|
revertToOriginal(uiSourceCode: Workspace.UISourceCode.UISourceCode): Promise<void> {
|
|
function callback(content: string|null): void {
|
|
if (typeof content !== 'string') {
|
|
return;
|
|
}
|
|
|
|
uiSourceCode.addRevision(content);
|
|
}
|
|
|
|
Host.userMetrics.actionTaken(Host.UserMetrics.Action.RevisionApplied);
|
|
return this.requestOriginalContentForUISourceCode(uiSourceCode).then(callback);
|
|
}
|
|
}
|
|
|
|
export const enum Events {
|
|
MODIFIED_STATUS_CHANGED = 'ModifiedStatusChanged',
|
|
}
|
|
|
|
export interface ModifiedStatusChangedEvent {
|
|
uiSourceCode: Workspace.UISourceCode.UISourceCode;
|
|
isModified: boolean;
|
|
}
|
|
|
|
export type EventTypes = {
|
|
[Events.MODIFIED_STATUS_CHANGED]: ModifiedStatusChangedEvent,
|
|
};
|
|
|
|
export class UISourceCodeDiff extends Common.ObjectWrapper.ObjectWrapper<UISourceCodeDiffEventTypes> {
|
|
private uiSourceCode: Workspace.UISourceCode.UISourceCode;
|
|
private requestDiffPromise: Promise<DiffResponse|null>|null;
|
|
private pendingChanges: number|null;
|
|
dispose: boolean;
|
|
constructor(uiSourceCode: Workspace.UISourceCode.UISourceCode) {
|
|
super();
|
|
this.uiSourceCode = uiSourceCode;
|
|
uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyChanged, this.uiSourceCodeChanged, this);
|
|
uiSourceCode.addEventListener(Workspace.UISourceCode.Events.WorkingCopyCommitted, this.uiSourceCodeChanged, this);
|
|
this.requestDiffPromise = null;
|
|
this.pendingChanges = null;
|
|
this.dispose = false;
|
|
}
|
|
|
|
private uiSourceCodeChanged(): void {
|
|
if (this.pendingChanges) {
|
|
clearTimeout(this.pendingChanges);
|
|
this.pendingChanges = null;
|
|
}
|
|
this.requestDiffPromise = null;
|
|
|
|
const content = this.uiSourceCode.content();
|
|
const delay = (!content || content.length < 65536) ? 0 : UpdateTimeout;
|
|
this.pendingChanges = window.setTimeout(emitDiffChanged.bind(this), delay);
|
|
|
|
function emitDiffChanged(this: UISourceCodeDiff): void {
|
|
if (this.dispose) {
|
|
return;
|
|
}
|
|
this.dispatchEventToListeners(UISourceCodeDiffEvents.DIFF_CHANGED);
|
|
this.pendingChanges = null;
|
|
}
|
|
}
|
|
|
|
requestDiff(diffRequestOptions: DiffRequestOptions): Promise<DiffResponse|null> {
|
|
if (!this.requestDiffPromise) {
|
|
this.requestDiffPromise = this.innerRequestDiff(diffRequestOptions);
|
|
}
|
|
return this.requestDiffPromise;
|
|
}
|
|
|
|
async originalContent(): Promise<string|null> {
|
|
const originalNetworkContent =
|
|
Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance().originalContentForUISourceCode(
|
|
this.uiSourceCode);
|
|
if (originalNetworkContent) {
|
|
return originalNetworkContent;
|
|
}
|
|
|
|
const content = await this.uiSourceCode.project().requestFileContent(this.uiSourceCode);
|
|
if (TextUtils.ContentData.ContentData.isError(content)) {
|
|
return content.error;
|
|
}
|
|
return content.asDeferedContent().content;
|
|
}
|
|
|
|
private async innerRequestDiff({shouldFormatDiff}: DiffRequestOptions): Promise<DiffResponse|null> {
|
|
if (this.dispose) {
|
|
return null;
|
|
}
|
|
|
|
let baseline = await this.originalContent();
|
|
if (baseline === null) {
|
|
return null;
|
|
}
|
|
if (baseline.length > 1024 * 1024) {
|
|
return null;
|
|
}
|
|
// ------------ ASYNC ------------
|
|
if (this.dispose) {
|
|
return null;
|
|
}
|
|
|
|
let current = this.uiSourceCode.workingCopy();
|
|
if (!current && !this.uiSourceCode.contentLoaded()) {
|
|
current = ((await this.uiSourceCode.requestContent()).content as string);
|
|
}
|
|
|
|
if (current.length > 1024 * 1024) {
|
|
return null;
|
|
}
|
|
|
|
if (this.dispose) {
|
|
return null;
|
|
}
|
|
|
|
if (current === null || baseline === null) {
|
|
return null;
|
|
}
|
|
let formattedCurrentMapping;
|
|
if (shouldFormatDiff) {
|
|
baseline = (await FormatterModule.ScriptFormatter.format(
|
|
this.uiSourceCode.contentType(), this.uiSourceCode.mimeType(), baseline))
|
|
.formattedContent;
|
|
const formatCurrentResult = await FormatterModule.ScriptFormatter.format(
|
|
this.uiSourceCode.contentType(), this.uiSourceCode.mimeType(), current);
|
|
current = formatCurrentResult.formattedContent;
|
|
formattedCurrentMapping = formatCurrentResult.formattedMapping;
|
|
}
|
|
const reNewline = /\r\n?|\n/;
|
|
const diff = Diff.Diff.DiffWrapper.lineDiff(baseline.split(reNewline), current.split(reNewline));
|
|
return {
|
|
diff,
|
|
formattedCurrentMapping,
|
|
};
|
|
}
|
|
}
|
|
|
|
export const enum UISourceCodeDiffEvents {
|
|
DIFF_CHANGED = 'DiffChanged',
|
|
}
|
|
|
|
export type UISourceCodeDiffEventTypes = {
|
|
[UISourceCodeDiffEvents.DIFF_CHANGED]: void,
|
|
};
|
|
|
|
let workspaceDiffImplInstance: WorkspaceDiffImpl|null = null;
|
|
|
|
export function workspaceDiff(): WorkspaceDiffImpl {
|
|
if (!workspaceDiffImplInstance) {
|
|
workspaceDiffImplInstance = new WorkspaceDiffImpl(Workspace.Workspace.WorkspaceImpl.instance());
|
|
}
|
|
return workspaceDiffImplInstance;
|
|
}
|
|
|
|
export const UpdateTimeout = 200;
|