You've already forked objdiff-web
mirror of
https://github.com/encounter/objdiff-web.git
synced 2026-03-30 11:32:18 -07:00
Auto-build, full symbols view and diff view & more
This commit is contained in:
-2007
File diff suppressed because it is too large
Load Diff
+36
-8
@@ -39,14 +39,30 @@
|
||||
"engines": {
|
||||
"vscode": "^1.96.0"
|
||||
},
|
||||
"browserslist": ["chrome 128"],
|
||||
"categories": ["Other"],
|
||||
"activationEvents": ["*"],
|
||||
"browserslist": [
|
||||
"chrome 128"
|
||||
],
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "objdiff.build",
|
||||
"title": "objdiff: build"
|
||||
},
|
||||
{
|
||||
"command": "objdiff.copySymbolName",
|
||||
"title": "Copy name",
|
||||
"enablement": "webviewId == 'objdiff' && contextType == 'symbol'"
|
||||
},
|
||||
{
|
||||
"command": "objdiff.copySymbolDemangledName",
|
||||
"title": "Copy demangled name",
|
||||
"enablement": "webviewId == 'objdiff' && contextType == 'symbol' && symbolDemangledName"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
@@ -59,6 +75,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"menus": {
|
||||
"webview/context": [
|
||||
{
|
||||
"command": "objdiff.copySymbolName",
|
||||
"when": "webviewId == 'objdiff' && contextType == 'symbol'"
|
||||
},
|
||||
{
|
||||
"command": "objdiff.copySymbolDemangledName",
|
||||
"when": "webviewId == 'objdiff' && contextType == 'symbol'"
|
||||
}
|
||||
]
|
||||
},
|
||||
"taskDefinitions": [
|
||||
{
|
||||
"type": "objdiff"
|
||||
}
|
||||
],
|
||||
"viewsContainers": {
|
||||
"panel": [
|
||||
{
|
||||
@@ -82,11 +115,6 @@
|
||||
"view": "objdiff",
|
||||
"contents": "Loading..."
|
||||
}
|
||||
],
|
||||
"taskDefinitions": [
|
||||
{
|
||||
"type": "objdiff"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ export default defineConfig({
|
||||
html: {
|
||||
inject: 'body',
|
||||
scriptLoading: 'blocking',
|
||||
title: 'objdiff',
|
||||
},
|
||||
plugins: [
|
||||
pluginReact({
|
||||
|
||||
@@ -296,9 +296,22 @@ export interface InstructionBranchTo {
|
||||
branch_index: number;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message objdiff.diff.FunctionDiff
|
||||
* @generated from protobuf message objdiff.diff.SymbolRef
|
||||
*/
|
||||
export interface FunctionDiff {
|
||||
export interface SymbolRef {
|
||||
/**
|
||||
* @generated from protobuf field: optional uint32 section_index = 1;
|
||||
*/
|
||||
section_index?: number;
|
||||
/**
|
||||
* @generated from protobuf field: uint32 symbol_index = 2;
|
||||
*/
|
||||
symbol_index: number;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message objdiff.diff.SymbolDiff
|
||||
*/
|
||||
export interface SymbolDiff {
|
||||
/**
|
||||
* @generated from protobuf field: objdiff.diff.Symbol symbol = 1;
|
||||
*/
|
||||
@@ -311,6 +324,12 @@ export interface FunctionDiff {
|
||||
* @generated from protobuf field: optional float match_percent = 3;
|
||||
*/
|
||||
match_percent?: number;
|
||||
/**
|
||||
* The symbol ref in the _other_ object that this symbol was diffed against
|
||||
*
|
||||
* @generated from protobuf field: optional objdiff.diff.SymbolRef target = 5;
|
||||
*/
|
||||
target?: SymbolRef;
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf message objdiff.diff.DataDiff
|
||||
@@ -352,9 +371,9 @@ export interface SectionDiff {
|
||||
*/
|
||||
address: bigint;
|
||||
/**
|
||||
* @generated from protobuf field: repeated objdiff.diff.FunctionDiff functions = 5;
|
||||
* @generated from protobuf field: repeated objdiff.diff.SymbolDiff symbols = 5;
|
||||
*/
|
||||
functions: FunctionDiff[];
|
||||
symbols: SymbolDiff[];
|
||||
/**
|
||||
* @generated from protobuf field: repeated objdiff.diff.DataDiff data = 6;
|
||||
*/
|
||||
@@ -405,17 +424,17 @@ export enum SymbolFlag {
|
||||
*/
|
||||
SYMBOL_LOCAL = 2,
|
||||
/**
|
||||
* @generated from protobuf enum value: SYMBOL_WEAK = 3;
|
||||
* @generated from protobuf enum value: SYMBOL_WEAK = 4;
|
||||
*/
|
||||
SYMBOL_WEAK = 3,
|
||||
SYMBOL_WEAK = 4,
|
||||
/**
|
||||
* @generated from protobuf enum value: SYMBOL_COMMON = 4;
|
||||
* @generated from protobuf enum value: SYMBOL_COMMON = 8;
|
||||
*/
|
||||
SYMBOL_COMMON = 4,
|
||||
SYMBOL_COMMON = 8,
|
||||
/**
|
||||
* @generated from protobuf enum value: SYMBOL_HIDDEN = 5;
|
||||
* @generated from protobuf enum value: SYMBOL_HIDDEN = 16;
|
||||
*/
|
||||
SYMBOL_HIDDEN = 5
|
||||
SYMBOL_HIDDEN = 16
|
||||
}
|
||||
/**
|
||||
* @generated from protobuf enum objdiff.diff.DiffKind
|
||||
@@ -1196,22 +1215,77 @@ class InstructionBranchTo$Type extends MessageType<InstructionBranchTo> {
|
||||
*/
|
||||
export const InstructionBranchTo = new InstructionBranchTo$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class FunctionDiff$Type extends MessageType<FunctionDiff> {
|
||||
class SymbolRef$Type extends MessageType<SymbolRef> {
|
||||
constructor() {
|
||||
super("objdiff.diff.FunctionDiff", [
|
||||
{ no: 1, name: "symbol", kind: "message", T: () => Symbol },
|
||||
{ no: 2, name: "instructions", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => InstructionDiff },
|
||||
{ no: 3, name: "match_percent", kind: "scalar", localName: "match_percent", opt: true, T: 2 /*ScalarType.FLOAT*/ }
|
||||
super("objdiff.diff.SymbolRef", [
|
||||
{ no: 1, name: "section_index", kind: "scalar", localName: "section_index", opt: true, T: 13 /*ScalarType.UINT32*/ },
|
||||
{ no: 2, name: "symbol_index", kind: "scalar", localName: "symbol_index", T: 13 /*ScalarType.UINT32*/ }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<FunctionDiff>): FunctionDiff {
|
||||
create(value?: PartialMessage<SymbolRef>): SymbolRef {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.symbol_index = 0;
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<SymbolRef>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SymbolRef): SymbolRef {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
switch (fieldNo) {
|
||||
case /* optional uint32 section_index */ 1:
|
||||
message.section_index = reader.uint32();
|
||||
break;
|
||||
case /* uint32 symbol_index */ 2:
|
||||
message.symbol_index = reader.uint32();
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
|
||||
let d = reader.skip(wireType);
|
||||
if (u !== false)
|
||||
(u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: SymbolRef, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* optional uint32 section_index = 1; */
|
||||
if (message.section_index !== undefined)
|
||||
writer.tag(1, WireType.Varint).uint32(message.section_index);
|
||||
/* uint32 symbol_index = 2; */
|
||||
if (message.symbol_index !== 0)
|
||||
writer.tag(2, WireType.Varint).uint32(message.symbol_index);
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message objdiff.diff.SymbolRef
|
||||
*/
|
||||
export const SymbolRef = new SymbolRef$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class SymbolDiff$Type extends MessageType<SymbolDiff> {
|
||||
constructor() {
|
||||
super("objdiff.diff.SymbolDiff", [
|
||||
{ no: 1, name: "symbol", kind: "message", T: () => Symbol },
|
||||
{ no: 2, name: "instructions", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => InstructionDiff },
|
||||
{ no: 3, name: "match_percent", kind: "scalar", localName: "match_percent", opt: true, T: 2 /*ScalarType.FLOAT*/ },
|
||||
{ no: 5, name: "target", kind: "message", T: () => SymbolRef }
|
||||
]);
|
||||
}
|
||||
create(value?: PartialMessage<SymbolDiff>): SymbolDiff {
|
||||
const message = globalThis.Object.create((this.messagePrototype!));
|
||||
message.instructions = [];
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<FunctionDiff>(this, message, value);
|
||||
reflectionMergePartial<SymbolDiff>(this, message, value);
|
||||
return message;
|
||||
}
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: FunctionDiff): FunctionDiff {
|
||||
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: SymbolDiff): SymbolDiff {
|
||||
let message = target ?? this.create(), end = reader.pos + length;
|
||||
while (reader.pos < end) {
|
||||
let [fieldNo, wireType] = reader.tag();
|
||||
@@ -1225,6 +1299,9 @@ class FunctionDiff$Type extends MessageType<FunctionDiff> {
|
||||
case /* optional float match_percent */ 3:
|
||||
message.match_percent = reader.float();
|
||||
break;
|
||||
case /* optional objdiff.diff.SymbolRef target */ 5:
|
||||
message.target = SymbolRef.internalBinaryRead(reader, reader.uint32(), options, message.target);
|
||||
break;
|
||||
default:
|
||||
let u = options.readUnknownField;
|
||||
if (u === "throw")
|
||||
@@ -1236,7 +1313,7 @@ class FunctionDiff$Type extends MessageType<FunctionDiff> {
|
||||
}
|
||||
return message;
|
||||
}
|
||||
internalBinaryWrite(message: FunctionDiff, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
internalBinaryWrite(message: SymbolDiff, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
||||
/* objdiff.diff.Symbol symbol = 1; */
|
||||
if (message.symbol)
|
||||
Symbol.internalBinaryWrite(message.symbol, writer.tag(1, WireType.LengthDelimited).fork(), options).join();
|
||||
@@ -1246,6 +1323,9 @@ class FunctionDiff$Type extends MessageType<FunctionDiff> {
|
||||
/* optional float match_percent = 3; */
|
||||
if (message.match_percent !== undefined)
|
||||
writer.tag(3, WireType.Bit32).float(message.match_percent);
|
||||
/* optional objdiff.diff.SymbolRef target = 5; */
|
||||
if (message.target)
|
||||
SymbolRef.internalBinaryWrite(message.target, writer.tag(5, WireType.LengthDelimited).fork(), options).join();
|
||||
let u = options.writeUnknownFields;
|
||||
if (u !== false)
|
||||
(u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
|
||||
@@ -1253,9 +1333,9 @@ class FunctionDiff$Type extends MessageType<FunctionDiff> {
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @generated MessageType for protobuf message objdiff.diff.FunctionDiff
|
||||
* @generated MessageType for protobuf message objdiff.diff.SymbolDiff
|
||||
*/
|
||||
export const FunctionDiff = new FunctionDiff$Type();
|
||||
export const SymbolDiff = new SymbolDiff$Type();
|
||||
// @generated message type with reflection information, may provide speed optimized methods
|
||||
class DataDiff$Type extends MessageType<DataDiff> {
|
||||
constructor() {
|
||||
@@ -1327,7 +1407,7 @@ class SectionDiff$Type extends MessageType<SectionDiff> {
|
||||
{ no: 2, name: "kind", kind: "enum", T: () => ["objdiff.diff.SectionKind", SectionKind] },
|
||||
{ no: 3, name: "size", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 4, name: "address", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
|
||||
{ no: 5, name: "functions", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => FunctionDiff },
|
||||
{ no: 5, name: "symbols", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => SymbolDiff },
|
||||
{ no: 6, name: "data", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => DataDiff },
|
||||
{ no: 7, name: "match_percent", kind: "scalar", localName: "match_percent", opt: true, T: 2 /*ScalarType.FLOAT*/ }
|
||||
]);
|
||||
@@ -1338,7 +1418,7 @@ class SectionDiff$Type extends MessageType<SectionDiff> {
|
||||
message.kind = 0;
|
||||
message.size = 0n;
|
||||
message.address = 0n;
|
||||
message.functions = [];
|
||||
message.symbols = [];
|
||||
message.data = [];
|
||||
if (value !== undefined)
|
||||
reflectionMergePartial<SectionDiff>(this, message, value);
|
||||
@@ -1361,8 +1441,8 @@ class SectionDiff$Type extends MessageType<SectionDiff> {
|
||||
case /* uint64 address */ 4:
|
||||
message.address = reader.uint64().toBigInt();
|
||||
break;
|
||||
case /* repeated objdiff.diff.FunctionDiff functions */ 5:
|
||||
message.functions.push(FunctionDiff.internalBinaryRead(reader, reader.uint32(), options));
|
||||
case /* repeated objdiff.diff.SymbolDiff symbols */ 5:
|
||||
message.symbols.push(SymbolDiff.internalBinaryRead(reader, reader.uint32(), options));
|
||||
break;
|
||||
case /* repeated objdiff.diff.DataDiff data */ 6:
|
||||
message.data.push(DataDiff.internalBinaryRead(reader, reader.uint32(), options));
|
||||
@@ -1394,9 +1474,9 @@ class SectionDiff$Type extends MessageType<SectionDiff> {
|
||||
/* uint64 address = 4; */
|
||||
if (message.address !== 0n)
|
||||
writer.tag(4, WireType.Varint).uint64(message.address);
|
||||
/* repeated objdiff.diff.FunctionDiff functions = 5; */
|
||||
for (let i = 0; i < message.functions.length; i++)
|
||||
FunctionDiff.internalBinaryWrite(message.functions[i], writer.tag(5, WireType.LengthDelimited).fork(), options).join();
|
||||
/* repeated objdiff.diff.SymbolDiff symbols = 5; */
|
||||
for (let i = 0; i < message.symbols.length; i++)
|
||||
SymbolDiff.internalBinaryWrite(message.symbols[i], writer.tag(5, WireType.LengthDelimited).fork(), options).join();
|
||||
/* repeated objdiff.diff.DataDiff data = 6; */
|
||||
for (let i = 0; i < message.data.length; i++)
|
||||
DataDiff.internalBinaryWrite(message.data[i], writer.tag(6, WireType.LengthDelimited).fork(), options).join();
|
||||
@@ -0,0 +1,36 @@
|
||||
export type DiffMessage = {
|
||||
type: 'diff';
|
||||
data: ArrayBuffer;
|
||||
};
|
||||
|
||||
export type TaskMessage = {
|
||||
type: 'task';
|
||||
taskType: string;
|
||||
running: boolean;
|
||||
};
|
||||
|
||||
export type StateMessage = {
|
||||
type: 'state';
|
||||
configLoaded: boolean;
|
||||
currentFile: string | null;
|
||||
};
|
||||
|
||||
// extension -> webview
|
||||
export type InboundMessage = DiffMessage | TaskMessage | StateMessage;
|
||||
|
||||
export type ReadyMessage = {
|
||||
type: 'ready';
|
||||
};
|
||||
|
||||
export type LineRangesMessage = {
|
||||
type: 'lineRanges';
|
||||
data: Array<{ start: number; end: number }>;
|
||||
};
|
||||
|
||||
export type RunTaskMessage = {
|
||||
type: 'runTask';
|
||||
taskType: string;
|
||||
};
|
||||
|
||||
// webview -> extension
|
||||
export type OutboundMessage = ReadyMessage | LineRangesMessage | RunTaskMessage;
|
||||
+191
-43
@@ -1,5 +1,6 @@
|
||||
import * as picomatch from 'picomatch';
|
||||
import * as vscode from 'vscode';
|
||||
import type { InboundMessage, OutboundMessage } from '../shared/messages';
|
||||
import { DEFAULT_WATCH_PATTERNS, type ObjdiffConfiguration } from './config';
|
||||
|
||||
const CONFIG_FILENAME = 'objdiff.json';
|
||||
@@ -7,13 +8,21 @@ const CONFIG_FILENAME = 'objdiff.json';
|
||||
export class ObjdiffWorkspace extends vscode.Disposable {
|
||||
public config?: ObjdiffConfiguration;
|
||||
public configWatcher: vscode.FileSystemWatcher;
|
||||
public currentFile?: string;
|
||||
public workspaceWatcher?: vscode.FileSystemWatcher;
|
||||
public onDidChangeConfig: vscode.Event<ObjdiffConfiguration | undefined>;
|
||||
public onDidChangeCurrentFile: vscode.Event<string | undefined>;
|
||||
|
||||
private subscriptions: vscode.Disposable[] = [];
|
||||
private wwSubscriptions: vscode.Disposable[] = [];
|
||||
private currentDiff?: vscode.Uri;
|
||||
// private currentTask?: () => void;
|
||||
private pathMatcher?: picomatch.Matcher;
|
||||
private didChangeConfigEmitter = new vscode.EventEmitter<
|
||||
ObjdiffConfiguration | undefined
|
||||
>();
|
||||
private didChangeCurrentFileEmitter = new vscode.EventEmitter<
|
||||
string | undefined
|
||||
>();
|
||||
|
||||
constructor(
|
||||
public readonly chan: vscode.LogOutputChannel,
|
||||
@@ -23,6 +32,9 @@ export class ObjdiffWorkspace extends vscode.Disposable {
|
||||
super(() => {
|
||||
this.disposeImpl();
|
||||
});
|
||||
this.onDidChangeConfig = this.didChangeConfigEmitter.event;
|
||||
this.onDidChangeCurrentFile = this.didChangeCurrentFileEmitter.event;
|
||||
|
||||
this.configWatcher = vscode.workspace.createFileSystemWatcher(
|
||||
new vscode.RelativePattern(workspaceFolder, CONFIG_FILENAME),
|
||||
);
|
||||
@@ -139,6 +151,7 @@ export class ObjdiffWorkspace extends vscode.Disposable {
|
||||
this.wwSubscriptions,
|
||||
);
|
||||
}
|
||||
this.didChangeConfigEmitter.fire(this.config);
|
||||
this.tryDiff();
|
||||
}
|
||||
|
||||
@@ -156,25 +169,22 @@ export class ObjdiffWorkspace extends vscode.Disposable {
|
||||
this.tryDiff();
|
||||
}
|
||||
|
||||
tryDiff() {
|
||||
private tryUpdateActiveFile() {
|
||||
if (!this.config) {
|
||||
return;
|
||||
}
|
||||
if (!this.currentDiff) {
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
this.chan.warn('No active editor');
|
||||
return;
|
||||
}
|
||||
if (activeEditor.document.uri.scheme !== 'file') {
|
||||
this.chan.warn('Active editor not a file');
|
||||
return;
|
||||
}
|
||||
this.currentDiff = activeEditor.document.uri;
|
||||
const activeEditor = vscode.window.activeTextEditor;
|
||||
if (!activeEditor) {
|
||||
this.chan.warn('No active editor');
|
||||
return;
|
||||
}
|
||||
const fsPath = this.currentDiff.fsPath;
|
||||
if (activeEditor.document.uri.scheme !== 'file') {
|
||||
this.chan.warn('Active editor not a file');
|
||||
return;
|
||||
}
|
||||
const fsPath = activeEditor.document.uri.fsPath;
|
||||
if (!fsPath.startsWith(this.workspaceFolder.uri.fsPath)) {
|
||||
this.chan.warn('Active editor not in workspace');
|
||||
this.chan.warn('Active editor not in workspace', fsPath);
|
||||
return;
|
||||
}
|
||||
const relPath = fsPath.slice(this.workspaceFolder.uri.fsPath.length + 1);
|
||||
@@ -182,7 +192,27 @@ export class ObjdiffWorkspace extends vscode.Disposable {
|
||||
(obj) => obj.metadata?.source_path === relPath,
|
||||
);
|
||||
if (!obj) {
|
||||
this.chan.warn('No object found for', relPath);
|
||||
this.chan.warn('No object found for', this.currentFile);
|
||||
return;
|
||||
}
|
||||
this.currentFile = relPath;
|
||||
this.didChangeCurrentFileEmitter.fire(this.currentFile);
|
||||
}
|
||||
|
||||
tryDiff() {
|
||||
if (!this.config) {
|
||||
return;
|
||||
}
|
||||
this.tryUpdateActiveFile();
|
||||
if (!this.currentFile) {
|
||||
this.chan.warn('No active file');
|
||||
return;
|
||||
}
|
||||
const obj = (this.config.units || this.config.objects)?.find(
|
||||
(obj) => obj.metadata?.source_path === this.currentFile,
|
||||
);
|
||||
if (!obj) {
|
||||
this.chan.warn('No object found for', this.currentFile);
|
||||
return;
|
||||
}
|
||||
const targetPath =
|
||||
@@ -252,6 +282,7 @@ export class ObjdiffWorkspace extends vscode.Disposable {
|
||||
const task = new vscode.Task(
|
||||
{
|
||||
type: 'objdiff',
|
||||
taskType: 'build',
|
||||
startTime,
|
||||
},
|
||||
this.workspaceFolder,
|
||||
@@ -312,6 +343,11 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
// const storageDir = storageUri.fsPath;
|
||||
// chan.info('Storage directory: ' + storageDir);
|
||||
|
||||
const webviews: {
|
||||
webview: vscode.Webview;
|
||||
subscriptions: vscode.Disposable[];
|
||||
}[] = [];
|
||||
|
||||
let workspace: ObjdiffWorkspace | undefined;
|
||||
if (vscode.workspace.workspaceFolders?.[0]) {
|
||||
workspace = new ObjdiffWorkspace(
|
||||
@@ -319,18 +355,63 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.workspace.workspaceFolders[0],
|
||||
storageUri,
|
||||
);
|
||||
workspace.onDidChangeConfig(
|
||||
(config) => {
|
||||
for (const view of webviews) {
|
||||
view.webview.postMessage({
|
||||
type: 'state',
|
||||
configLoaded: !!config,
|
||||
currentFile: workspace?.currentFile || null,
|
||||
} as InboundMessage);
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions,
|
||||
);
|
||||
workspace.onDidChangeCurrentFile(
|
||||
(currentFile) => {
|
||||
for (const view of webviews) {
|
||||
view.webview.postMessage({
|
||||
type: 'state',
|
||||
configLoaded: !!workspace?.config,
|
||||
currentFile,
|
||||
} as InboundMessage);
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions,
|
||||
);
|
||||
context.subscriptions.push(workspace);
|
||||
}
|
||||
chan.info('Workspace folders', vscode.workspace.workspaceFolders);
|
||||
vscode.workspace.onDidChangeWorkspaceFolders(
|
||||
(e) => {
|
||||
chan.info('Workspace folders changed', e);
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions,
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('objdiff.build', () => {
|
||||
if (workspace) {
|
||||
workspace.tryDiff();
|
||||
}
|
||||
workspace?.tryDiff();
|
||||
}),
|
||||
);
|
||||
|
||||
const webviews: vscode.Webview[] = [];
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('objdiff.copySymbolName', (opts) => {
|
||||
chan.info('Copy command', opts);
|
||||
vscode.env.clipboard.writeText(opts.symbolName);
|
||||
}),
|
||||
);
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand(
|
||||
'objdiff.copySymbolDemangledName',
|
||||
(opts) => {
|
||||
chan.info('Copy demangled command', opts);
|
||||
vscode.env.clipboard.writeText(opts.symbolDemangledName);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const backgroundColors = [
|
||||
'rgba(255, 0, 255, 0.3)',
|
||||
@@ -350,8 +431,42 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
});
|
||||
});
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.tasks.onDidEndTaskProcess(async (e) => {
|
||||
vscode.tasks.onDidStartTask(
|
||||
(e) => {
|
||||
if (e.execution.task.definition.type !== 'objdiff') {
|
||||
return;
|
||||
}
|
||||
for (const view of webviews) {
|
||||
view.webview.postMessage({
|
||||
type: 'task',
|
||||
taskType: e.execution.task.definition.taskType,
|
||||
running: true,
|
||||
} as InboundMessage);
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions,
|
||||
);
|
||||
vscode.tasks.onDidEndTask(
|
||||
(e) => {
|
||||
if (e.execution.task.definition.type !== 'objdiff') {
|
||||
return;
|
||||
}
|
||||
for (const view of webviews) {
|
||||
view.webview.postMessage({
|
||||
type: 'task',
|
||||
taskType: e.execution.task.definition.taskType,
|
||||
running: false,
|
||||
} as InboundMessage);
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions,
|
||||
);
|
||||
|
||||
let cachedData: Uint8Array | null = null;
|
||||
vscode.tasks.onDidEndTaskProcess(
|
||||
async (e) => {
|
||||
if (e.execution.task.definition.type !== 'objdiff') {
|
||||
return;
|
||||
}
|
||||
@@ -373,18 +488,15 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
'with size',
|
||||
data.byteLength,
|
||||
);
|
||||
// const diff = diff_pb.DiffResult.fromBinary(data);
|
||||
// treeDataProvider.update(diff);
|
||||
// vscode.window.showInformationMessage('Diff complete');
|
||||
// console.log('webviews', webviews);
|
||||
for (const webview of webviews) {
|
||||
webview.postMessage({
|
||||
for (const view of webviews) {
|
||||
view.webview.postMessage({
|
||||
type: 'diff',
|
||||
data: data.buffer,
|
||||
});
|
||||
} as InboundMessage);
|
||||
}
|
||||
cachedData = data;
|
||||
} catch (reason) {
|
||||
chan.error('Failed to read output file', reason);
|
||||
workspace?.showError('Failed to read output file', reason);
|
||||
}
|
||||
} else {
|
||||
workspace?.showError(`Build failed with code ${e.exitCode}`);
|
||||
@@ -392,10 +504,12 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.workspace.fs.delete(outputUri).then(
|
||||
() => {},
|
||||
(reason) => {
|
||||
chan.error('Failed to delete output file', reason);
|
||||
workspace?.showError('Failed to delete output file', reason);
|
||||
},
|
||||
);
|
||||
}),
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions,
|
||||
);
|
||||
|
||||
const textDecoder = new TextDecoder();
|
||||
@@ -423,10 +537,24 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
enableScripts: true,
|
||||
};
|
||||
view.webview.html = html;
|
||||
context.subscriptions.push(
|
||||
view.webview.onDidReceiveMessage((message) => {
|
||||
const subscriptions: vscode.Disposable[] = [];
|
||||
view.webview.onDidReceiveMessage(
|
||||
(untypedMessage) => {
|
||||
const message = untypedMessage as OutboundMessage;
|
||||
if (message.type === 'ready') {
|
||||
chan.info('Webview ready');
|
||||
view.webview.postMessage({
|
||||
type: 'state',
|
||||
configLoaded: !!workspace?.config,
|
||||
currentFile: workspace?.currentFile || null,
|
||||
} as InboundMessage);
|
||||
if (cachedData) {
|
||||
chan.info('Sending cached diff to webview');
|
||||
view.webview.postMessage({
|
||||
type: 'diff',
|
||||
data: cachedData.buffer,
|
||||
} as InboundMessage);
|
||||
}
|
||||
} else if (message.type === 'lineRanges') {
|
||||
for (const editor of vscode.window.visibleTextEditors) {
|
||||
if (editor.document.uri.scheme !== 'file') {
|
||||
@@ -444,18 +572,38 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
idx = (idx + 1) % decorationTypes.length;
|
||||
}
|
||||
}
|
||||
} else if (message.type === 'runTask') {
|
||||
if (message.taskType === 'build') {
|
||||
workspace?.tryDiff();
|
||||
} else {
|
||||
chan.warn('Unknown task type', message.taskType);
|
||||
}
|
||||
} else {
|
||||
chan.warn('Unknown message', message);
|
||||
}
|
||||
}),
|
||||
},
|
||||
undefined,
|
||||
subscriptions,
|
||||
);
|
||||
context.subscriptions.push(
|
||||
view.onDidDispose(() => {
|
||||
const idx = webviews.indexOf(view.webview);
|
||||
if (idx >= 0) {
|
||||
webviews.splice(idx, 1);
|
||||
view.onDidDispose(
|
||||
() => {
|
||||
for (const sub of subscriptions) {
|
||||
sub.dispose();
|
||||
}
|
||||
}),
|
||||
for (let i = 0; i < webviews.length; i++) {
|
||||
if (webviews[i].webview === view.webview) {
|
||||
webviews.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
undefined,
|
||||
context.subscriptions,
|
||||
);
|
||||
webviews.push(view.webview);
|
||||
webviews.push({
|
||||
webview: view.webview,
|
||||
subscriptions,
|
||||
});
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
+1
-1
@@ -19,5 +19,5 @@
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"include": ["src", "webview", "gen"]
|
||||
"include": ["src", "webview", "shared"]
|
||||
}
|
||||
|
||||
+57
-20
@@ -1,28 +1,32 @@
|
||||
:root {
|
||||
--font-size: var(--vscode-editor-font-size, 13px);
|
||||
--list-row-height: calc(var(--font-size) * 1.33);
|
||||
color-scheme: light dark;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
color: var(--vscode-editor-foreground, #fff);
|
||||
font-family: var(
|
||||
--vscode-font-family,
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
"Helvetica Neue",
|
||||
Arial,
|
||||
sans-serif
|
||||
);
|
||||
color: var(--vscode-foreground, #fff);
|
||||
font-family: var(--vscode-font-family, system-ui);
|
||||
font-weight: var(--vscode-font-weight, normal);
|
||||
font-size: var(--vscode-font-size, 13px);
|
||||
background-color: var(--vscode-editor-background, #1e1e1e);
|
||||
|
||||
&.vscode-light,
|
||||
&.vscode-high-contrast-light {
|
||||
color-scheme: light;
|
||||
}
|
||||
&.vscode-dark,
|
||||
&.vscode-high-contrast-dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
#root {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
@@ -34,15 +38,48 @@ body {
|
||||
text-align: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
h1 {
|
||||
font-size: 3.6rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 400;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.content h1 {
|
||||
font-size: 3.6rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
button {
|
||||
background-color: var(--vscode-button-secondaryBackground);
|
||||
color: var(--vscode-button-secondaryForeground);
|
||||
border: 1px solid var(--vscode-button-border);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
|
||||
.content p {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 400;
|
||||
opacity: 0.5;
|
||||
font-family: var(--vscode-font-family, system-ui);
|
||||
font-weight: var(--vscode-font-weight, normal);
|
||||
font-size: var(--vscode-font-size, 13px);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--vscode-button-secondaryHoverBackground);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
opacity: 1;
|
||||
outline-color: var(--vscode-focusBorder);
|
||||
outline-offset: -1px;
|
||||
outline-style: solid;
|
||||
outline-width: 1px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
background-color: var(--vscode-toolbar-activeBackground);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: var(--vscode-disabledForeground);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-12
@@ -1,30 +1,30 @@
|
||||
import './App.css';
|
||||
|
||||
import { SectionKind } from '../gen/diff_pb';
|
||||
import { SectionKind } from '../shared/gen/diff_pb';
|
||||
import type {
|
||||
Symbol as DiffSymbol,
|
||||
FunctionDiff,
|
||||
ObjectDiff,
|
||||
} from '../gen/diff_pb';
|
||||
SymbolDiff,
|
||||
} from '../shared/gen/diff_pb';
|
||||
import FunctionView from './FunctionView';
|
||||
import SymbolsView from './SymbolsView';
|
||||
import { useAppStore, useDiffStore } from './state';
|
||||
import { useAppStore, useExtensionStore, vscode } from './state';
|
||||
import type { SymbolRefByName } from './state';
|
||||
|
||||
const findSymbol = (
|
||||
obj: ObjectDiff | undefined,
|
||||
symbolRef: SymbolRefByName | null,
|
||||
): FunctionDiff | null => {
|
||||
): SymbolDiff | 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 fn of section.functions) {
|
||||
const symbol = fn.symbol as DiffSymbol;
|
||||
for (const diff of section.symbols) {
|
||||
const symbol = diff.symbol as DiffSymbol;
|
||||
if (symbol.name === symbolRef.symbol_name) {
|
||||
return fn;
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,23 +34,39 @@ const findSymbol = (
|
||||
};
|
||||
|
||||
const App = () => {
|
||||
const { diff } = useDiffStore();
|
||||
const { diff } = useExtensionStore();
|
||||
const selectedSymbolRef = useAppStore((state) => state.selectedSymbol);
|
||||
const buildRunning = useExtensionStore((state) => state.buildRunning);
|
||||
const configLoaded = useExtensionStore((state) => state.configLoaded);
|
||||
|
||||
if (diff) {
|
||||
const object = diff.left || diff.right || { sections: [] };
|
||||
const leftSymbol = findSymbol(diff.left, selectedSymbolRef);
|
||||
const rightSymbol = findSymbol(diff.right, selectedSymbolRef);
|
||||
if (leftSymbol || rightSymbol) {
|
||||
return <FunctionView left={leftSymbol} right={rightSymbol} />;
|
||||
}
|
||||
return <SymbolsView obj={object} />;
|
||||
return <SymbolsView diff={diff} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="content">
|
||||
<h1>objdiff</h1>
|
||||
<p>Coming soon to a VS Code near you.</p>
|
||||
{configLoaded ? (
|
||||
<p>
|
||||
Open a source file and{' '}
|
||||
<button
|
||||
onClick={() =>
|
||||
vscode.postMessage({ type: 'runTask', taskType: 'build' })
|
||||
}
|
||||
disabled={buildRunning}
|
||||
>
|
||||
Build
|
||||
</button>
|
||||
.
|
||||
</p>
|
||||
) : (
|
||||
<p>No configuration loaded.</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
.instruction-list {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.instruction-row {
|
||||
@@ -16,87 +17,91 @@
|
||||
text-wrap: nowrap;
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.highlightable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.diff_change {
|
||||
color: #6d6dff;
|
||||
}
|
||||
.rotation0 {
|
||||
color: magenta;
|
||||
}
|
||||
.rotation1 {
|
||||
color: cyan;
|
||||
}
|
||||
.rotation2 {
|
||||
color: rgb(0, 212, 0);
|
||||
}
|
||||
.rotation3 {
|
||||
color: red;
|
||||
}
|
||||
.rotation4 {
|
||||
color: rgb(103, 106, 255);
|
||||
}
|
||||
.rotation5 {
|
||||
color: lightpink;
|
||||
}
|
||||
.rotation6 {
|
||||
color: lightcyan;
|
||||
}
|
||||
.rotation7 {
|
||||
color: lightgreen;
|
||||
}
|
||||
.rotation8 {
|
||||
color: grey;
|
||||
}
|
||||
.symbol {
|
||||
color: white;
|
||||
}
|
||||
.line-number {
|
||||
color: var(--vscode-editorLineNumber-foreground);
|
||||
}
|
||||
.diff_any {
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
.diff_change {
|
||||
color: #6d6dff;
|
||||
}
|
||||
.diff_add {
|
||||
color: #45bd00;
|
||||
}
|
||||
.diff_remove {
|
||||
color: #c82829;
|
||||
}
|
||||
.symbol {
|
||||
color: light-dark(black, white);
|
||||
}
|
||||
|
||||
:global(body.vscode-light) {
|
||||
.rotation0 {
|
||||
.rotation0 {
|
||||
color: light-dark(
|
||||
/* hsv(0° 60% 80%) */
|
||||
color: rgb(205, 82, 82);
|
||||
}
|
||||
.rotation1 {
|
||||
rgb(205, 82, 82),
|
||||
magenta
|
||||
);
|
||||
}
|
||||
.rotation1 {
|
||||
color: light-dark(
|
||||
/* hsv(40° 60% 80%) */
|
||||
color: rgb(205, 164, 82);
|
||||
}
|
||||
.rotation2 {
|
||||
rgb(205, 164, 82),
|
||||
cyan
|
||||
);
|
||||
}
|
||||
.rotation2 {
|
||||
color: light-dark(
|
||||
/* hsv(80° 60% 80%) */
|
||||
color: rgb(164, 205, 82);
|
||||
}
|
||||
.rotation3 {
|
||||
rgb(164, 205, 82),
|
||||
rgb(0, 212, 0)
|
||||
);
|
||||
}
|
||||
.rotation3 {
|
||||
color: light-dark(
|
||||
/* hsv(120° 60% 80%) */
|
||||
color: rgb(82, 205, 82);
|
||||
}
|
||||
.rotation4 {
|
||||
rgb(82, 205, 82),
|
||||
red
|
||||
);
|
||||
}
|
||||
.rotation4 {
|
||||
color: light-dark(
|
||||
/* hsv(160° 60% 80%) */
|
||||
color: rgb(82, 205, 164);
|
||||
}
|
||||
.rotation5 {
|
||||
rgb(82, 205, 164),
|
||||
rgb(103, 106, 255)
|
||||
);
|
||||
}
|
||||
.rotation5 {
|
||||
color: light-dark(
|
||||
/* hsv(200° 60% 80%) */
|
||||
color: rgb(82, 164, 205);
|
||||
}
|
||||
.rotation6 {
|
||||
rgb(82, 164, 205),
|
||||
lightpink
|
||||
);
|
||||
}
|
||||
.rotation6 {
|
||||
color: light-dark(
|
||||
/* hsv(240° 60% 80%) */
|
||||
color: rgb(82, 82, 205);
|
||||
}
|
||||
.rotation7 {
|
||||
rgb(82, 82, 205),
|
||||
lightcyan
|
||||
);
|
||||
}
|
||||
.rotation7 {
|
||||
color: light-dark(
|
||||
/* hsv(280° 60% 80%) */
|
||||
color: rgb(164, 82, 205);
|
||||
}
|
||||
.rotation8 {
|
||||
rgb(164, 82, 205),
|
||||
lightgreen
|
||||
);
|
||||
}
|
||||
.rotation8 {
|
||||
color: light-dark(
|
||||
/* hsv(320° 60% 80%) */
|
||||
color: rgb(205, 82, 164);
|
||||
}
|
||||
.symbol {
|
||||
color: black;
|
||||
}
|
||||
rgb(205, 82, 164),
|
||||
grey
|
||||
);
|
||||
}
|
||||
|
||||
+67
-24
@@ -1,18 +1,20 @@
|
||||
import styles from './FunctionView.module.css';
|
||||
import headerStyles from './Header.module.css';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import memoize from 'memoize-one';
|
||||
import { memo } from 'react';
|
||||
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 { DiffKind } from '../gen/diff_pb';
|
||||
import { DiffKind } from '../shared/gen/diff_pb';
|
||||
import type {
|
||||
Symbol as DiffSymbol,
|
||||
FunctionDiff,
|
||||
InstructionDiff,
|
||||
} from '../gen/diff_pb';
|
||||
SymbolDiff,
|
||||
} from '../shared/gen/diff_pb';
|
||||
import { displayDiff } from './diff';
|
||||
import { useAppStore } from './state';
|
||||
import { useFontSize } from './util';
|
||||
|
||||
const AsmCell = ({
|
||||
@@ -132,13 +134,30 @@ const AsmCell = ({
|
||||
index++;
|
||||
}
|
||||
});
|
||||
return <div className={styles.instructionCell}>{out}</div>;
|
||||
|
||||
const classes = [styles.instructionCell];
|
||||
if (insDiff.diff_kind) {
|
||||
classes.push(styles.diff_any);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
return <div className={clsx(classes)}>{out}</div>;
|
||||
};
|
||||
|
||||
type ItemData = {
|
||||
itemCount: number;
|
||||
left: FunctionDiff | null;
|
||||
right: FunctionDiff | null;
|
||||
left: SymbolDiff | null;
|
||||
right: SymbolDiff | null;
|
||||
};
|
||||
|
||||
const AsmRow = memo(
|
||||
@@ -160,7 +179,7 @@ const AsmRow = memo(
|
||||
);
|
||||
|
||||
const createItemData = memoize(
|
||||
(left: FunctionDiff | null, right: FunctionDiff | null) => {
|
||||
(left: SymbolDiff | null, right: SymbolDiff | null) => {
|
||||
const itemCount = Math.max(
|
||||
left?.instructions.length || 0,
|
||||
right?.instructions.length || 0,
|
||||
@@ -172,25 +191,49 @@ const createItemData = memoize(
|
||||
const FunctionView = ({
|
||||
left,
|
||||
right,
|
||||
}: { left: FunctionDiff | null; right: FunctionDiff | null }) => {
|
||||
}: { left: SymbolDiff | null; right: SymbolDiff | null }) => {
|
||||
const setSelectedSymbol = useAppStore((state) => state.setSelectedSymbol);
|
||||
const setSymbolScrollOffset = useAppStore(
|
||||
(state) => state.setSymbolScrollOffset,
|
||||
);
|
||||
|
||||
const symbolName = left?.symbol?.name || right?.symbol?.name || '';
|
||||
const initialScrollOffset = useMemo(
|
||||
() => useAppStore.getState().symbolScrollOffsets[symbolName] || 0,
|
||||
[symbolName],
|
||||
);
|
||||
|
||||
const itemSize = useFontSize() * 1.33;
|
||||
const itemData = createItemData(left, right);
|
||||
const demangledName =
|
||||
left?.symbol?.demangled_name || right?.symbol?.demangled_name;
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
className={styles.instructionList}
|
||||
height={height}
|
||||
itemCount={itemData.itemCount}
|
||||
itemSize={itemSize}
|
||||
width={width}
|
||||
itemData={itemData}
|
||||
overscanCount={20}
|
||||
>
|
||||
{AsmRow}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
<>
|
||||
<div className={headerStyles.header}>
|
||||
<button onClick={() => setSelectedSymbol(null)}>Back</button>
|
||||
<span title={demangledName}>{demangledName}</span>
|
||||
</div>
|
||||
<div className={styles.instructionList}>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
height={height}
|
||||
itemCount={itemData.itemCount}
|
||||
itemSize={itemSize}
|
||||
width={width}
|
||||
itemData={itemData}
|
||||
overscanCount={20}
|
||||
onScroll={(e) => {
|
||||
setSymbolScrollOffset(symbolName, e.scrollOffset);
|
||||
}}
|
||||
initialScrollOffset={initialScrollOffset}
|
||||
>
|
||||
{AsmRow}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
padding: 0.5em;
|
||||
background-color: var(--vscode-panel-background);
|
||||
border-bottom: 1px solid var(--vscode-menu-separatorBackground);
|
||||
|
||||
> span {
|
||||
font-family: var(--vscode-editor-font-family, monospace);
|
||||
font-weight: var(--vscode-editor-font-weight, normal);
|
||||
font-size: var(--font-size);
|
||||
text-wrap: nowrap;
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,14 @@
|
||||
.symbols {
|
||||
flex: 1 1 0;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
overflow: auto;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.symbol-list {
|
||||
flex: 1 1 0;
|
||||
overflow-x: scroll;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -6,7 +16,13 @@
|
||||
li {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
/* margin: 0 0.5rem; */
|
||||
height: var(--list-row-height);
|
||||
|
||||
font-family: var(--vscode-editor-font-family, monospace);
|
||||
font-weight: var(--vscode-editor-font-weight, normal);
|
||||
font-size: var(--font-size);
|
||||
text-wrap: nowrap;
|
||||
white-space: pre;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--vscode-list-hoverBackground);
|
||||
@@ -15,9 +31,31 @@
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-left: 0;
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
.symbol {
|
||||
padding-left: 0.5rem;
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.flag-global {
|
||||
color: lightgreen;
|
||||
}
|
||||
|
||||
.flag-weak {
|
||||
/* todo */
|
||||
}
|
||||
|
||||
.symbol-name {
|
||||
color: light-dark(black, white);
|
||||
}
|
||||
|
||||
.percent-100 {
|
||||
color: lightgreen;
|
||||
}
|
||||
.percent-50 {
|
||||
color: lightblue;
|
||||
}
|
||||
.percent-0 {
|
||||
color: lightcoral;
|
||||
}
|
||||
|
||||
+130
-25
@@ -1,42 +1,147 @@
|
||||
import headerStyles from './Header.module.css';
|
||||
import styles from './SymbolsView.module.css';
|
||||
|
||||
import { SectionKind } from '../gen/diff_pb';
|
||||
import type { Symbol as DiffSymbol, ObjectDiff } from '../gen/diff_pb';
|
||||
import { useAppStore } from './state';
|
||||
import {
|
||||
type DiffResult,
|
||||
type Symbol as DiffSymbol,
|
||||
type ObjectDiff,
|
||||
SymbolFlag,
|
||||
} from '../shared/gen/diff_pb';
|
||||
import { useAppStore, useExtensionStore, vscode } from './state';
|
||||
|
||||
const SymbolsView = ({ obj }: { obj: ObjectDiff }) => {
|
||||
const SymbolsList = ({ obj }: { obj: ObjectDiff }) => {
|
||||
const setSelectedSymbol = useAppStore((state) => state.setSelectedSymbol);
|
||||
|
||||
const items = [];
|
||||
let sectionIndex = 0;
|
||||
for (const section of obj.sections) {
|
||||
const sectionKey = `section-${sectionIndex++}`;
|
||||
let percentElem = null;
|
||||
if (section.match_percent != null) {
|
||||
let className = styles.percent0;
|
||||
if (section.match_percent === 100) {
|
||||
className = styles.percent100;
|
||||
} else if (section.match_percent >= 50) {
|
||||
className = styles.percent50;
|
||||
}
|
||||
percentElem = (
|
||||
<>
|
||||
{' ('}
|
||||
<span className={className}>
|
||||
{Math.floor(section.match_percent).toFixed(0)}%
|
||||
</span>
|
||||
{')'}
|
||||
</>
|
||||
);
|
||||
}
|
||||
items.push(
|
||||
<li key={section.name} className={styles.section}>
|
||||
{section.name}
|
||||
<li key={sectionKey} className={styles.section}>
|
||||
{section.name} ({section.size.toString(16)}){percentElem}
|
||||
</li>,
|
||||
);
|
||||
if (section.kind === SectionKind.SECTION_TEXT) {
|
||||
for (const fn of section.functions) {
|
||||
const symbol = fn.symbol as DiffSymbol;
|
||||
items.push(
|
||||
<li
|
||||
key={symbol.name}
|
||||
className={styles.symbol}
|
||||
onClick={() => {
|
||||
setSelectedSymbol({
|
||||
symbol_name: symbol.name,
|
||||
section_name: section.name,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{symbol.name}
|
||||
</li>,
|
||||
for (const diff of section.symbols) {
|
||||
const symbol = diff.symbol as DiffSymbol;
|
||||
const flags = [];
|
||||
if (symbol.flags & SymbolFlag.SYMBOL_GLOBAL) {
|
||||
flags.push(
|
||||
<span key="g" className={styles.flagGlobal}>
|
||||
g
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// TODO
|
||||
if (symbol.flags & SymbolFlag.SYMBOL_WEAK) {
|
||||
flags.push(
|
||||
<span key="w" className={styles.flagWeak}>
|
||||
w
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
if (symbol.flags & SymbolFlag.SYMBOL_LOCAL) {
|
||||
flags.push(
|
||||
<span key="l" className={styles.flagLocal}>
|
||||
l
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
if (symbol.flags & SymbolFlag.SYMBOL_COMMON) {
|
||||
flags.push(
|
||||
<span key="c" className={styles.flagCommon}>
|
||||
c
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
let flagsElem = null;
|
||||
if (flags.length > 0) {
|
||||
flagsElem = <>[{flags}] </>;
|
||||
}
|
||||
let percentElem = null;
|
||||
if (diff.match_percent != null) {
|
||||
let className = styles.percent0;
|
||||
if (diff.match_percent === 100) {
|
||||
className = styles.percent100;
|
||||
} else if (diff.match_percent >= 50) {
|
||||
className = styles.percent50;
|
||||
}
|
||||
percentElem = (
|
||||
<>
|
||||
{'('}
|
||||
<span className={className}>
|
||||
{Math.floor(diff.match_percent).toFixed(0)}%
|
||||
</span>
|
||||
{') '}
|
||||
</>
|
||||
);
|
||||
}
|
||||
items.push(
|
||||
<li
|
||||
key={`${sectionKey}-${symbol.name}`}
|
||||
className={styles.symbol}
|
||||
onClick={() => {
|
||||
setSelectedSymbol({
|
||||
symbol_name: symbol.name,
|
||||
section_name: section.name,
|
||||
});
|
||||
}}
|
||||
data-vscode-context={JSON.stringify({
|
||||
contextType: 'symbol',
|
||||
preventDefaultContextMenuItems: true,
|
||||
symbolName: symbol.name,
|
||||
symbolDemangledName: symbol.demangled_name,
|
||||
})}
|
||||
>
|
||||
{flagsElem}
|
||||
{percentElem}
|
||||
<span className={styles.symbolName}>
|
||||
{symbol.demangled_name || symbol.name}
|
||||
</span>
|
||||
</li>,
|
||||
);
|
||||
}
|
||||
}
|
||||
return <ul className={styles.symbolList}>{items}</ul>;
|
||||
};
|
||||
|
||||
const SymbolsView = ({ diff }: { diff: DiffResult }) => {
|
||||
const buildRunning = useExtensionStore((state) => state.buildRunning);
|
||||
const currentFile = useExtensionStore((state) => state.currentFile);
|
||||
return (
|
||||
<>
|
||||
<div className={headerStyles.header}>
|
||||
<button
|
||||
onClick={() =>
|
||||
vscode.postMessage({ type: 'runTask', taskType: 'build' })
|
||||
}
|
||||
disabled={buildRunning}
|
||||
>
|
||||
Build
|
||||
</button>
|
||||
{buildRunning ? <span>Building...</span> : <span>{currentFile}</span>}
|
||||
</div>
|
||||
<div className={styles.symbols}>
|
||||
{diff.left && <SymbolsList obj={diff.left} />}
|
||||
{diff.right && <SymbolsList obj={diff.right} />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SymbolsView;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import type {
|
||||
ArgumentValue,
|
||||
InstructionDiff,
|
||||
RelocationTarget,
|
||||
} from '../gen/diff_pb';
|
||||
} from '../shared/gen/diff_pb';
|
||||
|
||||
export type DiffText =
|
||||
| DiffTextBasic
|
||||
|
||||
+99
-45
@@ -1,6 +1,6 @@
|
||||
import type { WebviewApi } from 'vscode-webview';
|
||||
import { create } from 'zustand';
|
||||
import { DiffKind, DiffResult } from '../gen/diff_pb';
|
||||
import { DiffResult } from '../shared/gen/diff_pb';
|
||||
import type { InboundMessage, OutboundMessage } from '../shared/messages';
|
||||
|
||||
export type SymbolRefByName = {
|
||||
symbol_name: string;
|
||||
@@ -9,23 +9,48 @@ export type SymbolRefByName = {
|
||||
|
||||
export interface AppState {
|
||||
selectedSymbol: SymbolRefByName | null;
|
||||
symbolScrollOffsets: Record<string, number>;
|
||||
setSelectedSymbol: (selectedSymbol: SymbolRefByName | null) => void;
|
||||
setSymbolScrollOffset: (symbolName: string, offset: number) => void;
|
||||
}
|
||||
export const useAppStore = create<AppState>((set) => ({
|
||||
selectedSymbol: null,
|
||||
symbolScrollOffsets: {},
|
||||
setSelectedSymbol: (selectedSymbol) => set({ selectedSymbol }),
|
||||
setSymbolScrollOffset: (symbolName, offset) =>
|
||||
set((state) => ({
|
||||
symbolScrollOffsets: {
|
||||
...state.symbolScrollOffsets,
|
||||
[symbolName]: offset,
|
||||
},
|
||||
})),
|
||||
}));
|
||||
|
||||
export type DiffState = {
|
||||
diff?: DiffResult;
|
||||
export type ExtensionState = {
|
||||
diff: DiffResult | null;
|
||||
buildRunning: boolean;
|
||||
configLoaded: boolean;
|
||||
currentFile: string | null;
|
||||
};
|
||||
export const useDiffStore = create<DiffState>(() => ({}));
|
||||
export const useExtensionStore = create<ExtensionState>(() => ({
|
||||
diff: null,
|
||||
buildRunning: false,
|
||||
configLoaded: false,
|
||||
currentFile: null,
|
||||
}));
|
||||
|
||||
let vscode: WebviewApi<DiffState>;
|
||||
// Copy of vscode.WebviewApi with concrete message types
|
||||
export interface MyWebviewApi<StateType> {
|
||||
postMessage(message: OutboundMessage): void;
|
||||
getState(): StateType | undefined;
|
||||
setState<T extends StateType | undefined>(newState: T): T;
|
||||
}
|
||||
|
||||
let vscode: MyWebviewApi<AppState>;
|
||||
if (typeof acquireVsCodeApi === 'function') {
|
||||
vscode = acquireVsCodeApi<DiffState>();
|
||||
vscode = acquireVsCodeApi<AppState>();
|
||||
} else {
|
||||
let state: DiffState | undefined;
|
||||
let state: AppState | undefined;
|
||||
vscode = {
|
||||
postMessage: () => {},
|
||||
getState: () => state,
|
||||
@@ -35,48 +60,77 @@ if (typeof acquireVsCodeApi === 'function') {
|
||||
},
|
||||
};
|
||||
}
|
||||
const storedState = vscode.getState();
|
||||
if (storedState) {
|
||||
useAppStore.setState(storedState);
|
||||
}
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
||||
useAppStore.subscribe((state) => {
|
||||
// Debounce state updates
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
vscode.setState(state);
|
||||
timeoutId = undefined;
|
||||
}, 100);
|
||||
});
|
||||
vscode.postMessage({ type: 'ready' });
|
||||
export { vscode };
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
const message = event.data;
|
||||
if (message && typeof message === 'object') {
|
||||
console.log('Received message', message);
|
||||
if (message.type === 'diff') {
|
||||
const diff = DiffResult.fromBinary(new Uint8Array(message.data));
|
||||
const message = event.data as InboundMessage;
|
||||
if (message.type === 'diff') {
|
||||
const start = performance.now();
|
||||
const diff = DiffResult.fromBinary(new Uint8Array(message.data));
|
||||
const end = performance.now();
|
||||
console.debug('Diff deserialization time:', end - start, 'ms');
|
||||
|
||||
const lineRanges = [];
|
||||
for (const section of diff.right?.sections || []) {
|
||||
for (const fn of section.functions) {
|
||||
let currentRange: { start: number; end: number } | null = null;
|
||||
for (const ins of fn.instructions) {
|
||||
let lineNumber = ins.instruction?.line_number;
|
||||
if (lineNumber == null) {
|
||||
continue;
|
||||
}
|
||||
lineNumber = lineNumber - 1;
|
||||
if (ins.diff_kind !== DiffKind.DIFF_NONE) {
|
||||
if (currentRange !== null) {
|
||||
currentRange.end = lineNumber;
|
||||
} else {
|
||||
currentRange = {
|
||||
start: lineNumber,
|
||||
end: lineNumber,
|
||||
};
|
||||
}
|
||||
} else if (currentRange !== null && lineNumber > currentRange.end) {
|
||||
lineRanges.push(currentRange);
|
||||
currentRange = null;
|
||||
}
|
||||
}
|
||||
if (currentRange !== null) {
|
||||
lineRanges.push(currentRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('lineRanges', lineRanges);
|
||||
vscode.postMessage({ type: 'lineRanges', data: lineRanges });
|
||||
// const lineRanges = [];
|
||||
// for (const section of diff.right?.sections || []) {
|
||||
// for (const diff of section.symbols) {
|
||||
// let currentRange: { start: number; end: number } | null = null;
|
||||
// for (const ins of diff.instructions) {
|
||||
// let lineNumber = ins.instruction?.line_number;
|
||||
// if (lineNumber == null) {
|
||||
// continue;
|
||||
// }
|
||||
// lineNumber = lineNumber - 1;
|
||||
// if (ins.diff_kind !== DiffKind.DIFF_NONE) {
|
||||
// if (currentRange !== null) {
|
||||
// currentRange.end = lineNumber;
|
||||
// } else {
|
||||
// currentRange = {
|
||||
// start: lineNumber,
|
||||
// end: lineNumber,
|
||||
// };
|
||||
// }
|
||||
// } else if (currentRange !== null && lineNumber > currentRange.end) {
|
||||
// lineRanges.push(currentRange);
|
||||
// currentRange = null;
|
||||
// }
|
||||
// }
|
||||
// if (currentRange !== null) {
|
||||
// lineRanges.push(currentRange);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// console.log('lineRanges', lineRanges);
|
||||
// vscode.postMessage({ type: 'lineRanges', data: lineRanges });
|
||||
|
||||
useDiffStore.setState({ diff });
|
||||
useExtensionStore.setState({ diff });
|
||||
} else if (message.type === 'task') {
|
||||
if (message.taskType === 'build') {
|
||||
useExtensionStore.setState({ buildRunning: message.running });
|
||||
} else {
|
||||
console.error('Unknown task type', message.taskType);
|
||||
}
|
||||
} else if (message.type === 'state') {
|
||||
useExtensionStore.setState({
|
||||
configLoaded: message.configLoaded,
|
||||
currentFile: message.currentFile,
|
||||
});
|
||||
} else {
|
||||
console.error('Unknown message', message);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user