diff --git a/css/ui.css b/css/ui.css index 0fe1e107..71563d17 100644 --- a/css/ui.css +++ b/css/ui.css @@ -537,6 +537,11 @@ div.markdown th { .seg_io { color: #ffcccc; } .seg_stack { color: #ffff66; } .seg_unknown { color: #cccccc; } +.goto_line { + background-color: #1155cc; + color: #ffffff; + width: 100%; +} .segment.segment-ram { background-color:#aaeeaa; } diff --git a/src/ide/ui.ts b/src/ide/ui.ts index 28c6f816..f8f870fb 100644 --- a/src/ide/ui.ts +++ b/src/ide/ui.ts @@ -1095,6 +1095,55 @@ function jumpToError(err: WorkerError) { } } +// go-to-address (mod+shift+g): works while the Disassembly, Memory Browser, +// or VRAM Browser is the active view; accepts a hex address ("$1234", "0x1234", +// "1234") or a symbol name ("main") + +function getGoToAddressView(): DisassemblerView | MemoryView | null { + var wnd = projectWindows.getActive(); + if (wnd instanceof DisassemblerView || wnd instanceof MemoryView) return wnd; + return null; +} + +function parseGoToTarget(s: string): number { + s = s.trim(); + if (!s) return -1; + // symbol name first (so symbols like "add" aren't parsed as hex) + var symmap = platform.debugSymbols && platform.debugSymbols.symbolmap; + if (symmap && s in symmap) return symmap[s]; + var ls = s.toLowerCase(); + if (symmap) { + for (var sym in symmap) { + if (sym.toLowerCase() === ls) return symmap[sym]; + } + } + // hex address (bare digits are parsed as hex, like the listings) + if (/^(0x|\$)[0-9a-f]+$/i.test(s)) return parseInt(s.replace(/^(0x|\$)/i, ''), 16); + if (/^[0-9a-f]+$/i.test(s)) return parseInt(s, 16); + return -1; +} + +function promptGoToAddress(): boolean { + var wnd = getGoToAddressView(); + if (!wnd) return false; + var cur = (wnd as any).getCursorPC ? (wnd as any).getCursorPC() : -1; + (bootbox as any).prompt({ + title: "Go to Address", + placeholder: "address in hex", + value: cur >= 0 ? hex(cur, 4) : "", + callback: (result: string) => { + if (!result) return; // canceled or empty + var addr = parseGoToTarget(result); + if (isNaN(addr) || addr < 0) { + alertError("Can't find address or symbol: " + DOMPurify.sanitize(result)); + return; + } + wnd.goToAddress(addr); + } + }); + return true; +} + function getGlobalShortcuts(): Shortcut[] { var shortcuts: Shortcut[] = []; if (platform && isPlatformReady()) { @@ -1108,9 +1157,13 @@ function getGlobalShortcuts(): Shortcut[] { if (platform.isRunning && platform.isRunning()) { // in a debug session but emulator running: just Pause shortcuts.push({ key: 'mod+shift+h', label: 'Pause', fn: pause }); - } else if (platform.isRunning && !platform.isRunning()) { + } else if (platform.isRunning && !platform.isRunning() && !getGoToAddressView()) { + // mod+shift+g becomes "Go To Address" while a debug tool view is active shortcuts.push({ key: 'mod+shift+g', label: 'Resume', fn: resume }); } + if (getGoToAddressView()) { + shortcuts.push({ key: 'mod+shift+g', label: 'Go To Address', fn: promptGoToAddress }); + } } return shortcuts; } @@ -1642,6 +1695,11 @@ function setupDebugControls() { uitoolbar.grp.prop('id', 'run_bar'); uitoolbar.add('mod+shift+r', 'Reset', 'glyphicon-refresh', resetAndRun).prop('id', 'dbg_reset'); uitoolbar.add('mod+shift+h', 'Pause', 'glyphicon-pause', pause).prop('id', 'dbg_pause'); + // bound before Resume so it takes priority in the debug tool views; + // falls through to Resume everywhere else + uitoolbar.add('mod+shift+g', 'Go To Address', '', (e) => { + if (!promptGoToAddress()) resume(); + }); uitoolbar.add('mod+shift+g', 'Resume', 'glyphicon-play', resume).prop('id', 'dbg_go'); if (platform.restartAtPC) { uitoolbar.add('mod+shift+a', 'Restart at Cursor', 'glyphicon-play-circle', restartAtCursor).prop('id', 'dbg_restartatline'); diff --git a/src/ide/views/debugviews.ts b/src/ide/views/debugviews.ts index b3365726..656756e6 100644 --- a/src/ide/views/debugviews.ts +++ b/src/ide/views/debugviews.ts @@ -37,6 +37,8 @@ export class MemoryView implements ProjectView { return this.maindiv = div; } + gotoRow = -1; // row highlighted by the Go To Address prompt + showMemoryWindow(workspace: HTMLElement, parent: HTMLElement) { this.memorylist = new VirtualList({ w: $(workspace).width(), @@ -50,6 +52,7 @@ export class MemoryView implements ProjectView { var dlr = this.dumplines[row]; if (dlr) linediv.classList.add('seg_' + this.getMemorySegment(this.dumplines[row].a | this.hibits)); } + if (row == this.gotoRow) linediv.classList.add('goto_line'); linediv.appendChild(document.createTextNode(s)); return linediv; } @@ -68,6 +71,28 @@ export class MemoryView implements ProjectView { } } + // jump to an address (or symbol), via the Go To Address prompt; + // centers the target row in the viewport and highlights it + goToAddress(addr: number) { + this.hibits = addr & 0xffff0000; + var dump = this.getDumpLines(); + var row; + if (dump && dump.length) { + row = this.findMemoryWindowLine(addr & 0xffff); + } else { + // no symbol map (e.g. VRAM): rows are raw 16-byte chunks + row = (addr & 0xffff) >> 4; + } + if (row === undefined) return; + this.gotoRow = row; + if (this.memorylist) { + var c = this.memorylist.container; + var y = row * this.memorylist.itemHeight - (c.clientHeight - this.memorylist.itemHeight) / 2; + c.scrollTop = Math.max(0, y); + } + this.tick(); + } + refresh() { this.dumplines = null; this.tick(); @@ -82,6 +107,7 @@ export class MemoryView implements ProjectView { var newtext = this.getMemoryLineAt(row); if (oldtext != newtext) div.text(newtext); + div.toggleClass('goto_line', row == this.gotoRow); }); } } diff --git a/src/ide/views/editors.ts b/src/ide/views/editors.ts index cdab19cd..6d86b4d1 100644 --- a/src/ide/views/editors.ts +++ b/src/ide/views/editors.ts @@ -793,9 +793,9 @@ export class DisassemblerView implements ProjectView { } // TODO: too many globals - refresh(moveCursor: boolean) { + refresh(moveCursor: boolean, centerAddr?: number) { let state = lastDebugState || platform.saveState(); // TODO? - let pc = state.c ? state.c.PC : 0; + let pc = centerAddr !== undefined ? centerAddr : (state.c ? state.c.PC : 0); let curline = 0; let selline = 0; let addr2symbol = (platform.debugSymbols && platform.debugSymbols.addr2symbol) || {}; @@ -862,6 +862,35 @@ export class DisassemblerView implements ProjectView { } } + // jump to an address (or symbol), via the Go To Address prompt + goToAddress(addr: number) { + addr |= 0; + if (!this.findAndSelectAddress(addr)) { + // re-center the disassembly window on this address + this.refresh(false, addr); + this.findAndSelectAddress(addr); + } + this.disasmview.focus(); + } + + private findAndSelectAddress(addr: number): boolean { + const h = hex(addr, 4); // every line starts with the address + tab + const doc = this.disasmview.state.doc; + for (let i = 1; i <= doc.lines; i++) { + const line = doc.line(i); + if (line.text.startsWith(h + "\t")) { + // select the address so the line stays highlighted; scrollIntoView + // centers it in the viewport + this.disasmview.dispatch({ + selection: { anchor: line.from, head: line.from + h.length }, + effects: EditorView.scrollIntoView(line.from, { y: "center" }), + }); + return true; + } + } + return false; + } + getCursorPC(): number { const pos = this.disasmview.state.selection.main.head; const lineNum = this.disasmview.state.doc.lineAt(pos).number;