You've already forked objdiff-web
mirror of
https://github.com/encounter/objdiff-web.git
synced 2026-07-10 12:18:37 -07:00
Implement data diffing & update to objdiff v3.2.0
This commit is contained in:
+2
-2
@@ -3,7 +3,7 @@
|
||||
"displayName": "objdiff",
|
||||
"description": "A local diffing tool for decompilation projects",
|
||||
"publisher": "decomp-dev",
|
||||
"version": "3.1.1",
|
||||
"version": "3.2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/encounter/objdiff-web"
|
||||
@@ -24,7 +24,7 @@
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"clsx": "^2.1.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"objdiff-wasm": "3.1.1",
|
||||
"objdiff-wasm": "3.2.0",
|
||||
"picomatch": "^4.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
Generated
+5
-5
@@ -18,8 +18,8 @@ importers:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0
|
||||
objdiff-wasm:
|
||||
specifier: 3.1.1
|
||||
version: 3.1.1
|
||||
specifier: 3.2.0
|
||||
version: 3.2.0
|
||||
picomatch:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
@@ -1613,8 +1613,8 @@ packages:
|
||||
nth-check@2.1.1:
|
||||
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
|
||||
|
||||
objdiff-wasm@3.1.1:
|
||||
resolution: {integrity: sha512-OXe1nmsxmYbXUSgHzZx1/bL/TLsSySR6S94MHAn9MEIWkaqGb+YkOK901NNBledbylMM/0llchl2FVgT54N3vw==}
|
||||
objdiff-wasm@3.2.0:
|
||||
resolution: {integrity: sha512-y6DXIOPtXPZMl/hxYYyks27ljtB0K5d51Mhx3K0yHddYW+558TYReo47lY9Ls8G9N/i1dAHWnYzpTAdCv/P0CQ==}
|
||||
|
||||
object-inspect@1.13.4:
|
||||
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
|
||||
@@ -3795,7 +3795,7 @@ snapshots:
|
||||
dependencies:
|
||||
boolbase: 1.0.0
|
||||
|
||||
objdiff-wasm@3.1.1: {}
|
||||
objdiff-wasm@3.2.0: {}
|
||||
|
||||
object-inspect@1.13.4: {}
|
||||
|
||||
|
||||
@@ -177,6 +177,9 @@ export function renderContextItems(
|
||||
items: display.ContextItem[],
|
||||
close: () => void,
|
||||
): React.ReactNode {
|
||||
if (items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return items.map((item, i) => {
|
||||
const key = `${item.tag}-${i}`;
|
||||
switch (item.tag) {
|
||||
|
||||
@@ -12,13 +12,29 @@
|
||||
}
|
||||
|
||||
.hover-item-label {
|
||||
color: var(--color-blue);
|
||||
color: var(--color-bright);
|
||||
}
|
||||
|
||||
.hover-item-value {
|
||||
color: var(--color-bright);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.hover-item-separator {
|
||||
border-color: var(--color-muted);
|
||||
}
|
||||
|
||||
.label-color-special {
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
.label-color-delete {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.label-color-insert {
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
.value-color-emphasized {
|
||||
color: var(--color-bright);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function createTooltip<T>(): {
|
||||
const parsedContent = JSON.parse(content) as T;
|
||||
return callback(parsedContent);
|
||||
}, [callback, content]);
|
||||
if (!items) {
|
||||
if (!items || items.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return <TooltipContentMemo items={items} />;
|
||||
@@ -57,23 +57,46 @@ const TooltipContent = ({ items }: { items: display.HoverItem[] }) => {
|
||||
for (const [i, item] of items.entries()) {
|
||||
let inner: React.ReactNode;
|
||||
switch (item.tag) {
|
||||
case 'text':
|
||||
case 'text': {
|
||||
let labelClass: string | null = null;
|
||||
let valueClass: string | null = null;
|
||||
switch (item.val.color) {
|
||||
case 'special':
|
||||
labelClass = styles.labelColorSpecial;
|
||||
break;
|
||||
case 'insert':
|
||||
labelClass = styles.labelColorInsert;
|
||||
break;
|
||||
case 'delete':
|
||||
labelClass = styles.labelColorDelete;
|
||||
break;
|
||||
case 'emphasized':
|
||||
valueClass = styles.valueColorEmphasized;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (item.val.label) {
|
||||
inner = (
|
||||
<>
|
||||
<span className={styles.hoverItemLabel}>
|
||||
<span className={clsx(styles.hoverItemLabel, labelClass)}>
|
||||
{item.val.label}
|
||||
{': '}
|
||||
</span>
|
||||
<span className={styles.hoverItemValue}>{item.val.value}</span>
|
||||
<span className={clsx(styles.hoverItemValue, valueClass)}>
|
||||
{item.val.value}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
inner = (
|
||||
<span className={styles.hoverItemValue}>{item.val.value}</span>
|
||||
<span className={clsx(styles.hoverItemValue, valueClass)}>
|
||||
{item.val.value}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'separator':
|
||||
inner = <hr className={styles.hoverItemSeparator} />;
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
.data-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: var(--vscode-editor-font-family, monospace);
|
||||
font-size: var(--vscode-editor-font-size, 13px);
|
||||
line-height: 1.5;
|
||||
white-space: pre;
|
||||
cursor: default;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.combined-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.data-row:hover {
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
}
|
||||
|
||||
.address {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.hex-bytes {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
color: var(--vscode-descriptionForeground);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.ascii-chars {
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.replace {
|
||||
color: var(--color-blue);
|
||||
}
|
||||
|
||||
.delete {
|
||||
color: var(--color-red);
|
||||
}
|
||||
|
||||
.insert {
|
||||
color: var(--color-green);
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
import styles from './DataView.module.css';
|
||||
|
||||
import { type diff, display } from 'objdiff-wasm';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { FixedSizeList, areEqual } from 'react-window';
|
||||
import type { ListChildComponentProps, ListOnScrollProps } from 'react-window';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { createContextMenu } from '../common/ContextMenu';
|
||||
import { createTooltip } from '../common/TooltipShared';
|
||||
import { useAppStore, useExtensionStore } from '../state';
|
||||
import { useFontSize } from '../util/util';
|
||||
|
||||
const BYTES_PER_ROW = 16;
|
||||
|
||||
export type DataTooltipContent = {
|
||||
column: number;
|
||||
row: number;
|
||||
};
|
||||
|
||||
export const { Tooltip: DataTooltip, useTooltip: useDataTooltip } =
|
||||
createTooltip<DataTooltipContent>();
|
||||
|
||||
export const {
|
||||
ContextMenuProvider: DataContextMenuProvider,
|
||||
useContextMenu: useDataContextMenu,
|
||||
} = createContextMenu<DataTooltipContent>();
|
||||
|
||||
const DataRow = ({
|
||||
obj,
|
||||
symbol,
|
||||
row,
|
||||
column,
|
||||
}: {
|
||||
obj: diff.ObjectDiff | undefined;
|
||||
symbol: display.SymbolDisplay | null;
|
||||
row: number;
|
||||
column: number;
|
||||
}) => {
|
||||
const onContextMenu = useDataContextMenu();
|
||||
const tooltipContent: DataTooltipContent = useMemo(
|
||||
() => ({
|
||||
column,
|
||||
row,
|
||||
}),
|
||||
[column, row],
|
||||
);
|
||||
const tooltipProps = useDataTooltip(tooltipContent);
|
||||
const onContextMenuMemo = useCallback(
|
||||
(e: React.MouseEvent<HTMLElement>) => onContextMenu(e, tooltipContent),
|
||||
[onContextMenu, tooltipContent],
|
||||
);
|
||||
|
||||
if (!obj || !symbol) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dataRow = display.displayDataRow(obj, symbol.info.id, row);
|
||||
const out: React.ReactNode[] = [];
|
||||
|
||||
// Display address
|
||||
out.push(
|
||||
<span key="addr" className={styles.address}>
|
||||
{dataRow.address.toString(16).padStart(8, '0')}:
|
||||
</span>,
|
||||
);
|
||||
|
||||
// Display hex bytes
|
||||
let byteIndex = 0;
|
||||
const hexBytes: React.ReactNode[] = [];
|
||||
const asciiChars: React.ReactNode[] = [];
|
||||
|
||||
for (const diff of dataRow.segments) {
|
||||
let className: string | undefined;
|
||||
switch (diff.kind) {
|
||||
case 'none':
|
||||
break;
|
||||
case 'replace':
|
||||
className = styles.replace;
|
||||
break;
|
||||
case 'delete':
|
||||
className = styles.delete;
|
||||
break;
|
||||
case 'insert':
|
||||
className = styles.insert;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for relocations that overlap with this diff segment
|
||||
const diffStartAddr = dataRow.address + BigInt(byteIndex);
|
||||
const diffEndAddr = diffStartAddr + BigInt(diff.size);
|
||||
const relocsForDiff = dataRow.relocations.filter((r) => {
|
||||
const relocStart = r.address;
|
||||
const relocEnd = r.address + BigInt(r.size);
|
||||
// Check if relocation overlaps with this diff segment
|
||||
return relocStart < diffEndAddr && relocEnd > diffStartAddr;
|
||||
});
|
||||
|
||||
if (diff.data.length === 0) {
|
||||
// Empty data (deletion on other side - show as blank spaces)
|
||||
for (let i = 0; i < diff.size; i++) {
|
||||
hexBytes.push(<span key={`hex-${byteIndex}`}>{' '}</span>);
|
||||
asciiChars.push(<span key={`ascii-${byteIndex}`}> </span>);
|
||||
byteIndex++;
|
||||
if (byteIndex % 8 === 0 && byteIndex < BYTES_PER_ROW) {
|
||||
hexBytes.push(<span key={`space-${byteIndex}`}> </span>);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Display actual bytes
|
||||
for (let i = 0; i < diff.data.length; i++) {
|
||||
const byte = diff.data[i];
|
||||
const currentAddress = dataRow.address + BigInt(byteIndex);
|
||||
const reloc = relocsForDiff.find(
|
||||
(r) =>
|
||||
r.address <= currentAddress &&
|
||||
currentAddress < r.address + BigInt(r.size),
|
||||
);
|
||||
|
||||
let byteClassName = className;
|
||||
let byteText = byte.toString(16).padStart(2, '0');
|
||||
|
||||
if (reloc) {
|
||||
if (byte === 0) {
|
||||
byteText = '??';
|
||||
}
|
||||
if (reloc.kind !== 'none') {
|
||||
switch (reloc.kind) {
|
||||
case 'replace':
|
||||
byteClassName = styles.replace;
|
||||
break;
|
||||
case 'delete':
|
||||
byteClassName = styles.delete;
|
||||
break;
|
||||
case 'insert':
|
||||
byteClassName = styles.insert;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hexBytes.push(
|
||||
<span key={`hex-${byteIndex}`} className={byteClassName}>
|
||||
{byteText}{' '}
|
||||
</span>,
|
||||
);
|
||||
|
||||
// ASCII representation
|
||||
const c = String.fromCharCode(byte);
|
||||
const asciiChar =
|
||||
c.charCodeAt(0) >= 32 && c.charCodeAt(0) < 127 ? c : '.';
|
||||
asciiChars.push(
|
||||
<span key={`ascii-${byteIndex}`} className={className}>
|
||||
{asciiChar}
|
||||
</span>,
|
||||
);
|
||||
|
||||
byteIndex++;
|
||||
if (byteIndex % 8 === 0 && byteIndex < BYTES_PER_ROW) {
|
||||
hexBytes.push(<span key={`space-${byteIndex}`}> </span>);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pad to full row width if needed
|
||||
while (byteIndex < BYTES_PER_ROW) {
|
||||
hexBytes.push(<span key={`hex-${byteIndex}`}>{' '}</span>);
|
||||
asciiChars.push(<span key={`ascii-${byteIndex}`}> </span>);
|
||||
byteIndex++;
|
||||
if (byteIndex % 8 === 0 && byteIndex < BYTES_PER_ROW) {
|
||||
hexBytes.push(<span key={`space-${byteIndex}`}> </span>);
|
||||
}
|
||||
}
|
||||
|
||||
out.push(
|
||||
<span key="hex" className={styles.hexBytes}>
|
||||
{hexBytes}
|
||||
</span>,
|
||||
);
|
||||
out.push(
|
||||
<span key="sep" className={styles.separator}>
|
||||
{' '}
|
||||
</span>,
|
||||
);
|
||||
out.push(
|
||||
<span key="ascii" className={styles.asciiChars}>
|
||||
{asciiChars}
|
||||
</span>,
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.dataRow}
|
||||
onContextMenu={onContextMenuMemo}
|
||||
{...tooltipProps}
|
||||
>
|
||||
{out}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const DataRowMemo = memo(DataRow);
|
||||
|
||||
// Row renderer for combined view
|
||||
const CombinedRowRenderer = memo(
|
||||
({
|
||||
index,
|
||||
style,
|
||||
data,
|
||||
}: ListChildComponentProps<{
|
||||
leftObj: diff.ObjectDiff | undefined;
|
||||
leftSymbol: display.SymbolDisplay | null;
|
||||
rightObj: diff.ObjectDiff | undefined;
|
||||
rightSymbol: display.SymbolDisplay | null;
|
||||
}>) => {
|
||||
const { leftObj, leftSymbol, rightObj, rightSymbol } = data;
|
||||
return (
|
||||
<div style={style} className={styles.combinedRow}>
|
||||
<div style={{ width: '50%' }}>
|
||||
<DataRowMemo
|
||||
obj={leftObj}
|
||||
symbol={leftSymbol}
|
||||
row={index}
|
||||
column={0}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ width: '50%' }}>
|
||||
<DataRowMemo
|
||||
obj={rightObj}
|
||||
symbol={rightSymbol}
|
||||
row={index}
|
||||
column={1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
areEqual,
|
||||
);
|
||||
|
||||
export const DataList = ({
|
||||
height,
|
||||
width,
|
||||
diff,
|
||||
leftSymbol,
|
||||
rightSymbol,
|
||||
}: {
|
||||
height: number;
|
||||
width: number;
|
||||
diff: diff.DiffResult;
|
||||
leftSymbol: display.SymbolDisplay | null;
|
||||
rightSymbol: display.SymbolDisplay | null;
|
||||
}) => {
|
||||
const { currentUnit } = useExtensionStore(
|
||||
useShallow((state) => ({
|
||||
currentUnit: state.currentUnit,
|
||||
})),
|
||||
);
|
||||
const { setSymbolScrollOffset } = useAppStore(
|
||||
useShallow((state) => ({
|
||||
setSymbolScrollOffset: state.setSymbolScrollOffset,
|
||||
})),
|
||||
);
|
||||
const fontSize = useFontSize();
|
||||
|
||||
const leftCount = leftSymbol?.rowCount ?? 0;
|
||||
const rightCount = rightSymbol?.rowCount ?? 0;
|
||||
const rowCount = Math.max(leftCount, rightCount);
|
||||
|
||||
// Get symbol name for scroll persistence
|
||||
const symbolName = useMemo(() => {
|
||||
if (leftSymbol && diff.left) {
|
||||
return leftSymbol.info.name;
|
||||
}
|
||||
if (rightSymbol && diff.right) {
|
||||
return rightSymbol.info.name;
|
||||
}
|
||||
return '';
|
||||
}, [diff, leftSymbol, rightSymbol]);
|
||||
|
||||
const itemData = useMemo(
|
||||
() => ({
|
||||
leftObj: diff.left,
|
||||
leftSymbol,
|
||||
rightObj: diff.right,
|
||||
rightSymbol,
|
||||
}),
|
||||
[diff, leftSymbol, rightSymbol],
|
||||
);
|
||||
|
||||
const currentUnitName = currentUnit?.name || '';
|
||||
|
||||
// Get initial scroll offset
|
||||
const initialScrollOffset = useMemo(
|
||||
() =>
|
||||
useAppStore.getState().getUnitState(currentUnitName).symbolScrollOffsets[
|
||||
symbolName
|
||||
] || 0,
|
||||
[currentUnitName, symbolName],
|
||||
);
|
||||
|
||||
// Handle scroll events to persist position
|
||||
const onScrollMemo = useCallback(
|
||||
(e: ListOnScrollProps) => {
|
||||
setSymbolScrollOffset(currentUnitName, symbolName, e.scrollOffset);
|
||||
},
|
||||
[currentUnitName, symbolName, setSymbolScrollOffset],
|
||||
);
|
||||
|
||||
if ((!diff.left || !leftSymbol) && (!diff.right || !rightSymbol)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<FixedSizeList
|
||||
height={height}
|
||||
width={width}
|
||||
itemCount={rowCount}
|
||||
itemSize={fontSize * 1.5}
|
||||
itemData={itemData}
|
||||
onScroll={onScrollMemo}
|
||||
initialScrollOffset={initialScrollOffset}
|
||||
>
|
||||
{CombinedRowRenderer}
|
||||
</FixedSizeList>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataList;
|
||||
+162
-26
@@ -23,6 +23,12 @@ import {
|
||||
useExtensionStore,
|
||||
} from '../state';
|
||||
import { percentClass } from '../util/util';
|
||||
import {
|
||||
DataContextMenuProvider,
|
||||
DataList,
|
||||
DataTooltip,
|
||||
type DataTooltipContent,
|
||||
} from './DataView';
|
||||
import {
|
||||
InstructionContextMenuProvider,
|
||||
InstructionList,
|
||||
@@ -47,6 +53,12 @@ type ColumnViewAsm = {
|
||||
symbol: display.SymbolDisplay;
|
||||
};
|
||||
|
||||
type ColumnViewData = {
|
||||
type: 'data';
|
||||
obj: diff.ObjectDiff;
|
||||
symbol: display.SymbolDisplay;
|
||||
};
|
||||
|
||||
type ColumnViewSymbols = {
|
||||
type: 'symbols';
|
||||
mappingSymbol: number | null;
|
||||
@@ -60,6 +72,7 @@ type ColumnView =
|
||||
| ColumnViewNone
|
||||
| ColumnViewBuildStatus
|
||||
| ColumnViewAsm
|
||||
| ColumnViewData
|
||||
| ColumnViewSymbols;
|
||||
|
||||
export const resolveSymbol = (
|
||||
@@ -140,22 +153,49 @@ const DiffView = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (leftSymbol && rightSymbol) {
|
||||
// Joint function view
|
||||
let leftColumnType: ColumnView['type'] | null = null;
|
||||
let rightColumnType: ColumnView['type'] | null = null;
|
||||
if (leftSymbol && leftSuccess) {
|
||||
switch (leftSymbol.info.sectionKind) {
|
||||
case 'code':
|
||||
leftColumnType = 'asm';
|
||||
break;
|
||||
case 'data':
|
||||
leftColumnType = 'data';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rightSymbol && rightSuccess) {
|
||||
switch (rightSymbol.info.sectionKind) {
|
||||
case 'code':
|
||||
rightColumnType = 'asm';
|
||||
break;
|
||||
case 'data':
|
||||
rightColumnType = 'data';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (leftSymbol && leftColumnType && rightSymbol && rightColumnType) {
|
||||
// Joint view
|
||||
leftColumnView = {
|
||||
type: 'asm',
|
||||
type: leftColumnType,
|
||||
obj: result.diff!.left!,
|
||||
symbol: leftSymbol,
|
||||
};
|
||||
rightColumnView = {
|
||||
type: 'asm',
|
||||
type: rightColumnType,
|
||||
obj: result.diff!.right!,
|
||||
symbol: rightSymbol,
|
||||
};
|
||||
} else if (leftSymbol) {
|
||||
// Left function view only
|
||||
} else if (leftSymbol && leftColumnType) {
|
||||
// Left view only
|
||||
leftColumnView = {
|
||||
type: 'asm',
|
||||
type: leftColumnType,
|
||||
obj: result.diff!.left!,
|
||||
symbol: leftSymbol,
|
||||
};
|
||||
@@ -166,10 +206,10 @@ const DiffView = ({
|
||||
mappingSymbol: leftSymbol.info.id,
|
||||
};
|
||||
}
|
||||
} else if (rightSymbol) {
|
||||
// Right function view only
|
||||
} else if (rightSymbol && rightColumnType) {
|
||||
// Right view only
|
||||
rightColumnView = {
|
||||
type: 'asm',
|
||||
type: rightColumnType,
|
||||
obj: result.diff!.right!,
|
||||
symbol: rightSymbol,
|
||||
};
|
||||
@@ -258,6 +298,32 @@ const DiffView = ({
|
||||
[result.diff, leftSymbol, rightSymbol, diffConfig],
|
||||
);
|
||||
|
||||
const dataContextMenuRender: ContextMenuRender<DataTooltipContent> =
|
||||
useCallback(
|
||||
({ data }, close) => {
|
||||
let obj: diff.ObjectDiff | undefined;
|
||||
let symbol: number | undefined;
|
||||
switch (data.column) {
|
||||
case 0:
|
||||
obj = result.diff?.left;
|
||||
symbol = leftSymbol?.info.id;
|
||||
break;
|
||||
case 1:
|
||||
obj = result.diff?.right;
|
||||
symbol = rightSymbol?.info.id;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!obj || symbol === undefined) {
|
||||
return null;
|
||||
}
|
||||
const items = display.dataContext(obj, symbol, data.row);
|
||||
return renderContextItems(items, close);
|
||||
},
|
||||
[result.diff, leftSymbol, rightSymbol],
|
||||
);
|
||||
|
||||
const instructionTooltipCallback: TooltipCallback<InstructionTooltipContent> =
|
||||
useCallback(
|
||||
(data) => {
|
||||
@@ -283,6 +349,30 @@ const DiffView = ({
|
||||
[result.diff, leftSymbol, rightSymbol, diffConfig],
|
||||
);
|
||||
|
||||
const dataTooltipCallback: TooltipCallback<DataTooltipContent> = useCallback(
|
||||
(data) => {
|
||||
let obj: diff.ObjectDiff | undefined;
|
||||
let symbol: number | undefined;
|
||||
switch (data.column) {
|
||||
case 0:
|
||||
obj = result.diff?.left;
|
||||
symbol = leftSymbol?.info.id;
|
||||
break;
|
||||
case 1:
|
||||
obj = result.diff?.right;
|
||||
symbol = rightSymbol?.info.id;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!obj || symbol === undefined) {
|
||||
return null;
|
||||
}
|
||||
return display.dataHover(obj, symbol, data.row);
|
||||
},
|
||||
[result.diff, leftSymbol, rightSymbol],
|
||||
);
|
||||
|
||||
const [showMappedSymbols, setShowMappedSymbols] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
@@ -299,19 +389,21 @@ const DiffView = ({
|
||||
<div className={styles.content}>
|
||||
<SymbolContextMenuProvider render={symbolContextMenuRender}>
|
||||
<InstructionContextMenuProvider render={instructionContextMenuRender}>
|
||||
<AutoSizer className={styles.content}>
|
||||
{({ height, width }) => (
|
||||
<DiffViewContent
|
||||
result={result}
|
||||
height={height}
|
||||
width={width}
|
||||
leftColumnView={leftColumnView}
|
||||
rightColumnView={rightColumnView}
|
||||
showMappedSymbols={showMappedSymbols}
|
||||
showHiddenSymbols={false} // TODO
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<DataContextMenuProvider render={dataContextMenuRender}>
|
||||
<AutoSizer className={styles.content}>
|
||||
{({ height, width }) => (
|
||||
<DiffViewContent
|
||||
result={result}
|
||||
height={height}
|
||||
width={width}
|
||||
leftColumnView={leftColumnView}
|
||||
rightColumnView={rightColumnView}
|
||||
showMappedSymbols={showMappedSymbols}
|
||||
showHiddenSymbols={false} // TODO
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</DataContextMenuProvider>
|
||||
</InstructionContextMenuProvider>
|
||||
</SymbolContextMenuProvider>
|
||||
</div>
|
||||
@@ -325,6 +417,11 @@ const DiffView = ({
|
||||
delayShow={500}
|
||||
callback={instructionTooltipCallback}
|
||||
/>
|
||||
<DataTooltip
|
||||
place="bottom"
|
||||
delayShow={500}
|
||||
callback={dataTooltipCallback}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -480,7 +577,12 @@ const DiffViewHeader = ({
|
||||
/>
|
||||
);
|
||||
|
||||
if (leftColumnView.type !== 'asm' && rightColumnView.type !== 'asm') {
|
||||
if (
|
||||
leftColumnView.type !== 'asm' &&
|
||||
rightColumnView.type !== 'asm' &&
|
||||
leftColumnView.type !== 'data' &&
|
||||
rightColumnView.type !== 'data'
|
||||
) {
|
||||
const unitNameRow = (
|
||||
<span className={clsx(headerStyles.label, headerStyles.emphasized)}>
|
||||
{currentUnitName}
|
||||
@@ -532,7 +634,7 @@ const DiffViewHeader = ({
|
||||
}
|
||||
|
||||
const matchPercent =
|
||||
leftColumnView.type === 'asm'
|
||||
leftColumnView.type === 'asm' || leftColumnView.type === 'data'
|
||||
? leftColumnView.symbol.matchPercent
|
||||
: undefined;
|
||||
|
||||
@@ -616,7 +718,8 @@ const SymbolLabel = ({
|
||||
currentUnitName: string;
|
||||
}) => {
|
||||
switch (view.type) {
|
||||
case 'asm': {
|
||||
case 'asm':
|
||||
case 'data': {
|
||||
const displayName =
|
||||
view.symbol.info.demangledName || view.symbol.info.name;
|
||||
return (
|
||||
@@ -705,6 +808,19 @@ const DiffViewContent = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (leftColumnView.type === 'data' && rightColumnView.type === 'data') {
|
||||
// Render joint data view
|
||||
return (
|
||||
<DataList
|
||||
height={height}
|
||||
width={width}
|
||||
diff={result.diff!}
|
||||
leftSymbol={leftColumnView.symbol}
|
||||
rightSymbol={rightColumnView.symbol}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let leftColumn = null;
|
||||
let rightColumn = null;
|
||||
if (leftColumnView.type === 'symbols') {
|
||||
@@ -733,6 +849,16 @@ const DiffViewContent = ({
|
||||
rightSymbol={null}
|
||||
/>
|
||||
);
|
||||
} else if (leftColumnView.type === 'data') {
|
||||
leftColumn = (
|
||||
<DataList
|
||||
height={height}
|
||||
width={width / 2}
|
||||
diff={result.diff!}
|
||||
leftSymbol={leftColumnView.symbol}
|
||||
rightSymbol={null}
|
||||
/>
|
||||
);
|
||||
} else if (leftColumnView.type === 'buildStatus') {
|
||||
leftColumn = (
|
||||
<BuildStatusView
|
||||
@@ -771,6 +897,16 @@ const DiffViewContent = ({
|
||||
rightSymbol={rightColumnView.symbol}
|
||||
/>
|
||||
);
|
||||
} else if (rightColumnView.type === 'data') {
|
||||
rightColumn = (
|
||||
<DataList
|
||||
height={height}
|
||||
width={width / 2}
|
||||
diff={result.diff!}
|
||||
leftSymbol={null}
|
||||
rightSymbol={rightColumnView.symbol}
|
||||
/>
|
||||
);
|
||||
} else if (rightColumnView.type === 'buildStatus') {
|
||||
rightColumn = (
|
||||
<BuildStatusView
|
||||
|
||||
Reference in New Issue
Block a user