Skip to content

Commit

Permalink
Refactored editor to be vim-like modeful. Got insert mode workingish.
Browse files Browse the repository at this point in the history
  • Loading branch information
luqui committed Jun 21, 2011
1 parent 71d08d3 commit 21cdead
Show file tree
Hide file tree
Showing 4 changed files with 244 additions and 77 deletions.
164 changes: 113 additions & 51 deletions StructuralEditor.js
Expand Up @@ -21,69 +21,131 @@ var elt = function(name, attrs) {
var text_node = function(text) { return document.createTextNode(text) };
// End CodeCatalog Snippet

var container = elt('div');
// CodeCatalog Snippet http://www.codecatalog.net/323/2/
var for_kv = function(object, body) {
for (var k in object) {
if (object.hasOwnProperty(k)) {
body(k, object[k]);
}
}
};
// End CodeCatalog Snippet

var input_buffer = '';
// CodeCatalog Snippet http://www.codecatalog.net/331/2/
var object = function(methods) {
var constr = methods['init'] || function() {};
for_kv(methods, function(k,v) {
constr.prototype[k] = v;
});
return constr;
};
// End CodeCatalog Snippet

var zipper = new SF.Zipper([], top_node);

var update = function(z) {
if (!z) return;
zipper = z;
container.empty();
container.append(elt('pre', {}, SF.render_zipper_with(z, function(t) {
return elt('span', {'class': 'selected'}, t, text_node(input_buffer))
})));
};

update(zipper);
var code_container = elt('div');
var mode_container = elt('span', {'class': 'modeline'});
var container = elt('div', {}, code_container, mode_container);

event_node.keydown(function(e) {
var head = typeof(zipper.expr) === 'string' ? new SF.SynClass({}) : zipper.expr.head;

var navigate = function(dir) {
input_buffer = '';
update(head[dir].call(head, zipper));
};

if (37 == e.which) { // left
navigate('nav_left');
}
else if (38 == e.which) { // up
navigate('nav_up');
}
else if (39 == e.which) { // right
navigate('nav_right');
}
else if (40 == e.which) { // down
navigate('nav_down');
}
else if (8 == e.which) { // backspace
input_buffer = input_buffer.slice(0, input_buffer.length-1);
update(zipper);
return false; // prevent browser from handling it
}
else {
//console.log(e.which, e.charCode);
var mode;

var NormalMode = object({
init: function(zipper) {
this.zipper = zipper;
this.update(zipper);
mode_container.text('Normal');
},
keydown: function(e) {
var head = typeof(this.zipper.expr) === 'string' ? new SF.SynClass({}) : this.zipper.expr.head;

var navigate = function(dir) {
this.update(head[dir].call(head, this.zipper));
};

if (37 == e.which) { // left
navigate('nav_left');
}
else if (38 == e.which) { // up
navigate('nav_up');
}
else if (39 == e.which) { // right
navigate('nav_right');
}
else if (40 == e.which) { // down
navigate('nav_down');
}
else {
//console.log(e.which, e.charCode);
}
},
keypress: function(e) {
var ch = String.fromCharCode(e.charCode);
if (ch == 'i') {
mode = new InsertMode(new SF.Cursor(this.zipper, 0));
}
},
render: function() {
code_container.empty();
code_container.append(elt('pre', {}, SF.render_zipper(this.zipper)));
},
update: function(z) {
if (!z) return;
console.log("Zipper:", z);
this.zipper = z;
this.render();
}
});

event_node.keypress(function(e) {
if (!(32 <= e.which && e.which <= 127)) return; // non-printable
var InsertMode = object({
init: function(cursor) {
this.cursor = cursor;
this.input_buffer = '';
this.update(cursor);
mode_container.text('Insert');
},
keydown: function(e) {
if (39 == e.which) { // right
this.update(this.cursor.forward_token());
}
else if (8 == e.which) { // backspace
this.input_buffer = this.input_buffer.slice(0, this.input_buffer.length-1);
this.render();
}
},
keypress: function(e) {
if (!(32 <= e.which && e.which <= 127)) return; // non-printable

if (typeof(zipper.expr) !== 'string') {
var ch = String.fromCharCode(e.charCode);
input_buffer += ch;
var tokresult = zipper.expr.head.parse_insert(zipper.expr)(input_buffer);
this.input_buffer += ch;
console.log("buffer:", this.input_buffer);

var tokresult = this.cursor.parse_insert(this.input_buffer);
if (tokresult) {
input_buffer = tokresult[1];
update(new SF.Zipper(zipper.contexts, tokresult[0]));
}
else {
update(zipper);
this.input_buffer = tokresult[1];
this.update(tokresult[0]);
}
}
return false;
this.render();
},
render: function() {
code_container.empty();
code_container.append(elt('pre', {}, SF.render_cursor(this.cursor, text_node(this.input_buffer))));
},
update: function(c) {
if (!c) return;
console.log("Cursor:", c);
this.cursor = c;
this.render();
},
});

mode = new NormalMode(new SF.Zipper([], top_node));

event_node.keydown(function(e) {
return mode.keydown(e);
});

event_node.keypress(function(e) {
return mode.keypress(e);
});

return container;
Expand Down
106 changes: 101 additions & 5 deletions StructuralFramework.js
Expand Up @@ -183,6 +183,13 @@ $$.Context = object({
}
});


var context_in = function(expr, pos) {
var args = expr.args.slice(0);
args[pos] = null;
return new $$.Context(expr.head, args);
};

$$.Zipper = object({
init: function(contexts, expr) {
this.contexts = contexts;
Expand All @@ -203,12 +210,9 @@ $$.Zipper = object({
down: function(n) {
if (!(0 <= n && typeof(this.expr) === 'object' && n < this.expr.args.length)) return null;

var args = this.expr.args.slice(0);
var focus = args[n];
args[n] = null;

var focus = this.expr.args[n];
return new $$.Zipper(
[new $$.Context(this.expr.head, args)].concat(this.contexts),
[context_in(this.expr, n)].concat(this.contexts),
focus);
},
left: function() {
Expand All @@ -223,6 +227,84 @@ $$.Zipper = object({
},
});


// A cursor is a zipper combined with a position. The position represents a position
// *between* symbols in the focused expression. So we are inside some symbols above
// us, and between symbols below us. The position cannot be at the start or the end
// of the focused expression, because then we are really between two symbols one level
// up. If the zipper's context is empty, then we can be at the start or the end.
$$.Cursor = object({
// pos represents the position right before zipper.expr.args[pos].
init: function(zipper, pos) {
// normalize to invariant
while (zipper.contexts.length > 0) {
if (pos == 0) {
pos = zipper.position();
zipper = zipper.up();
}
else if (pos == zipper.expr.args.length) {
pos = zipper.position()+1;
zipper = zipper.up();
}
else {
break;
}
}
this.zipper = zipper;
this.pos = pos;
},
after: function() {
return this.zipper.expr.args[this.pos];
},
forward_token: function() {
var zipper = this.zipper;
var pos = this.pos;
while (typeof(this.after()) === 'object') {
if (zipper.expr.args.length == 0) {
pos = zipper.position()+1;
zipper = zipper.up();
}
else {
zipper = zipper.down(0);
pos = 0;
}
}
return new $$.Cursor(zipper, pos+1);
},
parse_insert: function(text) {
var expr = this.zipper.expr;
if (expr.args.length == 0) {
var tokresult = expr.head.parse_prefix(text);
if (tokresult) {
var newcursor = tokresult[0];
return [new $$.Cursor(
new $$.Zipper(newcursor.zipper.contexts.concat(this.zipper.contexts),
newcursor.zipper.expr),
newcursor.pos),
tokresult[1] ];
}
else {
return null;
}
}

var tokresult = expr.args[this.pos].head.parse_prefix(text);
if (tokresult) {
var newcursor = tokresult[0];
var thiscx = new $$.Context(expr.head,
[].concat(expr.args.slice(0, this.pos), [null], expr.args.slice(this.pos+1)));
var contexts = [].concat(newcursor.zipper.contexts, [thiscx], this.zipper.contexts);
return [new $$.Cursor(
new $$.Zipper(contexts, newcursor.zipper.expr),
newcursor.pos),
tokresult[1] ];
}
else {
return null;
}
}
});

var render_head = function(head, args) {
var ret = elt('span');
foreach(head.render.apply(head, args), function(arg) {
Expand Down Expand Up @@ -264,6 +346,20 @@ $$.render_zipper = function(zipper) {
});
};

$$.render_cursor = function(cursor, ins) {
if (typeof(ins) === 'undefined') { ins = $([]) }
var args = cursor.zipper.expr.args.map(function(a,i) {
var r = render_expr_tree(a);
return i == cursor.pos ? elt('span', {'class': 'cursor_selected'}, ins, r) : r;
});
if (args.length > 0 && cursor.pos == cursor.zipper.expr.length) {
args[args.length-1] = elt('span', {'class': 'cursor_selected_right'}, args[args.length-1], ins);
}
return render_context(
cursor.zipper.contexts,
render_head(cursor.zipper.expr.head, args));
};

return $$;

};

0 comments on commit 21cdead

Please sign in to comment.