Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 054207581f
Fetching contributors…

Cannot retrieve contributors at this time

473 lines (426 sloc) 13.232 kb
<!DOCTYPE html>
<meta charset="utf-8">
<link href="tipsy.css" rel="stylesheet">
<style>
html {
font-family: "Helvetica Neue";
font-size: 12px;
}
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
}
#share-link {
display: inline-block;
padding: 4px;
margin: 4px 0;
color: black;
text-decoration: none;
background: #f0f0f0;
border: 1px solid black;
}
#share-link:hover {
color: white;
background: black;
}
code, textarea, pre.code, .log ol.scope-chain li .scope {
font-family: Monaco;
font-size: 10px;
padding: 4px;
}
.log {
white-space: pre-wrap;
}
.log .error {
color: red;
}
.log .debug {
color: gray;
}
.log .print {
color: black;
background: #f0f0f0;
padding: 4px;
}
.log .print:before {
content: 'print ';
color: gray;
}
.log .code {
color: gray;
}
.log .mini {
display: inline-block;
border: 1px dotted gray;
margin: 4px 4px 4px 0;
max-width: 40em;
-moz-transition: opacity 1s;
-webkit-transition: opacity 1s;
}
.log .mini pre {
white-space: pre-wrap;
}
.log .mini .desc {
font-size: 12px;
background: lightgray;
color: white;
}
.highlight-1, .highlight-2 {
cursor: default;
}
.highlight-1 {
background: lightgreen;
}
.highlight-2 {
background: rgba(0, 0, 0, 0.1);
}
#code {
display: block;
width: 100%;
height: 200px;
}
.log .indent {
margin-left: 6px;
padding-left: 4px;
border-left: 1px solid lightgray;
}
.log ol.scope-chain {
list-style-type: none;
padding: 4px;
background: lightgray;
}
.log ol.scope-chain:before {
content: 'enter ';
color: gray;
}
.log ol.scope-chain li {
display: inline-block;
}
.log ol.scope-chain li:before {
content: '';
}
.log ol.scope-chain li:first-child:before {
content: '';
}
.log ol.scope-chain li .scope:after {
content: '()';
}
.log.filtering .mini:not(.filtered) {
opacity: 0.35;
}
</style>
<title>SlowmoJS</title>
<a href="https://github.com/toolness/slowmo-js"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
<h1>SlowmoJS</h1>
<p>Javascript in Slow Motion.</p>
<textarea id="code">for (var i = 0; i < 3; i++) {
print("hey " + i);
}</textarea>
<a href="" id="share-link">Link to this code</a>
<div id="output" class="log"><div class="current-indent"></div></div>
<script>
var require = {
shim: {
'jquery-tipsy': {
deps: ['jquery'],
exports: 'jQuery'
}
}
};
</script>
<script src="require.js"></script>
<script>
var MAX_EXEC_MS = 250;
var RE_EXEC_MS = 300;
var EXPOSED_GLOBALS = [
"console",
"escape",
"unescape",
"encodeURIComponent",
"decodeURIComponent",
"encodeURI",
"decodeURI",
"JSON",
"Math",
"undefined"
];
function output(type, msg) {
var div = $('<div></div>');
div.addClass(type).text(msg).appendTo("#output .current-indent");
}
function makeHighlight(code, range, className, title) {
var span = $('<span></span>');
var highlight = $('<span></span>').addClass(className)
.text(code.slice(range[0], range[1]) || ' ');
var begin = document.createTextNode(code.slice(0, range[0]));
var end = document.createTextNode(code.slice(range[1]));
if (title)
highlight.attr("title", title).tipsy();
return span.append(begin).append(highlight).append(end);
}
function outputHighlight(code, range, caption, secondaryRange,
highlightTitle, secondaryTitle) {
var div = $('<pre class="code"></pre>');
var codeSpan = makeHighlight(code, range, "highlight-1",
highlightTitle).appendTo(div);
if (!caption)
caption = $('<div class="desc">&nbsp;</div>');
if (secondaryRange) {
var newChild, oldChild;
if (secondaryRange[1] < range[0]) {
oldChild = codeSpan[0].firstChild;
newChild = makeHighlight(oldChild.nodeValue, secondaryRange,
"highlight-2", secondaryTitle)[0];
} else if (secondaryRange[0] > range[1]) {
oldChild = codeSpan[0].lastChild;
newChild = makeHighlight(oldChild.nodeValue,
[secondaryRange[0] - range[1],
secondaryRange[1] - range[1]],
"highlight-2", secondaryTitle)[0];
} else {
// TODO: Implement this.
}
if (newChild && oldChild)
codeSpan[0].replaceChild(newChild, oldChild);
}
$('<div class="mini"></div>').append(div).append(caption)
.appendTo("#output .current-indent")
.data("ranges", [range].concat(secondaryRange ? [secondaryRange] : []));
}
function filterRange(start, end) {
if (start === end) {
$('.log').removeClass('filtering');
$('.log .mini').removeClass('filtered');
return;
}
$('.log').addClass('filtering');
$('.log .mini').removeClass('filtered').each(function() {
$(this).data("ranges").forEach(function(range) {
if (range[0] <= start && range[1] >= end) {
$(this).addClass("filtered");
}
}, this);
});
}
function LogListeners(listeners) {
return function(name) {
if (name in listeners) {
var args = [this].concat(Array.prototype.slice.call(arguments, 1));
listeners[name].apply(listeners, args);
}
};
}
function describeVar(name, value) {
var span = $('<div class="desc"><code class="name"></code> is ' +
'<span class="value"></span></div>');
$('.name', span).text(name);
if (typeof(value) == "function")
$('.value', span).text("a function");
else {
if (value === undefined) {
value = "undefined";
} else {
value = JSON.stringify(value);
}
$('<code></code>').text(value).appendTo($('.value', span));
}
return span;
}
// http://stackoverflow.com/questions/2090551/parse-query-string-in-javascript
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');
if (decodeURIComponent(pair[0]) == variable) {
return decodeURIComponent(pair[1]);
}
}
}
function setShareLink(code, filterRange) {
var url = location.protocol + "//" + location.host + location.pathname +
"?code=" + encodeURIComponent(code) +
"&filterrange=" + encodeURIComponent(filterRange.join('-'));
$("#share-link").attr("href", url);
}
define("main", function(require) {
var $ = require("jquery-tipsy");
var Scope = require("slowmo/scope");
var Attr = require("slowmo/attr");
var ScopeMangler = require("slowmo/scope-mangler");
var AttrMangler = require("slowmo/attr-mangler");
var LoopInserter = require("slowmo/loop-inserter");
var falafel = require("falafel");
var timeout;
var callStack = [];
var lastCode;
var lastFilterRange = [];
var updateFilter = function updateFilter(force) {
var codeArea = $("#code")[0];
var range = [codeArea.selectionStart, codeArea.selectionEnd];
if ((range[0] !== lastFilterRange[0] ||
range[1] !== lastFilterRange[1]) || force === true) {
lastFilterRange = range;
filterRange(range[0], range[1]);
setShareLink(lastCode, lastFilterRange);
}
};
var execute = function execute() {
var code = $("#code").val();
var attrLog = LogListeners({
get: function(attr, obj, prop, range) {
outputHighlight(code, range,
describeVar("[" + JSON.stringify(prop) + "]",
obj[prop]),
null, "retrieval of property " + prop);
},
update: function(attr, obj, prop, operator, prefix, oldValue, range) {
outputHighlight(code, range,
describeVar("[" + JSON.stringify(prop) + "]",
obj[prop]),
null, "update of property " + prop);
},
assign: function(attr, obj, prop, operator, oldValue, range) {
outputHighlight(code, range,
describeVar("[" + JSON.stringify(prop) + "]",
obj[prop]),
null, "assignment of property " + prop);
}
});
var scopeLog = LogListeners({
enter: function(scope) {
if (scope.name == "GLOBAL")
return;
var newIndent = $('<div class="indent current-indent"></div>');
var currIndent = $("#output .current-indent");
var scopes = $('<ol class="scope-chain"></ol>').appendTo(newIndent);
var textCallStack = [];
callStack.push(scope);
callStack.forEach(function(scope) {
$('<li></li>')
.append($('<span class="scope"></span>').text(scope.name))
.appendTo(scopes);
textCallStack.push(scope.name + '()');
});
newIndent.attr("title", "Inside " + textCallStack.join(' > '));
currIndent.removeClass("current-indent").append(newIndent);
},
leave: function(scope) {
callStack.pop();
if (scope.name == "GLOBAL")
return;
$("#output .current-indent").removeClass("current-indent")
.parent().addClass("current-indent");
},
declare: function(scope, name, value, range) {
if (!range) return;
outputHighlight(code, range, describeVar(name, value), null,
"declaration of " + name);
},
get: function(scope, name, value, range) {
outputHighlight(code, range, describeVar(name, value),
scope.declRanges[name],
"retrieval of " + name,
"original declaration of " + name);
},
update: function(scope, name, operator, prefix, oldValue, range) {
outputHighlight(code, range, describeVar(name, scope.vars[name]),
scope.declRanges[name],
"update of " + name,
"original declaration of " + name);
},
assign: function(scope, name, operator, value, oldValue, range) {
outputHighlight(code, range, describeVar(name, value),
scope.declRanges[name],
"assignment of " + name,
"original declaration of " + name);
}
});
var startTime = 0;
var loopCheck = function loopCheck(range) {
if (Date.now() - startTime > MAX_EXEC_MS) {
outputHighlight(code, range);
throw new Error("max run time exceeded (" + MAX_EXEC_MS + " ms)");
}
};
var loopInserter = LoopInserter(function(node) {
return "loopCheck(" + JSON.stringify(node.range) + ");";
});
var logResult = function(result, name, range, type) {
var typeTitles = {
CallExpression: "result of function call",
BinaryExpression: "result of binary operation",
UnaryExpression: "result of unary operation"
};
outputHighlight(code, range, describeVar(name, result), null,
typeTitles[type]);
return result;
};
if (lastCode == code)
return;
lastCode = code;
setShareLink(lastCode, [0, 0]);
$("#output > .current-indent").empty();
if (!code.trim())
return output("error", "no code to run!");
try {
var mangled = falafel(code, function(node) {
AttrMangler(node);
ScopeMangler(node);
loopInserter(node);
if (node.type == "CallExpression" ||
node.type == "BinaryExpression" ||
node.type == "UnaryExpression") {
var codeSlice = code.slice(node.range[0], node.range[1]);
node.update("logResult(" + node.source() + ", " +
JSON.stringify(codeSlice) + ", " +
JSON.stringify(node.range) + ", " +
JSON.stringify(node.type) + ")");
}
}).toString();
} catch (e) {
if (e.index) {
outputHighlight(code, [e.index, e.index+1]);
}
return output("error", e.toString());
}
var attr = new Attr(attrLog);
var scope = new Scope(null, "GLOBAL", [0, code.length], scopeLog);
scope.declare("print", function(msg) {
output("print", msg);
});
EXPOSED_GLOBALS.forEach(function(name) {
scope.declare(name, window[name]);
});
startTime = Date.now();
try {
eval(mangled);
} catch (e) {
if (e.range && Array.isArray(e.range))
outputHighlight(code, e.range);
return output("error", e.toString());
}
updateFilter(true);
};
$("#code").keyup(function() {
clearTimeout(timeout);
timeout = setTimeout(execute, RE_EXEC_MS);
}).on("select keyup mouseup", updateFilter);
function init() {
var code = getQueryVariable("code");
if (code !== undefined)
$("#code").val(code);
var filterrange = getQueryVariable("filterrange") || "0-0";
try {
filterrange = filterrange.split('-');
$("#code")[0].selectionStart = parseInt(filterrange[0]);
$("#code")[0].selectionEnd = parseInt(filterrange[1]);
} finally {
$("#code").focus();
execute();
}
}
init();
});
require(["main"], function() {});
</script>
Jump to Line
Something went wrong with that request. Please try again.