You've already forked objdiff-web
mirror of
https://github.com/encounter/objdiff-web.git
synced 2026-07-10 12:18:37 -07:00
Refactor based on objdiff v3.0.0-alpha.1
This commit is contained in:
Vendored
+2
-1
@@ -27,7 +27,8 @@
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
"editor.defaultFormatter": "biomejs.biome",
|
||||
"editor.formatOnSave": false
|
||||
},
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "biomejs.biome"
|
||||
|
||||
@@ -10,13 +10,13 @@ Open the project in Visual Studio Code.
|
||||
- Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
pnpm install
|
||||
```
|
||||
|
||||
- Start dev server using `Ctrl+Shift+B` or by running:
|
||||
|
||||
```bash
|
||||
npm run watch
|
||||
pnpm watch
|
||||
```
|
||||
|
||||
- Run the extension in debug mode using `F5`.
|
||||
|
||||
@@ -27,6 +27,12 @@
|
||||
"recommended": true,
|
||||
"a11y": {
|
||||
"all": false
|
||||
},
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
},
|
||||
"style": {
|
||||
"noNonNullAssertion": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
-3916
File diff suppressed because it is too large
Load Diff
+39
-18
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "objdiff",
|
||||
"displayName": "objdiff",
|
||||
"description": "objdiff",
|
||||
"description": "A local diffing tool for decompilation projects",
|
||||
"publisher": "decomp-dev",
|
||||
"version": "0.0.2",
|
||||
"version": "0.1.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/encounter/objdiff-code"
|
||||
@@ -20,13 +20,15 @@
|
||||
"@protobuf-ts/runtime": "^2.9.4",
|
||||
"@vscode/codicons": "^0.0.36",
|
||||
"clsx": "^2.1.1",
|
||||
"core-js": "^3.39.0",
|
||||
"memoize-one": "^6.0.0",
|
||||
"objdiff-wasm": "^3.0.0-alpha.1",
|
||||
"picomatch": "^4.0.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-tooltip": "^5.28.0",
|
||||
"react-virtualized-auto-sizer": "^1.0.24",
|
||||
"react-window": "^1.8.10",
|
||||
"shescape": "^2.1.1",
|
||||
"zustand": "^5.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,7 +37,6 @@
|
||||
"@rsbuild/plugin-react": "^1.0.7",
|
||||
"@rsbuild/plugin-type-check": "^1.1.0",
|
||||
"@rsbuild/plugin-typed-css-modules": "^1.0.2",
|
||||
"@types/core-js": "^2.5.8",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/picomatch": "^3.0.1",
|
||||
"@types/react": "^18.3.1",
|
||||
@@ -95,23 +96,31 @@
|
||||
}
|
||||
],
|
||||
"configuration": [
|
||||
{
|
||||
"title": "Extension",
|
||||
"properties": {
|
||||
"objdiff.binaryPath": {
|
||||
"type": "string",
|
||||
"description": "Path to the objdiff-cli binary",
|
||||
"scope": "machine"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "General",
|
||||
"properties": {
|
||||
"objdiff.relaxRelocDiffs": {
|
||||
"type": "boolean",
|
||||
"description": "Ignores differences in relocation targets. (Address, name, etc)",
|
||||
"default": false
|
||||
"objdiff.functionRelocDiffs": {
|
||||
"type": "string",
|
||||
"description": "How relocation targets will be diffed in the function view.",
|
||||
"default": "name_address",
|
||||
"enum": [
|
||||
"none",
|
||||
"name_address",
|
||||
"data_value",
|
||||
"all"
|
||||
],
|
||||
"enumItemLabels": [
|
||||
"None",
|
||||
"Name or address",
|
||||
"Data value",
|
||||
"Name or address, data value"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
"objdiff.spaceBetweenArgs": {
|
||||
"type": "boolean",
|
||||
@@ -122,6 +131,11 @@
|
||||
"type": "boolean",
|
||||
"description": "Combines data sections with equal names.",
|
||||
"default": false
|
||||
},
|
||||
"objdiff.combineTextSections": {
|
||||
"type": "boolean",
|
||||
"description": "Combines all text sections into one.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -324,5 +338,12 @@
|
||||
"contents": "Loading..."
|
||||
}
|
||||
]
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@biomejs/biome",
|
||||
"core-js",
|
||||
"esbuild"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+2388
File diff suppressed because it is too large
Load Diff
+18
-6
@@ -1,5 +1,6 @@
|
||||
import fs from 'node:fs';
|
||||
import type { ServerResponse } from 'node:http';
|
||||
import path from 'node:path';
|
||||
import { type RequestHandler, defineConfig } from '@rsbuild/core';
|
||||
import { pluginReact } from '@rsbuild/plugin-react';
|
||||
import { pluginTypeCheck } from '@rsbuild/plugin-type-check';
|
||||
@@ -92,15 +93,27 @@ export default defineConfig({
|
||||
},
|
||||
});
|
||||
|
||||
const PROJECT_ROOT = '../prime';
|
||||
|
||||
// Mock API middleware for development.
|
||||
const apiMiddleware: RequestHandler = (req, res, next) => {
|
||||
if (req.method === 'GET' && req.url === '/api/project') {
|
||||
return sendFile(res, '../prime/objdiff.json', 'application/json');
|
||||
if (!req.url || !req.headers.host || req.method !== 'GET') {
|
||||
return next();
|
||||
}
|
||||
if (req.method === 'GET' && req.url === '/api/diff') {
|
||||
return sendFile(res, '../prime/diff.binpb', 'application/octet-stream');
|
||||
const url = new URL(req.url, `http://${req.headers.host}`);
|
||||
if (!url) {
|
||||
return next();
|
||||
}
|
||||
next();
|
||||
if (url.pathname === '/api/get') {
|
||||
const file = url.searchParams.get('path');
|
||||
if (file) {
|
||||
const filepath = path.join(PROJECT_ROOT, file);
|
||||
if (filepath.startsWith(PROJECT_ROOT)) {
|
||||
return sendFile(res, filepath, 'application/octet-stream');
|
||||
}
|
||||
}
|
||||
}
|
||||
return next();
|
||||
};
|
||||
|
||||
// Send a file as a response.
|
||||
@@ -115,7 +128,6 @@ function sendFile(
|
||||
throw err;
|
||||
}
|
||||
let statusCode = 500;
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Node error
|
||||
if ((err as any).code === 'ENOENT') {
|
||||
statusCode = 404;
|
||||
}
|
||||
|
||||
@@ -1,230 +0,0 @@
|
||||
{
|
||||
"properties": [
|
||||
{
|
||||
"id": "relaxRelocDiffs",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Relax relocation diffs",
|
||||
"description": "Ignores differences in relocation targets. (Address, name, etc)"
|
||||
},
|
||||
{
|
||||
"id": "spaceBetweenArgs",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"name": "Space between args",
|
||||
"description": "Adds a space between arguments in the diff output."
|
||||
},
|
||||
{
|
||||
"id": "combineDataSections",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Combine data sections",
|
||||
"description": "Combines data sections with equal names."
|
||||
},
|
||||
{
|
||||
"id": "arm.archVersion",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "Architecture version",
|
||||
"description": "ARM architecture version to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "v4t",
|
||||
"name": "ARMv4T (GBA)"
|
||||
},
|
||||
{
|
||||
"value": "v5te",
|
||||
"name": "ARMv5TE (DS)"
|
||||
},
|
||||
{
|
||||
"value": "v6k",
|
||||
"name": "ARMv6K (3DS)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm.unifiedSyntax",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Unified syntax",
|
||||
"description": "Disassemble as unified assembly language (UAL)."
|
||||
},
|
||||
{
|
||||
"id": "arm.avRegisters",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Use A/V registers",
|
||||
"description": "Display R0-R3 as A1-A4 and R4-R11 as V1-V8."
|
||||
},
|
||||
{
|
||||
"id": "arm.r9Usage",
|
||||
"type": "choice",
|
||||
"default": "generalPurpose",
|
||||
"name": "Display R9 as",
|
||||
"items": [
|
||||
{
|
||||
"value": "generalPurpose",
|
||||
"name": "R9 or V6",
|
||||
"description": "Use R9 as a general-purpose register."
|
||||
},
|
||||
{
|
||||
"value": "sb",
|
||||
"name": "SB (static base)",
|
||||
"description": "Used for position-independent data (PID)."
|
||||
},
|
||||
{
|
||||
"value": "tr",
|
||||
"name": "TR (TLS register)",
|
||||
"description": "Used for thread-local storage."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm.slUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R10 as SL",
|
||||
"description": "Used for explicit stack limits."
|
||||
},
|
||||
{
|
||||
"id": "arm.fpUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R11 as FP",
|
||||
"description": "Used for frame pointers."
|
||||
},
|
||||
{
|
||||
"id": "arm.ipUsage",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "Display R12 as IP",
|
||||
"description": "Used for interworking and long branches."
|
||||
},
|
||||
{
|
||||
"id": "mips.abi",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "ABI",
|
||||
"description": "MIPS ABI to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "o32",
|
||||
"name": "O32"
|
||||
},
|
||||
{
|
||||
"value": "n32",
|
||||
"name": "N32"
|
||||
},
|
||||
{
|
||||
"value": "n64",
|
||||
"name": "N64"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mips.instrCategory",
|
||||
"type": "choice",
|
||||
"default": "auto",
|
||||
"name": "Instruction category",
|
||||
"description": "MIPS instruction category to use for disassembly.",
|
||||
"items": [
|
||||
{
|
||||
"value": "auto",
|
||||
"name": "Auto"
|
||||
},
|
||||
{
|
||||
"value": "cpu",
|
||||
"name": "CPU"
|
||||
},
|
||||
{
|
||||
"value": "rsp",
|
||||
"name": "RSP (N64)"
|
||||
},
|
||||
{
|
||||
"value": "r3000gte",
|
||||
"name": "R3000 GTE (PS1)"
|
||||
},
|
||||
{
|
||||
"value": "r4000allegrex",
|
||||
"name": "R4000 ALLEGREX (PSP)"
|
||||
},
|
||||
{
|
||||
"value": "r5900",
|
||||
"name": "R5900 EE (PS2)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "x86.formatter",
|
||||
"type": "choice",
|
||||
"default": "intel",
|
||||
"name": "Format",
|
||||
"description": "x86 disassembly syntax.",
|
||||
"items": [
|
||||
{
|
||||
"value": "intel",
|
||||
"name": "Intel"
|
||||
},
|
||||
{
|
||||
"value": "gas",
|
||||
"name": "AT&T"
|
||||
},
|
||||
{
|
||||
"value": "nasm",
|
||||
"name": "NASM"
|
||||
},
|
||||
{
|
||||
"value": "masm",
|
||||
"name": "MASM"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": "general",
|
||||
"name": "General",
|
||||
"properties": [
|
||||
"relaxRelocDiffs",
|
||||
"spaceBetweenArgs",
|
||||
"combineDataSections"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arm",
|
||||
"name": "ARM",
|
||||
"properties": [
|
||||
"arm.archVersion",
|
||||
"arm.unifiedSyntax",
|
||||
"arm.avRegisters",
|
||||
"arm.r9Usage",
|
||||
"arm.slUsage",
|
||||
"arm.fpUsage",
|
||||
"arm.ipUsage"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mips",
|
||||
"name": "MIPS",
|
||||
"properties": [
|
||||
"mips.abi",
|
||||
"mips.instrCategory"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "x86",
|
||||
"name": "x86",
|
||||
"properties": [
|
||||
"x86.formatter"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+1
-1
@@ -257,7 +257,7 @@ export type ConfigSchema = {
|
||||
groups: ConfigGroup[];
|
||||
};
|
||||
|
||||
import configSchema from './config-schema.json';
|
||||
import configSchema from 'objdiff-wasm/dist/config-schema.json';
|
||||
export const CONFIG_SCHEMA = configSchema as ConfigSchema;
|
||||
|
||||
export function getPropertyValue(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+16
-1
@@ -5,12 +5,27 @@ import type {
|
||||
Unit,
|
||||
} from './config';
|
||||
|
||||
export type WebviewProps = {
|
||||
extensionVersion: string;
|
||||
resourceRoot: string;
|
||||
};
|
||||
|
||||
export type BuildStatus = {
|
||||
success: boolean;
|
||||
cmdline: string;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
};
|
||||
|
||||
export type StateMessage = {
|
||||
type: 'state';
|
||||
buildRunning?: boolean;
|
||||
configProperties?: ConfigProperties;
|
||||
currentUnit?: Unit | null;
|
||||
data?: ArrayBuffer | null;
|
||||
leftStatus?: BuildStatus | null;
|
||||
rightStatus?: BuildStatus | null;
|
||||
leftObject?: ArrayBuffer | null;
|
||||
rightObject?: ArrayBuffer | null;
|
||||
projectConfig?: ProjectConfig | null;
|
||||
};
|
||||
|
||||
|
||||
+23
-7
@@ -1,6 +1,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import type { Unit } from '../shared/config';
|
||||
import type { InboundMessage, OutboundMessage } from '../shared/messages';
|
||||
import type {
|
||||
InboundMessage,
|
||||
OutboundMessage,
|
||||
WebviewProps,
|
||||
} from '../shared/messages';
|
||||
import { Workspace } from './workspace';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
@@ -42,7 +46,6 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
workspace = new Workspace(
|
||||
chan,
|
||||
vscode.workspace.workspaceFolders[0],
|
||||
storageUri,
|
||||
deferredCurrentUnit,
|
||||
);
|
||||
workspace.onDidChangeProjectConfig(
|
||||
@@ -61,7 +64,8 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
if (!unit) {
|
||||
sendMessage({
|
||||
type: 'state',
|
||||
data: null,
|
||||
leftObject: null,
|
||||
rightObject: null,
|
||||
currentUnit: null,
|
||||
});
|
||||
}
|
||||
@@ -73,7 +77,8 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
(data) => {
|
||||
sendMessage({
|
||||
type: 'state',
|
||||
data: data?.buffer || null,
|
||||
leftObject: data?.leftObject?.buffer || null,
|
||||
rightObject: data?.rightObject?.buffer || null,
|
||||
currentUnit: workspace?.currentUnit || null,
|
||||
});
|
||||
},
|
||||
@@ -244,8 +249,17 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
// For development, allow static assets (production will be inlined)
|
||||
html = html.replaceAll(/"\/static\/(.*?)"/g, (_, p) => {
|
||||
const assetUri = vscode.Uri.joinPath(webviewRoot, 'static', p);
|
||||
return `"${view.webview.asWebviewUri(assetUri)}"`;
|
||||
return JSON.stringify(view.webview.asWebviewUri(assetUri).toString());
|
||||
});
|
||||
// Inject extension information into the webview
|
||||
const props: WebviewProps = {
|
||||
extensionVersion: context.extension.packageJSON.version,
|
||||
resourceRoot: `${view.webview.asWebviewUri(webviewRoot).toString()}/`,
|
||||
};
|
||||
html = html.replace(
|
||||
'<head>',
|
||||
`<head><script>window.webviewProps=${JSON.stringify(props)}</script>`,
|
||||
);
|
||||
view.webview.options = {
|
||||
localResourceRoots: [context.extensionUri],
|
||||
enableScripts: true,
|
||||
@@ -257,7 +271,8 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
buildRunning: workspace?.buildRunning || false,
|
||||
configProperties: workspace?.configProperties || {},
|
||||
currentUnit: workspace?.currentUnit || null,
|
||||
data: workspace?.cachedData?.buffer || null,
|
||||
leftObject: workspace?.cachedData?.leftObject?.buffer || null,
|
||||
rightObject: workspace?.cachedData?.rightObject?.buffer || null,
|
||||
projectConfig: workspace?.projectConfig || null,
|
||||
} as InboundMessage);
|
||||
view.webview.onDidReceiveMessage(
|
||||
@@ -269,7 +284,8 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
buildRunning: workspace?.buildRunning || false,
|
||||
configProperties: workspace?.configProperties || {},
|
||||
currentUnit: workspace?.currentUnit || null,
|
||||
data: workspace?.cachedData?.buffer || null,
|
||||
leftObject: workspace?.cachedData?.leftObject?.buffer || null,
|
||||
rightObject: workspace?.cachedData?.rightObject?.buffer || null,
|
||||
projectConfig: workspace?.projectConfig || null,
|
||||
} as InboundMessage);
|
||||
chan.info('Webview ready');
|
||||
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export type Task = {
|
||||
type: string;
|
||||
command: string;
|
||||
args: string[];
|
||||
};
|
||||
|
||||
export interface TaskExecutor {
|
||||
run(task: Task): Promise<TaskResult>;
|
||||
}
|
||||
|
||||
export interface TaskResult {
|
||||
code: number;
|
||||
stdout?: string;
|
||||
stderr?: string;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
interface RunningTask {
|
||||
execution: vscode.TaskExecution;
|
||||
resolve: (result: TaskResult) => void;
|
||||
reject: (reason?: any) => void;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
export class VscodeTaskExecutor implements TaskExecutor, vscode.Disposable {
|
||||
private runningTasks: RunningTask[] = [];
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
private taskId = 0;
|
||||
|
||||
constructor(private workspaceFolder: vscode.WorkspaceFolder) {
|
||||
vscode.tasks.onDidEndTaskProcess(
|
||||
(e) => {
|
||||
if (e.execution.task.definition.type !== 'objdiff') {
|
||||
return;
|
||||
}
|
||||
const index = this.runningTasks.findIndex(
|
||||
(task) =>
|
||||
task.execution.task.definition.id ===
|
||||
e.execution.task.definition.id,
|
||||
);
|
||||
if (index !== -1) {
|
||||
const task = this.runningTasks[index];
|
||||
this.runningTasks.splice(index, 1);
|
||||
task.resolve({
|
||||
code: e.exitCode ?? -1,
|
||||
startTime: task.startTime,
|
||||
});
|
||||
}
|
||||
},
|
||||
null,
|
||||
this.disposables,
|
||||
);
|
||||
}
|
||||
|
||||
async run(task: Task): Promise<TaskResult> {
|
||||
const { type, command, args } = task;
|
||||
const startTime = performance.now();
|
||||
const vscodeTask = new vscode.Task(
|
||||
{
|
||||
type: 'objdiff',
|
||||
taskType: type,
|
||||
id: this.taskId++,
|
||||
},
|
||||
this.workspaceFolder,
|
||||
'objdiff',
|
||||
'objdiff',
|
||||
new vscode.ShellExecution(command, args),
|
||||
);
|
||||
vscodeTask.presentationOptions.reveal = vscode.TaskRevealKind.Silent;
|
||||
const execution = await vscode.tasks.executeTask(vscodeTask);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.runningTasks.push({
|
||||
execution,
|
||||
resolve,
|
||||
reject,
|
||||
startTime,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
for (const disposable of this.disposables) {
|
||||
disposable.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DirectTaskExecutor implements TaskExecutor {
|
||||
constructor(private workspaceFolder: vscode.WorkspaceFolder) {}
|
||||
|
||||
async run(task: Task): Promise<TaskResult> {
|
||||
const { command, args } = task;
|
||||
const startTime = performance.now();
|
||||
return new Promise((resolve, reject) => {
|
||||
const { execFile } = require('node:child_process');
|
||||
execFile(
|
||||
command,
|
||||
args,
|
||||
{
|
||||
cwd: this.workspaceFolder.uri.fsPath,
|
||||
encoding: 'utf8',
|
||||
},
|
||||
(error: any, stdout: string, stderr: string) => {
|
||||
if (error) {
|
||||
if (stdout || stderr) {
|
||||
resolve({
|
||||
code: error.code ?? -1,
|
||||
stdout,
|
||||
stderr,
|
||||
startTime,
|
||||
});
|
||||
} else {
|
||||
reject(error);
|
||||
}
|
||||
} else {
|
||||
resolve({
|
||||
code: 0,
|
||||
stdout,
|
||||
stderr,
|
||||
startTime,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
+101
-159
@@ -1,4 +1,5 @@
|
||||
import * as picomatch from 'picomatch';
|
||||
import { Shescape } from 'shescape';
|
||||
import * as vscode from 'vscode';
|
||||
import {
|
||||
CONFIG_FILENAME,
|
||||
@@ -6,13 +7,23 @@ import {
|
||||
type ConfigPropertyValue,
|
||||
type ProjectConfig,
|
||||
type Unit,
|
||||
getModifiedConfigProperties,
|
||||
resolveProjectConfig,
|
||||
} from '../shared/config';
|
||||
import {
|
||||
DirectTaskExecutor,
|
||||
type Task,
|
||||
type TaskResult,
|
||||
VscodeTaskExecutor,
|
||||
} from './util';
|
||||
|
||||
export type BuildData = {
|
||||
leftObject: Uint8Array | null;
|
||||
rightObject: Uint8Array | null;
|
||||
};
|
||||
|
||||
export class Workspace extends vscode.Disposable {
|
||||
public buildRunning = false;
|
||||
public cachedData: Uint8Array | null = null;
|
||||
public cachedData: BuildData | null = null;
|
||||
public configProperties: ConfigProperties = {};
|
||||
public currentUnit?: Unit;
|
||||
public projectConfig?: ProjectConfig;
|
||||
@@ -22,19 +33,22 @@ export class Workspace extends vscode.Disposable {
|
||||
public onDidChangeConfigProperties: vscode.Event<ConfigProperties>;
|
||||
public onDidChangeCurrentUnit: vscode.Event<Unit | undefined>;
|
||||
public onDidChangeBuildRunning: vscode.Event<boolean>;
|
||||
public onDidChangeData: vscode.Event<Uint8Array | null>;
|
||||
public onDidChangeData: vscode.Event<BuildData | null>;
|
||||
|
||||
private subscriptions: vscode.Disposable[] = [];
|
||||
private wwSubscriptions: vscode.Disposable[] = [];
|
||||
private pathMatcher?: picomatch.Matcher;
|
||||
|
||||
private buildTaskExecutor: VscodeTaskExecutor;
|
||||
private directTaskExecutor: DirectTaskExecutor;
|
||||
|
||||
private didChangeBuildRunningEmitter = new vscode.EventEmitter<boolean>();
|
||||
private didChangeConfigPropertiesEmitter =
|
||||
new vscode.EventEmitter<ConfigProperties>();
|
||||
private didChangeCurrentUnitEmitter = new vscode.EventEmitter<
|
||||
Unit | undefined
|
||||
>();
|
||||
private didChangeDataEmitter = new vscode.EventEmitter<Uint8Array | null>();
|
||||
private didChangeDataEmitter = new vscode.EventEmitter<BuildData | null>();
|
||||
private didChangeProjectConfigEmitter = new vscode.EventEmitter<
|
||||
ProjectConfig | undefined
|
||||
>();
|
||||
@@ -42,7 +56,6 @@ export class Workspace extends vscode.Disposable {
|
||||
constructor(
|
||||
public readonly chan: vscode.LogOutputChannel,
|
||||
public readonly workspaceFolder: vscode.WorkspaceFolder,
|
||||
private readonly storageUri: vscode.Uri,
|
||||
public deferredCurrentUnit?: string,
|
||||
) {
|
||||
super(() => {
|
||||
@@ -55,11 +68,9 @@ export class Workspace extends vscode.Disposable {
|
||||
this.onDidChangeData = this.didChangeDataEmitter.event;
|
||||
this.onDidChangeProjectConfig = this.didChangeProjectConfigEmitter.event;
|
||||
|
||||
vscode.tasks.onDidEndTaskProcess(
|
||||
this.onDidEndTaskProcess,
|
||||
this,
|
||||
this.subscriptions,
|
||||
);
|
||||
this.buildTaskExecutor = new VscodeTaskExecutor(workspaceFolder);
|
||||
this.directTaskExecutor = new DirectTaskExecutor(workspaceFolder);
|
||||
|
||||
vscode.workspace.onDidChangeConfiguration(
|
||||
this.onDidChangeConfiguration,
|
||||
this,
|
||||
@@ -90,7 +101,6 @@ export class Workspace extends vscode.Disposable {
|
||||
chan.info(`Initialized workspace: ${this.workspaceFolder.uri.toString()}`);
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: pass through message args
|
||||
showError(message: string, ...args: any[]) {
|
||||
this.chan.error(message, ...args);
|
||||
vscode.window
|
||||
@@ -104,7 +114,6 @@ export class Workspace extends vscode.Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: pass through message args
|
||||
showWarning(message: string, ...args: any[]) {
|
||||
this.chan.warn(message, ...args);
|
||||
vscode.window.showWarningMessage(`objdiff: ${message}`);
|
||||
@@ -262,7 +271,7 @@ export class Workspace extends vscode.Disposable {
|
||||
return true;
|
||||
}
|
||||
|
||||
tryBuild() {
|
||||
async tryBuild() {
|
||||
if (this.buildRunning) {
|
||||
return;
|
||||
}
|
||||
@@ -283,145 +292,101 @@ export class Workspace extends vscode.Disposable {
|
||||
const basePath =
|
||||
this.currentUnit.base_path &&
|
||||
vscode.Uri.joinPath(this.workspaceFolder.uri, this.currentUnit.base_path);
|
||||
this.chan.info('Diffing', targetPath?.toString(), basePath?.toString());
|
||||
if (!targetPath && !basePath) {
|
||||
this.showWarning('No target or base path');
|
||||
return;
|
||||
}
|
||||
const buildCmd = this.projectConfig.custom_make || 'make';
|
||||
const buildArgs = this.projectConfig.custom_args || [];
|
||||
const hash = cyrb53(this.workspaceFolder.uri.toString());
|
||||
const outputUri = vscode.Uri.joinPath(
|
||||
this.storageUri,
|
||||
`diff_${hash}.binpb`,
|
||||
);
|
||||
const args = [];
|
||||
if (this.currentUnit.target_path && this.projectConfig.build_target) {
|
||||
if (args.length) {
|
||||
args.push('&&');
|
||||
}
|
||||
args.push(buildCmd, ...buildArgs, this.currentUnit.target_path);
|
||||
}
|
||||
if (
|
||||
this.currentUnit.base_path &&
|
||||
(this.projectConfig.build_base ||
|
||||
this.projectConfig.build_base === undefined)
|
||||
) {
|
||||
if (args.length) {
|
||||
args.push('&&');
|
||||
}
|
||||
args.push(buildCmd, ...buildArgs, this.currentUnit.base_path);
|
||||
}
|
||||
if (args.length) {
|
||||
args.push('&&');
|
||||
}
|
||||
const binaryPath = this.configProperties.binaryPath as string | undefined;
|
||||
if (!binaryPath) {
|
||||
vscode.window
|
||||
.showWarningMessage('objdiff.binaryPath not set', {
|
||||
title: 'Open settings',
|
||||
})
|
||||
.then((item) => {
|
||||
if (item) {
|
||||
vscode.commands.executeCommand(
|
||||
'workbench.action.openSettings',
|
||||
'objdiff.binaryPath',
|
||||
);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
args.push(binaryPath, 'diff');
|
||||
if (targetPath) {
|
||||
args.push('-1', targetPath.fsPath);
|
||||
}
|
||||
if (basePath) {
|
||||
args.push('-2', basePath.fsPath);
|
||||
}
|
||||
args.push('--format', 'proto', '-o', outputUri.fsPath);
|
||||
const configProperties = getModifiedConfigProperties(this.configProperties);
|
||||
for (const key in configProperties) {
|
||||
args.push('-c', `${key}=${configProperties[key]}`);
|
||||
}
|
||||
const startTime = performance.now();
|
||||
const task = new vscode.Task(
|
||||
{
|
||||
type: 'objdiff',
|
||||
taskType: 'build',
|
||||
startTime,
|
||||
},
|
||||
this.workspaceFolder,
|
||||
'objdiff',
|
||||
'objdiff',
|
||||
new vscode.ShellExecution(args[0], args.slice(1)),
|
||||
);
|
||||
task.presentationOptions.reveal = vscode.TaskRevealKind.Silent;
|
||||
|
||||
this.buildRunning = true;
|
||||
this.didChangeBuildRunningEmitter.fire(true);
|
||||
vscode.tasks.executeTask(task).then(
|
||||
({ task, terminate: _ }) => {
|
||||
const curTime = performance.now();
|
||||
this.chan.info(
|
||||
'Diff task started in',
|
||||
curTime - task.definition.startTime,
|
||||
);
|
||||
},
|
||||
(reason) => {
|
||||
this.showError('Failed to start diff task', reason);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
private async onDidEndTaskProcess(e: vscode.TaskProcessEndEvent) {
|
||||
if (e.execution.task.definition.type !== 'objdiff') {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const endTime = performance.now();
|
||||
this.chan.info(
|
||||
'Task ended',
|
||||
e.exitCode,
|
||||
endTime - e.execution.task.definition.startTime,
|
||||
);
|
||||
const proc = e.execution.task.execution as vscode.ProcessExecution;
|
||||
const outputFile = proc.args[proc.args.indexOf('-o') + 1];
|
||||
const outputUri = vscode.Uri.file(outputFile);
|
||||
if (e.exitCode === 0) {
|
||||
const data = await vscode.workspace.fs.readFile(outputUri);
|
||||
this.chan.info(
|
||||
'Read output file',
|
||||
outputFile,
|
||||
'with size',
|
||||
data.byteLength,
|
||||
);
|
||||
this.cachedData = data;
|
||||
this.didChangeDataEmitter.fire(data);
|
||||
} else {
|
||||
this.showError(`Build failed with code ${e.exitCode}`);
|
||||
}
|
||||
let exists = false;
|
||||
try {
|
||||
const stat = await vscode.workspace.fs.stat(outputUri);
|
||||
exists = stat.type === vscode.FileType.File;
|
||||
} catch (reason) {
|
||||
if (
|
||||
reason instanceof vscode.FileSystemError &&
|
||||
reason.code !== 'FileNotFound'
|
||||
) {
|
||||
throw reason;
|
||||
const buildCmd = this.projectConfig.custom_make || 'make';
|
||||
const buildArgs = this.projectConfig.custom_args || [];
|
||||
|
||||
// Build target object
|
||||
if (this.currentUnit.target_path && this.projectConfig.build_target) {
|
||||
const result = await this.runTask({
|
||||
type: 'objdiff',
|
||||
command: buildCmd,
|
||||
args: [...buildArgs, this.currentUnit.target_path],
|
||||
});
|
||||
if (result.code !== 0) {
|
||||
this.showError(`Target build failed with code ${result.code}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (exists) {
|
||||
await vscode.workspace.fs.delete(outputUri);
|
||||
|
||||
// Build base object
|
||||
if (
|
||||
this.currentUnit.base_path &&
|
||||
(this.projectConfig.build_base ||
|
||||
this.projectConfig.build_base === undefined)
|
||||
) {
|
||||
const result = await this.runTask({
|
||||
type: 'objdiff',
|
||||
command: buildCmd,
|
||||
args: [...buildArgs, this.currentUnit.base_path],
|
||||
});
|
||||
if (result.code !== 0) {
|
||||
this.showError(`Base build failed with code ${result.code}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Read target object
|
||||
let targetData: Uint8Array | null = null;
|
||||
if (targetPath) {
|
||||
targetData = await vscode.workspace.fs.readFile(targetPath);
|
||||
}
|
||||
|
||||
// Read base object
|
||||
let baseData: Uint8Array | null = null;
|
||||
if (basePath) {
|
||||
baseData = await vscode.workspace.fs.readFile(basePath);
|
||||
}
|
||||
|
||||
this.cachedData = { leftObject: targetData, rightObject: baseData };
|
||||
this.didChangeDataEmitter.fire(this.cachedData);
|
||||
} catch (reason) {
|
||||
this.showError('Failed to process build result', reason);
|
||||
this.showError('Failed to execute build', reason);
|
||||
} finally {
|
||||
this.buildRunning = false;
|
||||
this.didChangeBuildRunningEmitter.fire(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async runTask(task: Task): Promise<TaskResult> {
|
||||
this.logTask(task);
|
||||
const result = await this.directTaskExecutor.run(task);
|
||||
this.logTaskResult(task, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private logTask(task: Task) {
|
||||
const command = new Shescape({ flagProtection: false, shell: true })
|
||||
.quoteAll([task.command, ...task.args])
|
||||
.join(' ');
|
||||
this.chan.info('Executing', command);
|
||||
}
|
||||
|
||||
private logTaskResult(task: Task, result: TaskResult) {
|
||||
if (result.code === 0) {
|
||||
const endTime = performance.now();
|
||||
const elapsed = (endTime - result.startTime).toFixed(0);
|
||||
this.chan.info(`Command ${task.command} succeeded in ${elapsed}ms`);
|
||||
return;
|
||||
}
|
||||
this.chan.error(
|
||||
[
|
||||
`Command ${task.command} failed with code ${result.code}`,
|
||||
result.stdout,
|
||||
result.stderr,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('\n'),
|
||||
);
|
||||
}
|
||||
|
||||
private async onDidChangeConfiguration(e: vscode.ConfigurationChangeEvent) {
|
||||
if (!e.affectsConfiguration('objdiff')) {
|
||||
return;
|
||||
@@ -455,6 +420,7 @@ export class Workspace extends vscode.Disposable {
|
||||
|
||||
private disposeImpl() {
|
||||
this.chan.info('Disposing workspace');
|
||||
this.buildTaskExecutor.dispose();
|
||||
this.projectConfigWatcher.dispose();
|
||||
for (const sub of this.wwSubscriptions) {
|
||||
sub.dispose();
|
||||
@@ -468,27 +434,3 @@ export class Workspace extends vscode.Disposable {
|
||||
this.subscriptions = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cyrb53 (c) 2018 bryc (github.com/bryc)
|
||||
* License: Public domain (or MIT if needed). Attribution appreciated.
|
||||
* A fast and simple 53-bit string hash function with decent collision resistance.
|
||||
* Largely inspired by MurmurHash2/3, but with a focus on speed/simplicity.
|
||||
*/
|
||||
function cyrb53(str: string, seed = 0) {
|
||||
let h1 = 0xdeadbeef ^ seed;
|
||||
let h2 = 0x41c6ce57 ^ seed;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const ch = str.charCodeAt(i);
|
||||
h1 = Math.imul(h1 ^ ch, 2654435761);
|
||||
h2 = Math.imul(h2 ^ ch, 1597334677);
|
||||
}
|
||||
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
|
||||
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
||||
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
|
||||
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
||||
return (
|
||||
(h2 >>> 0).toString(16).padStart(8, '0') +
|
||||
(h1 >>> 0).toString(16).padStart(8, '0')
|
||||
);
|
||||
}
|
||||
|
||||
+4
-4
@@ -3,12 +3,13 @@ import { CONFIG_SCHEMA } from './shared/config';
|
||||
|
||||
const packageJson = JSON.parse(readFileSync('./package.json', 'utf-8'));
|
||||
const extensionConfig = packageJson.contributes.configuration.find(
|
||||
// biome-ignore lint/suspicious/noExplicitAny: ignore
|
||||
(config: any) => config.title === 'Extension',
|
||||
);
|
||||
const categories = [extensionConfig];
|
||||
const categories: any[] = [];
|
||||
if (extensionConfig) {
|
||||
categories.push(extensionConfig);
|
||||
}
|
||||
for (const group of CONFIG_SCHEMA.groups) {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: ignore
|
||||
const category: any = {
|
||||
title: group.name,
|
||||
properties: {},
|
||||
@@ -18,7 +19,6 @@ for (const group of CONFIG_SCHEMA.groups) {
|
||||
if (!property) {
|
||||
continue;
|
||||
}
|
||||
// biome-ignore lint/suspicious/noExplicitAny: ignore
|
||||
const config: any = {
|
||||
type: property.type === 'boolean' ? 'boolean' : 'string',
|
||||
description: property.description,
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
--color-red: #f85149;
|
||||
--color-blue: #add8e6;
|
||||
--color-muted: var(--vscode-disabledForeground, rgba(204, 204, 204, 0.5));
|
||||
--color-bright: light-dark(black, white);
|
||||
|
||||
--panel-background: var(--vscode-panel-background, #181818);
|
||||
--panel-separator: var(--vscode-menu-separatorBackground, #454545);
|
||||
@@ -71,6 +72,12 @@
|
||||
--checkbox-border-color: var(--vscode-settings-checkboxBorder, #3c3c3c);
|
||||
|
||||
color-scheme: light dark;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
+24
-28
@@ -1,12 +1,7 @@
|
||||
import './App.css';
|
||||
|
||||
import type { diff, display } from 'objdiff-wasm';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { SectionKind } from '../shared/gen/diff_pb';
|
||||
import type {
|
||||
Symbol as DiffSymbol,
|
||||
ObjectDiff,
|
||||
SymbolDiff,
|
||||
} from '../shared/gen/diff_pb';
|
||||
import FunctionView from './FunctionView';
|
||||
import SettingsView from './SettingsView';
|
||||
import SymbolsView from './SymbolsView';
|
||||
@@ -15,39 +10,38 @@ import { useAppStore, useExtensionStore } from './state';
|
||||
import type { SymbolRefByName } from './state';
|
||||
|
||||
const findSymbol = (
|
||||
obj: ObjectDiff | undefined,
|
||||
obj: diff.ObjectDiff | undefined,
|
||||
symbolRef: SymbolRefByName | null,
|
||||
): SymbolDiff | null => {
|
||||
): display.SectionDisplaySymbol | null => {
|
||||
if (!obj || !symbolRef) {
|
||||
return null;
|
||||
}
|
||||
for (const section of obj.sections) {
|
||||
if (section.name === symbolRef.section_name) {
|
||||
if (section.kind === SectionKind.SECTION_TEXT) {
|
||||
for (const diff of section.symbols) {
|
||||
const symbol = diff.symbol as DiffSymbol;
|
||||
if (symbol.name === symbolRef.symbol_name) {
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const idx = obj.findSymbol(
|
||||
symbolRef.symbolName,
|
||||
symbolRef.sectionName ?? undefined,
|
||||
);
|
||||
if (idx !== undefined) {
|
||||
return {
|
||||
symbol: idx,
|
||||
isMappingSymbol: false,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const { buildRunning, diff, config, ready } = useExtensionStore(
|
||||
const { buildRunning, result, config, ready } = useExtensionStore(
|
||||
useShallow((state) => ({
|
||||
buildRunning: state.buildRunning,
|
||||
diff: state.diff,
|
||||
result: state.result,
|
||||
config: state.projectConfig,
|
||||
ready: state.ready,
|
||||
})),
|
||||
);
|
||||
const { selectedSymbolRef, currentView } = useAppStore(
|
||||
const { leftSymbolRef, rightSymbolRef, currentView } = useAppStore(
|
||||
useShallow((state) => ({
|
||||
selectedSymbolRef: state.selectedSymbol,
|
||||
leftSymbolRef: state.leftSymbol,
|
||||
rightSymbolRef: state.rightSymbol,
|
||||
currentView: state.currentView,
|
||||
})),
|
||||
);
|
||||
@@ -57,13 +51,15 @@ const App = () => {
|
||||
return <div className="loading-root" />;
|
||||
}
|
||||
|
||||
if (diff) {
|
||||
const leftSymbol = findSymbol(diff.left, selectedSymbolRef);
|
||||
const rightSymbol = findSymbol(diff.right, selectedSymbolRef);
|
||||
if (result) {
|
||||
const leftSymbol = findSymbol(result.left, leftSymbolRef);
|
||||
const rightSymbol = findSymbol(result.right, rightSymbolRef);
|
||||
if (leftSymbol || rightSymbol) {
|
||||
return <FunctionView left={leftSymbol} right={rightSymbol} />;
|
||||
return (
|
||||
<FunctionView diff={result} left={leftSymbol} right={rightSymbol} />
|
||||
);
|
||||
}
|
||||
return <SymbolsView diff={diff} />;
|
||||
return <SymbolsView diff={result} />;
|
||||
}
|
||||
|
||||
switch (currentView) {
|
||||
|
||||
@@ -27,23 +27,25 @@
|
||||
color: white;
|
||||
background-color: #aa8b00;
|
||||
}
|
||||
.line-number {
|
||||
color: var(--line-number-foreground);
|
||||
}
|
||||
.diff_any {
|
||||
|
||||
.diff-any {
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
.diff_change {
|
||||
|
||||
.segment-dim {
|
||||
color: var(--line-number-foreground);
|
||||
}
|
||||
.segment-bright {
|
||||
color: var(--color-bright);
|
||||
}
|
||||
.segment-replace {
|
||||
color: var(--color-blue);
|
||||
}
|
||||
.diff_add {
|
||||
color: var(--color-green);
|
||||
}
|
||||
.diff_remove {
|
||||
.segment-delete {
|
||||
color: var(--color-red);
|
||||
}
|
||||
.symbol {
|
||||
color: light-dark(black, white);
|
||||
.segment-insert {
|
||||
color: var(--color-green);
|
||||
}
|
||||
|
||||
.rotation0 {
|
||||
|
||||
+194
-108
@@ -3,25 +3,25 @@ import headerStyles from './Header.module.css';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { type diff, display } from 'objdiff-wasm';
|
||||
import { memo, useMemo } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList, areEqual } from 'react-window';
|
||||
import type { ListChildComponentProps } from 'react-window';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { DiffKind } from '../shared/gen/diff_pb';
|
||||
import type {
|
||||
Symbol as DiffSymbol,
|
||||
InstructionDiff,
|
||||
SymbolDiff,
|
||||
} from '../shared/gen/diff_pb';
|
||||
import { displayDiff } from './diff';
|
||||
import TooltipShared from './TooltipShared';
|
||||
import {
|
||||
type HighlightState,
|
||||
highlightColumn,
|
||||
highlightMatches,
|
||||
updateHighlight,
|
||||
} from './highlight';
|
||||
import { runBuild, useAppStore, useExtensionStore } from './state';
|
||||
import {
|
||||
buildDiffConfig,
|
||||
runBuild,
|
||||
useAppStore,
|
||||
useExtensionStore,
|
||||
} from './state';
|
||||
import { percentClass, useFontSize } from './util';
|
||||
|
||||
const ROTATION_CLASSES = [
|
||||
@@ -37,100 +37,118 @@ const ROTATION_CLASSES = [
|
||||
];
|
||||
|
||||
const AsmCell = ({
|
||||
insDiff,
|
||||
obj,
|
||||
config,
|
||||
symbol,
|
||||
row,
|
||||
column,
|
||||
highlight: highlightState,
|
||||
setHighlight,
|
||||
}: {
|
||||
insDiff: InstructionDiff | undefined;
|
||||
symbol: DiffSymbol | undefined;
|
||||
obj: diff.ObjectDiff | undefined;
|
||||
config: diff.DiffConfig;
|
||||
symbol: display.SectionDisplaySymbol | null;
|
||||
row: number;
|
||||
column: number;
|
||||
highlight: HighlightState;
|
||||
setHighlight: (highlight: HighlightState) => void;
|
||||
}) => {
|
||||
if (!insDiff || !symbol) {
|
||||
if (!obj || !symbol) {
|
||||
return <div className={styles.instructionCell} />;
|
||||
}
|
||||
|
||||
const highlight = highlightColumn(highlightState, column);
|
||||
const out: React.ReactNode[] = [];
|
||||
|
||||
const insRow = display.displayInstructionRow(obj, symbol, row, config);
|
||||
let index = 0;
|
||||
displayDiff(insDiff, symbol.address, (t) => {
|
||||
for (const segment of insRow.segments) {
|
||||
let className: string | undefined;
|
||||
if (t.diff_index != null) {
|
||||
className = ROTATION_CLASSES[t.diff_index % ROTATION_CLASSES.length];
|
||||
switch (segment.color.tag) {
|
||||
case 'normal':
|
||||
break;
|
||||
case 'dim':
|
||||
className = styles.segmentDim;
|
||||
break;
|
||||
case 'bright':
|
||||
className = styles.segmentBright;
|
||||
break;
|
||||
case 'replace':
|
||||
className = styles.segmentReplace;
|
||||
break;
|
||||
case 'delete':
|
||||
className = styles.segmentDelete;
|
||||
break;
|
||||
case 'insert':
|
||||
className = styles.segmentInsert;
|
||||
break;
|
||||
case 'rotating':
|
||||
className =
|
||||
ROTATION_CLASSES[segment.color.val % ROTATION_CLASSES.length];
|
||||
break;
|
||||
default:
|
||||
console.warn('Unknown color type', segment.color);
|
||||
break;
|
||||
}
|
||||
const t = segment.text;
|
||||
let text = '';
|
||||
let postText = ''; // unhighlightable text after the token
|
||||
let padTo = 0;
|
||||
let isToken = false;
|
||||
switch (t.type) {
|
||||
switch (t.tag) {
|
||||
case 'basic':
|
||||
text = t.text;
|
||||
break;
|
||||
case 'basic_color':
|
||||
text = t.text;
|
||||
className = ROTATION_CLASSES[t.index % ROTATION_CLASSES.length];
|
||||
text = t.val;
|
||||
break;
|
||||
case 'line':
|
||||
text = (t.line_number || 0).toString(10);
|
||||
className = styles.lineNumber;
|
||||
padTo = 5;
|
||||
text = t.val.toString(10);
|
||||
break;
|
||||
case 'address':
|
||||
text = (t.address || 0).toString(16);
|
||||
text = t.val.toString(16);
|
||||
postText = ':';
|
||||
padTo = 5;
|
||||
isToken = true;
|
||||
break;
|
||||
case 'opcode':
|
||||
text = t.mnemonic;
|
||||
padTo = 8;
|
||||
text = t.val.mnemonic;
|
||||
isToken = true;
|
||||
if (insDiff.diff_kind === DiffKind.DIFF_OP_MISMATCH) {
|
||||
className = styles.diff_change;
|
||||
}
|
||||
break;
|
||||
case 'argument': {
|
||||
const value = t.value.value;
|
||||
switch (value.oneofKind) {
|
||||
case 'signed':
|
||||
if (value.signed < 0) {
|
||||
text = `-0x${(-value.signed).toString(16)}`;
|
||||
} else {
|
||||
text = `0x${value.signed.toString(16)}`;
|
||||
}
|
||||
break;
|
||||
case 'unsigned':
|
||||
text = `0x${value.unsigned.toString(16)}`;
|
||||
break;
|
||||
case 'opaque':
|
||||
text = value.opaque;
|
||||
break;
|
||||
case 'signed':
|
||||
if (t.val < 0) {
|
||||
text = `-0x${(-t.val).toString(16)}`;
|
||||
} else {
|
||||
text = `0x${t.val.toString(16)}`;
|
||||
}
|
||||
isToken = true;
|
||||
break;
|
||||
}
|
||||
case 'branch_dest':
|
||||
text = (t.address || 0).toString(16);
|
||||
case 'unsigned':
|
||||
text = `0x${t.val.toString(16)}`;
|
||||
isToken = true;
|
||||
break;
|
||||
case 'symbol': {
|
||||
const symbol = t.target.symbol as DiffSymbol;
|
||||
text = symbol.demangled_name || symbol.name;
|
||||
if (t.diff_index == null) {
|
||||
className = styles.symbol;
|
||||
case 'opaque':
|
||||
text = t.val;
|
||||
isToken = true;
|
||||
break;
|
||||
case 'branch-dest':
|
||||
text = t.val.toString(16);
|
||||
isToken = true;
|
||||
break;
|
||||
case 'symbol':
|
||||
text = t.val.demangledName || t.val.name;
|
||||
isToken = true;
|
||||
break;
|
||||
case 'addend':
|
||||
if (t.val < 0) {
|
||||
text = `-0x${(-t.val).toString(16)}`;
|
||||
} else {
|
||||
text = `+0x${t.val.toString(16)}`;
|
||||
}
|
||||
isToken = true;
|
||||
break;
|
||||
}
|
||||
case 'spacing':
|
||||
text = ' '.repeat(t.count);
|
||||
text = ' '.repeat(t.val);
|
||||
break;
|
||||
case 'eol':
|
||||
continue;
|
||||
default:
|
||||
console.warn('Unknown text type', t);
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
out.push(
|
||||
<span
|
||||
@@ -151,39 +169,55 @@ const AsmCell = ({
|
||||
);
|
||||
index++;
|
||||
if (postText) {
|
||||
out.push(<span key={index}>{postText}</span>);
|
||||
out.push(
|
||||
<span key={index} className={className}>
|
||||
{postText}
|
||||
</span>,
|
||||
);
|
||||
index++;
|
||||
}
|
||||
if (padTo > text.length + postText.length) {
|
||||
const spacing = ' '.repeat(padTo - text.length - postText.length);
|
||||
if (segment.padTo > text.length + postText.length) {
|
||||
const spacing = ' '.repeat(segment.padTo - text.length - postText.length);
|
||||
out.push(<span key={index}>{spacing}</span>);
|
||||
index++;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const classes = [styles.instructionCell];
|
||||
if (insDiff.diff_kind) {
|
||||
classes.push(styles.diff_any);
|
||||
if (insRow.diffKind !== 'none') {
|
||||
classes.push(styles.diffAny);
|
||||
}
|
||||
switch (insDiff.diff_kind) {
|
||||
case DiffKind.DIFF_DELETE:
|
||||
classes.push(styles.diff_remove);
|
||||
break;
|
||||
case DiffKind.DIFF_INSERT:
|
||||
classes.push(styles.diff_add);
|
||||
break;
|
||||
case DiffKind.DIFF_REPLACE:
|
||||
classes.push(styles.diff_change);
|
||||
break;
|
||||
if (!out.length) {
|
||||
return <div className={clsx(classes)} />;
|
||||
}
|
||||
|
||||
return <div className={clsx(classes)}>{out}</div>;
|
||||
const tooltipContent: InstructionTooltipContent = { column, row };
|
||||
return (
|
||||
<div
|
||||
className={clsx(classes)}
|
||||
data-tooltip-id="instruction-tooltip"
|
||||
data-tooltip-content={JSON.stringify(tooltipContent)}
|
||||
>
|
||||
{out}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type InstructionTooltipContent = {
|
||||
column: number;
|
||||
row: number;
|
||||
};
|
||||
|
||||
type ItemData = {
|
||||
itemCount: number;
|
||||
left: SymbolDiff | null;
|
||||
right: SymbolDiff | null;
|
||||
symbolName: string;
|
||||
result: diff.DiffResult;
|
||||
config: diff.DiffConfig;
|
||||
matchPercent?: number;
|
||||
left: display.SectionDisplaySymbol | null;
|
||||
leftSymbol: display.SymbolDisplay | null;
|
||||
right: display.SectionDisplaySymbol | null;
|
||||
rightSymbol: display.SymbolDisplay | null;
|
||||
highlight: HighlightState;
|
||||
setHighlight: (highlight: HighlightState) => void;
|
||||
};
|
||||
@@ -192,10 +226,8 @@ const AsmRow = memo(
|
||||
({
|
||||
index,
|
||||
style,
|
||||
data: { left, right, highlight, setHighlight },
|
||||
data: { result, config, left, right, highlight, setHighlight },
|
||||
}: ListChildComponentProps<ItemData>) => {
|
||||
const leftIns = left?.instructions[index];
|
||||
const rightIns = right?.instructions[index];
|
||||
return (
|
||||
<div
|
||||
className={styles.instructionRow}
|
||||
@@ -212,15 +244,19 @@ const AsmRow = memo(
|
||||
}}
|
||||
>
|
||||
<AsmCell
|
||||
insDiff={leftIns}
|
||||
symbol={left?.symbol}
|
||||
obj={result.left}
|
||||
config={config}
|
||||
symbol={left}
|
||||
row={index}
|
||||
column={0}
|
||||
highlight={highlight}
|
||||
setHighlight={setHighlight}
|
||||
/>
|
||||
<AsmCell
|
||||
insDiff={rightIns}
|
||||
symbol={right?.symbol}
|
||||
obj={result.right}
|
||||
config={config}
|
||||
symbol={right}
|
||||
row={index}
|
||||
column={1}
|
||||
highlight={highlight}
|
||||
setHighlight={setHighlight}
|
||||
@@ -233,23 +269,41 @@ const AsmRow = memo(
|
||||
|
||||
const createItemData = memoizeOne(
|
||||
(
|
||||
left: SymbolDiff | null,
|
||||
right: SymbolDiff | null,
|
||||
result: diff.DiffResult,
|
||||
left: display.SectionDisplaySymbol | null,
|
||||
right: display.SectionDisplaySymbol | null,
|
||||
highlight: HighlightState,
|
||||
setHighlight: (highlight: HighlightState) => void,
|
||||
): ItemData => {
|
||||
const leftSymbol = left ? display.displaySymbol(result.left!, left) : null;
|
||||
const rightSymbol = right
|
||||
? display.displaySymbol(result.right!, right)
|
||||
: null;
|
||||
const itemCount = Math.max(
|
||||
left?.instructions.length || 0,
|
||||
right?.instructions.length || 0,
|
||||
leftSymbol?.rowCount || 0,
|
||||
rightSymbol?.rowCount || 0,
|
||||
);
|
||||
return { itemCount, left, right, highlight, setHighlight };
|
||||
const symbolName = leftSymbol?.name || rightSymbol?.name || '';
|
||||
const config = buildDiffConfig(null);
|
||||
return {
|
||||
itemCount,
|
||||
symbolName,
|
||||
result,
|
||||
config,
|
||||
left,
|
||||
leftSymbol,
|
||||
right,
|
||||
rightSymbol,
|
||||
highlight,
|
||||
setHighlight,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
const SymbolLabel = ({
|
||||
symbol,
|
||||
}: {
|
||||
symbol: SymbolDiff | null;
|
||||
symbol: display.SymbolDisplay | null;
|
||||
}) => {
|
||||
if (!symbol) {
|
||||
return (
|
||||
@@ -258,21 +312,26 @@ const SymbolLabel = ({
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const demangledName = symbol.symbol?.demangled_name || symbol.symbol?.name;
|
||||
const displayName = symbol.demangledName || symbol.name;
|
||||
return (
|
||||
<span
|
||||
className={clsx(headerStyles.label, headerStyles.emphasized)}
|
||||
title={demangledName}
|
||||
title={displayName}
|
||||
>
|
||||
{demangledName}
|
||||
{displayName}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const FunctionView = ({
|
||||
diff,
|
||||
left,
|
||||
right,
|
||||
}: { left: SymbolDiff | null; right: SymbolDiff | null }) => {
|
||||
}: {
|
||||
diff: diff.DiffResult;
|
||||
left: display.SectionDisplaySymbol | null;
|
||||
right: display.SectionDisplaySymbol | null;
|
||||
}) => {
|
||||
const { buildRunning, currentUnit, lastBuilt } = useExtensionStore(
|
||||
useShallow((state) => ({
|
||||
buildRunning: state.buildRunning,
|
||||
@@ -291,29 +350,27 @@ const FunctionView = ({
|
||||
})),
|
||||
);
|
||||
|
||||
const symbolName = left?.symbol?.name || right?.symbol?.name || '';
|
||||
const itemData = createItemData(diff, left, right, highlight, setHighlight);
|
||||
const initialScrollOffset = useMemo(
|
||||
() =>
|
||||
useAppStore.getState().getUnitState(currentUnitName).symbolScrollOffsets[
|
||||
symbolName
|
||||
itemData.symbolName
|
||||
] || 0,
|
||||
[currentUnitName, symbolName],
|
||||
[currentUnitName, itemData.symbolName],
|
||||
);
|
||||
|
||||
const itemSize = useFontSize() * 1.33;
|
||||
const itemData = createItemData(left, right, highlight, setHighlight);
|
||||
const matchPercent = right?.match_percent;
|
||||
return (
|
||||
<>
|
||||
<div className={headerStyles.header}>
|
||||
<div className={headerStyles.column}>
|
||||
<div className={headerStyles.row}>
|
||||
<button title="Back" onClick={() => setSelectedSymbol(null)}>
|
||||
<button title="Back" onClick={() => setSelectedSymbol(null, null)}>
|
||||
<span className="codicon codicon-chevron-left" />
|
||||
</button>
|
||||
</div>
|
||||
<div className={headerStyles.row}>
|
||||
<SymbolLabel symbol={left} />
|
||||
<SymbolLabel symbol={itemData.leftSymbol} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={headerStyles.column}>
|
||||
@@ -332,20 +389,20 @@ const FunctionView = ({
|
||||
)}
|
||||
</div>
|
||||
<div className={headerStyles.row}>
|
||||
{matchPercent !== undefined && (
|
||||
{itemData.matchPercent !== undefined && (
|
||||
<>
|
||||
<span
|
||||
className={clsx(
|
||||
headerStyles.label,
|
||||
percentClass(matchPercent),
|
||||
percentClass(itemData.matchPercent),
|
||||
)}
|
||||
>
|
||||
{Math.floor(matchPercent).toFixed(0)}%
|
||||
{Math.floor(itemData.matchPercent).toFixed(0)}%
|
||||
</span>
|
||||
{' | '}
|
||||
</>
|
||||
)}
|
||||
<SymbolLabel symbol={right} />
|
||||
<SymbolLabel symbol={itemData.rightSymbol} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -362,7 +419,7 @@ const FunctionView = ({
|
||||
onScroll={(e) => {
|
||||
setSymbolScrollOffset(
|
||||
currentUnitName,
|
||||
symbolName,
|
||||
itemData.symbolName,
|
||||
e.scrollOffset,
|
||||
);
|
||||
}}
|
||||
@@ -373,6 +430,35 @@ const FunctionView = ({
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
<TooltipShared
|
||||
id="instruction-tooltip"
|
||||
callback={(content) => {
|
||||
const data: InstructionTooltipContent = JSON.parse(content);
|
||||
let obj: diff.ObjectDiff | undefined;
|
||||
let symbol: display.SectionDisplaySymbol | undefined;
|
||||
switch (data.column) {
|
||||
case 0:
|
||||
obj = diff.left;
|
||||
symbol = itemData.left ?? undefined;
|
||||
break;
|
||||
case 1:
|
||||
obj = diff.right;
|
||||
symbol = itemData.right ?? undefined;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (!obj || !symbol) {
|
||||
return null;
|
||||
}
|
||||
return display.instructionHover(
|
||||
obj,
|
||||
symbol,
|
||||
data.row,
|
||||
itemData.config,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
|
||||
.emphasized {
|
||||
color: light-dark(black, white);
|
||||
color: var(--color-bright);
|
||||
}
|
||||
|
||||
.missing {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user