mirror of
https://github.com/jashkenas/coffeescript.git
synced 2022-11-09 12:23:24 -05:00
Merge branch 'node_repl_multiline' of https://github.com/asalant/coffee-script into repl-rewrite
This commit is contained in:
commit
2e191dc0e7
6 changed files with 158 additions and 448 deletions
|
@ -67,7 +67,7 @@
|
||||||
loadRequires();
|
loadRequires();
|
||||||
}
|
}
|
||||||
if (opts.interactive) {
|
if (opts.interactive) {
|
||||||
return require('./repl');
|
return require('./repl').start();
|
||||||
}
|
}
|
||||||
if (opts.watch && !fs.watch) {
|
if (opts.watch && !fs.watch) {
|
||||||
return printWarn("The --watch feature depends on Node v0.6.0+. You are running " + process.version + ".");
|
return printWarn("The --watch feature depends on Node v0.6.0+. You are running " + process.version + ".");
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
return compileScript(null, sources[0]);
|
return compileScript(null, sources[0]);
|
||||||
}
|
}
|
||||||
if (!sources.length) {
|
if (!sources.length) {
|
||||||
return require('./repl');
|
return require('./repl').start();
|
||||||
}
|
}
|
||||||
literals = opts.run ? sources.splice(1) : [];
|
literals = opts.run ? sources.splice(1) : [];
|
||||||
process.argv = process.argv.slice(0, 2).concat(literals);
|
process.argv = process.argv.slice(0, 2).concat(literals);
|
||||||
|
|
|
@ -1,276 +1,84 @@
|
||||||
// Generated by CoffeeScript 1.5.0-pre
|
// Generated by CoffeeScript 1.5.0-pre
|
||||||
(function() {
|
(function() {
|
||||||
var ACCESSOR, CoffeeScript, Module, REPL_PROMPT, REPL_PROMPT_CONTINUATION, REPL_PROMPT_MULTILINE, SIMPLEVAR, Script, autocomplete, backlog, completeAttribute, completeVariable, enableColours, error, getCompletions, inspect, multilineMode, pipedInput, readline, repl, run, stdin, stdout,
|
var CoffeeScript, addMultilineHandler, merge, nodeREPL, replDefaults, vm;
|
||||||
__indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
|
||||||
|
|
||||||
stdin = process.openStdin();
|
vm = require('vm');
|
||||||
|
|
||||||
stdout = process.stdout;
|
nodeREPL = require('repl');
|
||||||
|
|
||||||
CoffeeScript = require('./coffee-script');
|
CoffeeScript = require('./coffee-script');
|
||||||
|
|
||||||
readline = require('readline');
|
merge = require('./helpers').merge;
|
||||||
|
|
||||||
inspect = require('util').inspect;
|
replDefaults = {
|
||||||
|
prompt: 'coffee> ',
|
||||||
Script = require('vm').Script;
|
"eval": function(code, context, file, cb) {
|
||||||
|
|
||||||
Module = require('module');
|
|
||||||
|
|
||||||
REPL_PROMPT = 'coffee> ';
|
|
||||||
|
|
||||||
REPL_PROMPT_MULTILINE = '------> ';
|
|
||||||
|
|
||||||
REPL_PROMPT_CONTINUATION = '......> ';
|
|
||||||
|
|
||||||
enableColours = false;
|
|
||||||
|
|
||||||
if (process.platform !== 'win32') {
|
|
||||||
enableColours = !process.env.NODE_DISABLE_COLORS;
|
|
||||||
}
|
|
||||||
|
|
||||||
error = function(err) {
|
|
||||||
return stdout.write((err.stack || err.toString()) + '\n');
|
|
||||||
};
|
|
||||||
|
|
||||||
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/;
|
|
||||||
|
|
||||||
SIMPLEVAR = /(\w+)$/i;
|
|
||||||
|
|
||||||
autocomplete = function(text) {
|
|
||||||
return completeAttribute(text) || completeVariable(text) || [[], text];
|
|
||||||
};
|
|
||||||
|
|
||||||
completeAttribute = function(text) {
|
|
||||||
var all, candidates, completions, key, match, obj, prefix, _i, _len, _ref;
|
|
||||||
if (match = text.match(ACCESSOR)) {
|
|
||||||
all = match[0], obj = match[1], prefix = match[2];
|
|
||||||
try {
|
try {
|
||||||
obj = Script.runInThisContext(obj);
|
if (/^\(\s+\)$/.test(code)) {
|
||||||
} catch (e) {
|
return cb(null);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (obj == null) {
|
code = code.replace(/子/mg, '\n');
|
||||||
return;
|
code = CoffeeScript.compile(code, {
|
||||||
}
|
filename: file,
|
||||||
obj = Object(obj);
|
bare: true
|
||||||
candidates = Object.getOwnPropertyNames(obj);
|
|
||||||
while (obj = Object.getPrototypeOf(obj)) {
|
|
||||||
_ref = Object.getOwnPropertyNames(obj);
|
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
||||||
key = _ref[_i];
|
|
||||||
if (__indexOf.call(candidates, key) < 0) {
|
|
||||||
candidates.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
completions = getCompletions(prefix, candidates);
|
|
||||||
return [completions, prefix];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
completeVariable = function(text) {
|
|
||||||
var candidates, completions, free, key, keywords, r, vars, _i, _len, _ref;
|
|
||||||
free = (_ref = text.match(SIMPLEVAR)) != null ? _ref[1] : void 0;
|
|
||||||
if (text === "") {
|
|
||||||
free = "";
|
|
||||||
}
|
|
||||||
if (free != null) {
|
|
||||||
vars = Script.runInThisContext('Object.getOwnPropertyNames(Object(this))');
|
|
||||||
keywords = (function() {
|
|
||||||
var _i, _len, _ref1, _results;
|
|
||||||
_ref1 = CoffeeScript.RESERVED;
|
|
||||||
_results = [];
|
|
||||||
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
|
|
||||||
r = _ref1[_i];
|
|
||||||
if (r.slice(0, 2) !== '__') {
|
|
||||||
_results.push(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
})();
|
|
||||||
candidates = vars;
|
|
||||||
for (_i = 0, _len = keywords.length; _i < _len; _i++) {
|
|
||||||
key = keywords[_i];
|
|
||||||
if (__indexOf.call(candidates, key) < 0) {
|
|
||||||
candidates.push(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
completions = getCompletions(free, candidates);
|
|
||||||
return [completions, free];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
getCompletions = function(prefix, candidates) {
|
|
||||||
var el, _i, _len, _results;
|
|
||||||
_results = [];
|
|
||||||
for (_i = 0, _len = candidates.length; _i < _len; _i++) {
|
|
||||||
el = candidates[_i];
|
|
||||||
if (0 === el.indexOf(prefix)) {
|
|
||||||
_results.push(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _results;
|
|
||||||
};
|
|
||||||
|
|
||||||
process.on('uncaughtException', error);
|
|
||||||
|
|
||||||
backlog = '';
|
|
||||||
|
|
||||||
run = function(buffer) {
|
|
||||||
var code, returnValue, _;
|
|
||||||
buffer = buffer.replace(/(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, "$1$2$3");
|
|
||||||
buffer = buffer.replace(/[\r\n]+$/, "");
|
|
||||||
if (multilineMode) {
|
|
||||||
backlog += "" + buffer + "\n";
|
|
||||||
repl.setPrompt(REPL_PROMPT_CONTINUATION);
|
|
||||||
repl.prompt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!buffer.toString().trim() && !backlog) {
|
|
||||||
repl.prompt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
code = backlog += buffer;
|
|
||||||
if (code[code.length - 1] === '\\') {
|
|
||||||
backlog = "" + backlog.slice(0, -1) + "\n";
|
|
||||||
repl.setPrompt(REPL_PROMPT_CONTINUATION);
|
|
||||||
repl.prompt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
repl.setPrompt(REPL_PROMPT);
|
|
||||||
backlog = '';
|
|
||||||
try {
|
|
||||||
_ = global._;
|
|
||||||
returnValue = CoffeeScript["eval"]("_=(" + code + "\n)", {
|
|
||||||
filename: 'repl',
|
|
||||||
modulename: 'repl'
|
|
||||||
});
|
});
|
||||||
if (returnValue === void 0) {
|
return cb(null, vm.runInContext(code, context, file));
|
||||||
global._ = _;
|
|
||||||
}
|
|
||||||
repl.output.write("" + (inspect(returnValue, false, 2, enableColours)) + "\n");
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error(err);
|
return cb(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return repl.prompt();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (stdin.readable && stdin.isRaw) {
|
addMultilineHandler = function(repl) {
|
||||||
pipedInput = '';
|
var inputStream, multiline, nodeLineListener, outputStream, rli;
|
||||||
repl = {
|
rli = repl.rli, inputStream = repl.inputStream, outputStream = repl.outputStream;
|
||||||
prompt: function() {
|
multiline = {
|
||||||
return stdout.write(this._prompt);
|
enabled: false,
|
||||||
},
|
prompt: new Array(repl.prompt.length).join('.') + ' ',
|
||||||
setPrompt: function(p) {
|
buffer: ''
|
||||||
return this._prompt = p;
|
|
||||||
},
|
|
||||||
input: stdin,
|
|
||||||
output: stdout,
|
|
||||||
on: function() {}
|
|
||||||
};
|
};
|
||||||
stdin.on('data', function(chunk) {
|
nodeLineListener = rli.listeners('line')[0];
|
||||||
var line, lines, _i, _len, _ref;
|
rli.removeListener('line', nodeLineListener);
|
||||||
pipedInput += chunk;
|
rli.on('line', function(cmd) {
|
||||||
if (!/\n/.test(pipedInput)) {
|
if (multiline.enabled === true) {
|
||||||
return;
|
multiline.buffer += "" + cmd + "\n";
|
||||||
}
|
return rli.prompt(true);
|
||||||
lines = pipedInput.split("\n");
|
|
||||||
pipedInput = lines[lines.length - 1];
|
|
||||||
_ref = lines.slice(0, -1);
|
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
||||||
line = _ref[_i];
|
|
||||||
if (!(line)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
stdout.write("" + line + "\n");
|
|
||||||
run(line);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
stdin.on('end', function() {
|
|
||||||
var line, _i, _len, _ref;
|
|
||||||
_ref = pipedInput.trim().split("\n");
|
|
||||||
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
|
|
||||||
line = _ref[_i];
|
|
||||||
if (!(line)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
stdout.write("" + line + "\n");
|
|
||||||
run(line);
|
|
||||||
}
|
|
||||||
stdout.write('\n');
|
|
||||||
return process.exit(0);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (readline.createInterface.length < 3) {
|
return nodeLineListener(cmd);
|
||||||
repl = readline.createInterface(stdin, autocomplete);
|
}
|
||||||
stdin.on('data', function(buffer) {
|
|
||||||
return repl.write(buffer);
|
|
||||||
});
|
});
|
||||||
} else {
|
return inputStream.on('keypress', function(char, key) {
|
||||||
repl = readline.createInterface(stdin, stdout, autocomplete);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
multilineMode = false;
|
|
||||||
|
|
||||||
repl.input.on('keypress', function(char, key) {
|
|
||||||
var cursorPos, newPrompt;
|
|
||||||
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) {
|
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cursorPos = repl.cursor;
|
multiline.enabled = !multiline.enabled;
|
||||||
repl.output.cursorTo(0);
|
if (multiline.enabled === false) {
|
||||||
repl.output.clearLine(1);
|
if (!multiline.buffer.match(/\n/)) {
|
||||||
multilineMode = !multilineMode;
|
rli.setPrompt(repl.prompt);
|
||||||
if (!multilineMode && backlog) {
|
rli.prompt(true);
|
||||||
repl._line();
|
|
||||||
}
|
|
||||||
backlog = '';
|
|
||||||
repl.setPrompt((newPrompt = multilineMode ? REPL_PROMPT_MULTILINE : REPL_PROMPT));
|
|
||||||
repl.prompt();
|
|
||||||
return repl.output.cursorTo(newPrompt.length + (repl.cursor = cursorPos));
|
|
||||||
});
|
|
||||||
|
|
||||||
repl.input.on('keypress', function(char, key) {
|
|
||||||
if (!(multilineMode && repl.line)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'd')) {
|
multiline.buffer = multiline.buffer.replace(/\n/mg, '子');
|
||||||
return;
|
rli.emit('line', multiline.buffer);
|
||||||
}
|
return multiline.buffer = '';
|
||||||
multilineMode = false;
|
|
||||||
return repl._line();
|
|
||||||
});
|
|
||||||
|
|
||||||
repl.on('attemptClose', function() {
|
|
||||||
if (multilineMode) {
|
|
||||||
multilineMode = false;
|
|
||||||
repl.output.cursorTo(0);
|
|
||||||
repl.output.clearLine(1);
|
|
||||||
repl._onLine(repl.line);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (backlog || repl.line) {
|
|
||||||
backlog = '';
|
|
||||||
repl.historyIndex = -1;
|
|
||||||
repl.setPrompt(REPL_PROMPT);
|
|
||||||
repl.output.write('\n(^C again to quit)');
|
|
||||||
return repl._line((repl.line = ''));
|
|
||||||
} else {
|
} else {
|
||||||
return repl.close();
|
rli.setPrompt(multiline.prompt);
|
||||||
|
return rli.prompt(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
repl.on('close', function() {
|
module.exports = {
|
||||||
repl.output.write('\n');
|
start: function(opts) {
|
||||||
return repl.input.destroy();
|
var repl;
|
||||||
});
|
if (opts == null) {
|
||||||
|
opts = {};
|
||||||
repl.on('line', run);
|
}
|
||||||
|
opts = merge(replDefaults, opts);
|
||||||
repl.setPrompt(REPL_PROMPT);
|
repl = nodeREPL.start(opts);
|
||||||
|
addMultilineHandler(repl);
|
||||||
repl.prompt();
|
return repl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
|
|
@ -68,12 +68,12 @@ exports.run = ->
|
||||||
return usage() if opts.help
|
return usage() if opts.help
|
||||||
return version() if opts.version
|
return version() if opts.version
|
||||||
loadRequires() if opts.require
|
loadRequires() if opts.require
|
||||||
return require './repl' if opts.interactive
|
return require('./repl').start() if opts.interactive
|
||||||
if opts.watch and !fs.watch
|
if opts.watch and !fs.watch
|
||||||
return printWarn "The --watch feature depends on Node v0.6.0+. You are running #{process.version}."
|
return printWarn "The --watch feature depends on Node v0.6.0+. You are running #{process.version}."
|
||||||
return compileStdio() if opts.stdio
|
return compileStdio() if opts.stdio
|
||||||
return compileScript null, sources[0] if opts.eval
|
return compileScript null, sources[0] if opts.eval
|
||||||
return require './repl' unless sources.length
|
return require('./repl').start() unless sources.length
|
||||||
literals = if opts.run then sources.splice 1 else []
|
literals = if opts.run then sources.splice 1 else []
|
||||||
process.argv = process.argv[0..1].concat literals
|
process.argv = process.argv[0..1].concat literals
|
||||||
process.argv[0] = 'coffee'
|
process.argv[0] = 'coffee'
|
||||||
|
|
233
src/repl.coffee
233
src/repl.coffee
|
@ -1,197 +1,58 @@
|
||||||
# A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript
|
vm = require 'vm'
|
||||||
# and evaluates it. Good for simple tests, or poking around the **Node.js** API.
|
nodeREPL = require 'repl'
|
||||||
# Using it looks like this:
|
|
||||||
#
|
|
||||||
# coffee> console.log "#{num} bottles of beer" for num in [99..1]
|
|
||||||
|
|
||||||
# Start by opening up `stdin` and `stdout`.
|
|
||||||
stdin = process.openStdin()
|
|
||||||
stdout = process.stdout
|
|
||||||
|
|
||||||
# Require the **coffee-script** module to get access to the compiler.
|
|
||||||
CoffeeScript = require './coffee-script'
|
CoffeeScript = require './coffee-script'
|
||||||
readline = require 'readline'
|
{merge} = require './helpers'
|
||||||
{inspect} = require 'util'
|
|
||||||
{Script} = require 'vm'
|
|
||||||
Module = require 'module'
|
|
||||||
|
|
||||||
# REPL Setup
|
replDefaults =
|
||||||
|
prompt: 'coffee> ',
|
||||||
# Config
|
eval: (code, context, file, cb) ->
|
||||||
REPL_PROMPT = 'coffee> '
|
|
||||||
REPL_PROMPT_MULTILINE = '------> '
|
|
||||||
REPL_PROMPT_CONTINUATION = '......> '
|
|
||||||
enableColours = no
|
|
||||||
unless process.platform is 'win32'
|
|
||||||
enableColours = not process.env.NODE_DISABLE_COLORS
|
|
||||||
|
|
||||||
# Log an error.
|
|
||||||
error = (err) ->
|
|
||||||
stdout.write (err.stack or err.toString()) + '\n'
|
|
||||||
|
|
||||||
## Autocompletion
|
|
||||||
|
|
||||||
# Regexes to match complete-able bits of text.
|
|
||||||
ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/
|
|
||||||
SIMPLEVAR = /(\w+)$/i
|
|
||||||
|
|
||||||
# Returns a list of completions, and the completed text.
|
|
||||||
autocomplete = (text) ->
|
|
||||||
completeAttribute(text) or completeVariable(text) or [[], text]
|
|
||||||
|
|
||||||
# Attempt to autocomplete a chained dotted attribute: `one.two.three`.
|
|
||||||
completeAttribute = (text) ->
|
|
||||||
if match = text.match ACCESSOR
|
|
||||||
[all, obj, prefix] = match
|
|
||||||
try obj = Script.runInThisContext obj
|
|
||||||
catch e
|
|
||||||
return
|
|
||||||
return unless obj?
|
|
||||||
obj = Object obj
|
|
||||||
candidates = Object.getOwnPropertyNames obj
|
|
||||||
while obj = Object.getPrototypeOf obj
|
|
||||||
for key in Object.getOwnPropertyNames obj when key not in candidates
|
|
||||||
candidates.push key
|
|
||||||
completions = getCompletions prefix, candidates
|
|
||||||
[completions, prefix]
|
|
||||||
|
|
||||||
# Attempt to autocomplete an in-scope free variable: `one`.
|
|
||||||
completeVariable = (text) ->
|
|
||||||
free = text.match(SIMPLEVAR)?[1]
|
|
||||||
free = "" if text is ""
|
|
||||||
if free?
|
|
||||||
vars = Script.runInThisContext 'Object.getOwnPropertyNames(Object(this))'
|
|
||||||
keywords = (r for r in CoffeeScript.RESERVED when r[..1] isnt '__')
|
|
||||||
candidates = vars
|
|
||||||
for key in keywords when key not in candidates
|
|
||||||
candidates.push key
|
|
||||||
completions = getCompletions free, candidates
|
|
||||||
[completions, free]
|
|
||||||
|
|
||||||
# Return elements of candidates for which `prefix` is a prefix.
|
|
||||||
getCompletions = (prefix, candidates) ->
|
|
||||||
el for el in candidates when 0 is el.indexOf prefix
|
|
||||||
|
|
||||||
# Make sure that uncaught exceptions don't kill the REPL.
|
|
||||||
process.on 'uncaughtException', error
|
|
||||||
|
|
||||||
# The current backlog of multi-line code.
|
|
||||||
backlog = ''
|
|
||||||
|
|
||||||
# The main REPL function. **run** is called every time a line of code is entered.
|
|
||||||
# Attempt to evaluate the command. If there's an exception, print it out instead
|
|
||||||
# of exiting.
|
|
||||||
run = (buffer) ->
|
|
||||||
# remove single-line comments
|
|
||||||
buffer = buffer.replace /(^|[\r\n]+)(\s*)##?(?:[^#\r\n][^\r\n]*|)($|[\r\n])/, "$1$2$3"
|
|
||||||
# remove trailing newlines
|
|
||||||
buffer = buffer.replace /[\r\n]+$/, ""
|
|
||||||
if multilineMode
|
|
||||||
backlog += "#{buffer}\n"
|
|
||||||
repl.setPrompt REPL_PROMPT_CONTINUATION
|
|
||||||
repl.prompt()
|
|
||||||
return
|
|
||||||
if !buffer.toString().trim() and !backlog
|
|
||||||
repl.prompt()
|
|
||||||
return
|
|
||||||
code = backlog += buffer
|
|
||||||
if code[code.length - 1] is '\\'
|
|
||||||
backlog = "#{backlog[...-1]}\n"
|
|
||||||
repl.setPrompt REPL_PROMPT_CONTINUATION
|
|
||||||
repl.prompt()
|
|
||||||
return
|
|
||||||
repl.setPrompt REPL_PROMPT
|
|
||||||
backlog = ''
|
|
||||||
try
|
try
|
||||||
_ = global._
|
return cb(null) if /^\(\s+\)$/.test code # Empty command
|
||||||
returnValue = CoffeeScript.eval "_=(#{code}\n)", {
|
code = code.replace /子/mg, '\n' # Temporary hack, see TODO below
|
||||||
filename: 'repl'
|
code = CoffeeScript.compile(code, {filename: file, bare: true})
|
||||||
modulename: 'repl'
|
cb(null, vm.runInContext(code, context, file))
|
||||||
}
|
|
||||||
if returnValue is undefined
|
|
||||||
global._ = _
|
|
||||||
repl.output.write "#{inspect returnValue, no, 2, enableColours}\n"
|
|
||||||
catch err
|
catch err
|
||||||
error err
|
cb(err)
|
||||||
repl.prompt()
|
|
||||||
|
|
||||||
if stdin.readable and stdin.isRaw
|
# TODO: how to test?
|
||||||
# handle piped input
|
addMultilineHandler = (repl) ->
|
||||||
pipedInput = ''
|
{rli, inputStream, outputStream} = repl
|
||||||
repl =
|
|
||||||
prompt: -> stdout.write @_prompt
|
multiline =
|
||||||
setPrompt: (p) -> @_prompt = p
|
enabled: off
|
||||||
input: stdin
|
prompt: new Array(repl.prompt.length).join('.') + ' '
|
||||||
output: stdout
|
buffer: ''
|
||||||
on: ->
|
|
||||||
stdin.on 'data', (chunk) ->
|
# Proxy node's line listener
|
||||||
pipedInput += chunk
|
nodeLineListener = rli.listeners('line')[0]
|
||||||
return unless /\n/.test pipedInput
|
rli.removeListener 'line', nodeLineListener
|
||||||
lines = pipedInput.split "\n"
|
rli.on 'line', (cmd) ->
|
||||||
pipedInput = lines[lines.length - 1]
|
if multiline.enabled is on
|
||||||
for line in lines[...-1] when line
|
multiline.buffer += "#{cmd}\n"
|
||||||
stdout.write "#{line}\n"
|
rli.prompt true
|
||||||
run line
|
|
||||||
return
|
|
||||||
stdin.on 'end', ->
|
|
||||||
for line in pipedInput.trim().split "\n" when line
|
|
||||||
stdout.write "#{line}\n"
|
|
||||||
run line
|
|
||||||
stdout.write '\n'
|
|
||||||
process.exit 0
|
|
||||||
else
|
else
|
||||||
# Create the REPL by listening to **stdin**.
|
nodeLineListener(cmd)
|
||||||
if readline.createInterface.length < 3
|
|
||||||
repl = readline.createInterface stdin, autocomplete
|
|
||||||
stdin.on 'data', (buffer) -> repl.write buffer
|
|
||||||
else
|
|
||||||
repl = readline.createInterface stdin, stdout, autocomplete
|
|
||||||
|
|
||||||
multilineMode = off
|
# Handle Ctrl-v
|
||||||
|
inputStream.on 'keypress', (char, key) ->
|
||||||
# Handle multi-line mode switch
|
|
||||||
repl.input.on 'keypress', (char, key) ->
|
|
||||||
# test for Ctrl-v
|
|
||||||
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
|
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v'
|
||||||
cursorPos = repl.cursor
|
multiline.enabled = !multiline.enabled
|
||||||
repl.output.cursorTo 0
|
if multiline.enabled is off
|
||||||
repl.output.clearLine 1
|
unless multiline.buffer.match /\n/
|
||||||
multilineMode = not multilineMode
|
rli.setPrompt repl.prompt
|
||||||
repl._line() if not multilineMode and backlog
|
rli.prompt true
|
||||||
backlog = ''
|
|
||||||
repl.setPrompt (newPrompt = if multilineMode then REPL_PROMPT_MULTILINE else REPL_PROMPT)
|
|
||||||
repl.prompt()
|
|
||||||
repl.output.cursorTo newPrompt.length + (repl.cursor = cursorPos)
|
|
||||||
|
|
||||||
# Handle Ctrl-d press at end of last line in multiline mode
|
|
||||||
repl.input.on 'keypress', (char, key) ->
|
|
||||||
return unless multilineMode and repl.line
|
|
||||||
# test for Ctrl-d
|
|
||||||
return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'd'
|
|
||||||
multilineMode = off
|
|
||||||
repl._line()
|
|
||||||
|
|
||||||
repl.on 'attemptClose', ->
|
|
||||||
if multilineMode
|
|
||||||
multilineMode = off
|
|
||||||
repl.output.cursorTo 0
|
|
||||||
repl.output.clearLine 1
|
|
||||||
repl._onLine repl.line
|
|
||||||
return
|
return
|
||||||
if backlog or repl.line
|
# TODO: how to encode line breaks so the node repl will pass the complete multiline to our eval?
|
||||||
backlog = ''
|
multiline.buffer = multiline.buffer.replace /\n/mg, '子'
|
||||||
repl.historyIndex = -1
|
rli.emit 'line', multiline.buffer
|
||||||
repl.setPrompt REPL_PROMPT
|
multiline.buffer = ''
|
||||||
repl.output.write '\n(^C again to quit)'
|
|
||||||
repl._line (repl.line = '')
|
|
||||||
else
|
else
|
||||||
repl.close()
|
rli.setPrompt multiline.prompt
|
||||||
|
rli.prompt true
|
||||||
|
|
||||||
repl.on 'close', ->
|
module.exports =
|
||||||
repl.output.write '\n'
|
start: (opts = {}) ->
|
||||||
repl.input.destroy()
|
opts = merge(replDefaults, opts)
|
||||||
|
repl = nodeREPL.start opts
|
||||||
repl.on 'line', run
|
addMultilineHandler(repl)
|
||||||
|
repl
|
||||||
repl.setPrompt REPL_PROMPT
|
|
||||||
repl.prompt()
|
|
|
@ -1,4 +1,46 @@
|
||||||
# REPL
|
# REPL
|
||||||
# ----
|
# ----
|
||||||
|
|
||||||
# TODO: add tests
|
# TODO: add more tests
|
||||||
|
{spawn} = require 'child_process'
|
||||||
|
PROMPT = 'coffee> '
|
||||||
|
|
||||||
|
testOutput = (expected, actual) ->
|
||||||
|
eq expected, actual.slice(0, expected.length)
|
||||||
|
actual.substr expected.length
|
||||||
|
|
||||||
|
testCommands = (input, expectedOutput) ->
|
||||||
|
input = [input] if typeof input is 'string'
|
||||||
|
expectedOutput = [expectedOutput] if typeof expectedOutput in ['string', 'undefined']
|
||||||
|
output = ''
|
||||||
|
coffee = spawn 'bin/coffee'
|
||||||
|
input.push 'process.exit()'
|
||||||
|
|
||||||
|
coffee.stdout.on 'data', (data) ->
|
||||||
|
output += data.toString().replace(/\u001b\[\d{0,2}m/g, '')
|
||||||
|
coffee.stdin.write "#{input.shift()}\n"
|
||||||
|
|
||||||
|
coffee.on 'exit', ->
|
||||||
|
output = testOutput PROMPT, output
|
||||||
|
while expectedOutput.length > 0
|
||||||
|
output = testOutput "#{expectedOutput.shift()}\n#{PROMPT}", output
|
||||||
|
eq '', output
|
||||||
|
|
||||||
|
test "comments are ignored", ->
|
||||||
|
testCommands "1 + 1 #foo", "2"
|
||||||
|
|
||||||
|
test "output in inspect mode", ->
|
||||||
|
testCommands '"1 + 1\\n"', "'1 + 1\\n'"
|
||||||
|
|
||||||
|
test "variables are saved", ->
|
||||||
|
input = [
|
||||||
|
"foo = 'foo'"
|
||||||
|
'foobar = "#{foo}bar"'
|
||||||
|
]
|
||||||
|
testCommands input, [
|
||||||
|
"'foo'"
|
||||||
|
"'foobar'"
|
||||||
|
]
|
||||||
|
|
||||||
|
test "empty command evaluates to undefined", ->
|
||||||
|
testCommands "", undefined
|
||||||
|
|
|
@ -106,7 +106,6 @@
|
||||||
'option_parser'
|
'option_parser'
|
||||||
'ranges'
|
'ranges'
|
||||||
'regexps'
|
'regexps'
|
||||||
'repl'
|
|
||||||
'scope'
|
'scope'
|
||||||
'slicing_and_splicing'
|
'slicing_and_splicing'
|
||||||
'soaks'
|
'soaks'
|
||||||
|
|
Loading…
Reference in a new issue