Initial commit

This commit is contained in:
Luke Street
2024-12-16 22:37:21 -07:00
commit 6d4711ecc8
29 changed files with 8632 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
# Local
.DS_Store
*.local
*.log*
# Dist
node_modules/
dist/
# IDE
.vscode-test/
*.vsix
+8
View File
@@ -0,0 +1,8 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846
// for the documentation about the extensions.json format
"recommendations": [
"ms-vscode.extension-test-runner",
"jacobcassidy.css-nesting-syntax-highlighting"
]
}
+17
View File
@@ -0,0 +1,17 @@
// A launch configuration that compiles the extension and then opens it inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Extension",
"type": "extensionHost",
"request": "launch",
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
// "preLaunchTask": "${defaultBuildTask}"
}
]
}
+38
View File
@@ -0,0 +1,38 @@
// Place your settings in this file to overwrite default and user settings.
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports.biome": "always"
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off",
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[scss]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[yaml]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
+22
View File
@@ -0,0 +1,22 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch",
"group": {
"kind": "build",
"isDefault": true
},
"isBackground": true,
"label": "npm:watch",
"presentation": {
"group": "watch",
"reveal": "never"
},
"problemMatcher": "$tsc-watch"
}
]
}
+14
View File
@@ -0,0 +1,14 @@
.vscode/**
.vscode-test/**
out/**
node_modules/**
src/**
webview/**
.gitignore
.yarnrc
rsbuild.config.ts
**/tsconfig.json
**/biome.json
**/*.map
**/*.ts
**/.vscode-test.*
+22
View File
@@ -0,0 +1,22 @@
# objdiff-code
> [!WARNING]
> This extension is in early development and is **not yet ready for use**.
## Setup
Open the project in Visual Studio Code.
- Install dependencies:
```bash
npm install
```
- Start dev server using `Ctrl+Shift+B` or by running:
```bash
npm run watch
```
- Run the extension in debug mode using `F5`.
+33
View File
@@ -0,0 +1,33 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.0/schema.json",
"organizeImports": {
"enabled": true
},
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"formatter": {
"indentStyle": "space"
},
"javascript": {
"formatter": {
"quoteStyle": "single"
}
},
"css": {
"parser": {
"cssModules": true
}
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"all": false
}
}
}
}
+1515
View File
File diff suppressed because it is too large Load Diff
+2007
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3,1C1.89,1 1,1.89 1,3V14C1,15.11 1.89,16 3,16H5V14H3V3H14V5H16V3C16,1.89 15.11,1 14,1H3M9,7C7.89,7 7,7.89 7,9V11H9V9H11V7H9M13,7V9H14V10H16V7H13M18,7V9H20V20H9V18H7V20C7,21.11 7.89,22 9,22H20C21.11,22 22,21.11 22,20V9C22,7.89 21.11,7 20,7H18M14,12V14H12V16H14C15.11,16 16,15.11 16,14V12H14M7,13V16H10V14H9V13H7Z" /></svg>

After

Width:  |  Height:  |  Size: 391 B

