Skip to content

Instantly share code, notes, and snippets.

@enjalot
Created May 12, 2013 23:54
Show Gist options
  • Save enjalot/5565402 to your computer and use it in GitHub Desktop.
Save enjalot/5565402 to your computer and use it in GitHub Desktop.
codemirror number control
{"description":"codemirror number control","endpoint":"","display":"div","public":true,"require":[{"name":"esprima","url":"http://esprima.org/esprima.js"},{"name":"escodegen","url":"http://esprima.org/test/3rdparty/escodegen.browser.js"}],"fileconfigs":{"inlet.js":{"default":true,"vim":false,"emacs":false,"fontSize":12},"style.css":{"default":true,"vim":false,"emacs":false,"fontSize":12},"_.md":{"default":true,"vim":false,"emacs":false,"fontSize":12},"config.json":{"default":true,"vim":false,"emacs":false,"fontSize":12},"log.js":{"default":true,"vim":false,"emacs":false,"fontSize":12},"esprima.js":{"default":true,"vim":false,"emacs":false,"fontSize":12}},"fullscreen":false,"play":false,"loop":false,"restart":false,"autoinit":true,"pause":true,"loop_type":"period","bv":false,"nclones":15,"clone_opacity":0.4,"duration":3000,"ease":"linear","dt":0.01,"thumbnail":"http://i.imgur.com/1C1Pn2T.png"}
//this is where we use esprima to interperet our code
//mainly taken from https://github.com/nornagon/live/blob/master/xform.coffee
tributary.esprima = function(code, prefix) {
if(!prefix) prefix = '';
var parsed = esprima.parse(code, {loc:true});
//console.log(parsed)
__hasProp = {}.hasOwnProperty;
var transformed;
var $values = {};
var id, nextId = 0;
function replace(e) {
//Check for number
if (e.type === 'Literal' && typeof e.value === 'number') {
id = nextId++;
$values[prefix + id] = {
value: e.value,
loc: e.loc,
__trib__: true
};
var ret = {
type: "MemberExpression",
computed: true,
object: {
type: "MemberExpression",
computed: true,
object :{
type: 'Identifier',
name: '$values'
},
property: {
type: 'Literal',
value: prefix + '' + id
}
},
"property": {
"type": "Literal",
"value": "value"
}
}
//console.log(ret)
return ret;
} else if (e.type === 'UnaryExpression' && e.operator === '-' && e.argument.type === 'Literal' && typeof e.argument.value === 'number') {
id = nextId++;
$values[prefix + id] = {
value: -e.argument.value,
loc: e.loc,
__trib__: true
};
var ret = {
type: "MemberExpression",
computed: true,
object: {
type: "MemberExpression",
computed: true,
object :{
type: 'Identifier',
name: '$values'
},
property: {
type: 'Literal',
value: prefix + '' + id
}
},
"property": {
"type": "Literal",
"value": "value"
}
}
return ret;
//check for console.log
} else if(e.type === 'CallExpression' || (e.expression && e.expression.type === 'CallExpression')) {
expression = e.expression || e;
var callee = expression.callee;
//console.log("callee", callee)
if(callee.object && callee.object.name === 'console' && callee.property && callee.property.name === 'log') {
callee.property.name = 'logJack';
var pos = expression.loc.end;
pos.line -= 1;
var newArgs = [
{
"type": "ObjectExpression",
"properties": [
{
"type": "Property",
"key": {
"type": "Identifier",
"name": "line"
},
"value": {
"type": "Literal",
"value": pos.line,
"raw": pos.line + ""
},
"kind": "init"
},
{
"type": "Property",
"key": {
"type": "Identifier",
"name": "ch"
},
"value": {
"type": "Literal",
"value": pos.column,
"raw": pos.column + ""
},
"kind": "init"
}
]
},
{
"type": "ArrayExpression",
"elements": expression['arguments']
}
];
expression['arguments'] = newArgs;
//console.log("pos", e, callee.loc.end);
return transform(e,replace);
} else if((callee.object && callee.object.name === '_t') || (callee.name === '_t')) {
//we put the valueIndex as the first argument;
var replaced = replace(e['arguments'][0])
if(replaced.object && replaced.object.hasOwnProperty('property')) {
//this is actually looking at index in the values object
var valueIndex = replaced.object.property.value
$values[valueIndex].__control__ = true;
}
e['arguments'][0] = replaced;
e['arguments'].unshift({
"type": "Literal",
"value": valueIndex,
"raw": valueIndex + ""
});
return transform(e,replace)
} else {
return transform(e,replace);
}
} else {
return transform(e, replace);
}
}
function transform(object, f) {
var i, key, newObject, v, value, _i, _len;
if (object instanceof Array) {
newObject = [];
for (i = _i = 0, _len = object.length; _i < _len; i = ++_i) {
v = object[i];
if (typeof v === 'object' && v !== null) {
newObject[i] = f(v);
} else {
newObject[i] = v;
}
}
} else {
newObject = {};
for (key in object) {
if (!__hasProp.call(object, key)) continue;
value = object[key];
if (typeof value === 'object' && value !== null) {
newObject[key] = f(value);
} else {
newObject[key] = value;
}
}
}
return newObject;
}
transformed = transform(parsed, replace)
return {
ast: transformed,
values: $values
}
}
//TODO: persist tributary.$values when saving object
//this way we retain information about our control
//we can also save tributary.transformed which is
//the user code run thru esprima
//TODO: color scheme for reserved stuff ($control)
//always use dictionary {name: '___1'}
//console.log("values", tributary.$values);
if(!tributary.$values) tributary.$values = {};
//prototyping interactions with codemirror.
//cm and editor are the two main things you want to manipulate in this scope
var options = {
mode: 'javascript',
lineNumbers: true,
viewportMargin: Infinity,
theme: 'vibrant-ink'
}
var display = d3.select("#display")
var editor = display.append("div").classed("editor", true)
.classed("test-editor", true);
var cm = CodeMirror(editor.node(), options);
//cm.setValue("5")
cm.setValue("var x = _t(5)\nconsole.log(\"x\",x);\nx = _t(1337, 'elite');\nconsole.log(\"x\", x, x+42);\ny = _t(2, {min:0, max:10});\nconsole.log('y', y)")
var widgets = [];
cm.on("cursorActivity", function() {
//clear();
})
editor.on("click", function() {
//console.log("editor click", arguments);
// clear();
})
display.append("button")
.text("clear")
.on("click", clear);
function clear() {
for(var i = widgets.length; i--;) {
widgets.pop().clear(); //this remove's CodeMirror's handle to the widget;
}
// display.selectAll(".log-widget").remove()
}
cm.on("change", execute)
function execute() {
clear();
if(!tributary.__changing__) {
var js = cm.getValue();
try {
var transformed = tributary.esprima(js);
tributary.transformed = escodegen.generate(transformed.ast);
} catch(e) {
//e.stack();
console.log("esprima error", e)
return;
}
//console.log("ast", transformed.ast);
console.log("really?")
var values = 'var $values = ' + JSON.stringify(transformed.values) + '\n';
try {
var code = values + escodegen.generate(transformed.ast);
} catch (e) {
//e.stack();
console.log("AST", transformed.ast)
console.log("gen error", e)
return;
}
} else {
var values = 'var $values = ' + JSON.stringify(tributary.$values) + '\n';
code = values + tributary.transformed;
}
//console.log("code", code);
try {
eval(code);
} catch(e) { }
if(!tributary.__changing__) {
updateControls();
}
tributary.__changing__ = false;
}
if (typeof console !== "undefined") {
console.logJack = function(pos, args) {
var text = JSON.stringify(args)
text = text.slice(1, text.length-1);
var widget = display.append("div")
.text(text)
.classed("log-widget", true)
var lwidget = cm.addLineWidget(pos.line, widget.node());
widgets.push(lwidget);
console.log.apply(console, args);
}
}
function _t(valuesIndex, value, options) {
//TODO: we can inspect num and in the future handle
//other cases, like color
if(!tributary.__controls) tributary.__controls = {};
var controls = tributary.__controls;
var name;
var control = {};
if(!options) {
options = {}
}
if(typeof options === 'string') {
name = options
} else if (typeof options === 'object') {
if(options.hasOwnProperty('min')) {
control.min = options.min;
if(value < control.min) value = control.min;
}
if(options.max) {
control.max = options.max || 100;
if(value > control.max) value = control.max;
}
if(options.name) {
name = options.name
}
if(options.pos) {
//can add a widget if we want
}
}
if(!name) {
name = '___' + (Object.keys(controls).length + 1);
}
if(!control.hasOwnProperty('min')) {
control.min = -value * 3
}
if(!control.hasOwnProperty('max')) {
control.max = value * 5
}
control.value = value;
control.name = name;
//console.log(control)
controls[name] = control;
tributary.__controls = controls;
var v = tributary.$values[valuesIndex];
v.__control__ = control;
return value;
}
tributary.__controls = {};
var controldiv = display.append("div").classed("controls", true);
function updateControls() {
var controls = _.map(tributary.$values, function(d) {
return d
}).filter(function(d) {
return d.hasOwnProperty('__control__');
})
//console.log("controls", controls)
var csel = controldiv.selectAll("div.control")
.data(controls, function(d) { return d.__control__.name });
var enter = csel.enter().append("div").classed("control", true);
enter.append("input").classed("name", true)
.attr("value", function(d) {
var name = d.__control__.name.replace(/___/,'')
if(!d.alias) {
d.alias = name;
}
return d.alias
})
.on("keyup", function(d) {
console.log(d);
d.alias = this.value;
})
enter.append("div").classed("value", true)
.attr("text", function(d) {
return d.value
})
enter.append("input")
.classed("range", true)
.attr({
"type":"range",
"value": function(d) { return d.value },
"min": function(d) { return d.__control__.min },
"max": function(d) { return d.__control__.max }
})
.on("change", function(d,i) {
var oldvalue = d.value;
d.value = d.__control__.value = +this.value;
var start = {
line: d.loc.start.line -1,
ch: d.loc.start.column
}
var end = {
line: d.loc.end.line -1,
ch: d.loc.start.column + (''+oldvalue).length
}
//regex style
tributary.__changing__ = true;
cm.replaceRange(''+d.value, start, end);
d3.select(this.parentNode).select(".value").text(function(d) { return d.value })
//or one could just rerun the existing tributary function
//tributary.trigger("execute")
})
csel.exit().remove();
csel.select("div.value").text(function(d) { return d.value })
csel.select("input.range")
.text(function(d) {
d3.select(this).attr({
"value": function(d) { this.value = d.value; return d.value },
"min": function(d) { return d.__control__.min },
"max": function(d) { return d.__control__.max }
})
})
}
execute();
#display {
overflow: scroll;
}
.test-editor {
width: 400px;
height: 400px;
}
.test-editor .CodeMirror {
width: 100%;
height: 100%;
overflow-x: scroll;
}
.log-widget {
border: 1px solid red;
background: rgba(240,240,240,0.5);
}
.range, .value, .name {
float:left;
}
.name {
clear:left;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment