From 3284f6af18e7993762ab659fb94cf8e424c4b0d7 Mon Sep 17 00:00:00 2001 From: lihan Date: Wed, 31 Oct 2012 00:09:59 -0400 Subject: [PATCH 1/5] tests for the repl: ignoring comments, output in inspect mode, variable saving --- test/repl.coffee | 41 ++++++++++++++++++++++++++++++++++++++++- test/test.html | 1 - 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/test/repl.coffee b/test/repl.coffee index c683b32a..a6b74a76 100644 --- a/test/repl.coffee +++ b/test/repl.coffee @@ -1,4 +1,43 @@ # 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 is 'string' + 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'" + ] diff --git a/test/test.html b/test/test.html index 256b2988..a09692c9 100644 --- a/test/test.html +++ b/test/test.html @@ -106,7 +106,6 @@ 'option_parser' 'ranges' 'regexps' - 'repl' 'scope' 'slicing_and_splicing' 'soaks' From 627b921bca55fc9983c9c5463b9a60973d9c65c3 Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Mon, 14 Jan 2013 21:17:48 -0800 Subject: [PATCH 2/5] Initial commit of REPL based on node's REPLServer --- lib/coffee-script/command.js | 4 +- lib/coffee-script/repl.js | 277 +++-------------------------------- src/command.coffee | 4 +- src/repl.coffee | 208 ++------------------------ 4 files changed, 37 insertions(+), 456 deletions(-) diff --git a/lib/coffee-script/command.js b/lib/coffee-script/command.js index dfefacd5..2624e8da 100644 --- a/lib/coffee-script/command.js +++ b/lib/coffee-script/command.js @@ -67,7 +67,7 @@ loadRequires(); } if (opts.interactive) { - return require('./repl'); + return require('./repl').start(); } if (opts.watch && !fs.watch) { 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]); } if (!sources.length) { - return require('./repl'); + return require('./repl').start(); } literals = opts.run ? sources.splice(1) : []; process.argv = process.argv.slice(0, 2).concat(literals); diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index c14b8d1b..8a60ce24 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -1,276 +1,37 @@ // Generated by CoffeeScript 1.5.0-pre (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, - __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; }; + var CoffeeScript, merge, repl, replDefaults, vm; - stdin = process.openStdin(); + vm = require('vm'); - stdout = process.stdout; + repl = require('repl'); CoffeeScript = require('./coffee-script'); - readline = require('readline'); + merge = require('./helpers').merge; - inspect = require('util').inspect; - - Script = require('vm').Script; - - 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]; + replDefaults = { + prompt: "coffee> ", + "eval": function(code, context, file, cb) { try { - obj = Script.runInThisContext(obj); - } catch (e) { - return; + code = CoffeeScript.compile(code, { + filename: file, + bare: true + }); + return cb(null, vm.runInContext(code, context, file)); + } catch (err) { + return cb(err); } - if (obj == null) { - return; - } - obj = Object(obj); - 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); - } + module.exports = { + start: function(opts) { + if (opts == null) { + opts = {}; } - completions = getCompletions(free, candidates); - return [completions, free]; + return repl.start(merge(replDefaults, opts)); } }; - 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) { - global._ = _; - } - repl.output.write("" + (inspect(returnValue, false, 2, enableColours)) + "\n"); - } catch (err) { - error(err); - } - return repl.prompt(); - }; - - if (stdin.readable && stdin.isRaw) { - pipedInput = ''; - repl = { - prompt: function() { - return stdout.write(this._prompt); - }, - setPrompt: function(p) { - return this._prompt = p; - }, - input: stdin, - output: stdout, - on: function() {} - }; - stdin.on('data', function(chunk) { - var line, lines, _i, _len, _ref; - pipedInput += chunk; - if (!/\n/.test(pipedInput)) { - return; - } - 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 { - if (readline.createInterface.length < 3) { - repl = readline.createInterface(stdin, autocomplete); - stdin.on('data', function(buffer) { - return repl.write(buffer); - }); - } else { - 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')) { - return; - } - cursorPos = repl.cursor; - repl.output.cursorTo(0); - repl.output.clearLine(1); - multilineMode = !multilineMode; - if (!multilineMode && backlog) { - 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; - } - if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'd')) { - return; - } - 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 { - return repl.close(); - } - }); - - repl.on('close', function() { - repl.output.write('\n'); - return repl.input.destroy(); - }); - - repl.on('line', run); - - repl.setPrompt(REPL_PROMPT); - - repl.prompt(); - }).call(this); diff --git a/src/command.coffee b/src/command.coffee index 25cbd84d..fcb28596 100644 --- a/src/command.coffee +++ b/src/command.coffee @@ -68,12 +68,12 @@ exports.run = -> return usage() if opts.help return version() if opts.version loadRequires() if opts.require - return require './repl' if opts.interactive + return require('./repl').start() if opts.interactive if opts.watch and !fs.watch return printWarn "The --watch feature depends on Node v0.6.0+. You are running #{process.version}." return compileStdio() if opts.stdio 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 [] process.argv = process.argv[0..1].concat literals process.argv[0] = 'coffee' diff --git a/src/repl.coffee b/src/repl.coffee index d94c63eb..f8b64120 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -1,197 +1,17 @@ -# A very simple Read-Eval-Print-Loop. Compiles one line at a time to JavaScript -# and evaluates it. Good for simple tests, or poking around the **Node.js** API. -# 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. +vm = require 'vm' +repl = require 'repl' CoffeeScript = require './coffee-script' -readline = require 'readline' -{inspect} = require 'util' -{Script} = require 'vm' -Module = require 'module' +{merge} = require './helpers' -# REPL Setup +replDefaults = + prompt: "coffee> ", + eval: (code, context, file, cb) -> + try + code = CoffeeScript.compile(code, {filename: file, bare: true}) + cb(null, vm.runInContext(code, context, file)) + catch err + cb(err) -# Config -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 - _ = global._ - returnValue = CoffeeScript.eval "_=(#{code}\n)", { - filename: 'repl' - modulename: 'repl' - } - if returnValue is undefined - global._ = _ - repl.output.write "#{inspect returnValue, no, 2, enableColours}\n" - catch err - error err - repl.prompt() - -if stdin.readable and stdin.isRaw - # handle piped input - pipedInput = '' - repl = - prompt: -> stdout.write @_prompt - setPrompt: (p) -> @_prompt = p - input: stdin - output: stdout - on: -> - stdin.on 'data', (chunk) -> - pipedInput += chunk - return unless /\n/.test pipedInput - lines = pipedInput.split "\n" - pipedInput = lines[lines.length - 1] - for line in lines[...-1] when line - stdout.write "#{line}\n" - 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 - # Create the REPL by listening to **stdin**. - 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 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' - cursorPos = repl.cursor - repl.output.cursorTo 0 - repl.output.clearLine 1 - multilineMode = not multilineMode - repl._line() if not multilineMode and backlog - 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 - if backlog or repl.line - backlog = '' - repl.historyIndex = -1 - repl.setPrompt REPL_PROMPT - repl.output.write '\n(^C again to quit)' - repl._line (repl.line = '') - else - repl.close() - -repl.on 'close', -> - repl.output.write '\n' - repl.input.destroy() - -repl.on 'line', run - -repl.setPrompt REPL_PROMPT -repl.prompt() +module.exports = + start: (opts = {}) -> + repl.start merge(replDefaults, opts) From d84cd9466e517fc2d25e02ce7d5bea1ef15621aa Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Mon, 14 Jan 2013 22:22:28 -0800 Subject: [PATCH 3/5] empty command evaluates to undefined --- lib/coffee-script/repl.js | 3 +++ src/repl.coffee | 1 + test/repl.coffee | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index 8a60ce24..0e1b2432 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -14,6 +14,9 @@ prompt: "coffee> ", "eval": function(code, context, file, cb) { try { + if (/^\(\s+\)$/.test(code)) { + code = ''; + } code = CoffeeScript.compile(code, { filename: file, bare: true diff --git a/src/repl.coffee b/src/repl.coffee index f8b64120..459274df 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -7,6 +7,7 @@ replDefaults = prompt: "coffee> ", eval: (code, context, file, cb) -> try + code = '' if /^\(\s+\)$/.test code # Empty command won't parse code = CoffeeScript.compile(code, {filename: file, bare: true}) cb(null, vm.runInContext(code, context, file)) catch err diff --git a/test/repl.coffee b/test/repl.coffee index a6b74a76..d5051d8b 100644 --- a/test/repl.coffee +++ b/test/repl.coffee @@ -11,7 +11,7 @@ testOutput = (expected, actual) -> testCommands = (input, expectedOutput) -> input = [input] if typeof input is 'string' - expectedOutput = [expectedOutput] if typeof expectedOutput is 'string' + expectedOutput = [expectedOutput] if typeof expectedOutput in ['string', 'undefined'] output = '' coffee = spawn 'bin/coffee' input.push 'process.exit()' @@ -41,3 +41,6 @@ test "variables are saved", -> "'foo'" "'foobar'" ] + +test "empty command evaluates to undefined", -> + testCommands "", undefined From 69d80e2dede6a4e226f32cd35cad9c7246b905ae Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Wed, 16 Jan 2013 11:48:50 -0800 Subject: [PATCH 4/5] Single quotes --- src/repl.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/repl.coffee b/src/repl.coffee index 459274df..393aac28 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -4,7 +4,7 @@ CoffeeScript = require './coffee-script' {merge} = require './helpers' replDefaults = - prompt: "coffee> ", + prompt: 'coffee> ', eval: (code, context, file, cb) -> try code = '' if /^\(\s+\)$/.test code # Empty command won't parse From 4a52814a795e234afe401723fd38f14a2ae52e10 Mon Sep 17 00:00:00 2001 From: Alon Salant Date: Tue, 15 Jan 2013 23:31:40 -0800 Subject: [PATCH 5/5] Multiline WIP --- lib/coffee-script/repl.js | 54 +++++++++++++++++++++++++++++++++++---- src/repl.coffee | 46 ++++++++++++++++++++++++++++++--- 2 files changed, 92 insertions(+), 8 deletions(-) diff --git a/lib/coffee-script/repl.js b/lib/coffee-script/repl.js index 0e1b2432..2b39ec01 100644 --- a/lib/coffee-script/repl.js +++ b/lib/coffee-script/repl.js @@ -1,22 +1,23 @@ // Generated by CoffeeScript 1.5.0-pre (function() { - var CoffeeScript, merge, repl, replDefaults, vm; + var CoffeeScript, addMultilineHandler, merge, nodeREPL, replDefaults, vm; vm = require('vm'); - repl = require('repl'); + nodeREPL = require('repl'); CoffeeScript = require('./coffee-script'); merge = require('./helpers').merge; replDefaults = { - prompt: "coffee> ", + prompt: 'coffee> ', "eval": function(code, context, file, cb) { try { if (/^\(\s+\)$/.test(code)) { - code = ''; + return cb(null); } + code = code.replace(/子/mg, '\n'); code = CoffeeScript.compile(code, { filename: file, bare: true @@ -28,12 +29,55 @@ } }; + addMultilineHandler = function(repl) { + var inputStream, multiline, nodeLineListener, outputStream, rli; + rli = repl.rli, inputStream = repl.inputStream, outputStream = repl.outputStream; + multiline = { + enabled: false, + prompt: new Array(repl.prompt.length).join('.') + ' ', + buffer: '' + }; + nodeLineListener = rli.listeners('line')[0]; + rli.removeListener('line', nodeLineListener); + rli.on('line', function(cmd) { + if (multiline.enabled === true) { + multiline.buffer += "" + cmd + "\n"; + return rli.prompt(true); + } else { + return nodeLineListener(cmd); + } + }); + return inputStream.on('keypress', function(char, key) { + if (!(key && key.ctrl && !key.meta && !key.shift && key.name === 'v')) { + return; + } + multiline.enabled = !multiline.enabled; + if (multiline.enabled === false) { + if (!multiline.buffer.match(/\n/)) { + rli.setPrompt(repl.prompt); + rli.prompt(true); + return; + } + multiline.buffer = multiline.buffer.replace(/\n/mg, '子'); + rli.emit('line', multiline.buffer); + return multiline.buffer = ''; + } else { + rli.setPrompt(multiline.prompt); + return rli.prompt(true); + } + }); + }; + module.exports = { start: function(opts) { + var repl; if (opts == null) { opts = {}; } - return repl.start(merge(replDefaults, opts)); + opts = merge(replDefaults, opts); + repl = nodeREPL.start(opts); + addMultilineHandler(repl); + return repl; } }; diff --git a/src/repl.coffee b/src/repl.coffee index 393aac28..7a853caf 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -1,5 +1,5 @@ vm = require 'vm' -repl = require 'repl' +nodeREPL = require 'repl' CoffeeScript = require './coffee-script' {merge} = require './helpers' @@ -7,12 +7,52 @@ replDefaults = prompt: 'coffee> ', eval: (code, context, file, cb) -> try - code = '' if /^\(\s+\)$/.test code # Empty command won't parse + return cb(null) if /^\(\s+\)$/.test code # Empty command + code = code.replace /子/mg, '\n' # Temporary hack, see TODO below code = CoffeeScript.compile(code, {filename: file, bare: true}) cb(null, vm.runInContext(code, context, file)) catch err cb(err) +# TODO: how to test? +addMultilineHandler = (repl) -> + {rli, inputStream, outputStream} = repl + + multiline = + enabled: off + prompt: new Array(repl.prompt.length).join('.') + ' ' + buffer: '' + + # Proxy node's line listener + nodeLineListener = rli.listeners('line')[0] + rli.removeListener 'line', nodeLineListener + rli.on 'line', (cmd) -> + if multiline.enabled is on + multiline.buffer += "#{cmd}\n" + rli.prompt true + else + nodeLineListener(cmd) + + # Handle Ctrl-v + inputStream.on 'keypress', (char, key) -> + return unless key and key.ctrl and not key.meta and not key.shift and key.name is 'v' + multiline.enabled = !multiline.enabled + if multiline.enabled is off + unless multiline.buffer.match /\n/ + rli.setPrompt repl.prompt + rli.prompt true + return + # TODO: how to encode line breaks so the node repl will pass the complete multiline to our eval? + multiline.buffer = multiline.buffer.replace /\n/mg, '子' + rli.emit 'line', multiline.buffer + multiline.buffer = '' + else + rli.setPrompt multiline.prompt + rli.prompt true + module.exports = start: (opts = {}) -> - repl.start merge(replDefaults, opts) + opts = merge(replDefaults, opts) + repl = nodeREPL.start opts + addMultilineHandler(repl) + repl \ No newline at end of file