| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862 |
- import HostInteropType from "../interop";
- import * as debuggerProxy from "./HostDebuggerExtensionProxy";
- import {default as BreakpointDecoratorManager, Breakpoint} from "./BreakpointDecoratorManager";
- /*
- * Duktape debugger web client
- *
- * Talks to the NodeJS server using socket.io.
- *
- * http://unixpapa.com/js/key.html
- */
- export default class DuktapeDebugger {
- // Monaco editor
- editor: monaco.editor.IStandaloneCodeEditor;
- socket: SocketIOClient.Socket;
- breakpointDecorator: BreakpointDecoratorManager;
- // Update interval for custom source highlighting.
- SOURCE_UPDATE_INTERVAL = 350;
- // Source view
- activeFileName = null; // file that we want to be loaded in source view
- activeLine = null; // scroll to line once file has been loaded
- activeHighlight = null; // line that we want to highlight (if any)
- loadedFileName = null; // currently loaded (shown) file
- loadedLineCount = 0; // currently loaded file line count
- loadedFileExecuting = false; // true if currFileName (loosely) matches loadedFileName
- loadedLinePending = null; // if set, scroll loaded file to requested line
- highlightLine = null; // highlight line
- sourceEditedLines = []; // line numbers which have been modified
- // (added classes etc, tracked for removing)
- sourceUpdateInterval = null; // timer for updating source view
- sourceFetchXhr = null; // current AJAX request for fetching a source file (if any)
- forceButtonUpdate = false; // hack to reset button states
- bytecodeDialogOpen = false; // bytecode dialog active
- bytecodeIdxHighlight = null; // index of currently highlighted line (or null)
- bytecodeIdxInstr = 0; // index to first line of bytecode instructions
- // Execution state
- prevState = null; // previous execution state ("paused", "running", etc)
- prevAttached = null; // previous debugger attached state (true, false, null)
- currFileName = null; // current filename being executed
- currFuncName = null; // current function name being executed
- currLine = 0; // current line being executed
- currPc = 0; // current bytecode PC being executed
- // current execution state ("paused", "running", "detached", etc)
- currState: "connected" | "disconnected" | "running" | "paused" | "reconnecting";
- currAttached = false; // current debugger attached state (true or false)
- currLocals = []; // current local variables
- currCallstack = []; // current callstack (from top to bottom)
- currBreakpoints: Breakpoint[] = []; // current breakpoints
- startedRunning = 0; // timestamp when last started running (if running)
- // (used to grey out the source file if running for long enough)
- /*
- * Helpers
- */
- formatBytes(x) {
- if (x < 1024) {
- return String(x) + " bytes";
- } else if (x < 1024 * 1024) {
- return (x / 1024).toPrecision(3) + " kB";
- } else {
- return (x / (1024 * 1024)).toPrecision(3) + " MB";
- }
- }
- /*
- * Source view periodic update handling
- */
- doSourceUpdate() {
- var elem;
- // Remove previously added custom classes
- this.sourceEditedLines.forEach((linenum) => {
- elem = $("#source-code div")[linenum - 1];
- if (elem) {
- elem.classList.remove("breakpoint");
- elem.classList.remove("execution");
- elem.classList.remove("highlight");
- }
- });
- this.sourceEditedLines.length = 0;
- // If we"re executing the file shown, highlight current line
- if (this.loadedFileExecuting) {
- this.editor.revealLineInCenterIfOutsideViewport(this.currLine);
- this.editor.setPosition(new monaco.Position(this.currLine, 0));
- this.sourceEditedLines.push(this.currLine);
- elem = $("#source-code div")[this.currLine - 1];
- if (elem) {
- //sourceEditedLines.push(currLine);
- elem.classList.add("execution");
- }
- }
- // Add breakpoints
- this.breakpointDecorator.setCurrentFileName(this.loadedFileName);
- this.currBreakpoints.forEach((bp) => {
- if (bp.fileName === this.loadedFileName) {
- this.breakpointDecorator.addBreakpointDecoration(bp.fileName, bp.lineNumber);
- this.sourceEditedLines.push(bp.lineNumber);
- }
- });
- if (this.highlightLine !== null) {
- elem = $("#source-code div")[this.highlightLine - 1];
- if (elem) {
- this.sourceEditedLines.push(this.highlightLine);
- elem.classList.add("highlight");
- }
- }
- // Bytecode dialog highlight
- if (this.loadedFileExecuting && this.bytecodeDialogOpen && this.bytecodeIdxHighlight !== this.bytecodeIdxInstr + this.currPc) {
- if (typeof this.bytecodeIdxHighlight === "number") {
- $("#bytecode-preformatted div")[this.bytecodeIdxHighlight].classList.remove("highlight");
- }
- this.bytecodeIdxHighlight = this.bytecodeIdxInstr + this.currPc;
- $("#bytecode-preformatted div")[this.bytecodeIdxHighlight].classList.add("highlight");
- }
- // If no-one requested us to scroll to a specific line, finish.
- if (this.loadedLinePending == null) {
- return;
- }
- // Scroll to the requested line
- var reqLine = this.loadedLinePending;
- this.loadedLinePending = null;
- this.editor.revealLineInCenterIfOutsideViewport(reqLine);
- this.editor.setPosition(new monaco.Position(reqLine, 0));
- debuggerProxy.notifyHostCurrentSourcePosition("Resources/" + this.loadedFileName, reqLine);
- }
- /*
- * UI update handling when exec-status update arrives
- */
- doUiUpdate() {
- var now = Date.now();
- // Note: loadedFileName can be either from target or from server, but they
- // must match exactly. We could do a loose match here, but exact matches
- // are needed for proper breakpoint handling anyway.
- this.loadedFileExecuting = (this.loadedFileName === this.currFileName);
- // If we just started running, store a timestamp so we can grey out the
- // source view only if we execute long enough (i.e. we"re not just
- // stepping).
- if (this.currState !== this.prevState && this.currState === "running") {
- this.startedRunning = now;
- }
- // If we just became paused, check for eval watch
- if (this.currState !== this.prevState && this.currState === "paused") {
- if ($("#eval-watch").is(":checked")) {
- this.submitEval(); // don"t clear eval input
- }
- }
- // Update current execution state
- if (this.currFileName === "" && this.currLine === 0) {
- $("#current-fileline").text("");
- } else {
- $("#current-fileline").text(String(this.currFileName) + ":" + String(this.currLine));
- }
- if (this.currFuncName === "" && this.currPc === 0) {
- $("#current-funcpc").text("");
- } else {
- $("#current-funcpc").text(String(this.currFuncName) + "() pc " + String(this.currPc));
- }
- $("#current-state").text(String(this.currState));
- // Update buttons
- if (this.currState !== this.prevState || this.currAttached !== this.prevAttached || this.forceButtonUpdate) {
- $("#stepinto-button").prop("disabled", !this.currAttached || this.currState !== "paused");
- $("#stepover-button").prop("disabled", !this.currAttached || this.currState !== "paused");
- $("#stepout-button").prop("disabled", !this.currAttached || this.currState !== "paused");
- $("#resume-button").prop("disabled", !this.currAttached || this.currState !== "paused");
- $("#pause-button").prop("disabled", !this.currAttached || this.currState !== "running");
- $("#attach-button").prop("disabled", this.currAttached);
- if (this.currAttached) {
- $("#attach-button").removeClass("enabled");
- } else {
- $("#attach-button").addClass("enabled");
- }
- $("#detach-button").prop("disabled", !this.currAttached);
- $("#eval-button").prop("disabled", !this.currAttached);
- $("#add-breakpoint-button").prop("disabled", !this.currAttached);
- $("#delete-all-breakpoints-button").prop("disabled", !this.currAttached);
- $(".delete-breakpoint-button").prop("disabled", !this.currAttached);
- $("#putvar-button").prop("disabled", !this.currAttached);
- $("#getvar-button").prop("disabled", !this.currAttached);
- $("#heap-dump-download-button").prop("disabled", !this.currAttached);
- }
- if (this.currState !== "running" || this.forceButtonUpdate) {
- // Remove pending highlight once we"re no longer running.
- $("#pause-button").removeClass("pending");
- $("#eval-button").removeClass("pending");
- }
- this.forceButtonUpdate = false;
- // Make source window grey when running for a longer time, use a small
- // delay to avoid flashing grey when stepping.
- if (this.currState === "running" && now - this.startedRunning >= 500) {
- $("#source-pre").removeClass("notrunning");
- $("#current-state").removeClass("notrunning");
- } else {
- $("#source-pre").addClass("notrunning");
- $("#current-state").addClass("notrunning");
- }
- // Force source view to match currFileName only when running or when
- // just became paused (from running or detached).
- var fetchSource = false;
- if (typeof this.currFileName === "string") {
- if (this.currState === "running" ||
- (this.prevState !== "paused" && this.currState === "paused") ||
- (this.currAttached !== this.prevAttached)) {
- if (this.activeFileName !== this.currFileName) {
- fetchSource = true;
- this.activeFileName = this.currFileName;
- this.activeLine = this.currLine;
- this.activeHighlight = null;
- this.requestSourceRefetch();
- }
- }
- }
- // Force line update (scrollTop) only when running or just became paused.
- // Otherwise let user browse and scroll source files freely.
- if (!fetchSource) {
- if ((this.prevState !== "paused" && this.currState === "paused") ||
- this.currState === "running") {
- this.loadedLinePending = this.currLine || 0;
- }
- }
- }
- deleteAllBreakpoints(notifyHost = true) {
- this.socket.emit("delete-all-breakpoints");
- this.breakpointDecorator.clearBreakpointDecorations();
- if (notifyHost) {
- debuggerProxy.removeAllBreakpoints();
- }
- }
- addBreakpoint(fileName: string, lineNumber: number, notifyHost = true) {
- fileName = this.fixBreakpointFilename(fileName);
- this.breakpointDecorator.addBreakpointDecoration(fileName, lineNumber);
- this.socket.emit("add-breakpoint", {
- fileName,
- lineNumber
- });
- if (notifyHost) {
- debuggerProxy.addBreakpoint(fileName, lineNumber);
- }
- }
- toggleBreakpoint(fileName: string, lineNumber: number, notifyHost = true) {
- fileName = this.fixBreakpointFilename(fileName);
- this.breakpointDecorator.toggleBreakpoint(fileName, lineNumber);
- this.socket.emit("toggle-breakpoint", {
- fileName,
- lineNumber
- });
- if (notifyHost) {
- debuggerProxy.toggleBreakpoint(fileName, lineNumber);
- }
- }
- removeBreakpoint(fileName: string, lineNumber: number, notifyHost = true) {
- fileName = this.fixBreakpointFilename(fileName);
- this.breakpointDecorator.removeBreakpointDecoration(fileName, lineNumber);
- this.socket.emit("delete-breakpoint", {
- fileName,
- lineNumber
- });
- if (notifyHost) {
- debuggerProxy.removeBreakpoint(fileName, lineNumber);
- }
- }
- pause() {
- this.socket.emit("pause", {});
- // Pause may take seconds to complete so indicate it is pending.
- $("#pause-button").addClass("pending");
- }
- resume() {
- this.socket.emit("resume", {});
- }
- stepInto() {
- this.socket.emit("stepinto", {});
- setTimeout(() => this.doSourceUpdate(), 125);
- }
- stepOut() {
- this.socket.emit("stepout", {});
- setTimeout(() => this.doSourceUpdate(), 125);
- }
- stepOver() {
- this.socket.emit("stepover", {});
- setTimeout(() => this.doSourceUpdate(), 125);
- }
- initSocket() {
- /*
- * Init socket.io and add handlers
- */
- this.socket = io.connect("http://localhost:9092"); // returns a Manager
- setInterval(() => {
- this.socket.emit("keepalive", {
- userAgent: (navigator || {} as Navigator).userAgent
- });
- }, 30000);
- this.socket.on("connect", () => {
- $("#socketio-info").text("connected");
- this.currState = "connected";
- this.fetchSourceList();
- });
- this.socket.on("disconnect", () => {
- $("#socketio-info").text("not connected");
- this.currState = "disconnected";
- });
- this.socket.on("reconnecting", () => {
- $("#socketio-info").text("reconnecting");
- this.currState = "reconnecting";
- });
- this.socket.on("error", (err) => {
- $("#socketio-info").text(err);
- });
- this.socket.on("replaced", () => {
- // XXX: how to minimize the chance we"ll further communciate with the
- // server or reconnect to it? socket.reconnection()?
- // We"d like to window.close() here but can"t (not allowed from scripts).
- // Alert is the next best thing.
- alert("Debugger connection replaced by a new one, do you have multiple tabs open? If so, please close this tab.");
- });
- this.socket.on("keepalive", (msg) => {
- // Not really interesting in the UI
- // $("#server-info").text(new Date() + ": " + JSON.stringify(msg));
- });
- this.socket.on("basic-info", (msg) => {
- $("#duk-version").text(String(msg.duk_version));
- $("#duk-git-describe").text(String(msg.duk_git_describe));
- $("#target-info").text(String(msg.target_info));
- $("#endianness").text(String(msg.endianness));
- });
- this.socket.on("exec-status", (msg) => {
- // Not 100% reliable if callstack has several functions of the same name
- if (this.bytecodeDialogOpen && (this.currFileName != msg.fileName || this.currFuncName != msg.funcName)) {
- this.socket.emit("get-bytecode", {});
- }
- this.currFileName = msg.fileName;
- this.currFuncName = msg.funcName;
- this.currLine = msg.line;
- this.currPc = msg.pc;
- this.currState = msg.state;
- this.currAttached = msg.attached;
- // Duktape now restricts execution status updates quite effectively so
- // there"s no need to rate limit UI updates now.
- this.doUiUpdate();
- this.prevState = this.currState;
- this.prevAttached = this.currAttached;
- });
- // Update the "console" output based on lines sent by the server. The server
- // rate limits these updates to keep the browser load under control. Even
- // better would be for the client to pull this (and other stuff) on its own.
- this.socket.on("output-lines", (msg) => {
- var elem = $("#output");
- var i, n, ent;
- elem.empty();
- for (i = 0, n = msg.length; i < n; i++) {
- ent = msg[i];
- if (ent.type === "print") {
- elem.append($("<div></div>").text(ent.message));
- } else if (ent.type === "alert") {
- elem.append($("<div class='alert'></div>").text(ent.message));
- } else if (ent.type === "log") {
- elem.append($("<div class='log loglevel' + ent.level + ''></div>").text(ent.message));
- } else if (ent.type === "debugger-info") {
- elem.append($("<div class='debugger-info'><div>").text(ent.message));
- } else if (ent.type === "debugger-debug") {
- elem.append($("<div class='debugger-debug'><div>").text(ent.message));
- } else {
- elem.append($("<div></div>").text(ent.message));
- }
- }
- // http://stackoverflow.com/questions/14918787/jquery-scroll-to-bottom-of-div-even-after-it-updates
- // Stop queued animations so that we always scroll quickly to bottom
- $("#output").stop(true);
- $("#output").animate({ scrollTop: $("#output")[0].scrollHeight }, 1000);
- });
- this.socket.on("callstack", (msg) => {
- var elem = $("#callstack");
- var s1, s2, div;
- this.currCallstack = msg.callstack;
- elem.empty();
- msg.callstack.forEach((e) => {
- s1 = $("<a class='rest'></a>").text(e.fileName + ":" + e.lineNumber + " (pc " + e.pc + ")"); // float
- s1.on("click", () => {
- this.activeFileName = e.fileName;
- this.activeLine = e.lineNumber || 1;
- this.activeHighlight = this.activeLine;
- this.requestSourceRefetch();
- });
- s2 = $("<span class='func'></span>").text(e.funcName + "()");
- div = $("<div></div>");
- div.append(s1);
- div.append(s2);
- elem.append(div);
- });
- });
- this.socket.on("locals", (msg) => {
- var elem = $("#locals");
- var s1, s2, div;
- var i, n, e;
- this.currLocals = msg.locals;
- elem.empty();
- for (i = 0, n = msg.locals.length; i < n; i++) {
- e = msg.locals[i];
- s1 = $("<span class='value'></span>").text(e.value); // float
- s2 = $("<span class='key'></span>").text(e.key);
- div = $("<div></div>");
- div.append(s1);
- div.append(s2);
- elem.append(div);
- }
- });
- this.socket.on("debug-stats", (msg) => {
- $("#debug-rx-bytes").text(this.formatBytes(msg.rxBytes));
- $("#debug-rx-dvalues").text(msg.rxDvalues);
- $("#debug-rx-messages").text(msg.rxMessages);
- $("#debug-rx-kbrate").text((msg.rxBytesPerSec / 1024).toFixed(2));
- $("#debug-tx-bytes").text(this.formatBytes(msg.txBytes));
- $("#debug-tx-dvalues").text(msg.txDvalues);
- $("#debug-tx-messages").text(msg.txMessages);
- $("#debug-tx-kbrate").text((msg.txBytesPerSec / 1024).toFixed(2));
- });
- this.socket.on("breakpoints", (msg) => {
- var elem = $("#breakpoints");
- var div;
- var sub;
- this.currBreakpoints = msg.breakpoints;
- elem.empty();
- // First line is special
- div = $("<div></div>");
- sub = $("<button id='delete-all-breakpoints-button'></button>").text("Delete all breakpoints");
- sub.on("click", () => {
- this.deleteAllBreakpoints();
- });
- div.append(sub);
- //sub = $("<input id='add-breakpoint-file'></input>").val("file.js");
- //div.append(sub);
- //sub = $("<span></span>").text(":");
- //div.append(sub);
- //sub = $("<input id='add-breakpoint-line'></input>").val("123");
- //div.append(sub);
- //sub = $("<button id='add-breakpoint-button'></button>").text("Add breakpoint");
- //sub.on("click", () => {
- //this.addBreakpoint($("#add-breakpoint-file").val(), Number($("#add-breakpoint-line").val()));
- //});
- //div.append(sub);
- //sub = $("<span id='breakpoint-hint'></span>").text("or dblclick source");
- //div.append(sub);
- elem.append(div);
- // Active breakpoints follow
- msg.breakpoints.forEach((bp) => {
- var div;
- var sub;
- div = $("<div class='breakpoint-line'></div>");
- sub = $("<button class='delete-breakpoint-button'></button>").text("Delete");
- sub.on("click", () => {
- this.removeBreakpoint(bp.fileName, bp.lineNumber);
- });
- div.append(sub);
- sub = $("<a></a>").text((bp.fileName || "?") + ":" + (bp.lineNumber || 0));
- sub.on("click", () => {
- this.activeFileName = bp.fileName || "";
- this.activeLine = bp.lineNumber || 1;
- this.activeHighlight = this.activeLine;
- this.requestSourceRefetch();
- });
- div.append(sub);
- elem.append(div);
- });
- this.forceButtonUpdate = true;
- this.doUiUpdate();
- });
- this.socket.on("eval-result", (msg) => {
- $("#eval-output").text((msg.error ? "ERROR: " : "") + msg.result);
- // Remove eval button "pulsating" glow when we get a result
- $("#eval-button").removeClass("pending");
- });
- this.socket.on("getvar-result", (msg) => {
- $("#var-output").text(msg.found ? msg.result : "NOTFOUND");
- });
- this.socket.on("bytecode", (msg) => {
- var elem, div;
- elem = $("#bytecode-preformatted");
- elem.empty();
- msg.preformatted.split("\n").forEach((line, idx) => {
- div = $("<div></div>");
- div.text(line);
- elem.append(div);
- });
- this.bytecodeIdxHighlight = null;
- this.bytecodeIdxInstr = msg.idxPreformattedInstructions;
- });
- $("#stepinto-button").click(() => this.stepInto());
- $("#stepover-button").click(() => this.stepOver());
- $("#stepout-button").click(() => this.stepOut());
- $("#pause-button").click(() => this.pause());
- $("#resume-button").click(() => this.resume());
- $("#attach-button").click(() => {
- this.socket.emit("attach", {});
- this.retrieveBreakpoints();
- });
- $("#detach-button").click(() => {
- this.socket.emit("detach", {});
- });
- $("#about-button").click(() => {
- $("#about-dialog").dialog("open");
- });
- $("#show-bytecode-button").click(() => {
- this.bytecodeDialogOpen = true;
- $("#bytecode-dialog").dialog("open");
- let elem = $("#bytecode-preformatted");
- elem.empty().text("Loading bytecode...");
- this.socket.emit("get-bytecode", {});
- });
- $("#eval-button").click(() => {
- this.submitEval();
- $("#eval-input").val("");
- });
- $("#getvar-button").click(() => {
- this.socket.emit("getvar", { varname: $("#varname-input").val() });
- });
- $("#putvar-button").click(() => {
- // The variable value is parsed as JSON right now, but it"d be better to
- // also be able to parse buffer values etc.
- var val = JSON.parse($("#varvalue-input").val());
- this.socket.emit("putvar", { varname: $("#varname-input").val(), varvalue: val });
- });
- $("#source-code").dblclick((event) => {
- var target = event.target;
- var elems = $("#source-code div");
- var i, n;
- var line = 0;
- // XXX: any faster way; elems doesn"t have e.g. indexOf()
- for (i = 0, n = elems.length; i < n; i++) {
- if (target === elems[i]) {
- line = i + 1;
- }
- }
- this.toggleBreakpoint(this.loadedFileName, line);
- });
- }
- submitEval() {
- this.socket.emit("eval", { input: $("#eval-input").val() });
- // Eval may take seconds to complete so indicate it is pending.
- $("#eval-button").addClass("pending");
- }
- setSourceText(data) {
- this.editor.deltaDecorations([], []);
- this.editor.getModel().setValue(data);
- this.sourceEditedLines = [];
- }
- /*
- * AJAX request handling to fetch source files
- */
- requestSourceFile(fileName: string, lineNumber: number) {
- console.log(`Retrieving File: ${fileName}`);
- // get the code
- return HostInteropType.getInstance().getFileResource("Resources/" + fileName).then((data: string) => {
- this.loadedFileName = fileName;
- this.loadedLineCount = data.split("\n").length; // XXX: ignore issue with last empty line for now
- this.loadedFileExecuting = (this.loadedFileName === this.currFileName);
- this.setSourceText(data);
- this.loadedLinePending = this.activeLine || 1;
- this.highlightLine = this.activeHighlight; // may be null
- this.activeLine = null;
- this.activeHighlight = null;
- this.doSourceUpdate();
- }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
- console.log("Error loading code: " + e.error_message);
- // Not worth alerting about because source fetch errors happen
- // all the time, e.g. for dynamically evaluated code.
- this.sourceFetchXhr = null;
- // XXX: prevent retry of no-such-file by negative caching?
- this.loadedFileName = fileName;
- this.loadedLineCount = 1;
- this.loadedFileExecuting = false;
- this.setSourceText("// Cannot load source file: " + fileName);
- this.loadedLinePending = 1;
- this.activeLine = null;
- this.activeHighlight = null;
- this.doSourceUpdate();
- });
- }
- requestSourceRefetch() {
- if (!this.activeFileName) {
- return;
- }
- this.requestSourceFile(this.activeFileName, this.activeLine).then(() => {
- // XXX: hacky transition, make source change visible
- $("#source-pre").fadeTo("fast", 0.25, () => {
- $("#source-pre").fadeTo("fast", 1.0);
- });
- }).catch((e: Editor.ClientExtensions.AtomicErrorMessage) => {
- // XXX: error transition here
- console.log("Error loading code: " + e.error_message);
- $("#source-pre").fadeTo("fast", 0.25, function() {
- $("#source-pre").fadeTo("fast", 1.0);
- });
- });
- }
- /*
- * AJAX request for fetching the source list
- */
- fetchSourceList() {
- /* TODO: Fix Ajax
- $.ajax({
- type: "POST",
- url: "/sourceList",
- data: JSON.stringify({}),
- contentType: "application/json",
- success: function(data, status, jqxhr) {
- var elem = $("#source-select");
- data = JSON.parse(data);
- elem.empty();
- var opt = $("<option></option>").attr({ "value": "__none__" }).text("No source file selected");
- elem.append(opt);
- data.forEach(function(ent) {
- var opt = $("<option></option>").attr({ "value": ent }).text(ent);
- elem.append(opt);
- });
- elem.change(function() {
- activeFileName = elem.val();
- activeLine = 1;
- requestSourceRefetch();
- });
- },
- error: function(jqxhr, status, err) {
- // This is worth alerting about as the UI is somewhat unusable
- // if we don"t get a source list.
- alert("Failed to load source list: " + err);
- },
- dataType: "text"
- });
- */
- }
- /*
- * Initialization
- */
- initialize(editorProvided) {
- this.editor = editorProvided;
- this.breakpointDecorator = new BreakpointDecoratorManager(editorProvided);
- var showAbout = true;
- this.initSocket();
- // Source is updated periodically. Other code can also call doSourceUpdate()
- // directly if an immediate update is needed.
- // this.sourceUpdateInterval = setInterval(this.doSourceUpdate.bind(this), this.SOURCE_UPDATE_INTERVAL);
- this.editor.onMouseMove((e) => {
- var targetZone = e.target.toString();
- if (targetZone.indexOf("GUTTER_GLYPH_MARGIN") != -1) {
- var line = e.target.position.lineNumber;
- this.breakpointDecorator.updateMarginHover(line);
- } else {
- this.breakpointDecorator.removeMarginHover();
- }
- });
- this.editor.onMouseDown((e) => {
- var targetZone = e.target.toString();
- if (targetZone.indexOf("GUTTER_GLYPH_MARGIN") != -1) {
- var line = e.target.position.lineNumber;
- this.toggleBreakpoint(this.loadedFileName, line);
- }
- });
- // About dialog, shown automatically on first startup.
- $("#about-dialog").dialog({
- autoOpen: false,
- hide: "fade", // puff
- show: "fade", // slide, puff
- width: 500,
- height: 300
- });
- // Bytecode dialog
- $("#bytecode-dialog").dialog({
- autoOpen: false,
- hide: "fade", // puff
- show: "fade", // slide, puff
- width: 700,
- height: 600,
- close: () => {
- this.bytecodeDialogOpen = false;
- this.bytecodeIdxHighlight = null;
- this.bytecodeIdxInstr = 0;
- }
- });
- // http://diveintohtml5.info/storage.html
- if (typeof localStorage !== "undefined") {
- if (localStorage.getItem("about-shown")) {
- showAbout = false;
- } else {
- localStorage.setItem("about-shown", "yes");
- }
- }
- if (showAbout) {
- $("#about-dialog").dialog("open");
- }
- // onclick handler for exec status text
- function loadCurrFunc() {
- this.activeFileName = this.currFileName;
- this.activeLine = this.currLine;
- this.requestSourceRefetch();
- }
- $("#exec-other").on("click", loadCurrFunc);
- // Enter handling for eval input
- // https://forum.jquery.com/topic/bind-html-input-to-enter-key-keypress
- $("#eval-input").keypress((event) => {
- if (event.keyCode == 13) {
- this.submitEval();
- $("#eval-input").val("");
- }
- });
- // Eval watch handling
- $("#eval-watch").change(() => {
- // nop
- });
- this.registerDebuggerFunctions();
- this.forceButtonUpdate = true;
- this.doUiUpdate();
- };
- async retrieveBreakpoints() {
- let s = await debuggerProxy.getBreakpoints();
- // If the filename starts with "Resources" then trim it off since the module
- // name won't have that, but the editor uses it
- s.forEach(b => this.addBreakpoint(b.fileName, b.lineNumber));
- }
- registerDebuggerFunctions() {
- // Register the callback functions
- const interop = HostInteropType.getInstance();
- interop.addCustomHostRoutine(
- debuggerProxy.debuggerHostKeys.toggleBreakpoint,
- this.toggleBreakpoint.bind(this));
- interop.addCustomHostRoutine(
- debuggerProxy.debuggerHostKeys.addBreakpoint,
- this.addBreakpoint.bind(this));
- interop.addCustomHostRoutine(
- debuggerProxy.debuggerHostKeys.removeBreakpoint,
- this.removeBreakpoint.bind(this));
- interop.addCustomHostRoutine(
- debuggerProxy.debuggerHostKeys.pause,
- this.pause.bind(this));
- debuggerProxy.registerDebuggerListener("DuktapeDebugger");
- }
- fixBreakpointFilename(fileName: string): string {
- return fileName.replace(/^Resources\//, "");
- }
- }
|