Files
react-native-devtools-frontend/front_end/changes/ChangesView.js
T
Tim van der Lippe d87271d132 Remove usages of Runtime.sharedInstance
For all classes that were retrieved via Runtime.sharedInstance,
migrate them to the singletong instance pattern. This removes
the indirection via Runtime and improves code navigation as
well as typechecking.

R=jacktfranklin@chromium.org

Bug: 1011811
Change-Id: Ifcf0b73be703308fbdbad0c585f0d945cee9b3b7
Fixed: 1131423
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2434340
Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org>
Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
2020-09-29 10:59:08 +00:00

412 lines
13 KiB
JavaScript

// 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 '../common/common.js';
import * as Diff from '../diff/diff.js';
import * as UI from '../ui/ui.js';
import * as Workspace from '../workspace/workspace.js'; // eslint-disable-line no-unused-vars
import * as WorkspaceDiff from '../workspace_diff/workspace_diff.js';
import {ChangesSidebar, Events} from './ChangesSidebar.js';
import {ChangesTextEditor} from './ChangesTextEditor.js';
/** @type {!ChangesView} */
let changesViewInstance;
export class ChangesView extends UI.Widget.VBox {
/**
* @private
*/
constructor() {
super(true);
this.registerRequiredCSS('changes/changesView.css');
const splitWidget = new UI.SplitWidget.SplitWidget(true /* vertical */, false /* sidebar on left */);
const mainWidget = new UI.Widget.Widget();
splitWidget.setMainWidget(mainWidget);
splitWidget.show(this.contentElement);
this._emptyWidget = new UI.EmptyWidget.EmptyWidget('');
this._emptyWidget.show(mainWidget.element);
this._workspaceDiff = WorkspaceDiff.WorkspaceDiff.workspaceDiff();
this._changesSidebar = new ChangesSidebar(this._workspaceDiff);
this._changesSidebar.addEventListener(Events.SelectedUISourceCodeChanged, this._selectedUISourceCodeChanged, this);
splitWidget.setSidebarWidget(this._changesSidebar);
/** @type {?Workspace.UISourceCode.UISourceCode} */
this._selectedUISourceCode = null;
/** @type {!Array<!Row>} */
this._diffRows = [];
this._maxLineDigits = 1;
this._editor = new ChangesTextEditor({
bracketMatchingSetting: undefined,
devtoolsAccessibleName: ls`Changes diff viewer`,
lineNumbers: true,
lineWrapping: false,
mimeType: undefined,
autoHeight: undefined,
padBottom: undefined,
maxHighlightLength: Infinity, // Avoid CodeMirror bailing out of highlighting big diffs.
placeholder: undefined,
lineWiseCopyCut: undefined,
});
this._editor.setReadOnly(true);
const editorContainer = mainWidget.element.createChild('div', 'editor-container');
UI.ARIAUtils.markAsTabpanel(editorContainer);
this._editor.show(editorContainer);
this._editor.hideWidget();
self.onInvokeElement(this._editor.element, this._click.bind(this));
this._toolbar = new UI.Toolbar.Toolbar('changes-toolbar', mainWidget.element);
const revertButton = new UI.Toolbar.ToolbarButton(ls`Revert all changes to current file`, 'largeicon-undo');
revertButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this._revert.bind(this));
this._toolbar.appendToolbarItem(revertButton);
this._diffStats = new UI.Toolbar.ToolbarText('');
this._toolbar.appendToolbarItem(this._diffStats);
this._toolbar.setEnabled(false);
this._hideDiff(ls`No changes`);
this._selectedUISourceCodeChanged();
}
/**
* @param {{forceNew: ?boolean}} opts
*/
static instance(opts = {forceNew: null}) {
const {forceNew} = opts;
if (!changesViewInstance || forceNew) {
changesViewInstance = new ChangesView();
}
return changesViewInstance;
}
_selectedUISourceCodeChanged() {
this._revealUISourceCode(this._changesSidebar.selectedUISourceCode());
}
_revert() {
const uiSourceCode = this._selectedUISourceCode;
if (!uiSourceCode) {
return;
}
this._workspaceDiff.revertToOriginal(uiSourceCode);
}
/**
* @param {!Event} event
*/
_click(event) {
const selection = this._editor.selection();
if (!selection.isEmpty() || !this._selectedUISourceCode) {
return;
}
const row = this._diffRows[selection.startLine];
Common.Revealer.reveal(
this._selectedUISourceCode.uiLocation(row.currentLineNumber - 1, selection.startColumn), false);
event.consume(true);
}
/**
* @param {?Workspace.UISourceCode.UISourceCode} uiSourceCode
*/
_revealUISourceCode(uiSourceCode) {
if (this._selectedUISourceCode === uiSourceCode) {
return;
}
if (this._selectedUISourceCode) {
this._workspaceDiff.unsubscribeFromDiffChange(this._selectedUISourceCode, this._refreshDiff, this);
}
if (uiSourceCode && this.isShowing()) {
this._workspaceDiff.subscribeToDiffChange(uiSourceCode, this._refreshDiff, this);
}
this._selectedUISourceCode = uiSourceCode;
this._refreshDiff();
}
/**
* @override
*/
wasShown() {
this._refreshDiff();
}
_refreshDiff() {
if (!this.isShowing()) {
return;
}
if (!this._selectedUISourceCode) {
this._renderDiffRows(null);
return;
}
const uiSourceCode = this._selectedUISourceCode;
if (!uiSourceCode.contentType().isTextType()) {
this._hideDiff(ls`Binary data`);
return;
}
this._workspaceDiff.requestDiff(uiSourceCode).then(diff => {
if (this._selectedUISourceCode !== uiSourceCode) {
return;
}
this._renderDiffRows(diff);
});
}
/**
* @param {string} message
*/
_hideDiff(message) {
this._diffStats.setText('');
this._toolbar.setEnabled(false);
this._editor.hideWidget();
this._emptyWidget.text = message;
this._emptyWidget.showWidget();
}
/**
* @param {?Diff.Diff.DiffArray} diff
*/
_renderDiffRows(diff) {
this._diffRows = [];
if (!diff || (diff.length === 1 && diff[0][0] === Diff.Diff.Operation.Equal)) {
this._hideDiff(ls`No changes`);
return;
}
let insertions = 0;
let deletions = 0;
let currentLineNumber = 0;
let baselineLineNumber = 0;
const paddingLines = 3;
/** @type {!Array<string>} */
const originalLines = [];
/** @type {!Array<string>} */
const currentLines = [];
for (let i = 0; i < diff.length; ++i) {
const token = diff[i];
switch (token[0]) {
case Diff.Diff.Operation.Equal:
this._diffRows.push(...createEqualRows(token[1], i === 0, i === diff.length - 1));
originalLines.push(...token[1]);
currentLines.push(...token[1]);
break;
case Diff.Diff.Operation.Insert:
for (const line of token[1]) {
this._diffRows.push(createRow(line, RowType.Addition));
}
insertions += token[1].length;
currentLines.push(...token[1]);
break;
case Diff.Diff.Operation.Delete:
deletions += token[1].length;
originalLines.push(...token[1]);
if (diff[i + 1] && diff[i + 1][0] === Diff.Diff.Operation.Insert) {
i++;
this._diffRows.push(...createModifyRows(token[1].join('\n'), diff[i][1].join('\n')));
insertions += diff[i][1].length;
currentLines.push(...diff[i][1]);
} else {
for (const line of token[1]) {
this._diffRows.push(createRow(line, RowType.Deletion));
}
}
break;
}
}
this._maxLineDigits = Math.ceil(Math.log10(Math.max(currentLineNumber, baselineLineNumber)));
let insertionText = '';
if (insertions === 1) {
insertionText = ls`${insertions} insertion (+),`;
} else {
insertionText = ls`${insertions} insertions (+),`;
}
let deletionText = '';
if (deletions === 1) {
deletionText = ls`${deletions} deletion (-)`;
} else {
deletionText = ls`${deletions} deletions (-)`;
}
this._diffStats.setText(`${insertionText} ${deletionText}`);
this._toolbar.setEnabled(true);
this._emptyWidget.hideWidget();
this._editor.operation(() => {
this._editor.showWidget();
this._editor.setHighlightMode({
name: 'devtools-diff',
diffRows: this._diffRows,
mimeType: /** @type {!Workspace.UISourceCode.UISourceCode} */ (this._selectedUISourceCode).mimeType(),
baselineLines: originalLines,
currentLines: currentLines
});
this._editor.setText(this._diffRows.map(row => row.tokens.map(t => t.text).join('')).join('\n'));
this._editor.setLineNumberFormatter(this._lineFormatter.bind(this));
this._editor.updateDiffGutter(this._diffRows);
});
/**
* @param {!Array<string>} lines
* @param {boolean} atStart
* @param {boolean} atEnd
* @return {!Array<!Row>}}
*/
function createEqualRows(lines, atStart, atEnd) {
const equalRows = [];
if (!atStart) {
for (let i = 0; i < paddingLines && i < lines.length; i++) {
equalRows.push(createRow(lines[i], RowType.Equal));
}
if (lines.length > paddingLines * 2 + 1 && !atEnd) {
equalRows.push(createRow(
Common.UIString.UIString('( … Skipping %d matching lines … )', lines.length - paddingLines * 2),
RowType.Spacer));
}
}
if (!atEnd) {
const start = Math.max(lines.length - paddingLines - 1, atStart ? 0 : paddingLines);
let skip = lines.length - paddingLines - 1;
if (!atStart) {
skip -= paddingLines;
}
if (skip > 0) {
baselineLineNumber += skip;
currentLineNumber += skip;
}
for (let i = start; i < lines.length; i++) {
equalRows.push(createRow(lines[i], RowType.Equal));
}
}
return equalRows;
}
/**
* @param {string} before
* @param {string} after
* @return {!Array<!Row>}}
*/
function createModifyRows(before, after) {
const internalDiff = Diff.Diff.DiffWrapper.charDiff(before, after, true /* cleanup diff */);
const deletionRows = [createRow('', RowType.Deletion)];
const insertionRows = [createRow('', RowType.Addition)];
for (const token of internalDiff) {
const text = token[1];
const type = token[0];
const className = type === Diff.Diff.Operation.Equal ? '' : 'inner-diff';
const lines = text.split('\n');
for (let i = 0; i < lines.length; i++) {
if (i > 0 && type !== Diff.Diff.Operation.Insert) {
deletionRows.push(createRow('', RowType.Deletion));
}
if (i > 0 && type !== Diff.Diff.Operation.Delete) {
insertionRows.push(createRow('', RowType.Addition));
}
if (!lines[i]) {
continue;
}
if (type !== Diff.Diff.Operation.Insert) {
deletionRows[deletionRows.length - 1].tokens.push({text: lines[i], className});
}
if (type !== Diff.Diff.Operation.Delete) {
insertionRows[insertionRows.length - 1].tokens.push({text: lines[i], className});
}
}
}
return deletionRows.concat(insertionRows);
}
/**
* @param {string} text
* @param {!RowType} type
* @return {!Row}
*/
function createRow(text, type) {
if (type === RowType.Addition) {
currentLineNumber++;
}
if (type === RowType.Deletion) {
baselineLineNumber++;
}
if (type === RowType.Equal) {
baselineLineNumber++;
currentLineNumber++;
}
return {baselineLineNumber, currentLineNumber, tokens: text ? [{text, className: 'inner-diff'}] : [], type};
}
}
/**
* @param {number} lineNumber
* @return {string}
*/
_lineFormatter(lineNumber) {
const row = this._diffRows[lineNumber - 1];
let showBaseNumber = row.type === RowType.Deletion;
let showCurrentNumber = row.type === RowType.Addition;
if (row.type === RowType.Equal) {
showBaseNumber = true;
showCurrentNumber = true;
}
const baseText = showBaseNumber ? String(row.baselineLineNumber) : '';
const base = baseText.padStart(this._maxLineDigits, '\xA0');
const currentText = showCurrentNumber ? String(row.currentLineNumber) : '';
const current = currentText.padStart(this._maxLineDigits, '\xA0');
return base + '\xA0' + current;
}
}
/** @enum {string} */
export const RowType = {
Deletion: 'deletion',
Addition: 'addition',
Equal: 'equal',
Spacer: 'spacer'
};
/**
* @implements {Common.Revealer.Revealer}
*/
export class DiffUILocationRevealer {
/**
* @override
* @param {!Object} diffUILocation
* @param {boolean=} omitFocus
* @return {!Promise<void>}
*/
async reveal(diffUILocation, omitFocus) {
if (!(diffUILocation instanceof WorkspaceDiff.WorkspaceDiff.DiffUILocation)) {
throw new Error('Internal error: not a diff ui location');
}
await UI.ViewManager.ViewManager.instance().showView('changes.changes');
ChangesView.instance()._changesSidebar.selectUISourceCode(diffUILocation.uiSourceCode, omitFocus);
}
}
/**
* @typedef {!{
* baselineLineNumber: number,
* currentLineNumber: number,
* tokens: !Array<!{text: string, className: string}>,
* type: !RowType
* }}
*/
// @ts-ignore typedef
export let Row;