vscode: removed hex editor integration for now

This commit is contained in:
Steven Hugg committed 2026-10-01 10:29:37 +02:00
1 parent 2d09792b90
commit 36f2b9f63c
6 files changed
+23 -68

No files matched your search

+1 -1
View File
@@ -18,7 +18,7 @@ First preview.
a timeline to scrub through the recording while paused.
- Debug views: registers, the machine's internal state (the IDE's Debug
Tree), symbols, a call stack walked from return addresses, and
disassembly. Memory opens in VS Code's Hex Editor.
disassembly.
- `.s`, `.asm`, `.inc` and `.a` files in a project get the highlighting
for the platform's CPU. `.acme` files get 6502 highlighting.
- An `8bws` command in the integrated terminal builds and runs programs
+7 -7
View File
@@ -4,11 +4,12 @@
The original online 8-bit IDE, now in VS Code!
Write programs for classic consoles and computers, then compile and play
them without leaving VS Code.
This extension bundles the assemblers,
compilers, and emulators from [8bitworkshop.com](https://8bitworkshop.com).
Features:
- Built-in emulators, compilers, and libraries from [8bitworkshop.com](https://8bitworkshop.com).
- Compile as you type
- Single-step debugging with breakpoints, disassembly, call stack, symbols
- Sample code for each platform
- Syntax highlighting for C and assembly
Supported platforms include 6502, Z80, and 6809 CPUs:
@@ -71,9 +72,8 @@ Your local user settings:
| `8bitworkshop.autoBuild` | `onType` (default), `onSave`, or `off` |
| `8bitworkshop.reloadOnBuild` | `always` (default), `onSave`, or `never` |
| `8bitworkshop.exportRom` | Write each successful build's ROM to the export folder |
| `8bitworkshop.toolchainPath` | Use a local 8bitworkshop checkout instead of the included toolchains |
| `8bitworkshop.assetUrl` | Server or local directory to download toolchains from, tried before the default servers |
| `8bitworkshop.terminalCommand` | Add the `8bws` command to the integrated terminal (off by default) |
| `8bitworkshop.toolchainPath` | Use a local 8bitworkshop checkout instead of the included toolchains |
## Commands
-4
View File
@@ -73,10 +73,6 @@ In a folder with a launch configuration, <kbd>F5</kbd> does the same, and
symbol's address and the byte there). **Watch** and hovers take the
same expressions as conditions: registers, symbols, `[addr]` for a byte
of memory, `#mem16[addr]` for a word.
- **Memory:** the binary-data icon next to a register or symbol opens
memory at that address in the Hex Editor (VS Code offers to install
it). On most platforms you can edit it there; VCS and MAME-based
platforms are read-only.
- **Call Stack** shows the routines that called the current one, found
from return addresses on the stack. A caller marked `?` (dimmed) is a
guess: its call went through a pointer or a jump table.
+6 -7
View File
@@ -1845,7 +1845,7 @@ runs has to be a webview too.
| CPU registers | Variables → "Registers" scope (done) |
| Debug Tree (`getDebugTree()`) | Variables → "Machine" scope (done) |
| Symbols | Variables → "Symbols" scope (done) |
| Memory (hex) | "View Binary Data" on a variable → Hex Editor, via `readMemory`/`writeMemory` (done) |
| Memory (hex) | own webview editor (not started; the DAP Hex Editor integration was removed: it opened at the variable, not `$0`) |
| Debug info panel (`getDebugInfo()` text) | webview ("Machine" view) |
| Memory with symbols, memory map | webviews |
| Probe log, scanline I/O, heatmaps, stack map, frame calls | webviews |
@@ -1873,17 +1873,16 @@ runs has to be a webview too.
expensive (collapsed), and is shown only when the platform has
`getDebugTree` (`DebugCapabilities.tree`).
- **Symbols scope.** Every build symbol by name, with its address and the
byte there, and a `memoryReference` so "View Binary Data" opens it.
Collapsed by default.
byte there. Collapsed by default.
- **Memory writes.** `EmuCore.write()` goes through the machine's bus, so
writing an I/O address does what a store there does. It restarts the
timeline (the past can't be replayed into the edited state). It needs a
`Machine` with `write()`, so VCS and the MAME platforms can't write
(NES's `JSNESMachine` writes RAM or the mapper directly, skipping
`cpu.write()`'s bus cycle); `DebugCapabilities.write` says so and the adapter turns on
`supportsWriteMemoryRequest` only when it can. After a write the adapter
sends `invalidated` (variables); after every stop it sends a `memory`
event for the whole space so the Hex Editor reads again.
`cpu.write()`'s bus cycle); `DebugCapabilities.write` says so. The DAP `readMemory`/`writeMemory`
requests, `memoryReference`s and `memory` events were removed (the Hex
Editor opened at the variable, not `$0`); `DebugService` still has
`readMemory`/`writeMemory` for a future editor of our own.
- Tests: `test/unit/testdebugtree.ts`, and a case in
`test/unit/testdapsession.ts`.
+6 -33
View File
@@ -12,8 +12,8 @@
// level at a time, by path), and the build's symbols with the byte at each.
import {
Breakpoint as DapBreakpoint, CapabilitiesEvent, InitializedEvent, InvalidatedEvent, LoggingDebugSession,
MemoryEvent, OutputEvent, Scope, Source, StackFrame, StoppedEvent, TerminatedEvent, Thread,
Breakpoint as DapBreakpoint, CapabilitiesEvent, InitializedEvent, LoggingDebugSession,
OutputEvent, Scope, Source, StackFrame, StoppedEvent, TerminatedEvent, Thread,
} from '@vscode/debugadapter';
import { DebugProtocol } from '@vscode/debugprotocol';
import * as path from 'path';
@@ -91,8 +91,6 @@ export class EmuDebugSession extends LoggingDebugSession {
/** disassembly listings by sourceReference, and each one's reference by its text */
private listings: Listing[] = [];
private listingRefs = new Map<string, number>();
private clientMemoryEvents = false;
private clientInvalidatedEvents = false;
constructor(private backend: DebugBackend) {
super();
@@ -104,21 +102,17 @@ export class EmuDebugSession extends LoggingDebugSession {
}
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
this.clientMemoryEvents = !!args.supportsMemoryEvent;
this.clientInvalidatedEvents = !!args.supportsInvalidatedEvent;
response.body = {
supportsConfigurationDoneRequest: true,
supportsConditionalBreakpoints: true,
supportsFunctionBreakpoints: true,
supportsInstructionBreakpoints: true,
supportsSteppingGranularity: true,
supportsReadMemoryRequest: true,
supportsDisassembleRequest: true,
supportsEvaluateForHovers: true,
supportsTerminateRequest: true,
// until launch says whether this platform can
supportsStepBack: false,
supportsWriteMemoryRequest: false,
};
this.sendResponse(response);
}
@@ -129,8 +123,8 @@ export class EmuDebugSession extends LoggingDebugSession {
this.root = r.root;
this.stopOnEntry = !!args.stopOnEntry;
this.hasTree = r.capabilities.tree;
if (r.capabilities.rewind || r.capabilities.write) {
this.sendEvent(new CapabilitiesEvent({ supportsStepBack: r.capabilities.rewind, supportsWriteMemoryRequest: r.capabilities.write }));
if (r.capabilities.rewind) {
this.sendEvent(new CapabilitiesEvent({ supportsStepBack: r.capabilities.rewind }));
}
if (!r.capabilities.step) {
this.sendEvent(new OutputEvent(`This platform stops only between frames: stepping and breakpoints are frame by frame.\n`, 'console'));
@@ -255,8 +249,6 @@ export class EmuDebugSession extends LoggingDebugSession {
else if (e.message) ev.body.description = e.message;
if (e.breakpoints) ev.body.hitBreakpointIds = e.breakpoints;
this.sendEvent(ev);
// anything showing memory reads it again
if (this.clientMemoryEvents) this.sendEvent(new MemoryEvent(addressRef(0), 0, 0x10000));
}
//// where it is
@@ -370,12 +362,12 @@ export class EmuDebugSession extends LoggingDebugSession {
private async variables(ref: number): Promise<DebugProtocol.Variable[]> {
if (ref === REGISTERS_REF) {
const regs = await this.backend.registers();
return regs.map(r => ({ name: r.name, value: r.text, variablesReference: 0, memoryReference: addressRef(r.value) }));
return regs.map(r => ({ name: r.name, value: r.text, variablesReference: 0 }));
}
if (ref === SYMBOLS_REF) {
const syms = await this.backend.symbols();
return syms.map(s => ({
name: s.name, value: `$${hex(s.addr, 4)}: $${hex(s.value, 2)}`, variablesReference: 0, memoryReference: addressRef(s.addr),
name: s.name, value: `$${hex(s.addr, 4)}: $${hex(s.value, 2)}`, variablesReference: 0,
}));
}
const path = this.treeRefs[ref - FIRST_TREE_REF];
@@ -397,24 +389,6 @@ export class EmuDebugSession extends LoggingDebugSession {
return ref;
}
protected async readMemoryRequest(response: DebugProtocol.ReadMemoryResponse, args: DebugProtocol.ReadMemoryArguments) {
await this.run(response, async () => {
const addr = parseAddress(args.memoryReference) + (args.offset || 0);
const bytes = await this.backend.readMemory(addr, Math.min(args.count, 0x10000));
response.body = { address: addressRef(addr), data: Buffer.from(bytes).toString('base64') };
});
}
protected async writeMemoryRequest(response: DebugProtocol.WriteMemoryResponse, args: DebugProtocol.WriteMemoryArguments) {
await this.run(response, async () => {
const addr = parseAddress(args.memoryReference) + (args.offset || 0);
const bytes = [...Buffer.from(args.data, 'base64')];
response.body = { bytesWritten: await this.backend.writeMemory(addr, bytes) };
// the symbols and the machine may show what changed
if (this.clientInvalidatedEvents) this.sendEvent(new InvalidatedEvent(['variables']));
});
}
protected async disassembleRequest(response: DebugProtocol.DisassembleResponse, args: DebugProtocol.DisassembleArguments) {
await this.run(response, async () => {
const addr = parseAddress(args.memoryReference) + (args.offset || 0);
@@ -437,7 +411,6 @@ export class EmuDebugSession extends LoggingDebugSession {
await this.run(response, async () => {
const r = await this.backend.evaluate(args.expression, args.context || 'repl');
response.body = { result: r.result, variablesReference: 0 };
if (r.value != null) response.body.memoryReference = addressRef(r.value);
if (r.moved) this.stopped({ ...(await this.backend.location()), reason: 'goto' });
});
}
+3 -16
View File
@@ -22,13 +22,12 @@ async function launch(args: any = {}): Promise<DapClient> {
return { target, root: ROOT, debugInfo: { listings: build.listings, symbols: build.symbolmap, mainPath: 'game2.c' } };
});
const c = new DapClient(new EmuDebugSession(backend));
const caps = await c.request('initialize', { adapterID: '8bitworkshop', linesStartAt1: true, columnsStartAt1: true, pathFormat: 'path', supportsMemoryEvent: true, supportsInvalidatedEvent: true });
const caps = await c.request('initialize', { adapterID: '8bitworkshop', linesStartAt1: true, columnsStartAt1: true, pathFormat: 'path' });
assert.ok(caps.supportsDisassembleRequest);
await c.request('launch', { program: 'game2.c', ...args });
await c.event('initialized');
const { capabilities } = await c.event('capabilities');
assert.strictEqual(capabilities.supportsStepBack, true);
assert.strictEqual(capabilities.supportsWriteMemoryRequest, true);
return c;
}
@@ -88,21 +87,17 @@ describe('Debug adapter', function () {
const reg = variables.find((v: any) => v.name === 'PC');
assert.strictEqual(parseInt(reg.value.slice(1), 16), pc);
const mem = await c.request('readMemory', { memoryReference: frame.instructionPointerReference, count: 4 });
assert.strictEqual(Buffer.from(mem.data, 'base64').length, 4);
const { instructions } = await c.request('disassemble', { memoryReference: frame.instructionPointerReference, instructionOffset: -3, instructionCount: 6 });
assert.strictEqual(instructions.length, 6);
assert.strictEqual(parseInt(instructions[3].address, 16), pc);
assert.strictEqual(instructions[3].symbol, '_draw_char');
});
it('shows the machine tree and symbols, and writes memory', async function () {
it('shows the machine tree and symbols', async function () {
const c = await launch();
await c.request('setFunctionBreakpoints', { breakpoints: [{ name: 'draw_char' }] });
await c.request('configurationDone');
await c.event('stopped');
await c.event('memory');
const frame = await c.where();
const { scopes } = await c.request('scopes', { frameId: frame.id });
assert.deepStrictEqual(scopes.map((s: any) => s.name), ['Registers', 'Machine', 'Symbols']);
@@ -113,17 +108,9 @@ describe('Debug adapter', function () {
const stateVars = await c.request('variables', { variablesReference: state.variablesReference });
assert.ok(stateVars.variables.find((v: any) => v.name === 'c'), 'CPU state under state');
const addr = build.symbolmap['_draw_char'];
const syms = await c.request('variables', { variablesReference: scopes[2].variablesReference });
const sym = syms.variables.find((v: any) => v.name === '_draw_char');
assert.strictEqual(parseInt(sym.memoryReference, 16), addr);
// video RAM
const { bytesWritten } = await c.request('writeMemory', { memoryReference: '0x2400', data: Buffer.from([0x12, 0x34]).toString('base64') });
assert.strictEqual(bytesWritten, 2);
await c.event('invalidated');
const mem = await c.request('readMemory', { memoryReference: '0x2400', count: 2 });
assert.deepStrictEqual([...Buffer.from(mem.data, 'base64')], [0x12, 0x34]);
assert.ok(sym.value.includes('$' + build.symbolmap['_draw_char'].toString(16).padStart(4, '0').toUpperCase()), 'symbol shows its address');
});
it('runs run-script commands in the Debug Console, and expressions in watches', async function () {