var pxt; (function (pxt) { var runner; (function (runner) { /** * Starts the simulator and injects it into the provided container. * the simulator will attempt to establish a websocket connection * to the debugger's user interface on port 3234. * * @param container The container to inject the simulator into */ function startDebuggerAsync(container) { var debugRunner = new DebugRunner(container); debugRunner.start(); } runner.startDebuggerAsync = startDebuggerAsync; /** * Runner for the debugger that handles communication with the user * interface. Also talks to the server for anything to do with * the filesystem (like reading code) */ var DebugRunner = (function () { function DebugRunner(container) { this.container = container; this.pkgLoaded = false; this.intervalRunning = false; } DebugRunner.prototype.start = function () { var _this = this; this.initializeWebsocket(); if (!this.intervalRunning) { this.intervalRunning = true; this.intervalId = setInterval(function () { if (!_this.ws) { try { _this.initializeWebsocket(); } catch (e) { console.warn("Connection to server failed, retrying in " + DebugRunner.RETRY_MS + " ms"); } } }, DebugRunner.RETRY_MS); } this.session = new pxsim.SimDebugSession(this.container); this.session.start(this); }; DebugRunner.prototype.initializeWebsocket = function () { var _this = this; if (!pxt.Cloud.isLocalHost() || !pxt.Cloud.localToken) return; pxt.debug('initializing debug pipe'); this.ws = new WebSocket('ws://localhost:3234/' + pxt.Cloud.localToken + '/simdebug'); this.ws.onopen = function (ev) { pxt.debug('debug: socket opened'); }; this.ws.onclose = function (ev) { pxt.debug('debug: socket closed'); if (_this.closeListener) { _this.closeListener(); } _this.session.stopSimulator(); _this.ws = undefined; }; this.ws.onerror = function (ev) { pxt.debug('debug: socket closed due to error'); if (_this.errorListener) { _this.errorListener(ev.type); } _this.session.stopSimulator(); _this.ws = undefined; }; this.ws.onmessage = function (ev) { var message; try { message = JSON.parse(ev.data); } catch (e) { pxt.debug('debug: could not parse message'); } if (message) { // FIXME: ideally, we should just open two websockets instead of adding to the // debug protocol. One for the debugger, one for meta-information and file // system requests if (message.type === 'runner') { _this.handleRunnerMessage(message); } else { // Intercept the launch configuration and notify the server-side debug runner if (message.type === "request" && message.command === "launch") { _this.sendRunnerMessage("configure", { projectDir: message.arguments.projectDir }); } _this.dataListener(message); } } }; }; DebugRunner.prototype.send = function (msg) { this.ws.send(msg); }; DebugRunner.prototype.onData = function (cb) { this.dataListener = cb; }; DebugRunner.prototype.onError = function (cb) { this.errorListener = cb; }; DebugRunner.prototype.onClose = function (cb) { this.closeListener = cb; }; DebugRunner.prototype.close = function () { if (this.session) { this.session.stopSimulator(true); } if (this.intervalRunning) { clearInterval(this.intervalId); this.intervalId = undefined; } if (this.ws) { this.ws.close(); } }; DebugRunner.prototype.handleRunnerMessage = function (msg) { switch (msg.subtype) { case "ready": this.sendRunnerMessage("ready"); break; case "runcode": this.runCode(msg); break; } }; DebugRunner.prototype.runCode = function (msg) { var breakpoints = []; // The breakpoints are in the format returned by the compiler // and need to be converted to the format used by the DebugProtocol msg.breakpoints.forEach(function (bp) { breakpoints.push([bp.id, { verified: true, line: bp.line, column: bp.column, endLine: bp.endLine, endColumn: bp.endColumn, source: { path: bp.fileName } }]); }); this.session.runCode(msg.code, msg.usedParts, msg.usedArguments, new pxsim.BreakpointMap(breakpoints), pxt.appTarget.simulator.boardDefinition); }; DebugRunner.prototype.sendRunnerMessage = function (subtype, msg) { if (msg === void 0) { msg = {}; } msg["subtype"] = subtype; msg["type"] = "runner"; this.send(JSON.stringify(msg)); }; DebugRunner.RETRY_MS = 2500; return DebugRunner; }()); runner.DebugRunner = DebugRunner; })(runner = pxt.runner || (pxt.runner = {})); })(pxt || (pxt = {})); var pxt; (function (pxt) { var runner; (function (runner) { function appendBlocks($parent, $svg) { $parent.append($('
').append($svg)); } function appendJs($parent, $js, woptions) { $parent.append($('
').append($js)); $('code.highlight').each(function (i, block) { var hljs = pxt.docs.requireHighlightJs(); if (hljs) hljs.highlightBlock(block); }); } function fillWithWidget(options, $container, $js, $svg, woptions) { if (woptions === void 0) { woptions = {}; } if (!$svg || !$svg[0]) { var $c_1 = $('
'); $c_1.append($js); $container.replaceWith($c_1); return; } var cdn = pxt.webConfig.pxtCdnUrl; var images = cdn + "images"; var $h = $(''); var $c = $('
'); var $menu = $h.find('.right.menu'); if (options.showJavaScript) { // blocks $c.append($js); // js menu if ($svg) { var $svgBtn = $('').click(function () { if ($c.find('.blocks')[0]) $c.find('.blocks').remove(); else { if ($js) appendBlocks($js.parent(), $svg); else appendBlocks($c, $svg); } }); $menu.append($svgBtn); } } else { // blocks $c.append($svg); // js menu if (woptions.showJs) { appendJs($c, $js, woptions); } else { var $jsBtn = $('').click(function () { if ($c.find('.js')[0]) $c.find('.js').remove(); else { if ($svg) appendJs($svg.parent(), $js, woptions); else appendJs($c, $js, woptions); } }); $menu.append($jsBtn); } } // runner menu if (woptions.run) { var $runBtn = $('').click(function () { if ($c.find('.sim')[0]) $c.find('.sim').remove(); // remove previous simulators else { var padding = '81.97%'; if (pxt.appTarget.simulator) padding = (100 / pxt.appTarget.simulator.aspectRatio) + '%'; var $embed = $("
"); $c.append($embed); } }); $menu.append($runBtn); } if (woptions.hexname && woptions.hex) { var $hexBtn = $('').click(function () { pxt.BrowserUtils.browserDownloadBinText(woptions.hex, woptions.hexname, pxt.appTarget.compile.hexMimeType); }); $menu.append($hexBtn); } var r = [$c]; // don't add menu if empty if ($menu.children().length) r.push($h); // inject container $container.replaceWith(r); // download screenshots if (options.downloadScreenshots && woptions.hexname) { pxt.debug("Downloading screenshot for: " + woptions.hexname); var filename_1 = woptions.hexname.substr(0, woptions.hexname.lastIndexOf('.')); var fontSize = window.getComputedStyle($svg.get(0).querySelector(".blocklyText")).getPropertyValue("font-size"); var customCss = "\n.blocklyMainBackground {\n stroke:none !important;\n}\n\n.blocklyText {\n font-family:'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace !important;\n font-size:" + fontSize + " !important; \n}\n\n.blocklyCheckbox,\n.blocklyLed {\n fill: #ff3030 !important;\n text-shadow: 0px 0px 6px #f00;\n font-size: 17pt !important;\n}"; var svgElement = $svg.get(0); var bbox = $svg.get(0).getBoundingClientRect(); pxt.blocks.layout.svgToPngAsync(svgElement, customCss, 0, 0, bbox.width, bbox.height) .done(function (uri) { if (uri) pxt.BrowserUtils.browserDownloadDataUri(uri, (name || (pxt.appTarget.nickname || pxt.appTarget.id) + "-" + filename_1) + ".png"); }); } } function renderNextSnippetAsync(cls, render, options) { if (!cls) return Promise.resolve(); var $el = $("." + cls).first(); if (!$el[0]) return Promise.resolve(); if (!options.emPixels) options.emPixels = 14; if (!options.layout) options.layout = pxt.blocks.BlockLayout.Flow; return pxt.runner.decompileToBlocksAsync($el.text(), options) .then(function (r) { try { render($el, r); } catch (e) { console.error('error while rendering ' + $el.html()); $el.append($('
').addClass("ui segment warning").text(e.message)); } $el.removeClass(cls); return Promise.delay(1, renderNextSnippetAsync(cls, render, options)); }); } function renderSnippetsAsync(options) { if (options.tutorial) { // don't render chrome for tutorials return renderNextSnippetAsync(options.snippetClass, function (c, r) { var s = r.blocksSvg; if (options.snippetReplaceParent) c = c.parent(); var segment = $('
').append(s); c.replaceWith(segment); }, { package: options.package, snippetMode: false }); } var snippetCount = 0; return renderNextSnippetAsync(options.snippetClass, function (c, r) { var s = r.compileBlocks && r.compileBlocks.success ? $(r.blocksSvg) : undefined; var js = $('').text(c.text().trim()); if (options.snippetReplaceParent) c = c.parent(); var compiled = r.compileJS && r.compileJS.success; var hex = options.hex && compiled && r.compileJS.outfiles[pxtc.BINARY_HEX] ? r.compileJS.outfiles[pxtc.BINARY_HEX] : undefined; var hexname = (pxt.appTarget.nickname || pxt.appTarget.id) + "-" + (options.hexName || '') + "-" + snippetCount++ + ".hex"; fillWithWidget(options, c, js, s, { run: options.simulator && compiled, hexname: hexname, hex: hex, }); }, { package: options.package }); } function decompileCallInfo(stmt) { if (!stmt || stmt.kind != ts.SyntaxKind.ExpressionStatement) return null; var estmt = stmt; if (!estmt.expression || estmt.expression.kind != ts.SyntaxKind.CallExpression) return null; var call = estmt.expression; var info = call.callInfo; return info; } function renderSignaturesAsync(options) { return renderNextSnippetAsync(options.signatureClass, function (c, r) { var cjs = r.compileJS; if (!cjs) return; var file = r.compileJS.ast.getSourceFile("main.ts"); var info = decompileCallInfo(file.statements[0]); if (!info) return; var s = r.compileBlocks && r.compileBlocks.success ? $(r.blocksSvg) : undefined; var sig = info.decl.getText().replace(/^export/, ''); sig = sig.slice(0, sig.indexOf('{')).trim() + ';'; var js = $('').text(sig); if (options.snippetReplaceParent) c = c.parent(); fillWithWidget(options, c, js, s, { showJs: true, hideGutter: true }); }, { package: options.package, snippetMode: true }); } function renderShuffleAsync(options) { return renderNextSnippetAsync(options.shuffleClass, function (c, r) { var s = r.blocksSvg; if (options.snippetReplaceParent) c = c.parent(); var segment = $('
').append(s); c.replaceWith(segment); }, { emPixels: 14, layout: pxt.blocks.BlockLayout.Shuffle, aspectRatio: options.blocksAspectRatio, package: options.package }); } function renderBlocksAsync(options) { return renderNextSnippetAsync(options.blocksClass, function (c, r) { var s = r.blocksSvg; if (options.snippetReplaceParent) c = c.parent(); var segment = $('
').append(s); c.replaceWith(segment); }, { package: options.package, snippetMode: true }); } function renderInlineBlocksAsync(options) { options = pxt.Util.clone(options); options.emPixels = 18; options.snippetMode = true; var $els = $(":not(pre) > code"); var i = 0; function renderNextAsync() { if (i >= $els.length) return Promise.resolve(); var $el = $($els[i++]); var text = $el.text(); var mbtn = /^(\|+)([^\|]+)\|+$/.exec(text); if (mbtn) { var lev = mbtn[1].length == 1 ? "primary" : ""; var txt = mbtn[2]; $el.replaceWith($(""); $(content).find('#tryagain').click(function () { render(doctype, src); }); // notify parent iframe that docs weren't loaded if (window.parent) window.parent.postMessage({ type: "docfailed", docType: doctype, src: src }, "*"); }).finally(function () { $(loading).hide(); $(content).show(); }) .done(function () { }); } function renderHash() { var m = /^#(doc|md|tutorial):([^&?:]+)(:([^&?:]+):([^&?:]+))?/i.exec(window.location.hash); if (m) { // navigation occured var p = m[4] ? setEditorContextAsync(/^blocks$/.test(m[4]) ? LanguageMode.Blocks : LanguageMode.TypeScript, m[5]) : Promise.resolve(); p.then(function () { return render(m[1], decodeURIComponent(m[2])); }); } } window.addEventListener("message", receiveDocMessage, false); window.addEventListener("hashchange", function () { renderHash(); }, false); parent.postMessage({ type: "sidedocready" }, "*"); // delay load doc page to allow simulator to load first setTimeout(function () { return renderHash(); }, 1); } runner.startDocsServer = startDocsServer; function renderProjectAsync(content, projectid, template) { if (template === void 0) { template = "blocks"; } return pxt.Cloud.privateGetTextAsync(projectid + "/text") .then(function (txt) { return JSON.parse(txt); }) .then(function (files) { var md = "```" + template + "\n" + files["main.ts"] + "\n```"; return renderMarkdownAsync(content, md); }); } runner.renderProjectAsync = renderProjectAsync; function renderDocAsync(content, docid) { docid = docid.replace(/^\//, ""); return pxt.Cloud.downloadMarkdownAsync(docid, runner.editorLocale, pxt.Util.localizeLive) .then(function (md) { return renderMarkdownAsync(content, md, { path: docid }); }); } var template = "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n@breadcrumb@\n@body@"; function renderMarkdownAsync(content, md, options) { if (options === void 0) { options = {}; } var path = options.path; var parts = (path || '').split('/'); var bc = !path ? null : parts.map(function (e, i) { return { href: "/" + parts.slice(0, i + 1).join("/"), name: e }; }); var html = pxt.docs.renderMarkdown(template, md, pxt.appTarget.appTheme, null, bc); $(content).html(html); $(content).find('a').attr('target', '_blank'); return pxt.runner.renderAsync({ blocksAspectRatio: 0.5, snippetClass: 'lang-blocks', signatureClass: 'lang-sig', blocksClass: 'lang-block', shuffleClass: 'lang-shuffle', simulatorClass: 'lang-sim', linksClass: 'lang-cards', namespacesClass: 'lang-namespaces', codeCardClass: 'lang-codecard', packageClass: 'lang-package', projectClass: 'lang-project', snippetReplaceParent: true, simulator: true, hex: true, tutorial: !!options.tutorial, showJavaScript: runner.languageMode == LanguageMode.TypeScript, hexName: pxt.appTarget.id }).then(function () { // patch a elements $(content).find('a[href^="/"]').removeAttr('target').each(function (i, a) { $(a).attr('href', '#doc:' + $(a).attr('href').replace(/^\//, '')); }); // enable embeds $(content).find('.ui.embed').embed(); }); } runner.renderMarkdownAsync = renderMarkdownAsync; function renderTutorialAsync(content, tutorialid, step) { tutorialid = tutorialid.replace(/^\//, ""); return pxt.Cloud.downloadMarkdownAsync(tutorialid, runner.editorLocale, pxt.Util.localizeLive) .then(function (tutorialmd) { var steps = tutorialmd.split(/\###.*(?!$)/i); if (steps.length < 1) return; var options = steps[0]; steps = steps.slice(1, steps.length); // Extract toolbox block ids var uptoSteps = steps.slice(0, step + 1).join(); uptoSteps = uptoSteps.replace(/((?!.)\s)+/g, "\n"); var regex = /```(sim|block|blocks|shuffle)\n([\s\S]*?)\n```/gmi; var match; var code = ''; while ((match = regex.exec(uptoSteps)) != null) { code += match[2] + "\n"; } // Render current step return renderMarkdownAsync(content, steps[step], { tutorial: true }) .then(function () { if (code == '') return; // Convert all blocks to blocks return pxt.runner.decompileToBlocksAsync(code, { emPixels: 14, layout: pxt.blocks.BlockLayout.Flow, package: undefined }).then(function (r) { var blocksxml = r.compileBlocks.outfiles['main.blocks']; var toolboxSubset = {}; if (blocksxml) { var headless = pxt.blocks.loadWorkspaceXml(blocksxml); var allblocks = headless.getAllBlocks(); for (var bi = 0; bi < allblocks.length; ++bi) { var blk = allblocks[bi]; toolboxSubset[blk.type] = 1; } } if (toolboxSubset != {}) { window.parent.postMessage({ type: "tutorial", tutorial: tutorialid, subtype: "steploaded", data: toolboxSubset, location: "bottom" }, "*"); } }); }); }); } runner.renderTutorialAsync = renderTutorialAsync; function decompileToBlocksAsync(code, options) { return loadPackageAsync(options && options.package ? "docs:" + options.package : null, code) .then(function () { return getCompileOptionsAsync(pxt.appTarget.compile ? pxt.appTarget.compile.hasHex : false); }) .then(function (opts) { // compile opts.fileSystem["main.ts"] = code; opts.ast = true; var resp = pxtc.compile(opts); if (resp.diagnostics && resp.diagnostics.length > 0) resp.diagnostics.forEach(function (diag) { return console.error(diag.messageText); }); if (!resp.success) return Promise.resolve({ compileJS: resp }); // decompile to blocks var apis = pxtc.getApiInfo(resp.ast); return ts.pxtc.localizeApisAsync(apis, runner.mainPkg) .then(function () { var blocksInfo = pxtc.getBlocksInfo(apis); pxt.blocks.initBlocks(blocksInfo); var bresp = pxtc.decompiler.decompileToBlocks(blocksInfo, resp.ast.getSourceFile("main.ts"), { snippetMode: options && options.snippetMode }); if (bresp.diagnostics && bresp.diagnostics.length > 0) bresp.diagnostics.forEach(function (diag) { return console.error(diag.messageText); }); if (!bresp.success) return { compileJS: resp, compileBlocks: bresp }; pxt.debug(bresp.outfiles["main.blocks"]); return { compileJS: resp, compileBlocks: bresp, blocksSvg: pxt.blocks.render(bresp.outfiles["main.blocks"], options) }; }); }); } runner.decompileToBlocksAsync = decompileToBlocksAsync; runner.initCallbacks = []; function init() { initInnerAsync() .done(function () { for (var i = 0; i < runner.initCallbacks.length; ++i) { runner.initCallbacks[i](); } }); } runner.init = init; function windowLoad() { var f = window.ksRunnerWhenLoaded; if (f) f(); } windowLoad(); })(runner = pxt.runner || (pxt.runner = {})); })(pxt || (pxt = {}));