6Elm.debuggerAttach = function(module, hotSwapState /* =undefined */) {
8 make: function(runtime) {
9 var wrappedModule = debugModule(module, runtime);
10 Elm.Debugger = debuggerInit(wrappedModule, runtime, hotSwapState);
11 dispatchElmDebuggerInit();
12 return wrappedModule.moduleInstance;
17function dispatchElmDebuggerInit() {
19 var dispatch = function(_) {
20 parent.window.postMessage("elmDebuggerInit", window.location.origin);
22 if (parent.window.document.readyState === "complete") {
26 parent.window.addEventListener("load", dispatch);
31function debugModule(module, runtime) {
32 var programPaused = false;
33 var recordedEvents = [];
34 var asyncCallbacks = [];
36 function wrapNotify(id, v) {
37 var timestep = Date.now();
40 // ignore async events generated while playing back
41 // or user events while program is paused
45 var changed = runtime.notify(id, v, timestep);
46 recordEvent(id, v, timestep);
48 parent.window.postMessage("elmNotify", window.location.origin);
54 function wrapRunDelayed(func, delayMs) {
55 var cbObj = { func:func, delayMs:delayMs, timerId:0, executed:false };
56 var timerId = setTimeout(function() {
57 cbObj.executed = true;
60 cbObj.timerId = timerId;
61 asyncCallbacks.push(cbObj);
65 function recordEvent(id, v, timestep) {
66 recordedEvents.push({ id:id, value:v, timestep:timestep });
69 function clearAsyncCallbacks() {
70 asyncCallbacks.forEach(function(timer) {
71 if (!timer.executed) {
72 clearTimeout(timer.timerId);
77 function clearRecordedEvents() {
81 function getRecordedEventsLength() {
82 return recordedEvents.length;
85 function getRecordedEventAt(i) {
86 return recordedEvents[i];
89 function copyRecordedEvents() {
90 return recordedEvents.slice();
93 function loadRecordedEvents(events) {
94 recordedEvents = events.slice();
97 function setPaused(v) {
100 clearAsyncCallbacks();
101 tracePath.stopRecording();
104 // executeCallbacks should only be called when continuing, not when restarting.
105 //executeCallbacks(asyncCallbacks, false);
106 tracePath.startRecording();
110 function getPaused() {
111 return programPaused;
114 // runtime is the prototype of wrappedRuntime
115 // so we can access all runtime properties too
116 var wrappedRuntime = Object.create(runtime, {
118 runDelayed: wrapRunDelayed
121 var moduleInstance = module.make(wrappedRuntime);
122 var moduleNodes = flattenNodes(wrappedRuntime.inputs);
123 var tracePath = tracePathInit(runtime, moduleInstance.main);
126 moduleInstance: moduleInstance,
127 moduleNodes: moduleNodes,
128 initialNodeValues: saveNodeValues(moduleNodes),
129 initialAsyncCallbacks: asyncCallbacks.slice(),
131 clearAsyncCallbacks: clearAsyncCallbacks,
132 clearRecordedEvents: clearRecordedEvents,
133 getRecordedEventsLength: getRecordedEventsLength,
134 getRecordedEventAt: getRecordedEventAt,
135 copyRecordedEvents: copyRecordedEvents,
136 loadRecordedEvents: loadRecordedEvents,
137 getPaused: getPaused,
138 setPaused: setPaused,
143function debuggerInit(debugModule, runtime, hotSwapState /* =undefined */) {
144 var eventCounter = 0;
146 function resetProgram() {
147 debugModule.clearAsyncCallbacks();
148 restoreNodeValues(debugModule.moduleNodes, debugModule.initialNodeValues);
152 function restartProgram() {
154 debugModule.tracePath.clearTraces();
155 debugModule.clearRecordedEvents();
156 debugModule.setPaused(false);
157 executeCallbacks(debugModule.initialAsyncCallbacks, true);
160 function pauseProgram() {
161 debugModule.setPaused(true);
162 eventCounter = debugModule.getRecordedEventsLength();
165 function continueProgram() {
166 if (debugModule.getPaused())
168 debugModule.setPaused(false);
172 function stepTo(index) {
173 if (!debugModule.getPaused()) {
174 debugModule.setPaused(true);
178 if (index < 0 || index > getMaxSteps()) {
179 throw "Index out of bounds: " + index;
182 if (index < eventCounter) {
187 assert(index >= eventCounter, "index must be bad");
188 while (eventCounter < index) {
189 var nextEvent = debugModule.getRecordedEventAt(eventCounter);
190 runtime.notify(nextEvent.id, nextEvent.value, nextEvent.timestep);
194 assert(eventCounter == index, "while loop didn't work");
197 function getMaxSteps() {
198 return debugModule.getRecordedEventsLength();
201 function doPlayback(eventList) {
202 var x = eventList.shift();
204 runtime.notify(x[0], x[1], x[2]);
206 if (eventList.length > 0) {
207 var delta = eventList[0][2] - time;
208 setTimeout(function() { doPlayback(eventList); }, delta);
212 function redrawGraphics() {
213 var graphicsNode = debugModule.moduleInstance.main.kids[0];
214 graphicsNode.recv(Date.now(), true, debugModule.moduleInstance.main.id);
217 function getHotSwapState() {
218 var counter = eventCounter;
219 if (!debugModule.getPaused()) {
220 counter = getMaxSteps();
223 recordedEvents: debugModule.copyRecordedEvents(),
224 eventCounter: counter
229 var parentNode = runtime.node.parentNode;
230 parentNode.removeChild(debugModule.tracePath.canvas);
231 parentNode.removeChild(runtime.node);
235 debugModule.setPaused(true);
236 debugModule.loadRecordedEvents(hotSwapState.recordedEvents);
238 // draw new trace path
239 debugModule.tracePath.startRecording();
240 stepTo(getMaxSteps());
241 debugModule.tracePath.stopRecording();
243 stepTo(hotSwapState.eventCounter);
246 runtime.node.parentNode.appendChild(debugModule.tracePath.canvas);
249 restart: restartProgram,
251 kontinue: continueProgram,
252 getMaxSteps: getMaxSteps,
254 getPaused: debugModule.getPaused,
255 getHotSwapState: getHotSwapState,
257 allNodes: debugModule.moduleNodes,
258 mainNode: debugModule.moduleInstance.main
264function Point(x, y) {
268 this.translate = function(x, y) {
269 return new Point(this.x + x, this.y + y);
272 this.equals = function(p) {
273 return this.x == p.x && this.y == p.y;
277function tracePathInit(runtime, mainNode) {
278 var List = Elm.List.make(runtime);
279 var Signal = Elm.Signal.make(runtime);
280 var tracePathNode = A2(Signal.lift, graphicsUpdate, mainNode);
281 var tracePathCanvas = createCanvas();
282 var tracePositions = {};
283 var recordingTraces = true;
285 function findPositions(currentScene) {
287 function processElement(elem, offset) {
288 if (elem.element.ctor == "Custom" && elem.element.type == "Collage")
290 List.map(F2(processForm)(offset))(elem.element.model.forms);
292 else if (elem.element.ctor == "Image" && elem.props.debugTracePathId)
294 positions[elem.props.debugTracePathId] = new Point(offset.x, offset.y);
298 function processForm(offset, form) {
299 if (form.form.ctor == "FElement")
301 processElement(form.form._0, offset.translate(form.x, -form.y));
305 processElement(currentScene, new Point(0, 0));
309 function appendPositions(positions) {
310 for (var id in positions) {
311 var pos = positions[id];
312 if (tracePositions.hasOwnProperty(id)) {
313 var points = tracePositions[id];
314 if (!pos.equals(points[points.length-1])) {
319 tracePositions[id] = [pos];
324 function graphicsUpdate(currentScene) {
325 if (!recordingTraces) {
329 var ctx = tracePathCanvas.getContext('2d');
330 ctx.clearRect(0, 0, tracePathCanvas.width, tracePathCanvas.height);
333 ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2);
334 appendPositions(findPositions(currentScene));
335 for (var id in tracePositions)
338 var points = tracePositions[id];
339 for (var i=0; i < points.length; i++)
343 ctx.moveTo(p.x, p.y);
346 ctx.lineTo(p.x, p.y);
350 ctx.strokeStyle = "#888a85";
353 for (var i=0; i < points.length; i++)
357 ctx.arc(p.x, p.y, 2, 0, Math.PI * 2);
358 ctx.fillStyle = "#2e3436";
366 function clearTraces() {
370 function stopRecording() {
371 recordingTraces = false;
374 function startRecording() {
375 recordingTraces = true;
379 graphicsUpdate: graphicsUpdate,
380 canvas: tracePathCanvas,
381 clearTraces: clearTraces,
382 stopRecording: stopRecording,
383 startRecording: startRecording
388function executeCallbacks(callbacks, reexecute) {
389 callbacks.forEach(function(timer) {
390 if (reexecute || !timer.executed) {
391 var func = timer.func;
397function createCanvas() {
398 var c = document.createElement('canvas');
399 c.width = window.innerWidth;
400 c.height = window.innerHeight;
401 c.style.position = "absolute";
407function assert(bool, msg) {
409 throw "Assertion error: " + msg;
413function saveNodeValues(allNodes) {
416 allNodes.forEach(function(node) {
417 nodeValues.push({ value: node.value, id: node.id });
424function restoreNodeValues(allNodes, nodeStates) {
425 assert(allNodes.length == nodeStates.length, "saved program state has wrong length");
426 for (var i=0; i < allNodes.length; i++) {
427 var node = allNodes[i];
428 var state = nodeStates[i];
429 assert(node.id == state.id, "the nodes moved position");
431 node.value = state.value;
435function flattenNodes(nodes) {
438 function addAllToDict(node) {
439 nodesById[node.id] = node;
440 node.kids.forEach(addAllToDict);
442 nodes.forEach(addAllToDict);
444 var allNodes = Object.keys(nodesById).sort().map(function(key) {
445 return nodesById[key];
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list