+3374
View File
File diff suppressed because it is too large Load Diff
+92
View File
@@ -0,0 +1,92 @@
{
"name": "objdiff-code",
"displayName": "objdiff-code",
"description": "objdiff",
"version": "0.0.1",
"scripts": {
"build": "rsbuild build",
"watch": "rsbuild build --watch --mode development",
"check": "biome check --write",
"format": "biome format --write"
},
"dependencies": {
"@protobuf-ts/runtime": "^2.9.4",
"clsx": "^2.1.1",
"memoize-one": "^6.0.0",
"picomatch": "^4.0.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-virtualized-auto-sizer": "^1.0.24",
"react-window": "^1.8.10",
"zustand": "^5.0.2"
},
"devDependencies": {
"@biomejs/biome": "^1.9.3",
"@rsbuild/core": "^1.1.8",
"@rsbuild/plugin-react": "^1.0.7",
"@rsbuild/plugin-type-check": "^1.1.0",
"@types/picomatch": "^3.0.1",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.1",
"@types/react-window": "^1.8.8",
"@types/vscode": "^1.96.0",
"@types/vscode-webview": "^1.57.5",
"@vscode/test-cli": "^0.0.10",
"@vscode/test-electron": "^2.4.1",
"typescript": "^5.7.2"
},
"main": "./dist/extension.js",
"engines": {
"vscode": "^1.96.0"
},
"browserslist": ["chrome 128"],
"categories": ["Other"],
"activationEvents": ["*"],
"contributes": {
"commands": [
{
"command": "objdiff.build",
"title": "objdiff: build"
}
],
"configuration": {
"title": "objdiff",
"properties": {
"objdiff.binaryPath": {
"type": "string",
"description": "Path to the objdiff-cli binary",
"ignoreSync": true
}
}
},
"viewsContainers": {
"panel": [
{
"id": "objdiff",
"title": "objdiff",
"icon": "media/icon.svg"
}
]
},
"views": {
"objdiff": [
{
"id": "objdiff",
"name": "objdiff",
"type": "webview"
}
]
},
"viewsWelcome": [
{
"view": "objdiff",
"contents": "Loading..."
}
],
"taskDefinitions": [
{
"type": "objdiff"
}
]
}
}
+77
View File
@@ -0,0 +1,77 @@
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import { pluginTypeCheck } from '@rsbuild/plugin-type-check';
export default defineConfig({
// Disable HMR and live reload. Neither the extension nor the
// webview can communicate with the rsbuild dev server.
dev: {
hmr: false,
liveReload: false,
},
environments: {
extension: {
source: {
entry: {
extension: './src/extension.ts',
},
},
output: {
target: 'node',
distPath: {
root: 'dist',
},
externals: ['vscode'],
},
},
webview: {
source: {
entry: {
index: './webview/index.tsx',
},
},
output: {
target: 'web',
distPath: {
root: 'dist/webview',
},
// VS Code webviews don't have easy access to resources,
// (especially if the extension is running on web) so we
// simply inline everything into the HTML.
inlineScripts: true,
inlineStyles: true,
legalComments: 'none',
},
// <script defer> doesn't work with inline scripts,
// so we need to move the scripts to the body.
html: {
inject: 'body',
scriptLoading: 'blocking',
},
plugins: [
pluginReact({
fastRefresh: false,
}),
],
},
},
// Ensure that we never split chunks. Both the extension and
// the webview must be self-contained files.
performance: {
chunkSplit: {
strategy: 'all-in-one',
},
},
// Enable async TypeScript type checking.
plugins: [pluginTypeCheck()],
// We can't use async chunks for aforementioned reasons.
// Disabling them ensures that all code is bundled together
// even if dynamic imports are used.
tools: {
rspack: {
output: {
asyncChunks: false,
},
},
},
});
+182
View File
@@ -0,0 +1,182 @@
// import * as vscode from 'vscode';
export const DEFAULT_WATCH_PATTERNS = [
'*.c',
'*.cp',
'*.cpp',
'*.cxx',
'*.h',
'*.hp',
'*.hpp',
'*.hxx',
'*.s',
'*.S',
'*.asm',
'*.inc',
'*.py',
'*.yml',
'*.txt',
'*.json',
];
/**
* Configuration file for objdiff
*/
export interface ObjdiffConfiguration {
/**
* Minimum version of objdiff required to load this configuration file.
*/
min_version?: string;
/**
* By default, objdiff will use make to build the project.
* If the project uses a different build system (e.g. ninja), specify it here.
* The build command will be `[custom_make] [custom_args] path/to/object.o`.
*/
custom_make?: string;
/**
* Additional arguments to pass to the build command prior to the object path.
*/
custom_args?: string[];
/**
* Relative from the root of the project, this where the "target" or "expected" objects are located.
* These are the intended result of the match.
*/
target_dir?: string;
/**
* Relative from the root of the project, this is where the "base" or "actual" objects are located.
* These are objects built from the current source code.
*/
base_dir?: string;
/**
* If true, objdiff will tell the build system to build the target objects before diffing (e.g. `make path/to/target.o`).
* This is useful if the target objects are not built by default or can change based on project configuration or edits to assembly files.
* Requires the build system to be configured properly.
*/
build_target?: boolean;
/**
* If true, objdiff will tell the build system to build the base objects before diffing (e.g. `make path/to/base.o`).
* It's unlikely you'll want to disable this, unless you're using an external tool to rebuild the base object on source file changes.
*/
build_base?: boolean;
/**
* List of glob patterns to watch for changes in the project.
* If any of these files change, objdiff will automatically rebuild the objects and re-compare them.
* Supported syntax: https://docs.rs/globset/latest/globset/#syntax
*/
watch_patterns?: string[];
/**
* Use units instead.
*/
objects?: Unit[];
/**
* If specified, objdiff will display a list of objects in the sidebar for easy navigation.
*/
units?: Unit[];
/**
* Progress categories used for objdiff-cli report.
*/
progress_categories?: ProgressCategory[];
}
export interface Unit {
/**
* The name of the object in the UI. If not specified, the object's path will be used.
*/
name?: string;
/**
* Relative path to the object from the target_dir and base_dir.
* Requires target_dir and base_dir to be specified.
*/
path?: string;
/**
* Path to the target object from the project root.
* Required if path is not specified.
*/
target_path?: string;
/**
* Path to the base object from the project root.
* Required if path is not specified.
*/
base_path?: string;
/**
* Displays function symbols in reversed order.
* Used to support MWCC's -inline deferred option, which reverses the order of functions in the object file.
*/
reverse_fn_order?: boolean;
/**
* Marks the object as "complete" (or "linked") in the object list.
* This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
*/
complete?: boolean;
/**
* If present, objdiff will display a button to create a decomp.me scratch.
*/
scratch?: Scratch;
/**
* Metadata for the object.
*/
metadata?: Metadata;
/**
* Manual symbol mappings from target to base.
*/
symbol_mappings?: {
[k: string]: string;
};
}
export interface Scratch {
/**
* The decomp.me platform ID to use for the scratch.
*/
platform?: string;
/**
* The decomp.me compiler ID to use for the scratch.
*/
compiler?: string;
/**
* C flags to use for the scratch. Exclude any include paths.
*/
c_flags?: string;
/**
* Path to the context file to use for the scratch.
*/
ctx_path?: string;
/**
* If true, objdiff will run the build command with the context file as an argument to generate it.
*/
build_ctx?: boolean;
}
export interface Metadata {
/**
* Marks the object as "complete" (or "linked") in the object list.
* This is useful for marking objects that are fully decompiled. A value of `false` will mark the object as "incomplete".
*/
complete?: boolean;
/**
* Displays function symbols in reversed order.
* Used to support MWCC's -inline deferred option, which reverses the order of functions in the object file.
*/
reverse_fn_order?: boolean;
/**
* Path to the source file that generated the object.
*/
source_path?: string;
/**
* Progress categories used for objdiff-cli report.
*/
progress_categories?: string[];
/**
* Hides the object from the object list by default, but still includes it in reports.
*/
auto_generated?: boolean;
}
export interface ProgressCategory {
/**
* Unique identifier for the category.
*/
id?: string;
/**
* Human-readable name of the category.
*/
name?: string;
}
export function loadConfig() {}
+1
View File
@@ -0,0 +1 @@
/// <reference types="@rsbuild/core/types" />
+464
View File
@@ -0,0 +1,464 @@
import * as picomatch from 'picomatch';
import * as vscode from 'vscode';
import { DEFAULT_WATCH_PATTERNS, type ObjdiffConfiguration } from './config';
const CONFIG_FILENAME = 'objdiff.json';
export class ObjdiffWorkspace extends vscode.Disposable {
public config?: ObjdiffConfiguration;
public configWatcher: vscode.FileSystemWatcher;
public workspaceWatcher?: vscode.FileSystemWatcher;
private subscriptions: vscode.Disposable[] = [];
private wwSubscriptions: vscode.Disposable[] = [];
private currentDiff?: vscode.Uri;
// private currentTask?: () => void;
private pathMatcher?: picomatch.Matcher;
constructor(
public readonly chan: vscode.LogOutputChannel,
public readonly workspaceFolder: vscode.WorkspaceFolder,
private readonly storageUri: vscode.Uri,
) {
super(() => {
this.disposeImpl();
});
this.configWatcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(workspaceFolder, CONFIG_FILENAME),
);
this.configWatcher.onDidCreate(this.loadConfig, this, this.subscriptions);
this.configWatcher.onDidChange(this.loadConfig, this, this.subscriptions);
this.configWatcher.onDidDelete(this.loadConfig, this, this.subscriptions);
this.loadConfig();
chan.info(`Initialized workspace: ${this.workspaceFolder.uri.toString()}`);
// this.subscriptions.push(
// vscode.workspace.onDidOpenTextDocument((e) => {
// if (
// e.uri.scheme === 'file' &&
// e.uri.fsPath.startsWith(this.rootUri.fsPath)
// ) {
// const relPath = e.uri.fsPath.slice(this.rootUri.fsPath.length + 1);
// this.chan.info('Opened document in workspace:', relPath);
// }
// }, this),
// );
}
// biome-ignore lint/suspicious/noExplicitAny: pass through message args
showError(message: string, ...args: any[]) {
this.chan.error(message, ...args);
vscode.window
.showErrorMessage(`objdiff: ${message}`, {
title: 'Show log',
})
.then((item) => {
if (item) {
this.chan.show();
}
});
}
async loadConfig() {
const configUri = vscode.Uri.joinPath(
this.workspaceFolder.uri,
CONFIG_FILENAME,
);
try {
const stat = await vscode.workspace.fs.stat(configUri);
if (stat.type !== vscode.FileType.File) {
this.showError('Config path is not a file', configUri.toString());
return;
}
} catch (reason) {
if (reason instanceof vscode.FileSystemError) {
if (reason.code === 'FileNotFound') {
this.chan.warn('Config file not found', configUri.toString());
this.config = undefined;
this.onConfigChange();
return;
}
}
this.showError(
'Failed to stat config file',
configUri.toString(),
reason,
);
return;
}
try {
const data = await vscode.workspace.fs.readFile(configUri);
this.config = JSON.parse(new TextDecoder().decode(data));
} catch (reason) {
this.showError(
'Failed to load config file',
configUri.toString(),
reason,
);
return;
}
this.onConfigChange();
}
onConfigChange() {
this.chan.info('Loaded new config');
const watchPatterns = this.config?.watch_patterns || DEFAULT_WATCH_PATTERNS;
if (watchPatterns.length) {
this.pathMatcher = picomatch(watchPatterns, {
basename: true,
strictSlashes: true,
});
} else {
this.pathMatcher = undefined;
}
this.chan.info('Watch patterns:', watchPatterns);
if (this.workspaceWatcher) {
for (const sub of this.wwSubscriptions) {
sub.dispose();
}
this.wwSubscriptions = [];
this.workspaceWatcher.dispose();
this.workspaceWatcher = undefined;
}
if (this.pathMatcher) {
this.workspaceWatcher = vscode.workspace.createFileSystemWatcher(
new vscode.RelativePattern(this.workspaceFolder.uri, '*/**'),
);
this.workspaceWatcher.onDidChange(
this.onWorkspaceFileChange,
this,
this.wwSubscriptions,
);
this.workspaceWatcher.onDidCreate(
this.onWorkspaceFileChange,
this,
this.wwSubscriptions,
);
this.workspaceWatcher.onDidDelete(
this.onWorkspaceFileChange,
this,
this.wwSubscriptions,
);
}
this.tryDiff();
}
onWorkspaceFileChange(uri: vscode.Uri) {
if (!uri.fsPath.startsWith(this.workspaceFolder.uri.fsPath)) {
return;
}
const relPath = uri.fsPath.slice(
this.workspaceFolder.uri.fsPath.length + 1,
);
if (!this.pathMatcher || !this.pathMatcher(relPath)) {
return;
}
this.chan.info('Workspace file changed', uri.toString());
this.tryDiff();
}
tryDiff() {
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 fsPath = this.currentDiff.fsPath;
if (!fsPath.startsWith(this.workspaceFolder.uri.fsPath)) {
this.chan.warn('Active editor not in workspace');
return;
}
const relPath = fsPath.slice(this.workspaceFolder.uri.fsPath.length + 1);
const obj = (this.config.units || this.config.objects)?.find(
(obj) => obj.metadata?.source_path === relPath,
);
if (!obj) {
this.chan.warn('No object found for', relPath);
return;
}
const targetPath =
obj.target_path &&
vscode.Uri.joinPath(this.workspaceFolder.uri, obj.target_path);
const basePath =
obj.base_path &&
vscode.Uri.joinPath(this.workspaceFolder.uri, obj.base_path);
this.chan.info('Diffing', targetPath?.toString(), basePath?.toString());
if (!targetPath || !basePath) {
this.chan.warn('Missing target or base path');
return;
}
const buildCmd = this.config.custom_make || 'make';
const buildArgs = this.config.custom_args || [];
const outputUri = vscode.Uri.joinPath(this.storageUri, 'diff.binpb');
const args = [];
if (obj.target_path && this.config.build_target) {
if (args.length) {
args.push('&&');
}
args.push(buildCmd, ...buildArgs, obj.target_path);
}
if (
obj.base_path &&
(this.config.build_base || this.config.build_base === undefined)
) {
if (args.length) {
args.push('&&');
}
args.push(buildCmd, ...buildArgs, obj.base_path);
}
if (args.length) {
args.push('&&');
}
const binaryPath = vscode.workspace
.getConfiguration('objdiff')
.get('binaryPath') as string;
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',
'-1',
targetPath.fsPath,
'-2',
basePath.fsPath,
'--format',
'proto',
'-o',
outputUri.fsPath,
);
const startTime = performance.now();
const task = new vscode.Task(
{
type: 'objdiff',
startTime,
},
this.workspaceFolder,
'objdiff',
'objdiff',
new vscode.ShellExecution(args[0], args.slice(1)),
);
task.presentationOptions.reveal = vscode.TaskRevealKind.Silent;
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 disposeImpl() {
this.chan.info('Disposing workspace');
this.configWatcher.dispose();
for (const sub of this.wwSubscriptions) {
sub.dispose();
}
this.wwSubscriptions = [];
this.workspaceWatcher?.dispose();
this.workspaceWatcher = undefined;
for (const sub of this.subscriptions) {
sub.dispose();
}
this.subscriptions = [];
}
}
export function activate(context: vscode.ExtensionContext) {
const chan = vscode.window.createOutputChannel('objdiff', { log: true });
const storageUri = context.storageUri;
if (!storageUri || storageUri.scheme !== 'file') {
vscode.window.showErrorMessage('objdiff requires a file storage URI');
return;
}
vscode.workspace.fs.createDirectory(storageUri).then(
() => {
chan.info('Storage directory created', storageUri.toString());
},
(reason) => {
chan.error(
'Failed to create storage directory',
storageUri.toString(),
reason,
);
},
);
// const storageDir = storageUri.fsPath;
// chan.info('Storage directory: ' + storageDir);
let workspace: ObjdiffWorkspace | undefined;
if (vscode.workspace.workspaceFolders?.[0]) {
workspace = new ObjdiffWorkspace(
chan,
vscode.workspace.workspaceFolders[0],
storageUri,
);
context.subscriptions.push(workspace);
}
context.subscriptions.push(
vscode.commands.registerCommand('objdiff.build', () => {
if (workspace) {
workspace.tryDiff();
}
}),
);
const webviews: vscode.Webview[] = [];
const backgroundColors = [
'rgba(255, 0, 255, 0.3)',
'rgba(0, 255, 255, 0.3)',
'rgba(0, 212, 0, 0.3)',
'rgba(255, 0, 0, 0.3)',
'rgba(103, 106, 255, 0.3)',
'rgba(255, 182, 193, 0.3)',
'rgba(224, 255, 255, 0.3)',
'rgba(144, 238, 144, 0.3)',
'rgba(128, 128, 128, 0.3)',
];
const decorationTypes = backgroundColors.map((color) => {
return vscode.window.createTextEditorDecorationType({
isWholeLine: true,
backgroundColor: color,
});
});
context.subscriptions.push(
vscode.tasks.onDidEndTaskProcess(async (e) => {
if (e.execution.task.definition.type !== 'objdiff') {
return;
}
const endTime = performance.now();
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) {
try {
const data = await vscode.workspace.fs.readFile(outputUri);
chan.info(
'Read output file',
outputFile,
'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({
type: 'diff',
data: data.buffer,
});
}
} catch (reason) {
chan.error('Failed to read output file', reason);
}
} else {
workspace?.showError(`Build failed with code ${e.exitCode}`);
}
vscode.workspace.fs.delete(outputUri).then(
() => {},
(reason) => {
chan.error('Failed to delete output file', reason);
},
);
}),
);
const textDecoder = new TextDecoder();
const webviewRoot = vscode.Uri.joinPath(
context.extensionUri,
'dist',
'webview',
);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider('objdiff', {
async resolveWebviewView(view, _resolve, _token) {
let html = textDecoder.decode(
await vscode.workspace.fs.readFile(
vscode.Uri.joinPath(webviewRoot, 'index.html'),
),
);
// 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)}"`;
});
// chan.info(html);
view.webview.options = {
localResourceRoots: [context.extensionUri],
enableScripts: true,
};
view.webview.html = html;
context.subscriptions.push(
view.webview.onDidReceiveMessage((message) => {
if (message.type === 'ready') {
chan.info('Webview ready');
} else if (message.type === 'lineRanges') {
for (const editor of vscode.window.visibleTextEditors) {
if (editor.document.uri.scheme !== 'file') {
continue;
}
chan.info(
'Adding decorations to editor',
editor.document.uri.toString(),
);
let idx = 0;
for (const range of message.data) {
editor.setDecorations(decorationTypes[idx], [
new vscode.Range(range.start, 0, range.end, 0),
]);
idx = (idx + 1) % decorationTypes.length;
}
}
}
}),
);
context.subscriptions.push(
view.onDidDispose(() => {
const idx = webviews.indexOf(view.webview);
if (idx >= 0) {
webviews.splice(idx, 1);
}
}),
);
webviews.push(view.webview);
},
}),
);
}
export function deactivate() {}
+23
View File
@@ -0,0 +1,23 @@
{
"compilerOptions": {
"lib": ["DOM", "ES2023"],
"jsx": "react-jsx",
"target": "ES2022",
"noEmit": true,
"skipLibCheck": true,
"useDefineForClassFields": true,
/* modules */
"module": "ESNext",
"isolatedModules": true,
"resolveJsonModule": true,
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true,
/* type checking */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src", "webview", "gen"]
}
+48
View File
@@ -0,0 +1,48 @@
:root {
--font-size: var(--vscode-editor-font-size, 13px);
--list-row-height: calc(var(--font-size) * 1.33);
}
body {
margin: 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
);
background-color: var(--vscode-editor-background, #1e1e1e);
}
#root {
display: flex;
min-height: 100vh;
}
.content {
flex: 1;
display: flex;
min-height: 100vh;
line-height: 1.1;
text-align: center;
flex-direction: column;
justify-content: center;
}
.content h1 {
font-size: 3.6rem;
font-weight: 700;
}
.content p {
font-size: 1.2rem;
font-weight: 400;
opacity: 0.5;
}
+58
View File
@@ -0,0 +1,58 @@
import './App.css';
import { SectionKind } from '../gen/diff_pb';
import type {
Symbol as DiffSymbol,
FunctionDiff,
ObjectDiff,
} from '../gen/diff_pb';
import FunctionView from './FunctionView';
import SymbolsView from './SymbolsView';
import { useAppStore, useDiffStore } from './state';
import type { SymbolRefByName } from './state';
const findSymbol = (
obj: ObjectDiff | undefined,
symbolRef: SymbolRefByName | null,
): FunctionDiff | 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;
if (symbol.name === symbolRef.symbol_name) {
return fn;
}
}
}
}
}
return null;
};
const App = () => {
const { diff } = useDiffStore();
const selectedSymbolRef = useAppStore((state) => state.selectedSymbol);
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 (
<div className="content">
<h1>objdiff</h1>
<p>Coming soon to a VS Code near you.</p>
</div>
);
};
export default App;

Some files were not shown because too many files have changed in this diff Show More