summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/repl.js153
-rw-r--r--test/simple/test-repl-tab-complete.js87
-rw-r--r--test/simple/test-repl.js2
3 files changed, 237 insertions, 5 deletions
diff --git a/lib/repl.js b/lib/repl.js
index bbe658a8a5..3a5ac4346e 100644
--- a/lib/repl.js
+++ b/lib/repl.js
@@ -207,6 +207,8 @@ function REPLServer(prompt, stream, eval, useGlobal, ignoreUndefined) {
function finish(e, ret) {
+ self.memory(cmd);
+
// If error was SyntaxError and not JSON.parse error
if (isSyntaxError(e)) {
// Start buffering data like that:
@@ -265,6 +267,9 @@ REPLServer.prototype.createContext = function() {
context.global = context;
context.global.global = context;
+ this.lines = [];
+ this.lines.level = [];
+
return context;
};
@@ -278,7 +283,9 @@ REPLServer.prototype.resetContext = function(force) {
};
REPLServer.prototype.displayPrompt = function() {
- this.rli.setPrompt(this.bufferedCommand.length ? '... ' : this.prompt);
+ this.rli.setPrompt(this.bufferedCommand.length ?
+ '...' + new Array(this.lines.level.length).join('..') + ' ' :
+ this.prompt);
this.rli.prompt();
};
@@ -287,6 +294,21 @@ REPLServer.prototype.displayPrompt = function() {
REPLServer.prototype.readline = function(cmd) {
};
+// A stream to push an array into a REPL
+// used in REPLServer.complete
+function ArrayStream() {
+ this.run = function (data) {
+ var self = this;
+ data.forEach(function (line) {
+ self.emit('data', line);
+ });
+ }
+}
+util.inherits(ArrayStream, require('stream').Stream);
+ArrayStream.prototype.readable = true;
+ArrayStream.prototype.writable = true;
+ArrayStream.prototype.resume = function () {};
+ArrayStream.prototype.write = function () {};
var requireRE = /\brequire\s*\(['"](([\w\.\/-]+\/)?([\w\.\/-]*))/;
var simpleExpressionRE =
@@ -304,6 +326,28 @@ var simpleExpressionRE =
// Warning: This eval's code like "foo.bar.baz", so it will run property
// getter code.
REPLServer.prototype.complete = function(line, callback) {
+ // There may be local variables to evaluate, try a nested REPL
+ if (this.bufferedCommand != undefined && this.bufferedCommand.length) {
+ // Get a new array of inputed lines
+ var tmp = this.lines.slice();
+ // Kill off all function declarations to push all local variables into
+ // global scope
+ this.lines.level.forEach(function (kill) {
+ if (kill.isFunction) {
+ tmp[kill.line] = '';
+ }
+ });
+ var flat = new ArrayStream(); // make a new "input" stream
+ var magic = new REPLServer('', flat); // make a nested REPL
+ magic.context = magic.createContext();
+ flat.run(tmp); // eval the flattened code
+ // all this is only profitable if the nested REPL
+ // does not have a bufferedCommand
+ if (!magic.bufferedCommand) {
+ return magic.complete(line, callback);
+ }
+ }
+
var completions;
// list of completion lists, one for each inheritance "level"
@@ -586,6 +630,77 @@ REPLServer.prototype.defineCommand = function(keyword, cmd) {
this.commands['.' + keyword] = cmd;
};
+REPLServer.prototype.memory = function memory (cmd) {
+ var self = this;
+
+ self.lines = self.lines || [];
+ self.lines.level = self.lines.level || [];
+
+ // save the line so I can do magic later
+ if (cmd) {
+ // TODO should I tab the level?
+ self.lines.push(new Array(self.lines.level.length).join(' ') + cmd);
+ } else {
+ // I don't want to not change the format too much...
+ self.lines.push('');
+ }
+
+ // I need to know "depth."
+ // Because I can not tell the difference between a } that
+ // closes an object literal and a } that closes a function
+ if (cmd) {
+ // going down is { and ( e.g. function () {
+ // going up is } and )
+ var dw = cmd.match(/{|\(/g);
+ var up = cmd.match(/}|\)/g);
+ up = up ? up.length : 0;
+ dw = dw ? dw.length : 0;
+ var depth = dw - up;
+
+ if (depth) {
+ (function workIt(){
+ if (depth > 0) {
+ // going... down.
+ // push the line#, depth count, and if the line is a function.
+ // Since JS only has functional scope I only need to remove
+ // "function () {" lines, clearly this will not work for
+ // "function ()
+ // {" but nothing should break, only tab completion for local
+ // scope will not work for this function.
+ self.lines.level.push({ line: self.lines.length - 1,
+ depth: depth,
+ isFunction: /\s*function\s*/.test(cmd)});
+ } else if (depth < 0) {
+ // going... up.
+ var curr = self.lines.level.pop();
+ if (curr) {
+ var tmp = curr.depth + depth;
+ if (tmp < 0) {
+ //more to go, recurse
+ depth += curr.depth;
+ workIt();
+ } else if (tmp > 0) {
+ //remove and push back
+ curr.depth += depth;
+ self.lines.level.push(curr);
+ }
+ }
+ }
+ }());
+ }
+
+ // it is possible to determine a syntax error at this point.
+ // if the REPL still has a bufferedCommand and
+ // self.lines.level.length === 0
+ // TODO? keep a log of level so that any syntax breaking lines can
+ // be cleared on .break and in the case of a syntax error?
+ // TODO? if a log was kept, then I could clear the bufferedComand and
+ // eval these lines and throw the syntax error
+ } else {
+ self.lines.level = [];
+ }
+};
+
function defineDefaultCommands(repl) {
// TODO remove me after 0.3.x
@@ -625,6 +740,42 @@ function defineDefaultCommands(repl) {
this.displayPrompt();
}
});
+
+ repl.defineCommand('save', {
+ help: 'Save all evaluated commands in this REPL session to a file',
+ action: function(file) {
+ try {
+ fs.writeFileSync(file, this.lines.join('\n') + '\n');
+ this.outputStream.write('Session saved to:' + file + '\n');
+ } catch (e) {
+ this.outputStream.write('Failed to save:' + file+ '\n')
+ }
+ this.displayPrompt();
+ }
+ });
+
+ repl.defineCommand('load', {
+ help: 'Load JS from a file into the REPL session',
+ action: function(file) {
+ try {
+ var stats = fs.statSync(file);
+ if (stats && stats.isFile()) {
+ var self = this;
+ var data = fs.readFileSync(file, 'utf8');
+ var lines = data.split('\n');
+ this.displayPrompt();
+ lines.forEach(function (line) {
+ if (line) {
+ self.rli.write(line + '\n');
+ }
+ });
+ }
+ } catch (e) {
+ this.outputStream.write('Failed to load:' + file + '\n');
+ }
+ this.displayPrompt();
+ }
+ });
}
diff --git a/test/simple/test-repl-tab-complete.js b/test/simple/test-repl-tab-complete.js
index d82531e075..4fdd923c9f 100644
--- a/test/simple/test-repl-tab-complete.js
+++ b/test/simple/test-repl-tab-complete.js
@@ -73,16 +73,16 @@ testMe.complete('inner.o', function (error, data) {
putIn.run(['.clear']);
-// Tab Complete will not return localy scoped variables
+// Tab Complete will return a simple local variable
putIn.run([
'var top = function () {',
'var inner = {one:1};']);
testMe.complete('inner.o', function (error, data) {
- assert.deepEqual(data, doesNotBreak);
+ assert.deepEqual(data, works);
});
// When you close the function scope tab complete will not return the
-// localy scoped variable
+// locally scoped variable
putIn.run(['};']);
testMe.complete('inner.o', function (error, data) {
assert.deepEqual(data, doesNotBreak);
@@ -90,3 +90,84 @@ testMe.complete('inner.o', function (error, data) {
putIn.run(['.clear']);
+// Tab Complete will return a complex local variable
+putIn.run([
+ 'var top = function () {',
+ 'var inner = {',
+ ' one:1',
+ '};']);
+testMe.complete('inner.o', function (error, data) {
+ assert.deepEqual(data, works);
+});
+
+putIn.run(['.clear']);
+
+// Tab Complete will return a complex local variable even if the function
+// has paramaters
+putIn.run([
+ 'var top = function (one, two) {',
+ 'var inner = {',
+ ' one:1',
+ '};']);
+testMe.complete('inner.o', function (error, data) {
+ assert.deepEqual(data, works);
+});
+
+putIn.run(['.clear']);
+
+// Tab Complete will return a complex local variable even if the
+// scope is nested inside an immediately executed function
+putIn.run([
+ 'var top = function () {',
+ '(function test () {',
+ 'var inner = {',
+ ' one:1',
+ '};']);
+testMe.complete('inner.o', function (error, data) {
+ assert.deepEqual(data, works);
+});
+
+putIn.run(['.clear']);
+
+// currently does not work, but should not break note the inner function
+// def has the params and { on a seperate line
+putIn.run([
+ 'var top = function () {',
+ 'r = function test (',
+ ' one, two) {',
+ 'var inner = {',
+ ' one:1',
+ '};']);
+testMe.complete('inner.o', function (error, data) {
+ assert.deepEqual(data, doesNotBreak);
+});
+
+putIn.run(['.clear']);
+
+// currently does not work, but should not break, not the {
+putIn.run([
+ 'var top = function () {',
+ 'r = function test ()',
+ '{',
+ 'var inner = {',
+ ' one:1',
+ '};']);
+testMe.complete('inner.o', function (error, data) {
+ assert.deepEqual(data, doesNotBreak);
+});
+
+putIn.run(['.clear']);
+
+// currently does not work, but should not break
+putIn.run([
+ 'var top = function () {',
+ 'r = function test (',
+ ')',
+ '{',
+ 'var inner = {',
+ ' one:1',
+ '};']);
+testMe.complete('inner.o', function (error, data) {
+ assert.deepEqual(data, doesNotBreak);
+});
+
diff --git a/test/simple/test-repl.js b/test/simple/test-repl.js
index 783c3ab493..4fc65f3631 100644
--- a/test/simple/test-repl.js
+++ b/test/simple/test-repl.js
@@ -86,7 +86,7 @@ function error_test() {
tcp_test();
}
- } else if (read_buffer === prompt_multiline) {
+ } else if (read_buffer.indexOf(prompt_multiline) !== -1) {
// Check that you meant to send a multiline test
assert.strictEqual(prompt_multiline, client_unix.expect);
read_buffer = '';