You've already forked objdiff-web
mirror of
https://github.com/encounter/objdiff-web.git
synced 2026-07-10 12:18:37 -07:00
Initial commit
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
# Local
|
||||
.DS_Store
|
||||
*.local
|
||||
*.log*
|
||||
|
||||
# Dist
|
||||
node_modules/
|
||||
dist/
|
||||
|
||||
# IDE
|
||||
.vscode-test/
|
||||
*.vsix
|
||||
Vendored
+8
@@ -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"
|
||||
]
|
||||
}
|
||||
Vendored
+17
@@ -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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+38
@@ -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"
|
||||
}
|
||||
}
|
||||
Vendored
+22
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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.*
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+2007
File diff suppressed because it is too large
Load Diff
@@ -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 |
Generated
+3374
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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() {}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="@rsbuild/core/types" />
|
||||
@@ -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() {}
|
||||
@@ -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"]
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user