diff --git a/lib/repl.js b/lib/repl.js index c4608783..aeec45d7 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1,18 +1,14 @@ (function() { - var CoffeeScript, autocomplete, error, helpers, readline, repl, run, stdio; + var ACCESSOR, CoffeeScript, SIMPLEVAR, Script, autocomplete, completeAttribute, completeVariable, error, getCompletions, getPropertyNames, helpers, readline, repl, run, stdio; + var __hasProp = Object.prototype.hasOwnProperty; CoffeeScript = require('./coffee-script'); helpers = require('./helpers'); - autocomplete = require('./autocomplete'); readline = require('readline'); + Script = process.binding('evals').Script; stdio = process.openStdin(); error = function(err) { return stdio.write((err.stack || err.toString()) + '\n\n'); }; - helpers.extend(global, { - quit: function() { - return process.exit(0); - } - }); run = function(buffer) { var val; try { @@ -29,8 +25,54 @@ } return repl.prompt(); }; + ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/; + SIMPLEVAR = /\s*(\w*)$/i; + autocomplete = function(text) { + return completeAttribute(text) || completeVariable(text) || [[], text]; + }; + completeAttribute = function(text) { + var all, completions, match, obj, prefix, val; + if (match = text.match(ACCESSOR)) { + all = match[0], obj = match[1], prefix = match[2]; + try { + val = Script.runInThisContext(obj); + } catch (error) { + return [[], text]; + } + completions = getCompletions(prefix, getPropertyNames(val)); + return [completions, prefix]; + } + }; + completeVariable = function(text) { + var completions, free, scope, _ref; + if (free = (_ref = text.match(SIMPLEVAR)) != null ? _ref[1] : void 0) { + scope = Script.runInThisContext('this'); + completions = getCompletions(free, CoffeeScript.RESERVED.concat(getPropertyNames(scope))); + 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 (el.indexOf(prefix) === 0) { + _results.push(el); + } + } + return _results; + }; + getPropertyNames = function(obj) { + var name, _results; + _results = []; + for (name in obj) { + if (!__hasProp.call(obj, name)) continue; + _results.push(name); + } + return _results; + }; process.on('uncaughtException', error); - repl = readline.createInterface(stdio, autocomplete.complete); + repl = readline.createInterface(stdio, autocomplete); repl.setPrompt('coffee> '); stdio.on('data', function(buffer) { return repl.write(buffer); diff --git a/src/autocomplete.coffee b/src/autocomplete.coffee deleted file mode 100644 index 3f3beaea..00000000 --- a/src/autocomplete.coffee +++ /dev/null @@ -1,34 +0,0 @@ - -{RESERVED} = require './coffee-script' -Script = process.binding('evals').Script - -# Return elements of candidates for which `prefix` is a prefix. -get_completions = (prefix, candidates) -> - (el for el in candidates when el.indexOf(prefix) == 0) - -get_property_names = (o) -> - try - Object.getOwnPropertyNames(o) - catch error - (k for k of o) - -complete_attribute = (text) -> - match = /\s*([\w\.]+)(?:\.(\w*))$/.exec(text) - if match? - [ob, prefix] = [match[1], match[2]] - try - val = Script.runInThisContext ob - catch error - return [[], text] - completions = get_completions prefix, get_property_names val - [completions, prefix] - -complete_variable = (text) -> - free = /\W*(\w*)$/i.exec(text)?[1] - if free? - completions = get_completions free, RESERVED.concat(get_property_names Script.runInThisContext 'this') - [completions, free] - -# Returns a list of completions and the completed text -exports.complete = (text) -> - complete_attribute(text) or complete_variable(text) or [[], text] diff --git a/src/repl.coffee b/src/repl.coffee index cea29f51..86ea834a 100644 --- a/src/repl.coffee +++ b/src/repl.coffee @@ -7,8 +7,10 @@ # Require the **coffee-script** module to get access to the compiler. CoffeeScript = require './coffee-script' helpers = require './helpers' -autocomplete = require './autocomplete' readline = require 'readline' +Script = process.binding('evals').Script + +# REPL Setup # Start by opening up **stdio**. stdio = process.openStdin() @@ -17,9 +19,6 @@ stdio = process.openStdin() error = (err) -> stdio.write (err.stack or err.toString()) + '\n\n' -# Quick alias for quitting the REPL. -helpers.extend global, quit: -> process.exit(0) - # 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. @@ -31,11 +30,47 @@ run = (buffer) -> error err repl.prompt() +## Autocompletion + +# Regexes to match complete-able bits of text. +ACCESSOR = /\s*([\w\.]+)(?:\.(\w*))$/ +SIMPLEVAR = /\s*(\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 + val = Script.runInThisContext obj + catch error + return [[], text] + completions = getCompletions prefix, getPropertyNames val + [completions, prefix] + +# Attempt to autocomplete an in-scope free variable: `one`. +completeVariable = (text) -> + if free = text.match(SIMPLEVAR)?[1] + scope = Script.runInThisContext 'this' + completions = getCompletions free, CoffeeScript.RESERVED.concat(getPropertyNames scope) + [completions, free] + +# Return elements of candidates for which `prefix` is a prefix. +getCompletions = (prefix, candidates) -> + (el for el in candidates when el.indexOf(prefix) is 0) + +# Return all "own" properties of an object. +getPropertyNames = (obj) -> + (name for own name of obj) + # Make sure that uncaught exceptions don't kill the REPL. process.on 'uncaughtException', error # Create the REPL by listening to **stdin**. -repl = readline.createInterface stdio, autocomplete.complete +repl = readline.createInterface stdio, autocomplete repl.setPrompt 'coffee> ' stdio.on 'data', (buffer) -> repl.write buffer repl.on 'close', -> stdio.destroy() diff --git a/test/test_autocomplete.coffee b/test/test_autocomplete.coffee deleted file mode 100644 index 389155c8..00000000 --- a/test/test_autocomplete.coffee +++ /dev/null @@ -1,32 +0,0 @@ -return unless require? - -complete = require './../lib/autocomplete' - -eq_set = (left, right) -> - left = left.slice(0) - right = right.slice(0) - left.sort() - right.sort() - eq left.join(' '), right.join(' ') - -# JavaScript keywords -[completions, completed] = complete.complete "c" -ok completions instanceof Array -should_be = ["case", "catch", "class", "clearInterval", "clearTimeout", "console", "const", "continue"] -eq_set should_be, completions - -[completions, completed] = complete.complete 'E' -eq_set completions, ['EvalError', 'Error'] - -[completions, completed] = complete.complete "Math.c" -eq_set completions, ["cos", "ceil"] - -# I don't know how to make this testable :( -# a = {baba: 1, babo: 2} - -# [completions, completed] = complete.complete "a.bab" -# eq_set completions, ["baba", "babo"] - - - -